FX trading
FX runs across two rails behind one flow: the AMM (indicative quotes, gasless Permit2 relay) and RFQ (firm market-maker quotes, on-chain escrow). Both are non-custodial — you sign the value operation yourself.Supported assets are environment-specific. Call
GET /currencies for the live set. Sepolia currently serves e.g. USDC, USDT, EURC; symbols like EURx are not valid and return 400 unsupported_asset. The examples below use USDC → EURC.The flow
quoted → accepted → awaiting_funding → funded → settling → settled (or failed / expired / refunded). route is "amm" or "rfq"; RFQ quotes are firm, AMM quotes indicative.
AMM — gasless relay
The accept response returns apermit2_witness payload:
order as a Permit2 PermitWitnessTransferFrom using witnessTypeString, then POST /fx/orders/{id}/submit { signature }. The relayer only broadcasts your signed order — it cannot move funds without your signature, and the on-chain contract rejects a signature that doesn’t match the exact order.
RFQ — self-broadcast escrow funding
RFQ differs at the submit step: you broadcast the escrow-funding transaction (the MM settles against it), then post itstx_hash. The accept response’s signing.funding carries everything you need.
1
Sign the Permit2 witness
Sign a Permit2
PermitWitnessTransferFrom with your registered signer (= quote.taker), same Permit2 domain as the AMM path, witness type RfqEscrowFunding(address signer,bytes32 executionId,address escrow,bytes32 quoteHash,address token,uint256 amount,uint256 deadline,uint256 nonce).2
Broadcast the funding tx yourself
Call
escrowQuoteWithPermit2(quote, providerSignature, funding, permit2Signature) on funding.escrow. This tx costs you gas — RFQ funding is not relayed.3
Submit the tx hash
POST /fx/orders/{id}/submit { "tx_hash": "0x…" } — the backend verifies the on-chain QuoteEscrowed event before advancing to funded → settled.The
RfqEscrowFunding field order must match witnessTypeString exactly, or the escrow rejects the signature.