buy or sell, which reveals the direction and asks for that one price, or two_way, which hides it and takes a bid, an ask, or both. The first firm quote wins, and the taker accepts a side the quote priced. 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 API Connectivity; what the signature covers is on The Signed Quote. Each method and push has a reference page with its full schema and an example frame under Reference in this tab.
Summary
Lifecycle
A request is first visible aspending. 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 withpublic/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. Errors are per call and leave the socket usable; a retryable error on either method is retried with backoff on the same connection.
rfq.maker.poll, rfq.maker.respond, and rfq.maker.decline count against the 100-request account budget; rfq.maker.subscribe does not. See API Connectivity for the fixed-window semantics.
Frames for rfq.maker.subscribe and rfq.maker.poll are on their reference pages. A poll result, which lists every request still open for quotes:
next_cursor back as cursor. Paging ends when a result has no next_cursor.
The open request
After subscribing, each new request arrives as asubscription notification on the rfq_maker channel with type: "open" and an rfq object of the same shape as a poll item.
The taker fixes every leg’s quantity when creating the request. An older one-leg
rfq.create may send {instrument, size} instead of legs[]; the venue converts that pair to one positive leg. In either case, quote the exact legs[] shown here. A maker cannot change the quantity.
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. Send the side(s) the request’s side allows: only ask on a buy request, only bid on a sell request, either or both on two_way. When both are present bid <= ask is required. Either side may be negative, since a credit structure or an inverted ratio can have negative package value. A side you leave out is signed as a reserved sentinel inside the payload (see The Signed Quote); that is the venue’s concern, and your JSON simply omits the field.
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 either side of that value is bid 440, ask 500.
premium on the fill is the accepted package total, not the maker’s signed cash flow: the maker receives it when maker_side is sell and pays it when maker_side is buy.
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 writers.maker on the fill is true.
Responding
A response is a signed quote on one side or both. 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 isRFQ_CANONICAL_MISMATCH. What the signature covers is on The Signed Quote.
rfq.maker.respond carries no size; the taker’s requested quantities are already fixed in legs[]. A request that includes size, or any other field not listed below, is rejected with RFQ_INVALID_PARAMS.
The frame is on rfq.maker.respond. The result carries the quote_id that 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; the frame is on rfq.maker.decline.
Quote rules.
expires_atis at most 3600 seconds afterissued_atand no later thanmax_quote_expires_atfrom the open request. A later value isRFQ_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_nonceis 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 withRFQ_STATE_CONFLICT.max_maker_feeandmax_maker_locked_balanceare unsigned 6-decimal USDC strings covered by the signature. They must be at least the largerfeeandpost_fill_lockedacross the sides you quoted, recomputed by the venue at your signedbidandask; a smaller value isRFQ_SIGNED_LIMIT_BELOW_REQUIREMENT, and a subaccount with no covered anchor isRFQ_MAKER_UNFUNDED. One pair covers every quoted side because the taker chooses among them; an unquoted side contributes nothing. What they authorize is on The Signed Quote.- At least one of
bidandaskis required, and only the side(s) the request’ssideallows. A supplied unrequested side isRFQ_SIDE_NOT_REQUESTED; the request stays open and a corrected quote may follow. - One quote per request.
After quoting
Three push types follow a winning quote, all on therfq_maker channel and delivered only to the winning maker. Their frames are on rfq_maker pushes.
statearrives on every transition from the quote on:quoted,accepted,submitting(twice),expired, andfailed.accepted_sideisnulluntil the taker accepts and then carriesbuyorsell. On a taker buy the maker writes every positive leg and the taker every negative one; on a taker sell the roles flip.fillarrives when the fill lands.premiumis the accepted package total before fees, the same value asexecution_price: the quote’saskwhen the taker bought, itsbidwhen the taker sold. It is never negated for the maker, so read the direction frommaker_side: onsellthe maker receivespremium, onbuythe maker pays it, and a negativepremiumreverses that. Book the execution provisionally on this push, keyed byrfq_idandquote_id; a later re-land of the same accepted RFQ is not a second trade.finalityrepeats the fill object withfinalitychanged fromconfirmedtofinalized, or torevertedif the chain dropped that transaction.finalizedis terminal success.revertedis not terminal: the RFQ returns tosubmittingand the relayer may submit the same accepted trade again. Mark that transaction attempt reverted, but do not unwind an external hedge or declare the RFQ failed yet. Wait for either anotherfillfor the samerfq_idandquote_idunder a newtx_signature, in which case keep the existing hedge and do not hedge it a second time, or astatepush withstate: "failed", in which case the trade is terminally absent and the hedge can be unwound under the desk’s policy.
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, sent when the window closes with no quote or a leg stops trading. 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 and group attempts by rfq_id and quote identity. A reverted row invalidates that transaction attempt, not necessarily the accepted RFQ: a later row for the same RFQ may be its re-land under a new signature. Book the active confirmed or finalized attempt once. If the newest known attempt is reverted and no later fill or terminal failed state was observed, the fill history alone does not prove failure; leave the hedge in a pending-reversion state and reconcile the outcome rather than unwinding automatically. Send no status filter: it matches one finality value exactly and would hide other attempts. Rows come newest first; while a page carries next_cursor, pass it back as after to read the next one. The row schema and filters are on GET /api/query/rfq-fills.
Errors
Everyrfq.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 several 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 secondrfq.maker.respondon the same request returnsRFQ_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.declinewith 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 withsize_limit. - There is no maker read of live quotes over the socket. Each delivered quote must be recorded locally.
rfq.maker.pollon reconnect returns what is still open, andGET /api/query/rfq-fills?role=makeris the record of fills.