Skip to content

[Epic] Multi-token bridging: PROMPT + USDCx via Circle xReserve #361

Description

@sadiq1971

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

  • 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

  1. 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.
  2. 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.
  3. 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.

Child issues

Sequence

  1. feat(relayer): multi-token refactor — TokenBridge adapter interface #356 — foundation; existing PROMPT e2e must stay green.
  2. feat(relayer): xreserve adapter — USDCx inbound deposit tracking #357 and feat(api): bridge API — deposit quotes, registration, unified status #358 in parallel (feat(api): bridge API — deposit quotes, registration, unified status #358 is useful for PROMPT alone: quotes + the first production withdrawal API).
  3. feat: USDCx outbound — burn via BridgeUserAgreement + release tracking #359 — completes the USDCx loop.
  4. chore(bridge): hardening + mainnet enablement #360 — hardening + mainnet. Start the external dependencies early: DA Utilities onboarding, mainnet xReserve address, Circle attestation API auth/fees.

Dependencies & related

  • [Epic] Support USDCx #199 — [Epic] Support USDCx (Canton-internal; this epic is its EVM-bridge counterpart)
  • Faucet API endpoints for devnet test tokens (DEMO, PROMPT, USDCX) #350 — faucet endpoints for devnet test tokens (useful for e2e funding)
  • 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

  1. Pre-approval on BridgeUserAgreement at registration for everyone, or opt-in? (Design recommends default-on so deposits auto-mint.)
  2. 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.
  3. Wayfinder withdrawal initiation currently has no production API at all (scripts only) — feat(api): bridge API — deposit quotes, registration, unified status #358 fixes this; confirm the dapp wants it in the same release.

Metadata

Metadata

Assignees

No one assigned

    Labels

    CIP-0086: Phase 2Phase 2 multi-token generalizationDeliverable: D3D3: Middleware ServiceType: EpicAdded to issues to encompass many different types of issues together

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions