A winning quote tells the user what to do next: sign something, or send a transaction — and on the open flow, send a transaction and then sign the order intent. This page is the map from a quote’s shape to those steps, lane by lane.

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

Sign signPayload 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 byte 0x00 → 66 bytes as 0x hex.
  • 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-decoded transactionMessage bytes with Ed25519 — not digest, 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 payloads sender is the user and feePayer is the solver — that split is the gasless promise: the user never pays gas to open the order.
The SDK reads through the wrapper and discriminates the shape before anything is signed (Lanes). The wire encoding each lane expects on 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}.
  • chainId is 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): one wrapAndOpen step whose value is the amount — nothing to approve.
  • Token-in lane (oif-swap-token): one approval, then one swapAndOpenV2 step with value "0"; swapInput.amountMax is the real cost to show the user, not amountIn.
  • 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 executionTx themselves.

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.
  1. POST /quote (or preview → prepare, see Preview, then prepare below). The winner carries signPayload.
  2. The user signs signPayload verbatim, per family — see The signature step above.
  3. POST /order/openfor with quoteRequestId, quoteId and the signature.
  4. Poll GET /order/{orderId}/status every 2–3 s; confirm with GET /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):
  1. The winner discloses executionTx and no signPayload — see The transaction step above.
  2. The user sends the approval and the step, promptly and before expiresAt.
  3. As soon as the transaction is sent, POST /order/openfor without a signature, well before expiresAt. The hub keeps the quote registrable for 20 minutes after issue, but the aggregator refuses a registration once expiresAt has 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).
  4. 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, then GET /onchain/{orderId}/fill, and withdraw after the escrow expires if it is not filled.
What a refused registration does to the deposit: Refunds — when does an order become refundable?.

Open flow

  1. The winner discloses an executionTx outside the proxy lanes (the SDK’s lane is open-flow). POST /order takes these winners only: it refuses every OIF-lane quote with EIN0007 at its re-approval step, before any signature check (known issue). The quote should carry sourceAccount (the signer must equal it) — a quote requested without sourceAccount cannot compile a non-EVM leg and cannot be followed by a signed POST /order (EIN0010, after the re-approval step; Get quote). Check that signIntent is present before sending anything: it is absent when the hub could not build it — it discloses none rather than a wrong one — and a signed POST /order is then impossible, so re-quote now (Get quote).
  2. The user sends approvals, then steps, and pays the gas. The hub never submits.
  3. The user personal_signs signIntent verbatim.
  4. POST /order with artifactHash = disclosure.signableHash and {signature, signer, scheme: "eip191"}, before expiresAt: a later first registration is EIN0005. From step 2 on the transaction is out, so never re-quote, whatever POST /order answers: a new quote means a second transaction, and a refused registration doesn’t stop the one already sent (known issue).
  5. Follow it with GET /status/{orderId}.

Preview, then prepare

  1. While the user browses, POST /quote/preview — debounced, and paused while a wallet prompt is open.
  2. On confirmation, create one preparationId per signing attempt and POST /quote/prepare with the chosen provider. 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 extends expiresAt (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 one preparationId per deliberate signing attempt, not one per page load. A non-prepared POST /quote winner, 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 after expiresAt: known issue).
  3. Show the prepared terms — they can differ from the preview — then sign and submit as above.
  4. 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’s detectLane (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