Build a Canton Institutional Repo Agent
Tri-party repo is the canonical pattern that tier-1 banks and central counterparties use for collateralised short-term funding, and it's the workflow that justifies running an institutional ledger like Canton in the first place. This tutorial walks you through the full five-leg repo lifecycle — counterparty onboarding, collateral pledge, cash leg, intra-day margin call, and reverse repo at maturity — entirely from the Tenzro CLI and curl JSON-RPC calls. No Rust source code required. The AgentKit template ref-canton-trade-settler-v1 handles the delegation-gated dispatch, and Canton/DAML operations go through tenzro canton subcommands and tenzro_submitDamlCommand RPC calls.
What You'll Build
- An institutional client identity at KYC tier
Full(the highest tier) - A trading desk agent with a $50M per-leg cap, $200M daily cap, and a 1-day time bound
- A spawned AgentKit template (
ref-canton-trade-settler-v1) configured for tri-party repo - Five DAML
Createcommands modelling the full repo lifecycle, dispatched viatenzro canton submit - An oversized $75M repo rejection demo that proves the per-leg cap is binding
Why Canton for Institutional Workflows
Repo, securities lending, and OTC derivatives all share the same shape: multi-party contracts, strict privacy boundaries between counterparties, deterministic settlement against an institutional schedule, and zero tolerance for double-spend or replay. Public EVMs can't meet those constraints natively — every contract is visible to every node and the consensus latency is wrong by orders of magnitude for intra-day margin processes. Canton was designed specifically for this market, and DAML is the smart-contract language that drives it. Tenzro's contribution is to put a real DelegationScopein front of every Canton dispatch so a misbehaving trading desk agent can't exceed its risk budget regardless of how clever the strategy logic happens to be on any given day.
Prerequisites. You need a running Tenzro node (local or testnet). If you don't have one yet, run tenzro join to bootstrap your identity, wallet, and hardware profile in one step. All CLI commands below assume the node is reachable at http://localhost:8545 (the default JSON-RPC address). Canton operations work in graceful-degradation mode — when no Canton participant is live, commands are validated and serialized but the ledger dispatch is skipped.
Step 1: Join the Network
If you haven't already, bootstrap your local node identity and wallet with a single command:
tenzro joinThis provisions an Ed25519 keypair, registers a TDIP identity, creates an MPC wallet, and detects your hardware profile. The output includes your DID and wallet address — you'll need these in the next steps.
Step 2: Register the Institutional Client Identity
Institutional repo desks operate under the strictest counterparty due diligence requirements in finance. Register a human identity at KYC tier Full (the highest tier):
tenzro identity register \
--type human \
--display-name "Acme Asset Management" \
--kyc-tier fullThe output returns a DID in the form did:tenzro:human:<uuid>. Save this — it becomes the controller DID for the trading desk agent. You can verify the registration at any time:
tenzro identity resolve --did did:tenzro:human:<your-uuid>Step 3: Register the Trading Desk Agent with Delegation Scope
The trading desk agent is a machine identity controlled by the institutional client. The delegation scope enforces a $50M per-leg cap, $200M daily cap, and restricts the agent to repo-related operations with a 1-day time bound — matching how trading desks actually authorise their machines every morning:
tenzro identity register \
--type machine \
--controller did:tenzro:human:<your-uuid> \
--capabilities repo,canton,fixed-income \
--max-transaction-value 5000000000000000 \
--max-daily-spend 20000000000000000 \
--allowed-operations repo,collateral-pledge,margin-call,reverse-repo \
--time-bound-days 1The values are in the smallest unit (cents with 8 zeros: $50M = 5,000,000,000,000,000 in base units). The output returns a machine DID like did:tenzro:machine:<controller>:<uuid>. The 1-day time bound is deliberately shorter than the 7-day window used in cross-chain arbitrage tutorials — institutional desk delegations are refreshed every morning by the human risk officer.
Verify the delegation scope is set correctly:
tenzro identity document --did did:tenzro:machine:<controller>:<agent-uuid>Step 4: Check Canton Domain Availability
Before dispatching DAML commands, verify which Canton domains are reachable from your node:
tenzro canton domainsOr via JSON-RPC:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_listCantonDomains",
"params": [],
"id": 1
}' | jq .When no Canton participant is live (the dev default), the response returns an empty list or a status indicating offline mode. All subsequent DAML commands will still be validated and serialized — the only thing skipped is the actual ledger dispatch. This graceful-degradation path means you can iterate on the lifecycle locally without standing up a full Canton stack.
Step 5: Spawn the AgentKit Canton Trade Settler
The AgentKit template ref-canton-trade-settler-v1 is the primary vehicle for this workflow. It auto-discovers trade opportunities, submits DAML Create commands, and exercises settlement choices — all with delegation-gated hard caps. First, inspect the template:
tenzro agent get-template --template-id ref-canton-trade-settler-v1Then spawn an instance bound to your trading desk agent identity:
tenzro agent spawn-template \
--template-id ref-canton-trade-settler-v1 \
--agent-did did:tenzro:machine:<controller>:<agent-uuid> \
--params '{
"triparty_agent": "BNYM-Triparty",
"cash_buyer": "Goldman-Repo-Desk",
"collateral_seller": "Acme-AM",
"canton_host": "localhost",
"canton_port": 5001,
"max_leg_notional_usd": 50000000,
"max_daily_notional_usd": 200000000,
"repo_rate_bps": 525,
"haircut_bps": 200
}'The spawn command creates a running agent instance that is bound to the machine DID's delegation scope. The params object configures the three repo parties, the Canton participant address, and the risk parameters. The agent enforces the per-leg cap from the delegation scope — it physically cannot dispatch a DAML command whose notional exceeds $50M, regardless of what the template logic requests.
Step 6: Execute the Five-Leg Repo Lifecycle
Run the template to execute the full tri-party repo lifecycle:
tenzro agent run-template \
--template-id ref-canton-trade-settler-v1 \
--agent-did did:tenzro:machine:<controller>:<agent-uuid> \
--action execute-lifecycleThe agent executes five legs in order. You can also dispatch each leg individually using tenzro canton submit or curl. Below is each leg explained with its corresponding manual command.
Leg 1: Counterparty Onboarding (KYC)
The KYC contract establishes the relationships between the three parties. It has zero notional — no money is moving — so the delegation check is skipped:
tenzro canton submit \
--command-type create \
--template-module Onboarding \
--template-entity KycContract \
--args '{
"client": {"party": "Acme-AM"},
"counterparty": {"party": "Goldman-Repo-Desk"},
"triparty": {"party": "BNYM-Triparty"},
"kycLevel": {"text": "Tier3-Full"},
"jurisdiction": {"text": "US"}
}'Or via JSON-RPC:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_submitDamlCommand",
"params": [{
"command_type": "create",
"template_id": {"module": "Onboarding", "entity": "KycContract"},
"arguments": {
"client": {"party": "Acme-AM"},
"counterparty": {"party": "Goldman-Repo-Desk"},
"triparty": {"party": "BNYM-Triparty"},
"kycLevel": {"text": "Tier3-Full"},
"jurisdiction": {"text": "US"}
}
}],
"id": 2
}' | jq .Leg 2: Collateral Pledge (UST-10Y, $25M Face Value)
The collateral pledge locks the bond into the custodian. The notional is $25M, which passes the $50M per-leg delegation cap:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_submitDamlCommand",
"params": [{
"command_type": "create",
"template_id": {"module": "Repo", "entity": "CollateralPledge"},
"arguments": {
"pledger": {"party": "Acme-AM"},
"custodian": {"party": "BNYM-Triparty"},
"isin": {"text": "US91282CKY32"},
"faceValueCents": {"int": 2500000000},
"haircutBps": {"int": 200},
"maturityDate": {"text": "2034-11-15"}
}
}],
"id": 3
}' | jq .The ISIN US91282CKY32 identifies a real 10-year US Treasury. The 2% haircut (200 basis points) means the cash buyer lends less than the face value of the bond, providing a margin of safety. The face value is expressed in cents: $25,000,000 x 100 = 2,500,000,000.
Leg 3: Cash Leg ($24.5M After Haircut)
The actual purchase: $24.5M (the $25M face value minus the 2% haircut), at 5.25% repo rate for a 7-day tenor:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_submitDamlCommand",
"params": [{
"command_type": "create",
"template_id": {"module": "Repo", "entity": "CashLeg"},
"arguments": {
"payer": {"party": "Goldman-Repo-Desk"},
"payee": {"party": "Acme-AM"},
"triparty": {"party": "BNYM-Triparty"},
"amountCents": {"int": 2450000000},
"currency": {"text": "USD"},
"repoRateBps": {"int": 525},
"tenorDays": {"int": 7}
}
}],
"id": 4
}' | jq .Leg 4: Intra-Day Margin Call ($500K Top-Up)
When the collateral's market value drops 0.20%, the tri-party agent issues a $500K top-up call against the pledger:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_submitDamlCommand",
"params": [{
"command_type": "create",
"template_id": {"module": "Repo", "entity": "MarginCall"},
"arguments": {
"triparty": {"party": "BNYM-Triparty"},
"pledger": {"party": "Acme-AM"},
"topUpCents": {"int": 50000000},
"reason": {"text": "MTM markdown 0.20%"}
}
}],
"id": 5
}' | jq .Leg 5: Reverse Repo (Collateral Return + Cash + Interest)
The final leg closes the trade at maturity. The principal is returned, and the interest amount of approximately $25,010 (computed as $24.5M x 5.25% x 7/360 on an actual/360 day-count basis) is paid alongside it:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_submitDamlCommand",
"params": [{
"command_type": "create",
"template_id": {"module": "Repo", "entity": "ReverseRepo"},
"arguments": {
"triparty": {"party": "BNYM-Triparty"},
"cashBuyer": {"party": "Goldman-Repo-Desk"},
"collateralSeller": {"party": "Acme-AM"},
"principalCents": {"int": 2450000000},
"interestCents": {"int": 2501000},
"settlementDate": {"text": "2026-04-14"}
}
}],
"id": 6
}' | jq .Cross-VM token visibility. CIP-56 assets created via canton_create_asset are automatically registered in the unified TokenRegistry, making them queryable from the EVM (via the wTNZO pointer contract) and the SVM (via the SPL adapter). This means institutional RWA tokens issued on Canton can be accessed by EVM-based DeFi protocols without bridging or liquidity fragmentation. See the cross-VM token architecture documentation for details.
Step 7: Verify Contracts on the Canton Domain
After dispatching the lifecycle, verify the contracts that were created:
tenzro canton contractsOr via JSON-RPC:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_listDamlContracts",
"params": [],
"id": 7
}' | jq .The response includes all active DAML contracts on the domain — you should see the five contracts from the lifecycle (KycContract, CollateralPledge, CashLeg, MarginCall, ReverseRepo) along with their contract IDs, template IDs, and signatories.
Step 8: Confirm the Per-Leg Cap Is Binding
The delegation scope on the trading desk agent enforces a hard $50M per-leg cap. To prove this is binding, attempt to submit a $75M repo — 1.5x the cap:
tenzro agent run-template \
--template-id ref-canton-trade-settler-v1 \
--agent-did did:tenzro:machine:<controller>:<agent-uuid> \
--action test-cap \
--params '{"notional_usd": 75000000}'The agent returns a DelegationViolation error. You can also verify this directly via the identity enforcement endpoint:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_submitDamlCommand",
"params": [{
"command_type": "create",
"template_id": {"module": "Repo", "entity": "CashLeg"},
"arguments": {
"payer": {"party": "Goldman-Repo-Desk"},
"payee": {"party": "Acme-AM"},
"triparty": {"party": "BNYM-Triparty"},
"amountCents": {"int": 7500000000},
"currency": {"text": "USD"},
"repoRateBps": {"int": 525},
"tenorDays": {"int": 7}
},
"agent_did": "did:tenzro:machine:<controller>:<agent-uuid>"
}],
"id": 8
}' | jq .The response contains a typed DelegationViolation error. In an institutional setting, this is the boundary that the desk's risk officer relies on: the agent literally cannot ask Canton to settle a trade larger than the cap, regardless of whether the strategy logic would have wanted to.
Step 9: Monitor the Agent
Check the agent's available templates and status at any time:
# List all available AgentKit templates
tenzro agent list-templates
# Check running agent status
tenzro agent run-template \
--template-id ref-canton-trade-settler-v1 \
--agent-did did:tenzro:machine:<controller>:<agent-uuid> \
--action statusThe status action returns the agent's lifecycle state, the number of legs dispatched, total notional processed, delegation budget remaining, and Canton connectivity status.
What You Learned
- Institutional KYC tiering — TDIP's
Fulltier for the top-tier counterparty due diligence requirement, set viatenzro identity register --kyc-tier full - Daily-refresh delegation — 1-day time bound matching how trading desks actually authorise their machines, configured at identity registration
- AgentKit templates — spawning and running
ref-canton-trade-settler-v1withtenzro agent spawn-templateandtenzro agent run-template - DAML command dispatch — submitting Create commands via
tenzro canton submitandtenzro_submitDamlCommandJSON-RPC - Tri-party repo lifecycle — onboarding, pledge, cash, margin call, and reverse repo modelled as five DAML contracts
- Delegation enforcement — the $50M per-leg cap is binding and produces a typed
DelegationViolationerror on breach - Canton graceful degradation — all commands are validated and serialized even when the Canton participant is offline
- Cross-VM token visibility — Canton-issued CIP-56 assets are registered in the unified TokenRegistry and accessible from EVM and SVM without bridging
CLI Quick Reference
| Operation | Command |
|---|---|
| Bootstrap node | tenzro join |
| Register identity | tenzro identity register --type human --kyc-tier full |
| Register agent | tenzro identity register --type machine --controller <did> |
| List Canton domains | tenzro canton domains |
| Submit DAML command | tenzro canton submit --command-type create ... |
| List DAML contracts | tenzro canton contracts |
| Browse templates | tenzro agent list-templates |
| Spawn agent | tenzro agent spawn-template --template-id <id> |
| Run agent action | tenzro agent run-template --action <action> |
Next Steps
- Read the VM Workflows tutorial for a deeper look at how Tenzro dispatches across EVM, SVM, and DAML
- See the Cross-Chain Arbitrage tutorial for the EVM + bridge equivalent of this multi-leg pattern
- Read the Agentic Commerce Workflows tutorial for the full set of MPP / x402 / settlement patterns