You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bridge any token between EVM and Canton behind one interface — whether we operate the bridge (PROMPT: our lock/mint contracts on both sides) or an external party does (USDCx: Circle xReserve). Adding a future token becomes either a config entry (existing mechanism) or one new adapter package (new mechanism). Full design: docs/bridging-design.md (in-repo once the first PR lands).
Current state
Relayer bridges exactly one token through hardwired code: single eth_token_contract in config, decimals fixed at 18, one WayfinderBridgeConfig mint path, no token dimension in the transfers table. CantonBridge.sol already has multi-token mappings — the Go side never uses them.
USDCx is fully integrated Canton-internally (AllocationFactory offer/accept, registry client, indexer, devstack) but has zero EVM bridge integration ([Epic] Support USDCx #199 covers the internal epic).
USDCx cannot be bridged with our own contracts: it moves exclusively through Circle xReserve (live on Canton MainNet since 2025-12-04) — deposit USDC into Circle's Ethereum contract (depositToRemote, Canton domain 10001), Circle attests (~15 min), the recipient party's BridgeUserAgreement_Mint mints USDCx; reverse via BridgeUserAgreement_Burn. Mint/burn are user-party choices, not operator choices. SOW: contracts/canton-erc20/docs/sow/usdc.md.
Design decisions
Step-function abstraction. Don't abstract lock/mint verbs — mechanisms disagree on those. A transfer is a durable record advanced by an idempotent Step(ctx, *Transfer) (StepResult, error) until terminal; mechanisms differ only in their stage sequences. TokenBridge = Key() + Sources() + Step(); one generic driver loop (subsuming the current processor + reconcile loop) owns retry/backoff/alerting for every mechanism.
Executor vs observer. Event watching only where we execute the bridge (wayfinder — a missed event means stuck funds). For xreserve, Circle executes regardless: Sources() returns nil, transfer rows are registered at initiation via the api-server, and Step just polls status. Relayer downtime cannot affect USDCx bridging, only status display.
Quote pattern for the dapp. The dapp never encodes a bridge transaction. POST /bridge/deposit/quote returns ABI-encoded unsigned tx steps (+ fees, ETA); the wallet signs blindly — the EVM mirror of the Canton prepare/execute pattern. One dapp flow for every token, present and future.
contracts/canton-erc20/docs/sow/usdc.md — original xReserve SOW this supersedes in detail
External docs: Circle xReserve technical guide, DA "USDCx Support for Wallets", digital-asset/xreserve-deposits, circlefin/evm-xreserve-contracts
Open questions
Pre-approval on BridgeUserAgreement at registration for everyone, or opt-in? (Design recommends default-on so deposits auto-mint.)
Do we ever want an optional xreserve watcher to surface deposits made outside our dapp (e.g. Circle's hosted UI)? Accepted as out of scope for now — the indexer still shows the balance.
Goal
Bridge any token between EVM and Canton behind one interface — whether we operate the bridge (PROMPT: our lock/mint contracts on both sides) or an external party does (USDCx: Circle xReserve). Adding a future token becomes either a config entry (existing mechanism) or one new adapter package (new mechanism). Full design:
docs/bridging-design.md(in-repo once the first PR lands).Current state
eth_token_contractin config, decimals fixed at 18, oneWayfinderBridgeConfigmint path, no token dimension in thetransferstable.CantonBridge.solalready has multi-token mappings — the Go side never uses them.depositToRemote, Canton domain 10001), Circle attests (~15 min), the recipient party'sBridgeUserAgreement_Mintmints USDCx; reverse viaBridgeUserAgreement_Burn. Mint/burn are user-party choices, not operator choices. SOW:contracts/canton-erc20/docs/sow/usdc.md.Design decisions
Step(ctx, *Transfer) (StepResult, error)until terminal; mechanisms differ only in their stage sequences.TokenBridge = Key() + Sources() + Step(); one generic driver loop (subsuming the current processor + reconcile loop) owns retry/backoff/alerting for every mechanism.Sources()returns nil, transfer rows are registered at initiation via the api-server, andStepjust polls status. Relayer downtime cannot affect USDCx bridging, only status display.POST /bridge/deposit/quotereturns ABI-encoded unsigned tx steps (+ fees, ETA); the wallet signs blindly — the EVM mirror of the Canton prepare/execute pattern. One dapp flow for every token, present and future.Child issues
Sequence
Dependencies & related
contracts/canton-erc20/docs/sow/usdc.md— original xReserve SOW this supersedes in detailOpen questions
BridgeUserAgreementat registration for everyone, or opt-in? (Design recommends default-on so deposits auto-mint.)