At most one of signPayload and executionTx
A winner discloses at most one of signPayload and executionTx
(Get quote — Response; Get quote). A same-chain solver-swap winner
(executionClass: INFINITY_SOLVER, disclosure.signablePayload.kind: "solver-swap") discloses
neither, because the hub’s solver bids carry terms only, and neither does a transfer winner.
Treat both as not completable. For a solver-swap winner, POST /order/openfor answers
EIN0001 (pointing at POST /order), and POST /order can still record an order for it — its
re-approval step checks an INFINITY_SOLVER winner against the solver registry, not the product
registry — but that record moves no funds: the hub never submits a transaction, and this winner
gives the user none to send.
In code (the SDK’s detectLane makes the same decision, and names each outcome a
lane):
"none" covers a solver-swap winner, a transfer winner and no quote at all — check for it before
doing anything else with a quote.
The lanes
The reference uses two vocabularies for the same six lanes:disclosure.signablePayload.kind
(what the route pages and this table use) and the provider product id (oif-escrow-v0,
oif-3009-v0, …, what Flows and the SDK product-facing pages use). This table is the one
place both are shown side by side.
The signature step
SignsignPayload verbatim — never rebuild it, whatever family it is for: a rebuilt object can
drop or alter a field (EIP-712 hashes the typed values, so key order alone changes nothing):
- EVM (
oif-escrow-gasless, Permit2 EIP-712 typed data):eth_signTypedData_v4, then prefix the 65-byte ECDSA signature with the scheme byte0x00→ 66 bytes as0xhex. - External gasless (
aggregator-gasless, EIP-3009, wrapped as{chainId, payload}): unwrap it first, sign the inner payload, send the raw 65-byte signature (a 66-byte prefixed form is also accepted and normalised). - Solana (
oif-escrow-nonevm,{…, transactionMessage, digest}): the funding wallet signs the base64-decodedtransactionMessagebytes with Ed25519 — notdigest, which is a checksum, not what gets signed. The client sends a 97-byte envelope (0x00‖ 64-byte signature ‖ 32-byte public key) as standard base64. On non-EVM payloadssenderis the user andfeePayeris the solver — that split is the gasless promise: the user never pays gas to open the order.
POST /order/openfor is a route-page detail, not repeated here — see
Request body.
The transaction step
executionTx’s shape ({approvals[], steps[]}) is spec and lives on
Get quote — Response — see it there. What matters when you execute it:
- Approvals first, steps in order.
approvals[]is{chainId, token, spender, amount};steps[]is{chainId, to, data, value, description}. chainIdis an integer on the hub’s proxy lanes (oif-wrap-native,oif-swap-token) and a chain-domain string on the external aggregator lane — accept both.- Native-coin lane (
oif-wrap-native): onewrapAndOpenstep whosevalueis the amount — nothing to approve. - Token-in lane (
oif-swap-token): one approval, then oneswapAndOpenV2step withvalue "0";swapInput.amountMaxis the real cost to show the user, notamountIn. - The quote is advisory. The engine re-prices at admission against live gas, so send the
approval and the step promptly and before
expiresAt. - The hub never submits a transaction on the user’s behalf, on any lane — the user (or their
wallet) always sends
executionTxthemselves.
The contract calls
To decode a step before the user sends it, or to build a refund, these are the calls and their selectors.order is the escrow’s StandardOrder tuple.
Take the UserProxy and settler addresses from
GET /catalog (userProxy,
inputSettlers): they change when the hub is redeployed.
Common step flows
Gasless openFor
On the EVM lane (oif-escrow-gasless) the paying wallet must hold the source token and have
approved it to Permit2 before step 3. The hub checks neither: without them the solver declines the
signed order with EIN0021 (http 422 (SOLVER_REJECTED)), and nothing is opened or spent. Approve
before quoting: an approval mined while a quote is open can leave the signature on an expired
quote.
POST /quote(or preview → prepare, see Preview, then prepare below). The winner carriessignPayload.- The user signs
signPayloadverbatim, per family — see The signature step above. POST /order/openforwithquoteRequestId,quoteIdand the signature.- Poll
GET /order/{orderId}/statusevery 2–3 s; confirm withGET /onchain/….
Proxy lanes
The native-coin lane (oif-wrap-native, a source asset that is a chain’s native coin) and the
token-in lane (oif-swap-token, an undeclared token on a chain with a swap router):
- The winner discloses
executionTxand nosignPayload— see The transaction step above. - The user sends the approval and the step, promptly and before
expiresAt. - As soon as the transaction is sent,
POST /order/openforwithout a signature, well beforeexpiresAt. The hub keeps the quote registrable for 20 minutes after issue, but the aggregator refuses a registration onceexpiresAthas passed (EIN0007,fail-closed rejected: upstream refused: http 400 (QUOTE_EXPIRED)) (known issue); a repeat registration is idempotent. Not before sending, though: nothing checks the deposit, so a registration without one is acked all the same (known issue). - Once the transaction is out, never re-quote, whatever the registration answers: a new quote
means a second deposit. A refused registration leaves the deposit in escrow, and it completes
on chain anyway — the solver still fills it — but the hub never records the order. Follow it by
the send transaction’s hash on
GET /onchain/tx/{chainDomain}/{txHash}/order, thenGET /onchain/{orderId}/fill, and withdraw after the escrow expires if it is not filled.
Open flow
- The winner discloses an
executionTxoutside the proxy lanes (the SDK’s lane isopen-flow).POST /ordertakes these winners only: it refuses every OIF-lane quote withEIN0007at its re-approval step, before any signature check (known issue). The quote should carrysourceAccount(the signer must equal it) — a quote requested withoutsourceAccountcannot compile a non-EVM leg and cannot be followed by a signedPOST /order(EIN0010, after the re-approval step; Get quote). Check thatsignIntentis present before sending anything: it is absent when the hub could not build it — it discloses none rather than a wrong one — and a signedPOST /orderis then impossible, so re-quote now (Get quote). - The user sends
approvals, thensteps, and pays the gas. The hub never submits. - The user
personal_signssignIntentverbatim. POST /orderwithartifactHash=disclosure.signableHashand{signature, signer, scheme: "eip191"}, beforeexpiresAt: a later first registration isEIN0005. From step 2 on the transaction is out, so never re-quote, whateverPOST /orderanswers: a new quote means a second transaction, and a refused registration doesn’t stop the one already sent (known issue).- Follow it with
GET /status/{orderId}.
Preview, then prepare
- While the user browses,
POST /quote/preview— debounced, and paused while a wallet prompt is open. - On confirmation, create one
preparationIdper signing attempt andPOST /quote/preparewith the chosenprovider. On a lost response, retry with the same body and id — you get the same quote and the same expiry, never a new one, because a replay never extendsexpiresAt(Get quote). The prepared transaction template is stored with the quote, so it can be submitted through any hub replica sharing the event store — use onepreparationIdper deliberate signing attempt, not one per page load. A non-preparedPOST /quotewinner, by contrast, must be submitted to the replica that quoted it — its template is held in that process for 30 s (20 min on the proxy lanes, though the aggregator refuses a registration afterexpiresAt: known issue). - Show the prepared terms — they can differ from the preview — then sign and submit as above.
- On expiry before anything was sent, discard the signature, create a new
preparationId, and prepare again — never once the user’s transaction is out (proxy lanes, open flow).
Which step applies
The TypeScript SDK’sdetectLane (read into quote.lane on every quote it returns) names which of
the four flows above applies, as a lane: evm-permit2, solana-escrow or evm-3009 (openFor with
a signature), wrap-native or swap-token (openFor without one), open-flow (POST /order), or
informational (a solver-swap or transfer winner: nothing to execute). A malformed answer is
unsupported, with the reason. See Lanes.
See also
- Get quote, Submit order (openFor), Create order, Prepare quote
- SDK: Lanes and Executing orders — the same flows, run by
executeQuote - Trade types, Refunds