Skip to content

feat(api): bridge API — deposit quotes, registration, status - #375

Draft
sadiq1971 wants to merge 4 commits into
feat/xreserve-inboundfrom
feat/bridge-api
Draft

feat(api): bridge API — deposit quotes, registration, status#375
sadiq1971 wants to merge 4 commits into
feat/xreserve-inboundfrom
feat/bridge-api

Conversation

@sadiq1971

@sadiq1971 sadiq1971 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Part of #358 and epic #361. Stacked on #374 (xreserve adapter) — review the last two commits until #374 merges.

What

Token-agnostic bridge endpoints on the api-server (pkg/bridgeapi). The dapp never encodes a bridge transaction: it asks for a quote — fully ABI-encoded unsigned transactions — signs blindly, then registers the sent tx hash for status tracking. The EVM mirror of the Canton prepare/execute pattern; one dapp flow for every token, present and future.

Endpoints

  • GET /api/v2/bridge/tokens — bridgeable tokens (symbol, mechanism, decimals, fee, indicative ETA)
  • POST /api/v2/bridge/deposit/quote (auth) — {token, amount}{chain_id, steps[], fees, estimated_seconds}. Steps: conditional ERC-20 approve + the mechanism's deposit call. Recipient encoding (keccak256(partyId) + party id in hookData for xreserve) is resolved server-side from the authenticated user's Canton party and never leaves the server. Quoting is stateless — a pure encoding function, nothing persisted, quotes can be re-requested freely.
  • POST /api/v2/bridge/deposits (auth) — {token, amount, tx_hash} → forwards to the relayer's POST /api/v1/transfers. Token/amount are re-validated and the recipient party is re-derived from the authenticated session, never taken from the caller. No quote binding is needed: the chain is the source of truth, and a registration with wrong parameters only yields a status row the adapter never completes.
  • GET /api/v2/bridge/transfers/{id} — relayer status proxy (status + stage)

Auth is the existing per-handler X-Signature/X-Message EIP-191 pattern (same as pkg/transfer).

Design points

  • DepositQuoter per mechanism, xreserve first. The xreserve quoter's depositToRemote calldata is round-trip tested (unpack + assert every argument, including the keccak recipient hash). The ABI follows Circle's published interface; mainnet verification is a chore(bridge): hardening + mainnet enablement #360 item.
  • Allowance check is optional: with eth_rpc_url configured, an eth_call decides whether to include the approve step; unset (or on RPC failure) the approve step is always included — a redundant approve is safe, a missing one strands the deposit. This keeps the api-server free of a hard outbound-EVM dependency.
  • Config: new optional bridge block on APIServer (relayer_url, chain_id, optional eth_rpc_url, per-token mechanism/evm_address/decimals/xreserve{contract, remote_domain, max_fee}). Omitted → no routes mounted.

Not in this PR

Testing

  • Quoter: full calldata round-trip assertions; allowance sufficient/low/error paths
  • Service: quote happy path + validation matrix (unsupported token, bad amounts, precision overflow, unregistered user); registration derives recipient from session + validation matrix
  • golangci-lint clean; config tests pass with the new optional field

@codecov-commenter

codecov-commenter commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 38.53211% with 201 lines in your changes missing coverage. Please review.
✅ Project coverage is 35.27%. Comparing base (864d5ff) to head (2e1fdd7).

Files with missing lines Patch % Lines
pkg/bridgeapi/http.go 0.00% 74 Missing ⚠️
pkg/bridgeapi/relayer.go 0.00% 53 Missing ⚠️
pkg/bridgeapi/allowance.go 0.00% 22 Missing ⚠️
pkg/app/api/server.go 0.00% 21 Missing ⚠️
pkg/bridgeapi/service.go 81.72% 9 Missing and 8 partials ⚠️
pkg/bridgeapi/xreserve.go 79.66% 6 Missing and 6 partials ⚠️
pkg/bridgeapi/config.go 60.00% 1 Missing and 1 partial ⚠️

❌ Your patch status has failed because the patch coverage (38.53%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@                    Coverage Diff                    @@
##           feat/xreserve-inbound     #375      +/-   ##
=========================================================
+ Coverage                  35.24%   35.27%   +0.03%     
=========================================================
  Files                        170      176       +6     
  Lines                      13243    13569     +326     
=========================================================
+ Hits                        4667     4787     +120     
- Misses                      8198     8390     +192     
- Partials                     378      392      +14     
Flag Coverage Δ
unittests 35.27% <38.53%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/config/config.go 71.18% <ø> (ø)
pkg/bridgeapi/config.go 60.00% <60.00%> (ø)
pkg/bridgeapi/xreserve.go 79.66% <79.66%> (ø)
pkg/bridgeapi/service.go 81.72% <81.72%> (ø)
pkg/app/api/server.go 0.00% <0.00%> (ø)
pkg/bridgeapi/allowance.go 0.00% <0.00%> (ø)
pkg/bridgeapi/relayer.go 0.00% <0.00%> (ø)
pkg/bridgeapi/http.go 0.00% <0.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Token-agnostic bridge endpoints on the api-server (#358). The dapp never
encodes a bridge transaction: it asks for a quote (fully ABI-encoded
unsigned txs), signs blindly, then registers the sent tx for tracking -
the EVM mirror of the Canton prepare/execute pattern.

- GET  /api/v2/bridge/tokens - bridgeable tokens, fees, indicative ETA
- POST /api/v2/bridge/deposit/quote - conditional ERC-20 approve +
  mechanism deposit call; recipient encoding (keccak256 party id +
  hookData for xreserve) resolved server-side from the authenticated
  user's Canton party
- POST /api/v2/bridge/deposits - forwards the tx hash to the relayer;
  all transfer params come from the stored quote, never the caller
- GET  /api/v2/bridge/transfers/{id} - relayer status proxy

pkg/bridgeapi: DepositQuoter per mechanism (xreserve first), in-memory
TTL quote store, relayer HTTP client, optional eth_call allowance
checker (no eth_rpc_url -> approve always included). Enabled by the
optional 'bridge' config block on APIServer.

depositToRemote ABI follows Circle's published interface; mainnet
verification tracked in #360.
Quoting is a pure encoding function; nothing is persisted. Registration
takes {token, amount, tx_hash} with the recipient party re-derived from
the authenticated session, so no quote binding is needed - the chain is
the source of truth, and a registration with wrong parameters only
yields a status row the adapter never completes. Deletes the quote
store, quote ids, TTLs, and ownership plumbing.
- validate tx_hash as a 0x-prefixed 32-byte hex string and namespace the
  registered transfer id by mechanism (was: an unvalidated/suffix-crafted
  tx_hash could collide with the on-chain observer's <txhash>-<logindex>
  ids and squat a real deposit's row)
- reject amounts whose base-unit value exceeds uint256 (was: silent
  mod-2^256 wrap in the encoded calldata, diverging from the shown amount)
- require a session on GET /bridge/transfers/{id} (was: unauthenticated
  read of any user's sender/recipient/amount by public tx-hash id)
- document that the single approve(amount) step is correct for USDC-class
  tokens and would need an approve(0) reset for USDT-style tokens
The APIServer.bridge block (deposit quotes) landed but the shipped
configs never used it. Wire it into the docker config (devstack);
local-devnet + mainnet carry a documented commented block pending the
relayer URL and confirmed Circle endpoints (#360).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants