ChainSafe / ChainSafe/canton-middleware
[Epic] Multi-token bridging: PROMPT + USDCx via Circle xReserve
- Dominant language
- Go
- Stars
- 1
- Forks
- 1
- Avg merge
- 40m
- Merged PRs (30d)
- 1
Description
# 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).
**Priority (2026-07-11): USDCx first.** The `TokenBridge` foundation lands (#356), but PROMPT stays on the legacy pipeline for now — its port to the new interface is deferred (#372).
# 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 (#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 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
**USDCx track (active)**
- [ ] #356 — feat(relayer): TokenBridge adapter foundation — interface, registry, driver; legacy PROMPT pipeline untouched
- [ ] #357 — feat(relayer): xreserve adapter — USDCx inbound deposit tracking
- [ ] #358 — feat(api): bridge API — deposit quotes, registration, unified status
- [ ] #359 — feat: USDCx outbound — burn via BridgeUserAgreement + release tracking
- [ ] #360 — chore(bridge): hardening + mainnet enablement
**Deferred**
- [ ] #372 — feat(relayer): port PROMPT to wayfinder adapter, remove legacy pipeline (On Ice until USDCx ships)
# Sequence (USDCx-first)
1. **Inbound usable without the relayer**: `BridgeUserAgreement` onboarding with pre-approval (#357) + `GET /bridge/tokens` and `POST /bridge/deposit/quote` with the xreserve quoter (#358). Deposits auto-mint; balance visible via the existing indexer.
2. **Outbound (the must-have)**: #359 burn via prepare/execute + custodial — users cannot burn without middleware support.
3. **Foundation + status tracking**: #356 (driver alongside legacy pipeline), then the tracking halves of #357/#358/#359 (attestation polling, holding watch, release watch, `POST /bridge/deposits`, `GET /bridge/transfers`).
4. #360 hardening — but **start its external dependencies immediately, in parallel with everything**: DA Utilities onboarding, mainnet xReserve address, Circle attestation API auth/fees.
5. #372 when USDCx has shipped (or whenever PROMPT bridging is next touched).
# Dependencies & related
- #199 — [Epic] Support USDCx (Canton-internal; this epic is its EVM-bridge counterpart)
- #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) — #358's prepare/execute covers USDCx now; PROMPT's variant lands with #372.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with child issues #356–#360 in the stated USDCx-first sequence, then read contracts/canton-erc20/docs/sow/usdc.md and the existing relayer configuration and transfers-table references. Review the xReserve and BridgeUserAgreement entry points named in the issue. Done means the USDCx inbound, outbound, API, tracking, hardening, and mainnet-enablement child work is complete; PROMPT remains deferred to #372.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, solidity
- Domain
- backend-api-design, blockchain, distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100