Skip to main content

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: an Asset template and a Setup script that issues a token to Alice and transfers it to Bob.
  • asset-tests: tests for asset. 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.

tip

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:

  1. Builds both packages with dpm build --all.
  2. Drops asset-tests from the upload, because the config lists it under excludePackages.
  3. Uploads the asset DAR through the Ledger API and vets it.
  4. Allocates the parties Alice and Bob.
  5. Creates the user ledger-api-user with rights to act as both.
  6. Runs the Setup:setup Daml 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
PromptAnswer
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, and httpHost are needed only behind a name-routing proxy. For a validator with plain ports, leave them out. init asks for grpcAuthority but not httpHost, so add httpHost by hand. See Remote Validators.
  • parties are allocated on deploy, and excludePackages keeps 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
warning

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โ€‹