Deploy your first Daml package to Canton in 5 minutes
In this guide we will start a local Canton participant, deploy a Daml project to it with one command, and see the result on the ledger. Then we will point the same project at DevNet or TestNet by changing one flag.
You will use the canton-deploy-quickstart example repository. It is a small Daml project that is already set up for canton-deploy:
asset: anAssettemplate and aSetupscript that issues a token to Alice and transfers it to Bob.asset-tests: tests forasset. They are built but never uploaded to the ledger.canton-deploy.config.js: one profile per network (localnet,devnet,testnet).
The video below walks through every step on this page:
Before you startโ
You need:
- DPM 1.0.20 or later (bundles Daml SDK 3.5)
- Node.js 18 or later
Check both:
dpm version
node --version
Step 1: Get the example projectโ
git clone https://github.com/LYNC-WORLD/canton-deploy-quickstart
cd canton-deploy-quickstart
Open multi-package.yaml. canton-deploy is one line under components, next to the Daml compiler, Daml Script, and a local Canton sandbox:
packages:
- ./asset
- ./asset-tests
components:
- damlc:3.5.2
- daml-script:3.5.2
- canton-open-source:3.5.17
- oci://ghcr.io/lync-world/canton-deploy:0.2.2
Install everything the project declares:
dpm install package
dpm canton-deploy --help
On first install DPM pins canton-deploy by digest and rewrites that line to oci://ghcr.io/lync-world/canton-deploy:0.2.2@sha256:โฆ. That is expected, leave it in place.
Step 2: Start a local participantโ
In a separate terminal, from the same folder:
dpm sandbox --ledger-api-port 5001 --admin-api-port 5002 --json-api-port 7575
Wait for Canton sandbox is ready. and leave it running.
If the sandbox fails with Failed to bind to address /127.0.0.1:6868, another sandbox is already running. Stop it first, or skip this step and use the one that is running.
Step 3: Deployโ
Back in your first terminal, check that canton-deploy can reach the participant:
dpm canton-deploy status --network localnet
โ Admin API localhost:5002
โ Ledger API localhost:5001
โ JSON API http://localhost:7575
Ledger version: 3.5.17
Now deploy, and run the setup script when the upload is done:
dpm canton-deploy deploy --network localnet --script Setup:setup
That one command:
- Builds both packages with
dpm build --all. - Drops
asset-testsfrom the upload, because the config lists it underexcludePackages. - Uploads the
assetDAR through the Ledger API and vets it. - Allocates the parties
AliceandBob. - Creates the user
ledger-api-userwith rights to act as both. - Runs the
Setup:setupDaml Script.
On LocalNet, canton-deploy signs its own development token, so there is nothing to configure. You should see:
โ dpm build complete
DAR set (1):
ยท asset-0.1.0 (518.5 KB)
โ Uploaded asset-0.1.0.dar (Ledger API)
โ Party allocated: Alice::1220โฆ
โ Party allocated: Bob::1220โฆ
โ User created: ledger-api-user
Setup:setup SUCCESS
Deploy summary
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
DARs: 1
Upload: Ledger API
Target: localhost
Vetted: yes
Deployment complete.
Step 4: Check the ledgerโ
List the parties on the participant:
dpm canton-deploy parties --network localnet --local
Alice::1220โฆ yes
Bob::1220โฆ yes
sandbox::1220โฆ yes
Confirm the package is on the participant:
dpm canton-deploy packages --network localnet
asset 0.1.0 ceda04eaโฆ
See the contract the setup script created:
dpm canton-deploy contracts --network localnet --template '#asset:Asset:Asset'
โ Found 1 active contract(s)
issuer: "Alice::1220โฆ"
owner: "Bob::1220โฆ"
name: "Quickstart Token"
quantity: "100.0000000000"
One Asset, issued by Alice and now owned by Bob. That is the whole loop: build, upload, onboard, seed, verify.
Deploy the same project to DevNet or TestNetโ
The steps above run entirely on your machine. To deploy to a DevNet or TestNet validator, nothing in the Daml project changes. Only the network does: you describe each validator once in canton-deploy.config.js, then pick it with --network.
The video walks through every step in this section on DevNet and TestNet validators:
You need the details of each validator: its host, the Ledger API and JSON API ports, whether it uses TLS, whether it sits behind Splice's nginx proxy, and a JWT for it.
Generate a config with initโ
init asks a few questions per network and writes canton-deploy.config.js:
dpm canton-deploy init
| Prompt | Answer |
|---|---|
canton-deploy.config.js already exists. Overwrite? | Yes. The quickstart ships with a config. To keep it, run init in a subfolder instead (mkdir init-demo && cd init-demo). |
Add a devnet profile in addition to localnet? | Yes |
Add a testnet profile? | Yes |
Add a mainnet profile? | No |
Use LocalNet defaults? | Yes |
Devnet: fetch JWT via OAuth2 client credentials? | No, unless your validator uses Auth0 M2M (see Authentication) |
Devnet: SSH local port forward to remote validator? | No |
Use devnet placeholder defaults? / Use testnet placeholder defaults? | No to enter your validator's host, ports, TLS, and token source now, or Yes to fill them in by hand afterwards |
For the token source, choose Token file. The config then points at a file, and no JWT is ever written into it.
Fill in your validatorsโ
Open canton-deploy.config.js. Each network is one profile. This is what a DevNet and a TestNet validator behind Splice's nginx proxy look like, with every API on one port and routed by name:
devnet: {
host: "<devnet-validator-host>",
adminPort: 81,
ledgerPort: 81,
httpPort: 81,
tls: false,
grpcAuthority: "grpc-ledger-api.localhost",
adminGrpcAuthority: "grpc-admin-api.localhost",
httpHost: "json-ledger-api.localhost",
uploadVia: "ledger",
tokenFile: "./.tokens/devnet.jwt",
vetOnUpload: true,
parties: ["Alice", "Bob"],
excludePackages: ["./asset-tests"],
},
testnet: {
host: "<testnet-validator-host>",
adminPort: 8080,
ledgerPort: 8080,
httpPort: 8080,
tls: false,
grpcAuthority: "grpc-ledger-api.localhost",
adminGrpcAuthority: "grpc-admin-api.localhost",
httpHost: "json-ledger-api.localhost",
uploadVia: "ledger",
tokenFile: "./.tokens/testnet.jwt",
vetOnUpload: false,
parties: ["Alice", "Bob"],
excludePackages: ["./asset-tests"],
},
grpcAuthority,adminGrpcAuthority, andhttpHostare needed only behind a name-routing proxy. For a validator with plain ports, leave them out.initasks forgrpcAuthoritybut nothttpHost, so addhttpHostby hand. See Remote Validators.partiesare allocated on deploy, andexcludePackageskeeps the test package out of the upload, as on LocalNet.uploadVia: "ledger"uploads through the Ledger API, so the Admin API does not need to be exposed.
Paste the DevNet JWT into .tokens/devnet.jwt and the TestNet JWT into .tokens/testnet.jwt, then check that they are valid:
dpm canton-deploy token --decode --network devnet
dpm canton-deploy token --decode --network testnet
Never commit .tokens/ or paste a JWT into canton-deploy.config.js. Add .tokens/ to .gitignore. For other token sources (tokenCommand, OAuth2), see Authentication.
Deploy to DevNetโ
Check the validator before changing anything:
dpm canton-deploy status --network devnet
โ Admin API <devnet-validator-host>:81
โ Ledger API <devnet-validator-host>:81
โ JSON API http://<devnet-validator-host>:81 (Host: json-ledger-api.localhost)
Ledger version: 3.5.8
Synchronizers (Admin):
โข global-domain::1220โฆ (HEALTH_HEALTHY)
Deploy. canton-deploy builds the project, uploads the asset DAR through the Ledger API, vets it, and allocates Alice and Bob:
dpm canton-deploy deploy --network devnet
Confirm the package and a party are on DevNet:
dpm canton-deploy packages --network devnet | grep asset
dpm canton-deploy parties --network devnet --filter-party Alice
On a shared network the full party list is long, so filter it with --filter-party.
Deploy the same project to TestNetโ
Change only --network:
dpm canton-deploy status --network testnet
โ Admin API <testnet-validator-host>:8080 (optional)
โ Ledger API <testnet-validator-host>:8080
โ JSON API http://<testnet-validator-host>:8080 (Host: json-ledger-api.localhost)
Ledger version: 3.5.16
The Admin API is optional. Uploads go through the Ledger API.
Deploy. The TestNet profile is upload-only (vetOnUpload: false), so the package is uploaded but not vetted:
dpm canton-deploy deploy --network testnet
โ Preflight OK for deploy (Ledger reachable)
โ dpm build complete
DAR set (1):
ยท asset-0.1.0 (518.5 KB)
โ Uploaded asset-0.1.0.dar (Ledger API)
โ Party exists: Alice::1220โฆ
โ Party exists: Bob::1220โฆ
Deploy summary
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
DARs: 1
Upload: Ledger API
Vetted: no (upload-only)
Deployment complete.
Confirm Alice is on TestNet:
dpm canton-deploy parties --network testnet --filter-party Alice
Upload-only is the safe default for shared networks: review the upload, then vet it with dpm canton-deploy vet --network testnet. vet goes through the Admin API, so the validator must expose it. If only the Ledger and JSON APIs are reachable, set vetOnUpload: true to vet during upload instead.
Running deploy again is safe: existing parties and packages are detected and skipped (Party exists).
Use canton-deploy in your own projectโ
From your own project root, the folder with daml.yaml or multi-package.yaml:
dpm add component oci://ghcr.io/lync-world/canton-deploy:0.2.2
dpm install package
dpm canton-deploy init
dpm canton-deploy deploy --network localnet
init asks a few questions and writes canton-deploy.config.js with one profile per network. See Getting Started for what it asks, and Configuration for every option.
Next stepsโ
- Deployment: CI with pre-built DARs (
--skip-build), vetting, and dry runs. - Authentication: MainNet with
tokenCommand(for example Vault) and TLS. - Commands: every
dpm canton-deploycommand. - Troubleshooting: common errors and fixes.