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 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. 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.
items lists every request still open for quotes. next_cursor is present when another page follows.
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.
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 isRFQ_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_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.- 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.
statearrives on every transition after the quote:accepted,submitting,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 maker’s signed cash movement before fees:+askwhen the taker bought,-bidwhen the taker sold. Book the execution once, on this push.finalityrepeats the fill object withfinalitychanged fromconfirmedtofinalized, or torevertedif the chain dropped the transaction. Arevertedfill means the booked positions and cash did not happen; treat it as a manual correction.
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
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 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 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.