Skip to main content
The frontend is the user-facing layer of your Canton application. This page uses cn-quickstart as a running example. cn-quickstart is a full-stack reference application that implements a software licensing workflow on Canton Network. The cn-quickstart frontend is a React application built with TypeScript and Vite. It communicates with the backend over HTTP using types generated from a shared OpenAPI schema. The patterns shown here — connecting to the backend, displaying contract data, handling authentication — apply to any Canton frontend, but the code samples are drawn directly from cn-quickstart so you can see them in a working context.

Connecting to the Backend

In cn-quickstart, the frontend does not talk to the Ledger API directly — all ledger interactions go through the backend’s REST endpoints. Canton does provide a JSON API that frontends can use for direct ledger access, but the cn-quickstart architecture routes everything through the backend for separation of concerns. The API client is configured in api.ts using the openapi-client-axios library, which reads the OpenAPI schema and produces a typed HTTP client:
The shared common/openapi.yaml defines every endpoint, request body, and response shape. The openapi-client-axios library generates a typed Client interface from this spec at build time, so every API call in the frontend is type-checked against the backend’s contract:
If the backend API changes, the frontend build breaks rather than failing silently at runtime.

TypeScript Code Generation

The cn-quickstart frontend generates its TypeScript types from the OpenAPI spec using the gen:openapi script in package.json:
The build script runs type generation before compilation, so the TypeScript types always match the OpenAPI schema. Separately, dpm codegen-js generates TypeScript types from your compiled DAR file. These types mirror your Daml templates, choices, and data types:
Whether you use DAR-generated types depends on your architecture:
  • Fully mediated (cn-quickstart default) — The frontend uses OpenAPI-generated types from the backend’s REST schema. The Daml-generated TypeScript types are not needed in the frontend because the backend translates between ledger concepts and REST DTOs.
  • Direct ledger access via JSON API — The frontend submits commands through the JSON API using the Daml-generated TypeScript bindings. This gives tighter integration with the ledger but requires the frontend to handle party IDs, contract IDs, and command submission directly.
For most applications, the fully mediated approach is simpler. The JSON API approach makes sense when you want a thin or no backend layer.

Application Structure

The cn-quickstart frontend uses React Context providers for state management. App.tsx composes them at the top level:
Each domain has its own store under stores/ that wraps a React Context with the API calls and state for that domain. The stores use the typed Client from the OpenAPI schema for all backend communication.

Displaying Contract Data

The frontend fetches contract data from the backend’s GET endpoints and renders it in React components. The licenseStore manages license state and API calls:
LicensesView.tsx consumes this store and renders the data with periodic polling to keep the UI current:
Because the backend handles all ledger translation, the frontend works with plain JSON objects. Fields like contractId, expiresAt, and licenseNum appear as simple strings and numbers in the License type — not ledger-specific types.

Exercising Choices via the Backend

When the user takes an action (renew a license, expire a license), the frontend posts to the backend’s REST API. Each request includes a unique command ID that the backend passes to the Ledger API for deduplication. From the license store:
The generateCommandId() utility produces a UUID using the browser’s crypto.randomUUID() API. The backend forwards this ID to the Ledger API, which uses it to prevent duplicate command submission if the user retries an action.

Authentication

Canton applications typically use OAuth2 / OpenID Connect (OIDC) for user authentication. On LocalNet, cn-quickstart uses Keycloak as the identity provider. The backend handles the OAuth2 flow, and the frontend manages session state through the userStore:
The AuthenticatedUser type (from the OpenAPI spec) includes the user’s name, whether they’re an admin, and their wallet URL. Components use this to conditionally render admin-only features and to link to the Splice wallet. The backend handles all authorization decisions — the frontend is responsible only for checking whether the user is logged in and displaying the appropriate UI.

Wallet Integration

Applications that involve Canton Coin payments integrate with a wallet component in the frontend. In cn-quickstart, the license renewal flow triggers a payment through the Splice wallet system:
  1. The user clicks “Renew License” in the UI
  2. The frontend calls client.renewLicense(), which posts to the backend
  3. The backend exercises the License_Renew choice, creating a LicenseRenewalRequest on the ledger that implements the Splice AllocationRequest interface
  4. The Splice wallet detects the allocation request and creates an AppPaymentRequest for the user to approve
  5. Once payment is confirmed, the provider calls completeLicenseRenewal() to create the renewed license
The LicensesView tracks renewal state by polling. Each license object includes its renewalRequests array, and the UI shows the count of pending and accepted renewals. The user’s wallet URL comes from the AuthenticatedUser object and is used to link to the Splice wallet for payment approval.

Development Workflow

When developing the frontend iteratively:
For faster iteration, run the Vite dev server directly against a running LocalNet backend:
The Vite dev server proxies API requests to the backend. The proxy configuration in vite.config.ts routes /api, /login, and /oauth2 paths to the backend:
The ViteYaml plugin allows importing the OpenAPI YAML file directly as a JavaScript module, which is how api.ts loads the schema at build time.

Exercise: Add License Comments UI

This exercise builds on the backend exercise in Backend Development. Complete that first — you need the LicenseComment Daml template, the OpenAPI endpoints, and the backend implementation before the frontend can use them.
You’ll add a comment list and comment form to the licenses view, following the same store/view patterns that cn-quickstart uses for licenses.

Step 1: Regenerate TypeScript Types

The backend exercise added LicenseComment, AddCommentRequest, and two new endpoints to openapi.yaml. Regenerate the frontend types so the typed client picks them up:
This runs openapicmd typegen against the shared openapi.yaml and overwrites src/openapi.d.ts. After this, your Client type will include listLicenseComments() and addLicenseComment() methods with the correct parameter and return types.

Step 2: Create a Comment Store

Create a new store at quickstart/frontend/src/stores/commentStore.tsx. Follow the same Context + Provider pattern used by licenseStore.tsx:
The structure mirrors licenseStore.tsx: a React Context holds the state, each API call is wrapped with withErrorHandling (which displays toast messages on HTTP errors) and useCallback, and the typed Client provides compile-time checking against the OpenAPI spec. The addComment function generates a command ID for deduplication — the same pattern expireLicense and renewLicense use. Register the provider in App.tsx alongside the existing providers:

Step 3: Add the Comment UI

Add a comment section to LicensesView.tsx. You’ll need to import the comment store and add some local state:
Add an effect that polls for comments when a license’s comment panel is open. This follows the same 5-second setInterval pattern LicensesView already uses for the license list:
Add a “Comments” toggle button in each license row’s actions column, and render the comment panel below the license table. The panel shows existing comments and a form for new ones:
The showComments state tracks which license’s contract ID is currently expanded. The formatDateTime utility is already imported in LicensesView for the expiration column. The handleAddComment handler calls addComment from the store, then clears the input.

Step 4: Build and Test

Rebuild both the backend (to pick up the OpenAPI changes and new Java code) and the frontend:
Or for faster frontend iteration with hot reload:
Open the app, navigate to the Licenses page, click the “Comments” button on a license, and try posting a comment. The comment should appear in the list within 5 seconds (or immediately after the POST completes, since addComment calls fetchComments after success).

Next Steps