Canton DevNet · Low-Level Lab
Touching
the Ledger.
A session run entirely over the validator's low-level Admin and JSON Ledger APIs — no wallet, no dApp, no SDK on the machine. Authenticated, allocated an on-ledger party, and proved the read path end to end against a live Canton 3.5.5 node. What follows is the trace, the evidence, and feedback for the lab team.
The run
Authenticate
ConfirmedKeycloak client_credentials against the DevNet IdP returned a valid bearer token. Auth and base coordinates verified.
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwi… (ok)
Allocate a party
ConfirmedCreated a custodial party via POST /v2/parties. This is the on-ledger actor for every step that follows.
farmfort::12204e94c0e449c0efcd270dd1e68259c36471cebef132e5c7dfc2750fe8c9eed77f
Set up TransferPreapproval
Ready to runThe command path is built. Gated on the create-args for TransferPreapprovalProposal, which aren't in the lab sheet and can't be introspected without the DAR or a daml/dpm shell locally.
Read ledger state (ACS)
ConfirmedFull read path proven: /v2/state/ledger-end → /v2/state/active-contracts with an InterfaceFilter on the Holding interface. The empty result is the correct one — no holdings exist yet.
[] // clean — read path green
Receive Canton Coin
Awaiting teamBy design, not a faucet. On DevNet the validator operator distributes CC to guest parties, so this stage is a hand-off: partyId sent, awaiting the airdrop. Balance is empty until it lands.
Read balance (Holding UTXOs)
Ready to runBuilt on the same ACS path as S4, summing the Holding contracts. Gated only on S5 — and on Participant Query Store sync once the coin arrives.
Token Standard transfer
Ready to runFactory flow wired: fetch TransferFactory from the registry → exercise TransferFactory_Transfer with the returned context and disclosed contracts. Gated on the registry URL and the instrument-admin (DSO) party.
What got built
A reusable lab runner
One canton_lab.py (httpx) wrapping the whole flow — JWT refresh, party allocation, ACS-by-interface query, Holding-balance sum, a generic submit-and-wait command runner, and the transfer-factory exercise — driven by a subcommand per stage.
Shapes validated against the live node
Pulled /docs/openapi off the participant: node is Canton 3.5.5, and JsCommands → SubmitAndWaitResponse plus the /v2 paths match what the runner sends. No guessed request shapes left on the read path.
Feedback for the lab
Friction worth fixing
"Internal party" vs the external-party endpoints
The sheet says create an internal / custodial party, then routes to /v0/admin/external-party/topology/{generate,submit}. Those contradict. Worth one line stating which path is intended, and when each applies — it's the first place people stall.
Preapproval create-args aren't discoverable over REST
Anyone working purely through the JSON API — no daml/dpm installed — can't find the fields for TransferPreapprovalProposal. The OpenAPI doesn't carry Daml template fields. A sample create payload in the sheet would unblock it instantly.
Transfer prerequisites are missing
The Token Standard transfer needs the CC registry base URL and the DSO / instrument-admin party. Neither is in the sheet, and the registry-to-admin mapping has to be maintained client-side today. Pre-sharing both removes a hard blocker on the final step.
PQS lag isn't called out
After an airdrop, Holding contracts can take minutes (up to ~15) to surface while the Participant Query Store syncs. Without a note, an empty balance reads as a broken setup. One sentence saves a lot of confused retries.
Coin distribution is a manual round-trip
Routing every guest through the team for CC is a natural bottleneck under time pressure. A scoped DevNet faucet or self-mint for lab parties would let people self-serve through S5–S7 at their own pace.
What landed well
The "bare metal" framing earns its keep
Going under the wallet and dApp abstractions — party, topology, ACS, holdings, transfer — builds real intuition for what those layers actually hide. Exactly the right altitude for people who'll go on to build on Canton.
The mental model maps cleanly
Party = identity, validator = access layer, ACS = current truth, Holding = balance in pieces, Token Standard = reusable rail. That mapping is the most portable thing in the lab — keep it front and centre.
Live /docs/openapi is a great safety net
Being able to pull the node's own spec to confirm versions and request shapes mid-build is genuinely useful, and not obvious to first-timers. Worth pointing at explicitly in the sheet.