Skip to content

feat(relayer): xreserve adapter — USDCx inbound deposit tracking #357

Description

@sadiq1971

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

  • bridges/xreserve/bridge.goTokenBridge impl, deposit Step stages above
  • bridges/xreserve/circle.go — attestation API client (pin auth/schema from Circle docs; see hardening issue)
  • bridges/xreserve/ledger.go — USDCx Holding lookup for the recipient party; BridgeUserAgreement_Mint exercise (custodial, non-pre-approved) using the burn-mint-factory choice context
  • BridgeUserAgreement onboarding at user registration, with pre-approval enabled so deposits auto-mint
  • Relayer internal POST /api/v1/transfers (in pkg/relayer/service/http.go) for observer-mechanism registration
  • Per-token config: mechanism: xreserve block (contract, canton_domain, attestation_api, utility_backend, instrument admin)
  • Devstack: no real Circle — stub the attestation API + a scripted mint on P2 (same spirit as the existing usdcx-registry stub) so e2e can exercise the full stage machine
  • e2e: deposit registration -> attestation stub -> holding appears -> transfer completed

Depends on

  • Relayer multi-token refactor (TokenBridge interface + registry)

Out of scope

  • Outbound (burn) — separate issue
  • Deposit quote construction — bridge API issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    CIP-0086: Phase 2Phase 2 multi-token generalizationDeliverable: D3D3: Middleware ServiceType: FeatureAdded to issues and PRs to identify that the change is a new feature.

    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