feat: USDCx outbound — burn + release tracking - #376
Draft
sadiq1971 wants to merge 3 commits into
Draft
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## feat/bridge-api #376 +/- ##
==================================================
Coverage ? 35.52%
==================================================
Files ? 178
Lines ? 13930
Branches ? 0
==================================================
Hits ? 4948
Misses ? 8571
Partials ? 411
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
sadiq1971
force-pushed
the
feat/bridge-api
branch
from
July 27, 2026 11:09
850f752 to
a1833fb
Compare
sadiq1971
force-pushed
the
feat/usdcx-outbound
branch
from
July 27, 2026 11:11
a3d2275 to
914de24
Compare
sadiq1971
force-pushed
the
feat/bridge-api
branch
from
July 27, 2026 11:56
a1833fb to
6b32691
Compare
sadiq1971
force-pushed
the
feat/usdcx-outbound
branch
from
July 27, 2026 12:04
914de24 to
bdc38cd
Compare
sadiq1971
force-pushed
the
feat/bridge-api
branch
from
July 27, 2026 12:31
6b32691 to
2e1fdd7
Compare
sadiq1971
force-pushed
the
feat/usdcx-outbound
branch
from
July 27, 2026 17:58
bdc38cd to
90d6160
Compare
Completes the USDCx bridge loop (#359): Canton burn -> Circle releases USDC on Ethereum, relayer tracks the release. Token SDK (pkg/cantonsdk/token): - PrepareBurn / BurnByPartyID exercise BridgeUserAgreement_Burn on the user's agreement contract via Interactive Submission, reusing the existing prepare/execute and custodial signing machinery - Holdings selection reuses the UTXO picker from the transfer path - RegistryClient.GetBurnMintFactory fetches the factory + choice context + disclosed contracts (transfer-factory response envelope, shared parser) - config: burn_mint_package_id / burn_mint_module gate the feature Bridge API (pkg/bridgeapi): - POST /api/v2/bridge/withdraw/prepare|execute (external keys) and /custodial - mirrors the transfer prepare/execute pattern incl. fingerprint check; single-use prepared-burn store - execute/custodial register the transfer with the relayer (canton_to_ethereum, burn_request_id metadata) Relayer (bridges/xreserve): - stepWithdrawal: '' / awaiting_release -> completed(released), polling Circle burn status by request id; transient outages keep polling without burning transfer retries Daml choice/argument names and the burn-status API shape are pinned to the devstack stub; production verification tracked in #360.
- custodial and external withdrawals register the transfer with the relayer (keyed by a required idempotency_key) BEFORE the burn, so a client retry of a lost-response call returns the existing transfer instead of burning a second time (was: fund-losing double burn) - WithdrawExecute validates owner/fingerprint/signature via a store Peek and only consumes the prepared burn once validation passes, so a bad request or a foreign caller can no longer destroy a user's pending burn - burn holdings are filtered to the requested instrument admin before selection (was: could pick a same-id instrument from another issuer) - findBridgeUserAgreement errors on >1 agreement instead of picking one - xreserve withdrawals are reaped after a completion deadline, so a burn that never releases (or was never submitted) fails instead of polling forever - 429/408 burn-status responses treated as transient - burn_request_id metadata key shared via relayer.MetaBurnRequestID so writer (api-server) and reader (adapter) cannot drift
Add the withdraw xreserve fields (instrument_admin/instrument_id/ withdraw_destination_domain) to the docker bridge block, and a documented burn_mint block in canton.token (uncomment with the confirmed utility-bridge module name, #360).
sadiq1971
force-pushed
the
feat/usdcx-outbound
branch
from
July 29, 2026 10:06
90d6160 to
93d7d00
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #359 and epic #361. Stacked on #375 (bridge API) — review the last commit only until #375 merges.
What
USDCx outbound: burn on Canton via
BridgeUserAgreement_Burn, Circle releases USDC on Ethereum, the relayer tracks the release. Closes the loop — with this PR the full USDCx lifecycle (deposit quote → auto-mint tracking → withdraw → release tracking) is wired end to end.Token SDK (
pkg/cantonsdk/token)PrepareBurn/BurnByPartyIDexerciseBridgeUserAgreement_Burnon the user's own agreement contract (a user-party choice — never operator-signed), reusing the existing machinery end to end: agreement discovery via ACS, holdings selection via the transfer path's UTXO picker, choice context + disclosed contracts via the AnyValue converters, external signing viaprepareInstructionTx/ExecuteTransfer, custodial signing viaexerciseInstructionAsCustodial.RegistryClient.GetBurnMintFactory— burn-mint factory discovery; response envelope is identical to the transfer factory, so both now share one parser (postFactoryEndpoint).burn_mint_package_id/burn_mint_moduletoken-client config (values come from the utility-bridge DAR).Bridge API (
pkg/bridgeapi)POST /api/v2/bridge/withdraw/prepare+/execute(external keys, incl. the fingerprint-vs-signed_bycheck from the transfer flow; prepared burns are single-use) and/withdraw/custodial.canton_to_ethereum,source_tx_hash= burn request UUID,burn_request_idmetadata. Registration failure after a successful burn is reported as a tracking failure, not a withdrawal failure.instrument_admin,instrument_id,withdraw_destination_domain(0 = Ethereum).Relayer (
bridges/xreserve)stepWithdrawal:"" / awaiting_release → completed(released), polling Circle's burn status by request id. Same observer discipline as deposits: pending and transient-outage responses keep polling without consuming transfer retries;DestTxHashrecords the release tx.Pinned-to-stub caveats (verify in #360)
BridgeUserAgreement_Burnchoice/argument field names follow DA's devnet xReserve docs and the devstack stub./v1/burns/{requestId}) and burn-mint factory path mirror the registrar prefix the devstack stub implements; DA's hosted Utilities backend may differ.Testing
golangci-lintclean across all touched packages; whole-modulego testgreen (except two pre-existing Docker-dependent tests that panic without a local Docker daemon — they run in CI)