Sub-Transaction Privacy & Atomic Collateral Repledging on Canton Network & Daml
In traditional finance and current Web3 debt protocols, loan refinancing suffers from two critical flaws:
- Public Exposure: Lenders can inspect competitors' interest rates, maturity dates, and borrower balance sheets.
- Settlement Counterparty Risk: Collateral release and payoff funding are fragmented into separate asynchronous steps, creating bridge risk where either a lender releases collateral without payoff or a borrower defaults before pledging.
The Canton Private Refinancing Application solves this by leveraging Canton’s sub-transaction privacy and atomic composability.
A borrower (Alice) refinances an existing loan with an outgoing lender (Lender A / LegacyBank, $101,000 payoff) using fresh funds from an incoming lender (Lender B / NeoCapital, $100,000 commitment) plus $1,000 borrower equity. In one single committed atomic ledger transaction:
- $101,000 is transferred to Lender A.
- Loan A is archived and discharged.
- 150 test collateral units (
COLLAT-TEST) are atomically repledged from securing Loan A to securing Loan B. - Loan B is instantiated under negotiated terms.
- Privacy is mathematically guaranteed: Lender A cannot see Lender B's interest rate, terms, or identity. Lender B cannot see Lender A's historical interest rate or loan parameters.
┌───────────────────────────────────────────────┐
│ BORROWER (Alice) │
│ - 150 COLLAT-TEST locked for Loan A │
│ - Contributes $1,000 USD-TEST equity │
└──────────────┬─────────────────┬──────────────┘
│ │
1. Issues Payoff Quote │ │ 2. Commits Replacement Offer
($101,000 Payoff) │ │ ($100,000 Loan B @ 7.5%)
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ LENDER A (LegacyBank) │ │ LENDER B (NeoCapital) │
│ Loan A ($100k @ 8.5%) │ │ Allocates $100,000 cash│
└─────────────────────────┘ └─────────────────────────┘
│ │
└────────┬────────┘
│
▼
┌───────────────────────────────────────────────┐
│ ATOMIC CLOSING CHOICE │
│ │
│ 1. Transfers $101k to Lender A ($100k B + $1k)│
│ 2. Archives Loan A │
│ 3. Repledges 150 COLLAT-TEST to Lender B │
│ 4. Creates Active Loan B │
│ 5. Emits Role-Filtered Closing Receipts │
└───────────────────────────────────────────────┘
| Contract / Data | Alice (Borrower) | Lender A (LegacyBank) | Lender B (NeoCapital) | Operator |
|---|---|---|---|---|
| Loan A Terms ($100k, 8.5%) | Visible | Visible | ❌ Hidden | Visible |
| Payoff Quote ($101k) | Visible | Visible | ❌ Hidden | Visible |
| Loan B Terms ($100k, 7.5%) | Visible | ❌ Hidden | Visible | Visible |
| Replacement Offer ($100k) | Visible | ❌ Hidden | Visible | Visible |
| Atomic Settlement | Visible | Paid $101k, Collateral Freed | Received Collateral, Loan B Active | Sequenced |
.
├── daml/ # Daml Smart Contracts
│ ├── Assets.daml # Collateral & Cash token models with enforced locking
│ ├── Loan.daml # LoanInterface adapter + LoanA (Bullet) & LoanB (Amortizing)
│ ├── Approvals.daml # PayoffQuote and ReplacementOffer binding commitments
│ ├── Closing.daml # ClosingRequest and atomic Execute choice
│ ├── Setup.daml # Deterministic ledger initialization script
│ └── TestRefinancing.daml # Comprehensive test suite (Happy path, failures, race conditions)
├── backend/ # Backend Gateway & Projection Service
│ ├── daml-js/ # Generated TypeScript bindings (daml codegen js)
│ ├── src/
│ │ ├── types/ # TypeScript interfaces for ledger domain & responses
│ │ ├── config/ # Financial parameters and baseline constants
│ │ ├── services/ # AtomicClosing, CantonPrivacyEngine & LedgerStore
│ │ ├── routes/ # Express API routers (quotes, offers, closing, state)
│ │ ├── ledger.ts # Domain facade coordinating ledger operations
│ │ └── server.ts # Express REST server & static web app host
│ └── package.json
├── frontend/ # Modern Web Application
│ ├── index.html # Semantic HTML5 layout with Role Switcher & Modals
│ ├── style.css # Dark mode design system (glassmorphism, vibrant accents)
│ ├── app.js # Application coordinator & event dispatcher
│ └── js/
│ ├── api.js # Centralized async API client
│ ├── components/ # Toast, Privacy Modal, Audit Log Modal
│ └── views/ # Borrower, Lender A & Lender B view controllers
├── scripts/ # Developer tooling
│ └── generate_bindings.sh # Daml codegen wrapper script
├── daml.yaml # Daml package definition (SDK 3.4.11)
├── package.json # Root script runner
└── README.md # This document
- Daml SDK:
3.4.11(or Daml 3.x) - Node.js:
>= 18.0.0 - Java JDK:
>= 17(required by Daml compiler)
curl -sSL https://get.daml.com/ | sh
export PATH="$HOME/.daml/bin:$PATH"
daml versionCompile the DAR package:
npm run daml:build
# Generates .daml/dist/ref-canton-0.0.1.darExecute the Daml contract tests and backend security regression suite:
npm testTest Coverage Includes (22 Daml Tests + 9 Backend Security Tests):
- Smart Contract Security & Invariants:
testRefinancingLifecycle: Complete happy-path atomic swap and balance verification.testBorrowerCreatedZeroPaymentFails: Rejects borrower attempts to unilaterally forge or issue payment proof with zero cash transfer; enforces dual signatures (signatory lenderA, payer), positive amounts (amount > 0.0), and genuine cash transfer throughPayTowardsQuote.testUnauthorizedCollateralReleaseFails: Rejects attempts to unlock collateral without lender authorization.testPaymentFreeReleaseFails: Rejects collateral release through quotes if payment cash is omitted.testDuplicatePaymentCashFails: Rejects attempts to duplicate payment cash contract IDs in settlement payments.testReusedPaymentCashFails: Rejects attempts to reuse already-settled cash holdings or past evidence across fresh loans/settlements (Anti-Replay).testDisbursementRequiresCollateralSecuring: Proves replacement funds cannot disburse independently without securing collateral.testBoundCollateralExclusivelyLocked: Verifies bound collateral cannot be re-pledged or double-attached to multiple loans.testWrongAssetFails: Rejects settlement when counterfeit or unexpected asset instruments are used.testCorrectAssetNameWrongIssuerFails: Rejects tokens matching the asset symbol but minted by an unauthorized operator.testExcessFundingReturnsChange: Verifies excess cash disbursed by Lender B is returned as change viaTransferPartial.testCollateralReuseWithFreshOffersFails: Prevents multiple loans from securing against the same collateral.testReplayAttackFails: Ensures double-spend and replay attacks on archived contracts fail immediately.testExpiredApprovalFails&testInsufficientBorrowerFundsFails: Enforces deadlines and equity requirements.testQuoteRevocationFails,testOfferWithdrawalFails,testBorrowerCancelFails: Choice authorization rules.auditParticipant2&auditParticipant3: Multi-participant sub-transaction privacy assertions.
- Backend API, Cryptographic Authentication & Security Tests:
- Closure of public credential bypass (
GET /api/auth/demo-tokensstrictly disabled with HTTP 403ENDPOINT_DISABLED; anonymous users cannot obtain privileged tokens). - Verified party credential authentication (
POST /api/auth/token) issuing cryptographically signed HMAC-SHA256 bearer tokens with timing-safe comparison. - HTTP 401 unauthenticated request rejection across all secured endpoints.
- Rejection of forged
Bearer adminbackdoor and caller-selectedX-Party-Idspoofing without HMAC-SHA256 signature. - HTTP 403 cross-party snooping rejection (e.g. Borrower accessing Lender A state, Lender A accessing Lender B).
- Authenticated party-isolated transaction history filtering (zero competitor leak).
- Mandatory private deployment secrets with startup refusal: verifies required deployment secrets and refuses startup (exit 1) if secrets are missing or use default fallback keys.
- Fail-closed settlement verification without false success: executes exact requested contract ID, eliminates synthetic fallback IDs, and enforces ledger verification of Loan A archival and genuine receipt contract IDs (controlled mock with zero events returns HTTP 400).
- HTTP 503 fail-closed rejection when Canton ledger nodes are offline.
- HTTP 400 rejection of nonexistent closing requests (no synthetic fallback or false success).
- Closure of public credential bypass (
With the cluster running, execute the end-to-end integration test through the HTTP API Gateway:
./scripts/test_live_api_closing.shThis tests genuine cryptographic authentication, quote issuance, offer commitment, genuine atomic closing submitted directly to Canton (ClosingRequest.Execute) returning a committed updateId, confirms the exact transaction on Canton, and queries Canton participant nodes directly (Participant 2 :5024 for Lender A, Participant 3 :5034 for Lender B) and via the transaction tree API (GET /api/transactions/:updateId/tree using TRANSACTION_SHAPE_LEDGER_EFFECTS). It verifies that payoff operations (PayTowardsQuote and SettleAndRepledge) are executed as sibling choices under ClosingRequest.Execute rather than being nested under Lender B's choices, mathematically ensuring Lender B cannot witness Lender A's payoff even in nested sub-transaction trees.
npm startThe server will start on: http://localhost:4000
Open http://localhost:4000 in your web browser.
- Navigate to
http://localhost:4000. - By default, the active role is Borrower (Alice).
- Review Alice's initial state:
- Cash Balance: $1,000 USD-TEST (Equity contribution ready).
- Collateral: 150 COLLAT-TEST locked securing Loan A.
- Active Debt: $100,000 USD-TEST with Lender A at 8.5% interest.
- Switch role to Outgoing Lender A (LegacyBank) using the top role switcher.
- Note that Lender A cannot see any data about Lender B.
- Click "Issue Payoff Quote" ($101,000 payoff = $100k principal + $1k accrued interest).
- The Payoff Quote is confirmed on-ledger.
- Switch role to Incoming Lender B (NeoCapital).
- Note that Lender B cannot see Lender A's 8.5% interest rate.
- Click "Commit Offer & Capital" ($100k offer @ 7.5% interest).
- Lender B's cash is allocated to escrow and the replacement offer is confirmed on-ledger.
- Switch back to Borrower (Alice).
- The pipeline status displays: "Ready to Close".
- Review terms comparison:
- Old Rate: 8.5% ➔ New Rate: 7.5% (100 bps savings!).
- Net Borrower Equity Required: $1,000 USD-TEST.
- Click "Execute Atomic Close".
- Within 1 block confirmation:
- $101,000 settles to Lender A.
- Loan A is archived.
- 150 COLLAT-TEST is repledged to Lender B.
- Active Loan B is created.
- Closing Receipt
RCP-REF-894102is issued.
- Click the "Privacy Inspector" button in the header.
- Inspect the live 3-column projection matrix:
- Lender A view: Sees their payoff quote and received payment. Loan B terms, rates, and identity are
REDACTED (Canton Sub-transaction Privacy). - Lender B view: Sees their new loan facility and received collateral pledge. Loan A historical rates and payoff details are
REDACTED (Canton Sub-transaction Privacy). - Borrower view: Complete visibility across both legs of the refinancing.
- Lender A view: Sees their payoff quote and received payment. Loan B terms, rates, and identity are
In a production Canton deployment, each party operates their own Canton participant node connected to a shared synchronizer (sequencer + mediator):
┌─────────────────────────────────────────────────────────────┐
│ CANTON SYNCHRONIZER DOMAIN │
│ [Sequencer] ◄───► [Mediator] │
└──────┬──────────────────────┬──────────────────────┬────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Alice Node │ │ LegacyBank │ │ NeoCapital │
│(Participant) │ │(Participant) │ │(Participant) │
│ [Borrower] │ │ [Lender A] │ │ [Lender B] │
└──────────────┘ └──────────────┘ └──────────────┘
RefCanton includes a containerized multi-participant Canton cluster and fullstack web application orchestrated via Docker Compose:
# Option A: One-touch launcher script
./scripts/run_docker.sh
# Option B: Standard Docker Compose
docker compose up --build -d| Service / Node | Component | Host Port | Protocol | Description |
|---|---|---|---|---|
refcanton-app |
Web UI & API Gateway | 4000 |
HTTP | Fullstack RefCanton application |
canton-network |
Sequencer Public | 5001 |
gRPC | Canton Synchronizer Sequencer |
canton-network |
Sequencer Admin | 5002 |
gRPC | Canton Sequencer Admin API |
canton-network |
Mediator Admin | 5003 |
gRPC | Canton Synchronizer Mediator |
participant1 |
Borrower (Alice) | 5011 / 5014 |
gRPC / HTTP | Isolated node hosting Alice |
participant2 |
Lender A (LegacyBank) | 5021 / 5024 |
gRPC / HTTP | Isolated node hosting Lender A |
participant3 |
Lender B (NeoCapital) | 5031 / 5034 |
gRPC / HTTP | Isolated node hosting Lender B |
# View live logs
docker compose logs -f
# Check container status
docker compose ps
# Teardown cluster
docker compose downIn accordance with zero-trust security principles, hardcoded secrets are strictly forbidden. The application requires private deployment secrets passed as environment variables and fails fast on startup if missing or insecure:
AUTH_SECRET: HMAC-SHA256 signing secret for session tokens.BORROWER_SECRET: Private credential for Borrower (Alice).LENDER_A_SECRET: Private credential for Lender A (LegacyBank).LENDER_B_SECRET: Private credential for Lender B (NeoCapital).OPERATOR_SECRET: Private credential for Operator.
When running ./scripts/run_docker.sh or in CI, standardized test credentials are provided automatically if not already exported.
RefCanton uses a modular, discrete GitHub Actions CI pipeline (.github/workflows/ci.yml) separating concerns into three specialized jobs:
Daml Smart Contracts(daml-contracts): Compiles the DAR package, executes all 22 Daml integration and negative security tests (unbacked evidence rejection, collateral locking, anti-replay), and uploads the compiled DAR artifact.Backend Security & Access Control(backend-security): Executes in parallel with the Daml job; compiles TypeScript and executes all 9 security regression tests (HMAC token verification, timing attack resistance, cross-party snooping rejection, offline fail-closed, and startup secret validation).Live Canton E2E & Privacy Audit(canton-e2e): Depends on the successful completion of both Daml and Backend jobs; boots the full containerized multi-participant Canton cluster and executes./scripts/test_live_api_closing.sh, verifying end-to-end atomic settlement and full transaction tree isolation (TRANSACTION_SHAPE_LEDGER_EFFECTS).
If running Canton directly on your host machine:
- Start Canton Console:
canton -c canton/canton.conf --bootstrap canton/bootstrap.canton
- Start the Application Gateway:
npm start
An end-to-end MP4 demonstration video walking through the full lifecycle — borrower onboarding, quote and offer commitments, atomic execution on Canton, privacy inspector verification, and failure modes — is available locally in the repository:
- Path:
demo/refcanton_master_walkthrough.mp4 - Format: MP4 (1080p, H.264)
- Content: Complete 3-party workflow showing role switching, atomic settlement with Canton update ID, and live privacy validation across lender views.
In strict adherence to the problem definition:
- Test Assets: All assets (
USD-TEST,COLLAT-TEST) are simulated on-ledger test tokens. They carry no real-world monetary value. - Fixed Negotiated Terms: Rates and terms are fixed bilaterally between borrower and lenders. Dynamic lender auction orderbooks, AMM liquidity pools, and AI underwriting are excluded from this MVP.
- Legal Discharge: On-ledger execution archives the Daml smart contract debt obligations. Real-world legal UCC filings and lien releases must be coordinated by off-ledger legal counsel.
This project is licensed under the Apache License 2.0. See LICENSE for details.