Goal
Token-agnostic bridge API on the api-server so the dapp can bridge any configured token without knowing its mechanism. Today there are no bridge endpoints at all (PROMPT withdrawals are initiated by scripts only).
Design rule
The dapp never encodes a bridge transaction. Different tokens need different EVM calls (PROMPT: approve + depositToCanton on our CantonBridge; USDCx: approve + depositToRemote on Circle's xReserve with domain 10001, keccak recipient, hookData). The API returns a quote — fully ABI-encoded unsigned transactions the wallet signs blindly. EVM mirror of the existing Canton prepare/execute pattern.
Endpoints
GET /api/v2/bridge/tokens — supported tokens, mechanism, limits, fees, indicative ETA
POST /api/v2/bridge/deposit/quote — body {token, amount}; authenticated user's Canton party resolved server-side (SIWE session -> userstore), so recipient encoding never leaks to the dapp:
{
"quote_id": "q_7f3a...",
"chain_id": 1,
"steps": [
{"kind": "approve", "to": "0x<token>", "data": "0x095ea7b3...", "value": "0"},
{"kind": "deposit", "to": "0x<bridge>", "data": "0x...", "value": "0"}
],
"fees": {"bridge_fee": "0.15", "currency": "USDC"},
"estimated_seconds": 900,
"expires_at": "..."
}
approve step included only when on-chain allowance is insufficient
- fees/ETA come from the mechanism (seconds + zero fee for wayfinder; ~15 min + Circle fee for xreserve)
- quote params persisted against
quote_id so registration can be validated against what was quoted
- future optimization: EIP-2612 permit (USDC supports it) to collapse the approve step
POST /api/v2/bridge/deposits — {quote_id, tx_hash}: registers the transfer for tracking (no-op-ish for wayfinder — the Source detects independently; required for xreserve)
POST /api/v2/bridge/withdraw/prepare|execute — mechanism-dispatched Canton-side signing: wayfinder builds InitiateWithdrawal; xreserve builds BridgeUserAgreement_Burn. Execute registers the transfer.
GET /api/v2/bridge/transfers?address=... and /{id} — unified status (status + stage)
Dapp flow for any token, present and future: GET tokens -> POST deposit/quote -> sign & send steps -> POST deposits -> poll GET transfers/{id}. Adding a token changes the token list, nothing else.
Structure
// pkg/bridgeapi — api-server local; quote construction is stateless ABI
// encoding and does not belong in the relayer.
type DepositQuoter interface {
DepositQuote(ctx context.Context, req QuoteRequest) (*Quote, error)
}
One implementation per mechanism, built from the same per-token config as the relayer registry. Registration and status read go to the relayer (internal POST /api/v1/transfers, GET /api/v1/transfers).
Work items
Depends on
- Relayer multi-token refactor (shared per-token config, transfers registration endpoint)
Related
- Works for PROMPT alone even before the xreserve adapter lands; USDCx quotes activate with the xreserve inbound issue.
Goal
Token-agnostic bridge API on the api-server so the dapp can bridge any configured token without knowing its mechanism. Today there are no bridge endpoints at all (PROMPT withdrawals are initiated by scripts only).
Design rule
The dapp never encodes a bridge transaction. Different tokens need different EVM calls (PROMPT:
approve+depositToCantonon ourCantonBridge; USDCx:approve+depositToRemoteon Circle's xReserve with domain 10001, keccak recipient, hookData). The API returns a quote — fully ABI-encoded unsigned transactions the wallet signs blindly. EVM mirror of the existing Canton prepare/execute pattern.Endpoints
GET /api/v2/bridge/tokens— supported tokens, mechanism, limits, fees, indicative ETAPOST /api/v2/bridge/deposit/quote— body{token, amount}; authenticated user's Canton party resolved server-side (SIWE session -> userstore), so recipient encoding never leaks to the dapp:{ "quote_id": "q_7f3a...", "chain_id": 1, "steps": [ {"kind": "approve", "to": "0x<token>", "data": "0x095ea7b3...", "value": "0"}, {"kind": "deposit", "to": "0x<bridge>", "data": "0x...", "value": "0"} ], "fees": {"bridge_fee": "0.15", "currency": "USDC"}, "estimated_seconds": 900, "expires_at": "..." }approvestep included only when on-chain allowance is insufficientquote_idso registration can be validated against what was quotedPOST /api/v2/bridge/deposits—{quote_id, tx_hash}: registers the transfer for tracking (no-op-ish for wayfinder — the Source detects independently; required for xreserve)POST /api/v2/bridge/withdraw/prepare|execute— mechanism-dispatched Canton-side signing: wayfinder buildsInitiateWithdrawal; xreserve buildsBridgeUserAgreement_Burn. Execute registers the transfer.GET /api/v2/bridge/transfers?address=...and/{id}— unified status (status+stage)Dapp flow for any token, present and future:
GET tokens->POST deposit/quote-> sign & send steps ->POST deposits-> pollGET transfers/{id}. Adding a token changes the token list, nothing else.Structure
One implementation per mechanism, built from the same per-token config as the relayer registry. Registration and status read go to the relayer (internal
POST /api/v1/transfers,GET /api/v1/transfers).Work items
pkg/bridgeapi/— types,DepositQuoter, wayfinder + xreserve quoters, allowance check via eth clientInitiateWithdrawalgives PROMPT withdrawals their first production API)stagehonestly (offer submitted vs settled)Depends on
Related