Quickstart
This quickstart takes you from a running ledger to a traced transaction: set up the example ledger, submit a command, trace it, and read it as another party. Before you start:
dpm traceis registered, as described in Register with DPM.- You have a clone of the repository, because the example ledger lives in
examples/:git clone https://github.com/walnuthq/dpm-trace.git && cd dpm-trace. - Java 17 or newer,
curl, andpython3are on yourPATH. - The Canton JAR is in the DPM cache. If it is missing, run
dpm install <sdk-version>, or setCANTON_JAR=<path-to-canton.jar>.
Set up the example ledger
The example used in these docs comes from examples/asset. Its Asset template has an issuer, an owner, a name, and a quantity. The owner can Split it into two assets, Transfer it, Withdraw part of its quantity, or Burn it.
To set everything up, run up.sh. It builds the DAR, starts a local Canton, uploads the DAR, creates the Issuer, Alice, and Bob parties, and exports their IDs. The network is defined in examples/devnet-trace.conf: two participants on one synchronizer, with Issuer and Alice on participant1 and Bob on participant2.
building the example DAR ...
Running single package build of asset-tests as no multi-package.yaml was found.
2026-10-19 08:42:04.88 [INFO] [build]
Compiling asset-tests to a DAR.
File: daml
Hidden: no
Range: 1:1-2:1
Source: Daml-LF typechecker
Severity: DsWarning
Message:
warning while type checking package:
The following dependencies are not used:
- 48c08b5fdedad07e9f4d8033732fa5d36714e2eec849b8b58c112bb2aa5da096 (daml-script, 3.5.1)
These dependencies can be removed from your daml.yaml
Upgrade this warning to an error -Werror=unused-dependency
Disable this warning entirely with -Wno-unused-dependency
2026-10-19 08:42:06.36 [INFO] [build]
Created .daml/dist/asset-tests-1.0.0.dar
starting Canton (pid 38754, log <path-to-dpm-trace>/examples/.up/canton.log) ...
export LEDGER='http://127.0.0.1:6113'
export LEDGER2='http://127.0.0.1:6123'
export ISSUER='Issuer::1220943998ef3c20591cb7d919a92935a8c290e3f010cab947845648e1b147ce337f'
export ALICE='Alice::1220943998ef3c20591cb7d919a92935a8c290e3f010cab947845648e1b147ce337f'
export BOB='Bob::12203f2834c8e8555dd26a1e0c564fcb76828079e70b89bc6cfea3d20260f633dbf9'
Canton is ready: participant1 http://127.0.0.1:6113, participant2 http://127.0.0.1:6123
To use in another shell run: source examples/.env
Stop Canton with: ./examples/up.sh down
The variables LEDGER, ISSUER, ALICE, and BOB are used in every example below. In a new shell, run source examples/.env to set them. When you are done, stop Canton with up.sh down.
If you prefer to set this up yourself, start a local ledger with dpm sandbox (or LocalNet for several validators), upload the DAR, and create the parties by following the Canton documentation.
Submit a command
To create an Asset as the issuer, we use dpm trace submit:
dpm trace submit \
--submitter "$LEDGER" \
--act-as "$ISSUER" \
--template '#asset-tests:Asset:Asset' \
--arg issuer="$ISSUER" --arg owner="$ALICE" \
--arg name=GOLD --arg quantity=100
Output is the update ID:
12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde
--submitter: URL of the participant's JSON Ledger API.--act-as: the party that submits the command.--template: the template to create, as#package:Module:Template.--arg: input field of the contract; in our exampleissuerisIssuer,ownerisAlice,nameis the name of the asset (GOLD), andquantityis how much of it there is (100).
Trace the update ID
Pass the update ID and the party to read as.
dpm trace 12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde \
--submitter "$LEDGER" --read-as "$ISSUER"
Both flags are required:
--submitter: the participant's JSON Ledger API URL, the ledger to read the update from. Without it,dpm tracehas no source and fails withchoose a source.--read-as: the party whose view of the update you want. Events and data that this party is not allowed to see are left out.
Canton trace
update: 12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde
source: ledger-json-api (http://127.0.0.1:6113/v2/updates/update-by-id)
offset: 25
record time: 2026-10-19T10:07:31.035562Z
synchronizer: local::1220694f7d09ada2d4393803880616b6129f6a381e81dadb9b1c247e7fd00fa27ea5
read-as: Issuer (Issuer::1220e92d...89b47e)
visibility: Authorized participant projection. Private data outside these party rights is not available.
events: +1 create, >0 exercise, x0 archive
parties:
Alice = Alice::1220e92d30a9b48e588f55405a4ab8e51a58a08d2ebd7864b58afb6082d5b489b47e
Issuer = Issuer::1220e92d30a9b48e588f55405a4ab8e51a58a08d2ebd7864b58afb6082d5b489b47e
Trace
└── [0] CREATE Asset:Asset
├── contract: 00a43a18ee777bbd4ebb3ba11c1fa71ed9d2fcfeced06d0b8504ee3c1487579...
├── signatories: Issuer
├── observers: Alice
├── witnesses: Issuer
└── payload: { issuer: Issuer, owner: Alice, name: GOLD, quantity: 100 }
The output has a header and a tree of events, where each event is one line, like [ID] KIND Template.Choice. See Reading trace output for the details of every field.
In this trace there is one CREATE event: Issuer signs it, and Alice sees it as an observer.
Read the update as another party
Read the same update as Alice.
dpm trace 12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde \
--submitter "$LEDGER" --read-as "$ALICE"
Output:
Canton trace
update: 12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde
source: ledger-json-api (http://127.0.0.1:6113/v2/updates/update-by-id)
offset: 25
record time: 2026-10-19T10:07:31.035562Z
synchronizer: local::1220694f7d09ada2d4393803880616b6129f6a381e81dadb9b1c247e7fd00fa27ea5
read-as: Alice (Alice::1220e92d...89b47e)
visibility: Authorized participant projection. Private data outside these party rights is not available.
events: +1 create, >0 exercise, x0 archive
parties:
Alice = Alice::1220e92d30a9b48e588f55405a4ab8e51a58a08d2ebd7864b58afb6082d5b489b47e
Issuer = Issuer::1220e92d30a9b48e588f55405a4ab8e51a58a08d2ebd7864b58afb6082d5b489b47e
Trace
└── [0] CREATE Asset:Asset
├── contract: 00a43a18ee777bbd4ebb3ba11c1fa71ed9d2fcfeced06d0b8504ee3c1487579...
├── signatories: Issuer
├── observers: Alice
├── witnesses: Alice
└── payload: { issuer: Issuer, owner: Alice, name: GOLD, quantity: 100 }
Alice is the owner of the asset, so she sees the same tree.
Now read the same update as Bob.
dpm trace 12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde \
--submitter "$LEDGER" --read-as "$BOB"
Output:
error: POST http://127.0.0.1:6113/v2/updates/update-by-id failed with HTTP 404: {"code":"UPDATE_NOT_FOUND","cause":"Update not found, or not visible.","correlationId":null,"traceId":"644bf607fad306c6fb13a2a58a828e28","context":{"participant":"participant1","definite_answer":"false","tid":"644bf607fad306c6fb13a2a58a828e28","category":"11"},"resources":[["ErrorResource(UPDATE_ID)","12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde"]],"errorCategory":11,"grpcCodeValue":5,"retryInfo":null,"definiteAnswer":null}
Bob is not a stakeholder of the asset, so the ledger returns UPDATE_NOT_FOUND: the update is not visible to him.
Bob is hosted on participant2, not on the participant behind LEDGER. To ask Bob's own participant, use LEDGER2:
dpm trace 12200833d9637f296c082ec1f6322f47cc8e7d934c1e27f10c76aa017abba1d14fde \
--submitter "$LEDGER2" --read-as "$BOB"
The answer is the same UPDATE_NOT_FOUND, now with "participant":"participant2" in its context, because even on his own participant Bob is not a stakeholder of this asset.