Canton Enterprise DvP Settlement & RWA Tokenization
Build an institutional DeFi agent on Canton (DAML 3.x) using the Tenzro Rust SDK and the Canton MCP tools. This tutorial covers the complete lifecycle of a Delivery-vs-Payment transaction: allocating DAML parties, tokenizing a US Treasury bond via CIP-56, funding participants with Canton Coin, executing an atomic DvP settlement where both legs (delivery + payment) settle or neither does, and finalizing the record on the Tenzro Ledger with a verifiable credential.
What You'll Build
- A TDIP identity and MPC wallet via the Rust SDK
- An enterprise agent with Canton DvP capabilities
- DAML party allocation for buyer and seller counterparties
- Real-world asset (RWA) tokenization of a US Treasury bond via CIP-56
- Canton Coin balance queries and transfers
- Atomic Delivery-vs-Payment settlement (bond + cash, all-or-nothing)
- DAML escrow contract creation and lifecycle management
- Contract event queries and fee schedule inspection
- DAR package upload for custom settlement logic
- Settlement finality on the Tenzro Ledger with a verifiable credential
Canton MCP Tools Used
This tutorial uses all 14 tools available on the Canton MCP server (port 3005):
canton_get_health— participant health checkcanton_list_domains— available synchronizer domainscanton_allocate_party— create DAML partiescanton_list_parties— list all partiescanton_create_asset— CIP-56 asset tokenizationcanton_get_balance— Canton Coin balancescanton_transfer— Canton Coin transferscanton_dvp_settle— atomic Delivery-vs-Paymentcanton_submit_command— DAML create/exercise commandscanton_list_contracts— query active contractscanton_get_events— contract event streamcanton_get_fee_schedule— synchronizer feescanton_upload_dar— upload DAML packagescanton_get_transaction— transaction lookup
What is Delivery-vs-Payment?
DvP (Delivery-vs-Payment) is the gold standard for securities settlement. It guarantees that the delivery of the security (e.g., a bond) and the payment (e.g., cash) happen atomically — either both legs complete or neither does. This eliminates counterparty risk: the buyer never pays without receiving the bond, and the seller never delivers without receiving payment. Canton's DAML runtime enforces this atomicity at the smart contract level, and CIP-56 provides the tokenization standard for the underlying assets. CIP-56 tokens used in DvP flows are cross-VM accessible via the Sei V2 pointer model — the same asset is visible as an ERC-20 on EVM, an SPL token on SVM, and a CIP-56 holding on Canton, all sharing a single native balance.
Prerequisites
[dependencies]
tenzro-sdk = "0.1"
tokio = { version = "1", features = ["full"] }
tracing-subscriber = "0.3"
uuid = { version = "1", features = ["v4"] }
serde_json = "1"Step 1: Connect & Provision Identity
use tenzro_sdk::{TenzroClient, SettlementRequest, config::SdkConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = SdkConfig::testnet();
let client = TenzroClient::connect(config).await?;// Register a human identity via TDIP
let identity = client.identity().register_human("Canton Fund Manager").await?;
println!("DID: {}", identity.did);
// Create an MPC wallet
let wallet = client.wallet().create_wallet().await?;
println!("Wallet: {}", wallet.address);Step 2: Register the Canton Agent
// Register the Canton DvP agent
let agent = client.agent().register(
"canton-dvp-agent",
"Canton DvP Settler",
&["enterprise", "canton", "daml", "dvp"],
).await?;
println!("Agent ID: {}", agent.agent_id);Step 3: Check Canton Health & Domains
// Check Canton network health and available domains
let health = client.agent().send_message(
&agent.agent_id,
"Use canton_get_health tool",
).await?;
println!("Canton health: {}", health.payload);
let domains = client.agent().send_message(
&agent.agent_id,
"Use canton_list_domains tool",
).await?;
println!("Domains: {}", domains.payload);Canton health: { "status": "healthy", "participant": "tenzro-participant-1", "api_version": "v2" }
Domains: [
{ "id": "tenzro-domain-1", "status": "active", "participants": 3 },
{ "id": "global-domain", "status": "active", "participants": 12 }
]Step 4: Allocate DAML Parties
Every Canton transaction involves parties. Allocate a buyer and seller party for the DvP:
// Allocate DAML parties for the transaction
let buyer = client.agent().send_message(
&agent.agent_id,
"Use canton_allocate_party tool: \
party_id_hint=buyer-fund-a, \
display_name=Fund A (Buyer)",
).await?;
println!("Buyer party: {}", buyer.payload);
let seller = client.agent().send_message(
&agent.agent_id,
"Use canton_allocate_party tool: \
party_id_hint=seller-bank-b, \
display_name=Bank B (Seller)",
).await?;
println!("Seller party: {}", seller.payload);
// List all allocated parties
let parties = client.agent().send_message(
&agent.agent_id,
"Use canton_list_parties tool",
).await?;
println!("All parties: {}", parties.payload);Buyer party: { "party_id": "buyer-fund-a::tenzro-participant-1", "display_name": "Fund A (Buyer)" }
Seller party: { "party_id": "seller-bank-b::tenzro-participant-1", "display_name": "Bank B (Seller)" }
All parties: [
{ "party_id": "buyer-fund-a::tenzro-participant-1", "display_name": "Fund A (Buyer)" },
{ "party_id": "seller-bank-b::tenzro-participant-1", "display_name": "Bank B (Seller)" }
]Step 5: Tokenize a Bond (CIP-56)
CIP-56 is Canton's standard for tokenizing real-world assets. Tokenize a US Treasury bond with full ISIN metadata:
// Tokenize a US Treasury bond via CIP-56
let asset = client.agent().send_message(
&agent.agent_id,
"Use canton_create_asset tool: \
owner=seller-bank-b, \
asset_type=bond, \
quantity=1000000, \
metadata={\"isin\": \"US912828ZT58\", \"coupon\": \"2.5%\", \"maturity\": \"2030-11-15\"}",
).await?;
println!("Asset created: {}", asset.payload);Asset created: {
"asset_id": "bond-us912828zt58",
"owner": "seller-bank-b",
"type": "bond",
"quantity": 1000000,
"isin": "US912828ZT58",
"coupon": "2.5%",
"maturity": "2030-11-15"
}Cross-VM Token Visibility
In the cross-VM architecture, CIP-56 assets created on Canton are automatically registered in the unified TokenRegistry. This means the bond tokenized above is immediately queryable from EVM (via the wTNZO ERC-20 pointer contract) and SVM (via the SPL adapter) without any manual bridging. All VMs share the same underlying native balance — there is no liquidity fragmentation. See the Cross-VM Token Architecture documentation for details.
Step 6: Check Balances & Fund the Buyer
// Check Canton Coin balances for both parties
let buyer_balance = client.agent().send_message(
&agent.agent_id,
"Use canton_get_balance tool: party=buyer-fund-a",
).await?;
println!("Buyer balance: {}", buyer_balance.payload);
let seller_balance = client.agent().send_message(
&agent.agent_id,
"Use canton_get_balance tool: party=seller-bank-b",
).await?;
println!("Seller balance: {}", seller_balance.payload);// Fund the buyer with Canton Coin
let transfer = client.agent().send_message(
&agent.agent_id,
"Use canton_transfer tool: \
sender=seller-bank-b, \
receiver=buyer-fund-a, \
amount=5000000",
).await?;
println!("Transfer: {}", transfer.payload);Transfer: {
"tx_id": "txn-a1b2c3d4",
"sender": "seller-bank-b",
"receiver": "buyer-fund-a",
"amount": 5000000,
"status": "committed"
}Step 7: Execute Atomic DvP Settlement
This is the core of the tutorial. The DvP settles both legs atomically — the bond transfers from seller to buyer, and the payment transfers from buyer to seller. If either leg fails, neither settles:
// Execute atomic Delivery-vs-Payment settlement
// Bond moves from seller to buyer; payment moves from buyer to seller
// Both legs settle atomically — neither settles unless both do
let dvp = client.agent().send_message(
&agent.agent_id,
"Use canton_dvp_settle tool: \
buyer=buyer-fund-a, \
seller=seller-bank-b, \
asset_id=bond-us912828zt58, \
quantity=100, \
price=5000000",
).await?;
println!("DvP settlement: {}", dvp.payload);DvP settlement: {
"settlement_id": "dvp-settle-001",
"status": "settled",
"delivery": {
"asset_id": "bond-us912828zt58",
"quantity": 100,
"from": "seller-bank-b",
"to": "buyer-fund-a"
},
"payment": {
"amount": 5000000,
"from": "buyer-fund-a",
"to": "seller-bank-b"
}
}Step 8: DAML Escrow Contract
For additional protection, create a DAML escrow contract. The canton_submit_command tool sends a DAML Create command via the JSON Ledger API v2:
// Create a DAML escrow contract for additional protection
let escrow = client.agent().send_message(
&agent.agent_id,
"Use canton_submit_command tool: \
command_type=create, \
template_id=Tenzro.Escrow:EscrowContract, \
payload={\"buyer\": \"buyer-fund-a\", \"seller\": \"seller-bank-b\", \
\"amount\": 1000000, \"asset_ref\": \"bond-us912828zt58\"}, \
act_as=buyer-fund-a",
).await?;
println!("Escrow contract: {}", escrow.payload);Escrow contract: {
"contract_id": "#1:0",
"template_id": "Tenzro.Escrow:EscrowContract",
"status": "active",
"created_at": "2026-04-08T14:30:00Z"
}Step 9: Query Contracts & Events
// Query active DAML contracts
let contracts = client.agent().send_message(
&agent.agent_id,
"Use canton_list_contracts tool: \
template_id=Tenzro.Escrow:EscrowContract, \
party=buyer-fund-a",
).await?;
println!("Active contracts: {}", contracts.payload);
// Get contract events
let events = client.agent().send_message(
&agent.agent_id,
"Use canton_get_events tool: \
contract_id=escrow-001, \
party=buyer-fund-a",
).await?;
println!("Events: {}", events.payload);Step 10: Fee Schedule & DAR Upload
// Check synchronizer fee schedule
let fees = client.agent().send_message(
&agent.agent_id,
"Use canton_get_fee_schedule tool",
).await?;
println!("Fee schedule: {}", fees.payload);Fee schedule: {
"base_fee": 1000,
"per_byte_fee": 10,
"currency": "Canton Coin",
"synchronizer": "tenzro-domain-1"
}// Upload a custom DAR package for specialized settlement logic
let dar = client.agent().send_message(
&agent.agent_id,
"Use canton_upload_dar tool: \
dar_path=/packages/tenzro-settlement-1.0.dar",
).await?;
println!("DAR upload: {}", dar.payload);Step 11: Settle on Tenzro Ledger
Finalize the DvP on the Tenzro Ledger and add a verifiable credential for audit trail:
// Settle the DvP on the Tenzro Ledger
let settlement = client.settlement().settle(SettlementRequest {
request_id: format!("canton-dvp-{}", uuid::Uuid::new_v4()),
provider: wallet.address.clone(),
customer: "0x0000000000000000000000000000000000000000".to_string(),
amount: 5000000,
asset: "TNZO".to_string(),
}).await?;
println!("Settlement: {}", settlement.receipt_id);
println!("Status: {}", settlement.status);The "TNZO" asset settles on the native Tenzro Ledger. When settlement involves cross-VM parties (e.g., a Canton seller and an EVM buyer), the CROSS_VM_BRIDGE precompile (0x1003) handles atomic transfers between VMs automatically — no manual bridging step is required.
// Add a settlement attestation credential to the identity
let cred = client.identity().add_credential(
&identity.did,
"DvPSettlementAttestation",
None,
Some(serde_json::json!({
"settlement_id": settlement.receipt_id,
"asset": "US912828ZT58",
"quantity": 100,
"counterparty": "seller-bank-b",
"chain": "canton",
})),
).await?;
println!("Credential: {}", cred);Run the Full Example
The complete example is available at sdk/tenzro-sdk/examples/defi_canton_dvp.rs:
cargo run --example defi_canton_dvpWhat You Learned
- DAML parties — allocating and managing counterparties for institutional transactions
- CIP-56 tokenization — creating on-ledger representations of real-world assets with ISIN metadata
- Canton Coin — balance queries and transfers between parties
- Atomic DvP — simultaneous delivery and payment that settles all-or-nothing
- DAML contracts — creating, querying, and managing smart contract lifecycle
- JSON Ledger API v2 — Canton's modern API for submitting commands and querying state
- Fee schedule — understanding synchronizer costs for transaction budgeting
- DAR packages — uploading custom DAML logic for specialized workflows
- Verifiable credentials — attaching settlement attestations to TDIP identities for audit
- Cross-VM TokenRegistry — how CIP-56 DAML assets integrate with the unified TokenRegistry for EVM/SVM visibility
Next Steps
- See the Canton Institutional Repo tutorial for a five-leg tri-party repo lifecycle
- See the Base L2 Yield Strategy tutorial for Chainlink price feeds and ERC-4626 vaults
- See the Solana DEX Aggregation tutorial for Jupiter swap routing