canton-network-docs
Backend Development
Backend Development - Canton Network Docs
Skip to main content
The
On LocalNet, the token comes from Keycloak. In production, it comes from your OAuth2 provider.
The
A REST endpoint that exercises a choice ties these pieces together. Here’s how
The pattern is: look up the contract (from PQS), build the choice object (from generated code), and submit it through the Ledger API.
The
PQS stores contract payloads as JSONB, so you use PostgreSQL’s
In cn-quickstart, PQS handles read-side projection, so the backend does not maintain its own event-sourced state. If you are not using PQS, streaming transactions and maintaining your own projections is the alternative.
For contention errors, a retry strategy often resolves the issue: re-read the current contract ID from PQS, then resubmit the command.
The
In cn-quickstart, the Gradle build handles both steps. Run the Daml build task, which compiles the
After this step, you’ll have a generated
Then add two endpoints under
The GET endpoint takes only the contract ID (to look up the license’s
This reads
PQS stores all contract payloads as JSON, so
Add a comment — authenticate the caller, look up the license, create a
Note the fully-qualified
This follows the same pattern as
Wait for all services to come up. The onboarding containers will automatically register the app-user tenant.
Get an auth token. The backend uses OAuth2 via Keycloak. Obtain a token using the client credentials grant with the backend service account:
These credentials are from
Then accept the request and create a license using the backend API:
Test the comment endpoints. List the active licenses and pick a contract ID:
Now test the comment endpoints:
The backend is the layer between your frontend and the Canton ledger. It submits commands, reads transactions, and queries contract state. 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. It includes a Spring Boot backend, a React frontend, Daml smart contracts, and all the configuration needed to run locally or deploy to DevNet. The patterns shown here — connecting to the Ledger API, submitting commands, querying PQS, handling errors — apply to any Canton backend, but the code samples are drawn directly from the cn-quickstart backend so you can see them in a working context.Documentation Index
Fetch the complete documentation index at: https://docs.canton.network/llms.txt
Use this file to discover all available pages before exploring further.
Backend Language
cn-quickstart uses a Java backend built on Spring Boot. Java has first-class code generation support (dpm codegen-java).
TypeScript is also supported through dpm codegen-js. A TypeScript backend uses the same Ledger API (via gRPC-js or the JSON API) and can be a good choice if your team prefers a single language across frontend and backend.
Connecting to the Ledger API
The Ledger API is a service exposed by the validator’s participant node. Your backend connects to it as an authenticated client, acting on behalf of one or more parties. In cn-quickstart,LedgerApi.java manages this connection. The constructor builds a gRPC channel with an authentication interceptor that attaches a bearer token to every call:
Interceptor class attaches the bearer token to gRPC metadata on every call:
Command Submission
Commands are how you write to the ledger. There are two primary operations: creating a contract and exercising a choice.Creating a contract
To create a contract, build aCreate command with the template identifier and payload, then submit it. In cn-quickstart, the LedgerApi.create() method handles this:
dto2Proto converter translates generated Java classes into Protobuf values. The commandId is a UUID that the Ledger API uses for deduplication.
Exercising a choice
Exercising a choice requires the contract ID and the choice arguments. TheexerciseAndGetResult() method builds an Exercise command and submits it through the CommandService, which waits for the transaction result:
LicenseApiImpl.java expires a license:
Handling Contract IDs
Every active contract has a unique contract ID. To exercise a choice, you need the target contract’s ID. Your backend obtains contract IDs by querying PQS or reading them from the transaction stream. The cn-quickstart REST API passes contract IDs in URL paths (e.g.,POST /api/licenses/{contractId}/expire).
Contract IDs are opaque strings. Do not parse or construct them manually.
Querying PQS
For read operations, the cn-quickstart backend queries PQS rather than the Ledger API. PQS maintains a PostgreSQL database that mirrors the ledger state visible to the validator’s hosted parties.The Pqs adapter
Pqs.java wraps Spring’s JdbcTemplate and uses PQS’s active() table-valued function to query active contracts:
active() function takes a qualified template name (e.g., quickstart_licensing:Licensing.License:License) and returns all active contracts of that type. Each row contains a contract_id and a JSON payload.
For filtered queries, activeWhere() appends a WHERE clause:
Domain-specific queries
DamlRepository.java builds higher-level queries. For example, findActiveLicenses() joins licenses with their renewal requests and allocation contracts in a single SQL query:
-> and ->> operators to filter and join on contract fields. You can create additional PostgreSQL indexes on frequently queried JSON paths for performance.
Reading Transactions
The Ledger API also exposes a transaction stream that your backend can subscribe to. Each transaction includes the created and archived contracts visible to your party. This is useful when you need real-time event processing rather than polling PQS. A transaction stream subscription uses gRPC server streaming. Here’s the pattern from the docs-website quickstart:Error Handling
Canton uses structured error codes. When a command fails, the Ledger API returns a gRPCStatusRuntimeException with a Canton-specific error code. Common categories:
- NOT_FOUND — The contract ID does not exist or is not visible to the submitting party
- ALREADY_EXISTS — A duplicate command was submitted (commands are deduplicated by command ID)
- INVALID_ARGUMENT — The command payload does not match the template or choice signature
- FAILED_PRECONDITION — A contract was archived between the time you read it and the time you exercised a choice on it (contention)
StatusRuntimeException and inspect the status code:
Set a unique command ID on each new submission. The Ledger API deduplicates commands by this ID, which prevents double-submission if your backend retries a command that actually succeeded but whose response was lost in transit.
Backend Architecture in cn-quickstart
The cn-quickstart backend follows this module structure (underbackend/src/main/java/com/digitalasset/quickstart/):
service/— REST endpoint implementations. Each endpoint combines a PQS query or a Ledger API command.ledger/— The gRPC Ledger API client. Submits commands to the validator.repository/— Domain-specific PQS query methods.pqs/— Low-level SQL generation and PostgreSQL access.utility/— JSON configuration, tracing, and helper methods.security/— OAuth2 bearer token validation and party authentication.config/— Spring Boot configuration properties.
Exercise: Add License Comments
This exercise walks you through adding a comment feature to cn-quickstart. Users will be able to post comments on licenses and view all comments for a given license. The feature touches every layer of the backend: Daml model, code generation, OpenAPI spec, PQS queries, and REST endpoints. By the end, you’ll have a working/licenses/{contractId}/comments API that creates LicenseComment contracts on the ledger and reads them back through PQS.
Step 1: Define the Daml Template
Create a new filequickstart/daml/licensing/daml/Licensing/LicenseComment.daml:
commenter is the signatory — only the person writing the comment can create it. Both provider and user are observers so they can see comments on their licenses. The licenseNum field links the comment to a specific license without requiring a contract ID reference (which would break if the license is renewed or archived).
Compare this with the License template in Licensing/License.daml, which uses dual signatories (signatory provider, user). A comment only needs the commenter’s authority, making it simpler to create — no multi-party workflow required.
Step 2: Build and Generate Java Bindings
Compile the new Daml module and regenerate the Java bindings. If you’re working outside cn-quickstart, usedpm directly:
.daml files into a DAR and then runs the transcode codegen plugin to produce Java classes:
LicenseComment Java class under backend/build/generated-daml-bindings/ in the quickstart_licensing.licensing.licensecomment package. The generated class uses public final fields (getProvider, getUser, getLicenseNum, etc.) rather than getter methods — this is the transcode codegen style used throughout cn-quickstart.
Step 3: Add the OpenAPI Spec
Add the comment schema and endpoints toquickstart/common/openapi.yaml.
First, define the schemas in the components/schemas section:
paths:
licenseNum). The POST endpoint also takes a commandId for Ledger API deduplication, following the same convention as renewLicense and expireLicense.
Use tags: [Licenses] so these endpoints are grouped with the existing license operations. The cn-quickstart OpenAPI generator produces a LicensesApi Java interface from all endpoints whose paths start with /licenses/, so your new methods will appear in the same interface that LicenseApiImpl already implements.
After updating the spec, regenerate the Spring server stubs:
common/openapi.yaml and writes the generated interfaces and model classes to backend/build/generated-spring/. Your new listLicenseComments and addLicenseComment methods will appear in the LicensesApi interface, and the LicenseComment and AddCommentRequest model classes will be generated in org.openapitools.model.
Step 4: Add the PQS Query
Add a method toDamlRepository.java that finds comments by license number. This follows the same activeWhere() pattern used for filtering licenses:
payload->>'licenseNum' extracts the field as text. The activeWhere() method in Pqs.java appends this WHERE clause to the active() table-valued function, returning only LicenseComment contracts whose licenseNum matches.
Step 5: Add the REST Endpoints
Add two methods toLicenseApiImpl.java. After the Gradle build regenerates LicensesApi from the updated OpenAPI spec, LicenseApiImpl won’t compile until you implement the two new interface methods. Both follow the existing pattern: authenticate, look up data, execute, and return a response.
List comments — authenticate the caller, look up the license to get its licenseNum, query PQS for matching comments:
LicenseComment contract on the ledger:
quickstart_licensing.licensing.licensecomment.LicenseComment type name — this is the Daml-generated class, distinct from the OpenAPI-generated org.openapitools.model.LicenseComment that the REST response uses. The field accessors (license.payload.getProvider, license.payload.getLicenseNum) are public final fields on the transcode-generated classes, not getter methods.
You’ll also need a converter method to map between the two LicenseComment types — from the Daml contract (read from PQS) to the API model (returned as JSON):
toLicenseApi() already in the file. The write path uses ledger.create() — the same method shown in the Command Submission section above. The read path queries PQS through DamlRepository, keeping reads and writes separated.
Step 6: Test It
Since you changed the Daml model, you need a clean environment — the new DAR won’t upload over an existing one. Stop and reset LocalNet, then rebuild and start fresh:docker/backend-service/onboarding/env/oauth2.env and are only valid for the local development environment.
Create a license to test with. The quickstart doesn’t create licenses automatically — they’re created by the app provider through the AppInstall workflow. First, create an AppInstallRequest from the app-user participant:
Next Steps
- Frontend Development — Build a React UI that consumes this backend’s REST API, including a frontend exercise that adds comment UI on top of these backend endpoints
- Canton Coin and Traffic — Understand traffic costs and wallet integration for payments
- cn-quickstart repository — Full working backend implementation
Advanced Topics
- Command Deduplication — Designing application command flows so an intended ledger change is executed exactly once, even under retries, crashes, and lost network messages.
- Explicit Contract Disclosure — Submitting commands that read a contract you do not stakeholder by passing it as a disclosed contract on the Ledger API.