Canton Builder Tool
One command to run and deploy on a Canton Network LocalNet in your system. Built for anyone who needs a local Canton Network without waiting for DevNet whitelisting giving three validators, wallet UIs, Canton Coin, Scan UI, the whole official Splice LocalNet stack.
Install
macOS / Linux (WSL 2 on Windows):
curl -fsSL https://raw.githubusercontent.com/canton-network-devs/Canton-Builder-Tool/main/install.sh | bash
Then reload your shell:
source ~/.zshrc # zsh
source ~/.bashrc # bash
Requirements
- Docker Desktop with ≥ 8 GB memory allocated
curl,jq,gitviabrew(macOS) orapt(Linux)- macOS or Linux only. Windows: use WSL 2.
The installer handles PATH setup and /etc/hosts entries for *.localhost domains.
Commands
canton builder start # download bundle + boot LocalNet
canton builder stop # stop containers (data preserved)
canton builder status # health check + port reference
canton builder deploy ./my-app-0.0.1.dar # upload your DAR to both participants
canton builder logs # tail all logs
canton builder logs <service> # tail one service
canton builder reset # wipe everything, start clean
What starts
| Service | URL | Credential |
|---|---|---|
| App User Wallet UI | wallet.localhost:2000 | app-user |
| App Provider Wallet UI | wallet.localhost:3000 | app-provider |
| Scan UI | scan.localhost:4000 | — |
| SV UI | sv.localhost:4000 | sv |
| App Provider JSON API | localhost:3975 | — |
| App User JSON API | localhost:2975 | — |
| SV JSON API | localhost:4975 | — |
| App Provider Ledger API (gRPC) | localhost:3901 | — |
| App User Ledger API (gRPC) | localhost:2901 | — |
| SV Ledger API (gRPC) | localhost:4901 | — |
| PostgreSQL | localhost:5432 | — |
First run
On canton builder start, the tool:
- Downloads the official Splice LocalNet bundle from the Splice GitHub release, cached at
~/.canton-builder/bundle/ - Pulls the Canton/Splice Docker images (~5 min, also cached)
- Boots the full network using the official LocalNet compose configuration
Subsequent runs skip steps 1 and 2 entirely and boot in ~30 seconds.
Deploying your DAR
Build your Daml project with dpm build, then:
canton builder deploy ./your-project/.daml/dist/your-project-0.0.1.dar
Uploads your DAR to both the App Provider and App User participants, retrieves your package ID, and prints the template ID format for API calls. Once deployed, use the JSON Ledger API to create contracts, exercise choices, and query state.
What it is / isn't
Is: a CLI wrapping the official Splice LocalNet: the same Docker Compose configuration Ships with every Splice release, invoked with the exact commands from the official docs. No custom compose files, no approximations.
Isn't: a replacement for cn-quickstart. Quickstart is a full developer project template with a reference app, Java backend, and React frontend. This tool is just the network layer to bring your own Daml project.
Troubleshooting
First run is slow
Normal: the Splice bundle and Docker images download on first run. Everything is cached after that.
Containers crash on startup
Docker memory. Docker Desktop → Settings → Resources → Memory → set to 8 GB minimum.
*.localhost domains don't resolve
echo "127.0.0.1 wallet.localhost scan.localhost sv.localhost" | sudo tee -a /etc/hosts
Weird state / things not working
canton builder reset
canton builder start
See what's failing
canton builder logs
canton builder logs canton
canton builder logs splice
Re-download the bundle (if corrupted or upgrading)
rm -rf ~/.canton-builder/bundle
canton builder start
Upgrading LocalNet version
Edit ~/.canton-builder/.env and change IMAGE_TAG:
IMAGE_TAG=0.5.11
Then reset and restart:
canton builder reset
rm -rf ~/.canton-builder/bundle
canton builder start
Deploy your Daml project on Canton LocalNet
You showed up at a hackathon (or a bootcamp). You've written some Daml, built your .dar, and now you're staring at the Canton docs wondering where to even begin. This is the walkthrough that fills that gap of real validators, a real synchronizer, real wallets, no DevNet whitelisting, no waiting. macOS and Linux only; Windows users can use WSL 2.
What you're actually running
Canton LocalNet is a full Canton Network running in Docker. It's built into the cn-quickstart repository from Digital Asset, which pairs LocalNet with a reference licensing app you'll partially replace with your own project.
| Role | What it actually is | Why you care |
|---|---|---|
| Synchronizer | Sequences and orders transactions | Already running inside LocalNet |
| Validator | Runs a participant node + wallet | LocalNet gives you three of them |
| Party | A named identity on the ledger like an address | Your Daml contracts sit between parties |
LocalNet gives you three validators: app-provider, app-user, and sv (Super Validator). Treat app-provider as your app or company and app-user as a customer; sv runs the infrastructure. For most hackathon projects you'll deploy your DAR to app-provider and have parties on both app-provider and app-user interact as a multi-party workflow.
make setup will ask). It's required for the wallet UI and Canton Coin transfers. Just say yes and move on.Prerequisites checklist
- Docker Desktop installed and running (
docker inforeturns something, not an error) - Docker Desktop has at least 8 GB of memory allocated (Settings → Resources → Memory)
- Git installed
dpminstalled and in your PATH as you need it to build your project- Your Daml project has a
daml.yamland source files ready - Docker Hub account, and you're logged in via
docker login
dpm version first; if it errors, your dpm bin isn't on PATH.1Clone cn-quickstart
git clone https://github.com/digital-asset/cn-quickstart.git
cd cn-quickstart
Now move into the quickstart directory, this is where you'll live for the rest of the guide:
cd quickstart
quickstart/. The Makefile lives here — if a make command errors with "no Makefile found," you're in the wrong directory.2Configure LocalNet
make setup
Answer the prompts exactly like this:
| Prompt | Answer |
|---|---|
| Enable Observability? | n |
| Enable OAuth2? | y |
| Party hint | just press Enter |
| Enable TEST MODE? | n |
This writes a .env.local file. You can re-run make setup any time you need to change these.
3Add your Daml project to the quickstart
This is the part nobody explains. Here's how your contracts actually get into LocalNet.
3a · Where to put your Daml files
The quickstart's Daml code lives in quickstart/daml/licensing/: the reference licensing app. You have two options.
Option A: add alongside the existing app (recommended for hackathons). Create a new directory for your project inside daml/:
quickstart/
daml/
licensing/ ← the existing reference app, leave this alone
your-project/ ← create this
daml/
YourModule.daml
daml.yaml
You keep the reference app intact for reference and add your own project as a separate package.
Option B: replace the existing app. Delete the contents of daml/licensing/ and drop your project files there, updating paths in the steps below accordingly. Only do this if you're confident you won't need the reference app.
3b · Set up your daml.yaml
If you built your project with dpm, you already have a daml.yaml just make sure its SDK version matches the quickstart's.
cat .env | grep DAML_SDK_VERSION
# daml.yaml
sdk-version: 3.x.x # should match what you just found
If they don't match, update your daml.yaml to the quickstart's SDK version.
3c · Register your package in multi-package.yaml
The quickstart builds all Daml packages together via quickstart/daml/multi-package.yaml. Add your project:
packages:
- licensing # existing reference app
- your-project # add this line (relative to daml/)
3d · Build everything
make build
Compiles all Daml packages, generates Java bindings for the backend, and builds the frontend. Your compiled DAR lands at:
quickstart/daml/your-project/.daml/dist/your-project-<version>.dar
daml.yaml. If your project depends on Splice DARs (Canton Coin interfaces etc.), check daml/dars/: the quickstart bundles them there.4Start LocalNet
In a second terminal, start log collection and keep it running the whole time:
cd quickstart
make capture-logs
Back in your first terminal:
make start
This spins up the whole Canton Network locally around five minutes the first time. Once the flood of log output settles, you're up.
5Upload your DAR to LocalNet
LocalNet is running, but your contracts aren't on it yet, you need to upload your DAR to the participant nodes that will use it.
5a · Get an admin token
The JSON API needs an auth token. Grab the App Provider's:
export PROVIDER_ADMIN_TOKEN=$(curl -fsS \
"http://keycloak.localhost:8082/realms/AppProvider/protocol/openid-connect/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=app-provider-validator' \
-d 'client_secret=6m12QyyGl81d9nABWQXMycZdXho6ejEX' \
-d 'grant_type=client_credentials' \
-d 'scope=openid' | jq -r .access_token)
echo $PROVIDER_ADMIN_TOKEN # should print a long JWT string, not empty
jq usually means Keycloak is still starting wait a minute and retry.5b · Upload your DAR
# Replace the path with your actual DAR file path
curl -X POST http://localhost:3975/v2/packages \
-H "Authorization: Bearer $PROVIDER_ADMIN_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @./daml/your-project/.daml/dist/your-project-0.0.1.dar
A {} response means success — your contracts are deployed on the App Provider's participant node.
If your contracts involve multiple parties across both App Provider and App User, upload to the App User node too:
export USER_ADMIN_TOKEN=$(curl -fsS \
"http://keycloak.localhost:8082/realms/AppUser/protocol/openid-connect/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=app-user-validator' \
-d 'client_secret=6m12QyyGl81d9nABWQXMycZdXho6ejEX' \
-d 'grant_type=client_credentials' \
-d 'scope=openid' | jq -r .access_token)
curl -X POST http://localhost:2975/v2/packages \
-H "Authorization: Bearer $USER_ADMIN_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @./daml/your-project/.daml/dist/your-project-0.0.1.dar
5c · Get your package ID
You'll need this to create contracts via the API:
dpm damlc inspect-dar ./daml/your-project/.daml/dist/your-project-0.0.1.dar
Find the line with your project name and no -dalf extension, it looks like your-project-0.0.1-<64-char-hex-string>. That hex string is your package ID:
export PACKAGE_ID=$(dpm damlc inspect-dar ./daml/your-project/.daml/dist/your-project-0.0.1.dar \
| grep "your-project-0.0.1-" | grep -v "dalf" | tail -1 | awk '{print $2}' | tr -d '"')
echo $PACKAGE_ID
6Discover your party IDs
Parties in Canton have long identifier strings, not just names. Grab them:
# App Provider party
APP_PROVIDER_PARTY=$(curl -s -H "Authorization: Bearer $PROVIDER_ADMIN_TOKEN" \
http://localhost:3975/v2/parties | \
jq -r '.partyDetails[] | select(.party | startswith("app_provider_quickstart-")) | .party')
echo "Provider: $APP_PROVIDER_PARTY"
# App User party
APP_USER_PARTY=$(curl -s -H "Authorization: Bearer $USER_ADMIN_TOKEN" \
http://localhost:2975/v2/parties | \
jq -r '.partyDetails[] | select(.party | startswith("app_user_quickstart-")) | .party')
echo "User: $APP_USER_PARTY"
You'll get back something like app_provider_quickstart-1::122045abc... — that full string is the party identifier, used everywhere a party is needed in API calls.
7Create a contract
Now the good part of creating a contract on LocalNet via the JSON Ledger API. This example assumes a template like:
template MyContract
with
provider : Party
user : Party
someData : Text
where
signatory provider
observer user
Create it:
curl -X POST http://localhost:3975/v2/commands/submit-and-wait \
-H "Authorization: Bearer $PROVIDER_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"commands\": [{
\"CreateCommand\": {
\"template_id\": \"${PACKAGE_ID}:YourModule:MyContract\",
\"create_arguments\": {
\"provider\": \"$APP_PROVIDER_PARTY\",
\"user\": \"$APP_USER_PARTY\",
\"someData\": \"hello from hackathon\"
}
}
}],
\"act_as\": [\"$APP_PROVIDER_PARTY\"],
\"read_as\": [\"$APP_PROVIDER_PARTY\"]
}"
A response containing a transaction_id means your contract is live on LocalNet.
<packageId>:<ModuleName>:<TemplateName> — note the colons, not dots.8Tap Canton Coin and test transfers
LocalNet has full wallet functionality, with test CC available via the Wallet UI's Tap option.
Open the wallet UIs
- App User Wallet —
wallet.localhost:2000 - App Provider Wallet —
wallet.localhost:3000
Log in with app-user / abc123 or app-provider / abc123. If those show invalid, log into the main admin Keycloak (admin / admin) and set a new password from the Users tab.
Tap CC (App User)
Find the Tap button, add an amount in USD (say, 500), it shows up in the balance almost immediately, converted into CC.
Send CC between parties
In the App User wallet: click Send → paste the App Provider's party ID from Step 6 → enter an amount → confirm. The transfer appears in both wallets real Canton Coin mechanics, privacy preserving, mediated by the local synchronizer.
9Interact via Daml Shell
A REPL for your LocalNet great for exercising choices interactively without writing curl commands.
make shell
You'll drop into an interactive shell connected to the App Provider's participant, where you can query active contracts, exercise choices, and inspect ledger state.
daml> import DA.List
daml> -- your Daml expressions here
Resetting and starting clean
Things will break. That's fine here's how to wipe and start fresh:
make stop # stop all containers
make clean-all # remove data, volumes, everything
make build # rebuild
make start # start fresh
make clean-all nukes all LocalNet ledger data, party registrations, and CC balances, a full reset. Use it whenever you hit a weird state you can't debug.Port reference
Bookmark this as you'll refer to it constantly.
| Service | URL / Port | What it is |
|---|---|---|
| App User JSON API | localhost:2975 | Submit commands, query as App User |
| App Provider JSON API | localhost:3975 | Submit commands, query as App Provider |
| SV JSON API | localhost:4975 | Super Validator, rarely needed directly |
| App User Ledger API (gRPC) | localhost:2901 | Lower-level gRPC access |
| App Provider Ledger API (gRPC) | localhost:3901 | Lower-level gRPC access |
| App User Wallet UI | wallet.localhost:2000 | Wallet for App User |
| App Provider Wallet UI | wallet.localhost:3000 | Wallet for App Provider |
| Scan UI | scan.localhost:4000 | Network transaction explorer |
| Keycloak | keycloak.localhost:8082 | Auth server |
Common issues
| Symptom | Fix |
|---|---|
| "Container failed to start" | Almost always a memory issue: 8 GB minimum, 12 GB if you can spare it |
| "401 Unauthorized" on API calls | Token expired: re-run the token export from Step 5a |
| "Empty reply from server" on DAR upload | Participant isn't ready yet: wait 30 seconds and retry |
| "409 Conflict" on DAR upload | You already uploaded that DAR: it's idempotent, the package is already there |
| Wallet UI blank / can't log in | Keycloak still starting: wait, refresh, or make stop && make clean-all && make start |
make build fails with SDK mismatch | Match your daml.yaml SDK version to DAML_SDK_VERSION in the quickstart's .env |
Quick reference: the commands you'll use most
make install-daml-sdk # install the pinned Daml SDK
make setup # configure LocalNet
make build # compile everything (Daml + backend + frontend)
make capture-logs # start log collection (separate terminal)
make start # start LocalNet
make stop # stop LocalNet
make clean-all # full reset
make canton-console # open Canton Console (admin REPL)
make shell # open Daml Shell (contract REPL)
make help # see all available commands