Goal
Add USDCx inbound bridging (Ethereum USDC -> Canton USDCx) via Circle xReserve as the first observer mechanism adapter: pkg/relayer/bridges/xreserve/.
Background (confirmed, July 2026)
- USDCx is bridged exclusively through Circle xReserve (live on Canton MainNet since 2025-12-04). Deposit =
depositToRemote(amount, remoteDomain=10001, remoteRecipient, localToken, maxFee, hookData) on Circle's xReserve contract; remoteRecipient = keccak256(cantonPartyId), full party id in hookData. Circle attests after ~15 min finality; the recipient party's BridgeUserAgreement_Mint mints USDCx (auto-mints with pre-approval).
- Mint is a user-party choice, not an operator choice — we can exercise it only for custodial parties.
- Factory/choice-context discovery via DA Utilities registry (
.../registry/burn-mint-instruction/v0/burn-mint-factory) — same disclosed-contract pattern as pkg/cantonsdk/token/registry_client.go.
- Refs: Circle xReserve technical guide (developers.circle.com/xreserve), DA "USDCx Support for Wallets" docs, digital-asset/xreserve-deposits, circlefin/evm-xreserve-contracts. SOW:
contracts/canton-erc20/docs/sow/usdc.md.
Key decision: no event watching
Circle executes the bridge whether we watch or not — we are not in the critical path, so Sources() returns nil. Transfer rows are registered at initiation via the api-server (bridge API issue); the adapter's Step only tracks status:
"" -> awaiting_attestation poll Circle attestation API (RetryAfter ~1m during finality window)
-> awaiting_mint watch for recipient's Holding via Ledger API;
exercise BridgeUserAgreement_Mint for custodial non-pre-approved parties
-> completed
If the relayer is down, bridging is unaffected; only status display lags. Trade-off (accepted): deposits made entirely outside our dapp (e.g. Circle's hosted UI) won't appear in transfer history — the indexer still reflects the resulting balance.
Work items
Depends on
- Relayer multi-token refactor (TokenBridge interface + registry)
Out of scope
- Outbound (burn) — separate issue
- Deposit quote construction — bridge API issue
Goal
Add USDCx inbound bridging (Ethereum USDC -> Canton USDCx) via Circle xReserve as the first observer mechanism adapter:
pkg/relayer/bridges/xreserve/.Background (confirmed, July 2026)
depositToRemote(amount, remoteDomain=10001, remoteRecipient, localToken, maxFee, hookData)on Circle's xReserve contract;remoteRecipient = keccak256(cantonPartyId), full party id inhookData. Circle attests after ~15 min finality; the recipient party'sBridgeUserAgreement_Mintmints USDCx (auto-mints with pre-approval)..../registry/burn-mint-instruction/v0/burn-mint-factory) — same disclosed-contract pattern aspkg/cantonsdk/token/registry_client.go.contracts/canton-erc20/docs/sow/usdc.md.Key decision: no event watching
Circle executes the bridge whether we watch or not — we are not in the critical path, so
Sources()returns nil. Transfer rows are registered at initiation via the api-server (bridge API issue); the adapter'sSteponly tracks status:If the relayer is down, bridging is unaffected; only status display lags. Trade-off (accepted): deposits made entirely outside our dapp (e.g. Circle's hosted UI) won't appear in transfer history — the indexer still reflects the resulting balance.
Work items
bridges/xreserve/bridge.go—TokenBridgeimpl, depositStepstages abovebridges/xreserve/circle.go— attestation API client (pin auth/schema from Circle docs; see hardening issue)bridges/xreserve/ledger.go— USDCxHoldinglookup for the recipient party;BridgeUserAgreement_Mintexercise (custodial, non-pre-approved) using the burn-mint-factory choice contextBridgeUserAgreementonboarding at user registration, with pre-approval enabled so deposits auto-mintPOST /api/v1/transfers(inpkg/relayer/service/http.go) for observer-mechanism registrationmechanism: xreserveblock (contract, canton_domain, attestation_api, utility_backend, instrument admin)usdcx-registrystub) so e2e can exercise the full stage machineDepends on
Out of scope