Appearance
RFQ
Three endpoints: ask for a quote, report the transaction you sent, read the status.
Request a quote
POST /v2/rfq/requestRequires trades:write. This request blocks while makers answer — up to the corridor's reply budget (750 ms by default). Set your client timeout well above it.
Send exactly one of sellAmount (exact-input) or buyAmount (exact-output):
json
{
"chainId": 56,
"sellToken": "0xCNGN",
"buyToken": "0xUSDT",
"sellAmount": "1000000000",
"taker": "0xYourWallet"
}| Field | Notes |
|---|---|
chainId | Must match your key's environment. |
sellToken / buyToken | Must differ, and must form an enabled RFQ corridor. |
sellAmount | Exact-input: the gross, fee-inclusive spend cap. Makers may debit less. |
buyAmount | Exact-output: the exact amount you want to receive. |
taker | The wallet that will sign and broadcast. The quote is bound to it. |
preferredLiquidityWallets | Optional. Solicit these makers first; fall back to the rest. Max 10. |
restrictedLiquidityWallets | Optional. Solicit only these makers, no fallback. Max 10. |
preferredLiquidityWallets and restrictedLiquidityWallets are mutually exclusive — sending both is a 400. They work the same way as on v1.
A firm quote
json
{
"data": {
"rfqId": "rfq_…",
"status": "quoted",
"quote": {
"sellAmount": "1000000000",
"buyAmount": "612000000",
"feeAmount": "99990",
"takerPays": "999900010",
"rateRay": "612000000000000000000000000",
"expiresAt": "2026-08-07T12:00:04.500Z",
"orderDeadline": "2026-08-07T12:00:34.000Z",
"reactor": "0xReactor",
"taker": "0xYourWallet",
"encodedOrder": "0x…",
"signature": "0x…"
},
"transactions": {
"approval": { "to": "0xCNGN", "data": "0x095ea7b3…", "value": "0", "chainId": 56 },
"swap": { "to": "0xReactor", "data": "0x…", "value": "0", "chainId": 56 }
},
"routing": {
"preferenceApplied": false,
"restrictionApplied": false,
"fallbackUsed": false,
"targetMakerWallets": [],
"preferredQuotesReceived": 0,
"openMarketQuotesReceived": 2
}
}
}The two numbers that matter for execution:
takerPaysis what actually leaves your wallet — the maker's order output plus the protocol fee. On exact-input this is at mostsellAmount; a maker that prices better than your cap debits less. ApprovetakerPays, notsellAmount.buyAmountis what you receive.
expiresAt is the accept cutoff — the maker's own, which may be sooner than the corridor's TTL cap. orderDeadline is when the signed order stops being executable on chain; it is always at or after expiresAt. Treat expiresAt as your deadline.
The quote is returned exactly once
encodedOrder and signature come back from this call and nowhere else. GET /v2/rfq/{id} never replays them. Store them if you need them.
Executing it
- Approve
takerPaysof the sell token toquote.reactor. Skip it if the standing allowance already covers it — the TTL is seconds, don't spend it on a redundant approval. - Send
transactions.swapfrom thetakerwallet.valueis always"0"; the fee is taken in the sell token, not native. - Re-check the clock before broadcasting. If a slow approval pushed you past
expiresAt, request a new quote instead of sending — a late broadcast can only revert.
No quote
A no-quote is a 200, not an error. It means nobody would price your trade, which is a normal outcome.
json
{
"data": {
"rfqId": "rfq_…",
"status": "no_quote",
"reason": "no_valid_quote",
"routing": { "…": "…" }
}
}reason | What happened |
|---|---|
no_makers_online | No eligible maker was connected for this corridor. |
no_restricted_liquidity | You restricted to specific wallets and none of them could quote. |
no_valid_quote | Makers were asked but none returned a usable quote in time. |
Retrying immediately is reasonable for no_valid_quote — makers reprice constantly. Retrying no_makers_online in a tight loop is not; back off.
Report the transaction
POST /v2/rfq/{id}/submitRequires trades:write. Tell us the hash you broadcast so we can track it.
json
{ "txHash": "0xabc…" }Idempotent on the same hash — resubmitting returns the current status. A different hash for an RFQ already reported is a 409.
Reporting is a courtesy, not a requirement: settlement is reconciled from the filled order on chain, so a fill is detected whether or not you call this. If the call fails transiently, retry it — but don't treat the failure as a failed swap.
Reporting after the fill is fine
If you wait for the receipt before reporting, reconciliation may already have marked the RFQ filled. Submitting the same hash then still returns 200 with the current status. That's the happy path finishing out of order, not a conflict.
Read the status
GET /v2/rfq/{id}Requires trades:read. Scoped to the caller that created it — another partner's key gets 404, and so does a test key reading a live RFQ.
json
{
"data": {
"rfqId": "rfq_…",
"status": "filled",
"chainId": 56,
"sellToken": "0xCNGN",
"buyToken": "0xUSDT",
"sellAmount": "999900010",
"buyAmount": "612000000",
"feeAmount": "99990",
"taker": "0xYourWallet",
"expiresAt": "2026-08-07T12:00:04.500Z",
"txHash": "0xabc…",
"filledAt": "2026-08-07T12:00:09.000Z",
"failReason": null,
"routing": { "…": "…" },
"createdAt": "2026-08-07T12:00:00.000Z"
}
}status | Meaning |
|---|---|
soliciting | Makers are being asked. You won't normally see this. |
quoted | A firm quote was returned and is live. |
no_quote | Nobody quoted. Terminal. |
submitted | You reported a transaction; we're waiting for it to settle. |
filled | Settled on chain. Terminal. |
failed | The signed order's deadline passed without the transaction landing. |
expired | The quote lapsed without being executed. Terminal. |
Once a winner exists, sellAmount and buyAmount are the settled amounts — the actual debit (including fee) and the amount delivered — not the cap you asked for. Before that they echo the request.
failed and expired aren't always final
A fill that indexes late corrects the record for up to 24 hours, so an RFQ can move from failed to filled. If you broadcast a transaction and it settled, trust the chain and keep polling rather than treating the first terminal verdict as the end.
Prefer webhooks over polling for settlement.