Skip to main content
An RFQ is a package of one to thirteen option legs on one underlying and one expiry. The taker submits the legs. A quoter prices the package as one signed bid and one signed ask. The first firm quote wins, and the taker accepts one side of it. This page covers the maker methods and pushes. It assumes the quoter role and the readiness checks on Becoming an RFQ Quoter. Session mechanics are on Connectivity for Bots; what the signature covers is on The Signed Quote. Full schemas for every method and push are in the WebSocket API reference under Builders.

Summary

Lifecycle

A request is first visible as pending. States after quoted are delivered only to the maker whose quote won. After another quoter wins, the request is no longer visible. A late rfq.maker.respond returns RFQ_STATE_CONFLICT, and no push reports the outcome.

Connecting

The maker channel is per socket. On every connection, including a resume with public/session_resume, call rfq.maker.subscribe, then page rfq.maker.poll until a result has no next_cursor. Subscribing first closes the gap in which a request could open unseen. Poll items and open pushes have the same shape and overlap in time, so deduplicate on rfq_id. An error from either method means the connection is not usable; reconnect with backoff. All four methods count against the 100 requests per second account budget on Connectivity for Bots.
The first page. items lists every request still open for quotes. next_cursor is present when another page follows.
Pass next_cursor back as cursor. Paging ends when a result has no next_cursor.

The open request

After subscribing, each new request arrives as a subscription notification on the rfq_maker channel with type: "open" and an rfq object of the same shape as a poll item.

Pricing a package

bid and ask are signed USDC totals for the whole package in legs[], not per-contract or per-leg prices, and no size multiplier applies. bid <= ask is required. Either side may be negative, since a credit structure or an inverted ratio can have negative package value. The accepted side fixes both the cash and the contracts. Quantities are in taker-buy orientation: on a buy, the taker’s leg vector is legs[] as given and the maker’s is its negation. The same rules apply to negative prices. With bid: "-120.000000" and ask: "-80.000000", a taker who buys pays -80, so the taker collects 80 USDC from the maker. A taker who sells receives -120, so the taker pays 120 USDC to the maker. Example: 1×2 call spread. The request above asks for +1.000000 of the 77000 call and -2.000000 of the 80000 call. With marks of 1270 USDC and 400 USDC, the package is worth 1 × 1270 - 2 × 400 = 470 USDC to a buyer, and a quote 30 USDC wide around that value is bid 440, ask 500. maker_fee is 0 at launch; the fee formula and rates are on Fees and Limits. Either accepted side leaves the maker short at least one leg, so writer on the fill is true.

Responding

A response is a signed two-sided quote. The maker signs the quote with the wallet key bound to the account and sends the fields from which the venue rebuilds it. The venue verifies the signature over its own reconstruction and compares every field; any difference is RFQ_CANONICAL_MISMATCH. What the signature covers is on The Signed Quote.
quote_id identifies the signed quote in every later push. Declining. A decline ends the request. The venue moves it to declined at once, the taker sees the reason instead of waiting for the 5-second timeout, and any later rfq.maker.respond or rfq.maker.decline on it, from any quoter, returns RFQ_STATE_CONFLICT. Decline only requests that would not be quoted at any price, once per request. The result echoes rfq_id with state: "declined" and the decline_reason.
Quote rules.
  • expires_at is at most 3600 seconds after issued_at and no later than max_quote_expires_at from the open request. A later value is RFQ_VALUE_OUT_OF_RANGE; the venue never clamps.
  • A quote must reach the venue with more than 2 seconds of life left, or it is RFQ_QUOTE_EXPIRED. The taker’s accept and submission windows end 2 seconds before the quote’s expiry.
  • A quote is firm for its full TTL regardless of market moves, so a short, fixed TTL is the recommended policy.
  • quote_nonce is an unsigned 64-bit integer sent as a decimal string, and it must be higher than every nonce the account has ever sent, across restarts; a wall-clock seed at startup satisfies this. A nonce at or below the account’s high-water mark is refused with RFQ_STATE_CONFLICT.
  • One quote per request.

After quoting

Three push types follow a winning quote, all on the rfq_maker channel and delivered only to the winning maker.
  • state arrives on every transition after the quote: accepted, submitting, expired, and failed. accepted_side is null until the taker accepts and then carries buy or sell. On a taker buy the maker writes every positive leg and the taker every negative one; on a taker sell the roles flip.
  • fill arrives when the fill lands. premium is the maker’s signed cash movement before fees: +ask when the taker bought, -bid when the taker sold. Book the execution once, on this push.
  • finality repeats the fill object with finality changed from confirmed to finalized, or to reverted if the chain dropped the transaction. A reverted fill means the booked positions and cash did not happen; treat it as a manual correction.
Losing and late quotes. A losing or late quote receives no push. If another quoter’s respond reached the venue first, the later respond returns RFQ_STATE_CONFLICT and nothing further is sent about the request. The error means another quoter won, and any reserved risk can be released. A respond that arrives after request_deadline returns the same error, because the request has drained. The only push every quoter receives after open is the broadcast state with declined and quote_id: null when the window closes with no quote. Reserved risk should be released on local timers, one per request from request_deadline and one per delivered quote from expires_at, not on the arrival of a message.

Reconciliation

On reconnect, run the connect sequence above: rfq.maker.subscribe, then page rfq.maker.poll, which returns every request still inside its window. Missed fills are not replayed over the socket, so read them from the query API with the session token as a bearer token, match rows to delivered quotes by rfq_id, book any fill not yet booked, and skip rows whose finality is reverted. The row schema and filters are in the REST API reference under Builders.

Errors

Every rfq.maker.* failure is JSON-RPC error 1200. data.error carries the RFQ code, data.retryable states whether the same request can succeed later, and data.operation_id identifies the call for support@joyride.exchange. RFQ_NOT_READY has five causes, distinguished by message and listed on Becoming an RFQ Quoter.

What is not supported

  • There is no cancel and no replace of a delivered quote. A quote is firm until expires_at, and a second rfq.maker.respond on the same request returns RFQ_STATE_CONFLICT. Re-pricing means letting the quote expire, so the TTL is the only control over firmness.
  • The first firm quote wins and every later quote is refused. rfq.maker.decline with a reason ends the request at once instead of at the 5-second timeout.
  • A losing or late quote receives no push. Reserved risk is released on local timers per request and per delivered quote.
  • There are no partial fills. A package fills for its exact legs[] or not at all; a request that cannot be filled whole is declined with size_limit.
  • There is no maker read of live quotes over the socket. Each delivered quote must be recorded locally. rfq.maker.poll on reconnect returns what is still open, and GET /api/query/rfq-fills?role=maker is the record of fills.
Next: The Signed Quote