> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joyride.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Quoting RFQs

> The maker RFQ methods and pushes, from receiving a request and pricing the package as one signed bid and ask, to responding or declining inside the window and reconciling fills after a reconnect

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](/market-makers/becoming-a-quoter). Session mechanics are on [Connectivity for Bots](/market-makers/connectivity); what the signature covers is on [The Signed Quote](/market-makers/quote-payload). Full schemas for every method and push are in the WebSocket API reference under Builders.

## Summary

| Parameter          | Value                                                                        | Applies to                                                                                                                                                 |
| ------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Request window     | 5 seconds from creation                                                      | how long a request accepts quotes; `request_deadline` on the `open` object                                                                                 |
| Quote TTL maximum  | 3600 seconds, bounded by the nearest leg close                               | `expires_at - issued_at` on `rfq.maker.respond`; `max_quote_expires_at` on the `open` object is the hard ceiling                                           |
| Landing haircut    | 2 seconds                                                                    | a quote is refused if fewer than 2 seconds of life remain when it lands, and the accept and submission deadlines are clamped 2 seconds before `expires_at` |
| Submission window  | 30 seconds after accept                                                      | time allowed to submit the fill on chain; shortened if the quote expires first                                                                             |
| Legs per request   | 1 to 13 legs, one underlying, one expiry                                     | `legs[]` on every request                                                                                                                                  |
| Quotes per request | 1 quote per request; the first firm quote wins                               | `rfq.maker.respond`                                                                                                                                        |
| Decimal precision  | 6 decimals, decimal string                                                   | every `bid`, `ask`, `quantity`, `premium`, and `maker_fee`                                                                                                 |
| Quote nonce        | unsigned 64-bit integer as a decimal string, strictly increasing per account | `quote_nonce` on `rfq.maker.respond`                                                                                                                       |
| Poll page size     | 1 to 100 items, default 100                                                  | `limit` on `rfq.maker.poll`                                                                                                                                |

## Lifecycle

A request is first visible as `pending`. States after `quoted` are delivered only to the maker whose quote won.

| State        | Meaning                                                                                                           | What the maker receives                                                                                                                                               |
| ------------ | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending`    | The request is open and no quote has been accepted by the venue yet                                               | an `open` push, or an item in a `rfq.maker.poll` page                                                                                                                 |
| `quoted`     | The maker's quote won and is firm until `expires_at`                                                              | the `rfq.maker.respond` result with `quote_id` and `state: "quoted"`                                                                                                  |
| `accepted`   | The taker accepted one side of the maker's quote                                                                  | a `state` push with `state: "accepted"` and `accepted_side` set                                                                                                       |
| `submitting` | The fill transaction is being submitted on chain                                                                  | a `state` push with `state: "submitting"`                                                                                                                             |
| `filled`     | The fill landed on chain                                                                                          | a `fill` push carrying the fill object, then a `finality` push when the fill is finalized or reverted                                                                 |
| `declined`   | A quoter declined it, which ends the request for everyone, or the request window closed with no quote from anyone | a `state` push with `state: "declined"` if this maker declined; a broadcast `state` push with `quote_id: null` when nobody quoted; nothing if another quoter declined |
| `expired`    | The winning quote reached `expires_at` without an accept                                                          | a `state` push with `state: "expired"`                                                                                                                                |
| `failed`     | The fill was attested or submitted and did not land                                                               | a `state` push with `state: "failed"`                                                                                                                                 |

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.

| Method                | Request                                                                             | Response                                      | Errors                                                                                                                                                                                                                                                       |
| --------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `rfq.maker.subscribe` | `{}`                                                                                | `{channel: "rfq_maker", subscribed: true}`    | `1200` `RFQ_WRONG_ROLE`                                                                                                                                                                                                                                      |
| `rfq.maker.poll`      | `cursor?` (an `rfq_id`), `limit?` 1 to 100                                          | `{items: [open object, ...], next_cursor?}`   | `1200` `RFQ_NOT_READY`, `RFQ_WRONG_ROLE`, `RFQ_RATE_LIMITED`                                                                                                                                                                                                 |
| `rfq.maker.respond`   | `rfq_id`, `quote_nonce`, `bid`, `ask`, `issued_at`, `expires_at`, `maker_signature` | `{rfq_id, quote_id, state: "quoted"}`         | `1200` `RFQ_NOT_READY`, `RFQ_WRONG_ROLE`, `RFQ_STATE_CONFLICT`, `RFQ_CANONICAL_MISMATCH`, `RFQ_INVALID_SIGNATURE`, `RFQ_QUOTE_EXPIRED`, `RFQ_VALUE_OUT_OF_RANGE`, `RFQ_PRECISION_UNSUPPORTED`, `RFQ_INVALID_INSTRUMENT`, `RFQ_NOT_FOUND`, `RFQ_RATE_LIMITED` |
| `rfq.maker.decline`   | `rfq_id`, `reason`                                                                  | `{rfq_id, state: "declined", decline_reason}` | `1200` `RFQ_WRONG_ROLE`, `RFQ_STATE_CONFLICT`, `RFQ_NOT_FOUND`, `RFQ_RATE_LIMITED`                                                                                                                                                                           |

All four methods count against the 100 requests per second account budget on [Connectivity for Bots](/market-makers/connectivity).

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "rfq.maker.subscribe",
  "params": {}
}
```

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "channel": "rfq_maker",
    "subscribed": true
  }
}
```

The first page. `items` lists every request still open for quotes. `next_cursor` is present when another page follows.

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "rfq.maker.poll",
  "params": { "limit": 100 }
}
```

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "items": [
      {
        "rfq_id": "0192f1a3b4c5d6e7f8091a2b3c4d5e6f",
        "legs": [
          { "instrument": "BTC_USDC-2MAY26-77000-C", "quantity": "1.000000" },
          { "instrument": "BTC_USDC-2MAY26-80000-C", "quantity": "-2.000000" }
        ],
        "taker_account": 2077,
        "taker_subaccount": "<base58 subaccount PDA>",
        "taker_funded": true,
        "request_deadline": "<RFC3339 UTC>",
        "deployment": {
          "program_id": "<base58 program id>",
          "deployment_id": "<64 hex characters>",
          "config_pda": "<base58 config PDA>",
          "epoch": 3
        },
        "nearest_leg_close": 1777680000,
        "max_quote_expires_at": 1777653600
      }
    ],
    "next_cursor": "0192f1a3b4c5d6e7f8091a2b3c4d5e6f"
  }
}
```

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.

| Field                  | Meaning                                                                                                                                                                                                                                                                                                                                      |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rfq_id`               | 16-byte request id as 32 lowercase hex characters; the key for every later method and push, and a field of the signed payload                                                                                                                                                                                                                |
| `legs[]`               | The package, in the taker's submitted order. Each leg is a listed `instrument` symbol and a signed 6-decimal `quantity` in taker-buy orientation: positive means the taker receives that many contracts on a `buy` accept, negative means the taker delivers them. Legs are signed in this order; the venue never reorders them              |
| `taker_account`        | The taker's account id                                                                                                                                                                                                                                                                                                                       |
| `taker_subaccount`     | The taker's on-chain subaccount PDA, base58. Copied into the signed payload, which binds the quote to this counterparty                                                                                                                                                                                                                      |
| `taker_funded`         | `true` when the taker has a positive free balance. A coarse signal: it does not show that the taker can fund either side of this package, and it discloses no balances                                                                                                                                                                       |
| `request_deadline`     | RFC3339 UTC. Quotes that arrive after it are refused and the request drains to `declined`                                                                                                                                                                                                                                                    |
| `deployment`           | The margin-vault deployment the request is bound to: `program_id` and `config_pda` in base58, `deployment_id` as 64 hex characters, and the integer `epoch` the request was created under. All four are copied verbatim into the signed payload. A `program_id` that differs from the pinned program id for the cluster should not be quoted |
| `nearest_leg_close`    | Unix seconds; the earliest expiry among the legs. No quote may expire after it                                                                                                                                                                                                                                                               |
| `max_quote_expires_at` | Unix seconds; the latest `expires_at` the venue accepts for this request. It is never later than `nearest_leg_close` and is further bounded by the 3600-second TTL ceiling. Sign an `expires_at` at or before it; a later value is rejected, never clamped                                                                                   |

## 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.

| Taker accepts | Cash                                   | Taker receives per leg | Maker receives per leg |
| ------------- | -------------------------------------- | ---------------------- | ---------------------- |
| `buy`         | taker pays `ask`; maker receives `ask` | `quantity`             | `-quantity`            |
| `sell`        | taker receives `bid`; maker pays `bid` | `-quantity`            | `quantity`             |

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.

| Taker accepts | Cash movement                                    | Taker position after the fill | Maker position after the fill | Maker `premium` | `maker_fee` |
| ------------- | ------------------------------------------------ | ----------------------------- | ----------------------------- | --------------- | ----------- |
| `buy`         | taker pays 500.000000; maker receives 500.000000 | +1 of 77000-C, −2 of 80000-C  | −1 of 77000-C, +2 of 80000-C  | 500.000000      | 0.000000    |
| `sell`        | taker receives 440.000000; maker pays 440.000000 | −1 of 77000-C, +2 of 80000-C  | +1 of 77000-C, −2 of 80000-C  | −440.000000     | 0.000000    |

`maker_fee` is 0 at launch; the fee formula and rates are on [Fees and Limits](/market-makers/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](/market-makers/quote-payload).

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "rfq.maker.respond",
  "params": {
    "rfq_id": "0192f1a3b4c5d6e7f8091a2b3c4d5e6f",
    "quote_nonce": "1777650001234567890",
    "bid": "440.000000",
    "ask": "500.000000",
    "issued_at": 1777650001,
    "expires_at": 1777650031,
    "maker_signature": "<base64 of 64 bytes>"
  }
}
```

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "rfq_id": "0192f1a3b4c5d6e7f8091a2b3c4d5e6f",
    "quote_id": "<64 hex characters>",
    "state": "quoted"
  }
}
```

`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`.

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "rfq.maker.decline",
  "params": {
    "rfq_id": "0192f1a3b4c5d6e7f8091a2b3c4d5e6f",
    "reason": "size_limit"
  }
}
```

| Reason                | Use it when                                                       | A reference policy                                                                                                                                                                                |
| --------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unlisted_instrument` | A leg names an instrument the maker does not quote                | a leg symbol is outside the configured instrument set; decided before pricing                                                                                                                     |
| `near_expiry`         | The package is too close to `nearest_leg_close` to hedge          | time to the leg expiry is under a configured minimum                                                                                                                                              |
| `size_limit`          | The package exceeds the maker's size or position limit            | a leg quantity exceeds the maximum size, or the position on the instrument is at its cap; a quote commits both sides at the taker's option, so the whole request is declined rather than one side |
| `stale_data`          | Spot, IV, or mark inputs are missing or stale                     | no fair value can be computed because spot or IV is absent                                                                                                                                        |
| `low_confidence`      | A price can be computed but the maker will not stand behind it    | the pricing parameters are invalid, or the rounded bid falls to zero                                                                                                                              |
| `queue_overflow`      | More requests are open than the maker can price inside the window | not needed when pricing keeps up with the request rate                                                                                                                                            |

**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.

```json theme={null}
{
  "jsonrpc": "2.0",
  "method": "subscription",
  "params": {
    "channel": "rfq_maker",
    "data": {
      "type": "fill",
      "fill": {
        "rfq_id": "0192f1a3b4c5d6e7f8091a2b3c4d5e6f",
        "quote_id": "<64 hex characters>",
        "legs": [
          { "instrument": "BTC_USDC-2MAY26-77000-C", "quantity": "1.000000" },
          { "instrument": "BTC_USDC-2MAY26-80000-C", "quantity": "-2.000000" }
        ],
        "maker_side": "sell",
        "bid": "440.000000",
        "ask": "500.000000",
        "premium": "500.000000",
        "maker_fee": "0.000000",
        "writer": true,
        "tx_signature": "<base58 transaction signature>",
        "finality": "confirmed",
        "filled_at": "<RFC3339 UTC>"
      }
    }
  }
}
```

**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.

```http theme={null}
GET /api/query/rfq-fills?role=maker&status=finalized&limit=100
Authorization: Bearer <jwt>
```

## 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](mailto:support@joyride.exchange). `RFQ_NOT_READY` has five causes, distinguished by `message` and listed on [Becoming an RFQ Quoter](/market-makers/becoming-a-quoter).

| `data.error`                | Meaning                                                                                                                                                                       | Action                                                                                                        |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `RFQ_NOT_READY`             | The venue cannot admit the call yet; the five causes are on [Becoming an RFQ Quoter](/market-makers/becoming-a-quoter)                                                        | Retry with backoff inside the window; escalate the Joyride-owned causes if they persist                       |
| `RFQ_WRONG_ROLE`            | The session is not authenticated, the account lacks `rfq_quoter`, or the caller acted on its own request                                                                      | Check `roles` from `public/auth`; do not retry the same call                                                  |
| `RFQ_STATE_CONFLICT`        | The request already holds a quote, was declined, drained at its deadline, or is otherwise past `pending`; or `quote_nonce` is at or below the account's high-water mark       | On respond, treat it as another quoter won and release risk; on a nonce conflict, re-seed above the mark      |
| `RFQ_CANONICAL_MISMATCH`    | The payload the venue rebuilt from the submitted fields differs from the bytes the signature covers                                                                           | Fix the payload construction against [The Signed Quote](/market-makers/quote-payload); do not retry unchanged |
| `RFQ_INVALID_SIGNATURE`     | `maker_signature` is not a canonical base64 encoding of 64 bytes, or does not verify under the bound wallet key                                                               | Sign with the provisioned wallet key; check the encoding                                                      |
| `RFQ_QUOTE_EXPIRED`         | Fewer than 2 seconds of the quote's life remained when it landed                                                                                                              | Re-issue with a later `expires_at` while the request is still `pending`                                       |
| `RFQ_VALUE_OUT_OF_RANGE`    | A decimal is outside the signed 64-bit 6-decimal range, `bid > ask`, `issued_at >= expires_at`, the TTL exceeds 3600 seconds, or `expires_at` is after `max_quote_expires_at` | Fix the value; the venue never clamps                                                                         |
| `RFQ_PRECISION_UNSUPPORTED` | A decimal carries more than 6 fractional digits or is not a canonical string                                                                                                  | Round to 6 decimals and send a string, never a JSON number                                                    |
| `RFQ_INVALID_INSTRUMENT`    | A leg closed, halted, or went into settlement since the request opened                                                                                                        | Do not retry; the request cannot fill                                                                         |
| `RFQ_NOT_FOUND`             | `rfq_id` names no request the venue knows                                                                                                                                     | Check the id; a request purged at settlement is gone                                                          |
| `RFQ_RATE_LIMITED`          | The 100 requests per second account budget is spent                                                                                                                           | Back off for the rest of the second; the call was not admitted                                                |

## 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](/market-makers/quote-payload)
