ChainSafe / ChainSafe/canton-middleware
feat(relayer): xreserve adapter — USDCx inbound deposit tracking
- Dominant language
- Go
- Stars
- 1
- Forks
- 1
- Avg merge
- 40m
- Merged PRs (30d)
- 1
Description
# 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.go` — `TokenBridge` 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
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the relayer multi-token refactor dependency, then inspect pkg/relayer/bridges/xreserve/ and pkg/relayer/service/http.go, including the existing usdcx-registry devstack stub. Trace the listed attestation, Holding, mint, registration, configuration, and e2e work items. Done means the transfer stages complete from registration through the attestation stub and holding appearance in e2e.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, backend, blockchain, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100