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

# RFQ (block trades)

> Request quotes for a block size, accept the best allocation, and settle privately with one or more makers

RFQ (request for quote) trades a block privately. A taker asks for a size on a market, makers quote it privately, and the taker accepts one side of the aggregated view. The makers it selects get a short last look, and the trade then settles as one sweep against their quotes. RFQ works on spot and perpetual markets.

RFQ is available to accounts it is enabled for. Every RFQ call from another account is refused with `RFQ_DISABLED`. Check `GET /api/v1/rfq/capabilities` (`sdk.rfq.getCapabilities()`) first: it is refused with `RFQ_DISABLED` too when RFQ is not enabled for you, and otherwise lists the markets RFQ is offered on with each one's minimum and maximum size, the timings and your creation quota.

A delegated agent session cannot trade through RFQ: creating, accepting, cancelling, quoting, withdrawing and answering last look are refused with `403`. It can still read capabilities, an RFQ's status and your RFQ list.

## The flow

**1. The taker requests quotes**

`POST /api/v1/rfqs` names the market, the product (`SPOT` or `PERPETUAL`) and the maximum base quantity. The quantity is not a guaranteed fill. It must sit between the market's minimum and maximum order size.

* **Two-way (default):** omit `side`, and makers never see your direction.
* **One-way:** set `side` to `BUY` or `SELL`. Makers then see it and may quote only the side that fills you: an ask when you buy, a bid when you sell. A quote on the other side is refused with `RFQ_SIDE_NOT_REQUESTED`.
* **Perpetuals:** set `leverage` (and optionally `marginMode`, `ISOLATED` by default). It applies to whichever side you accept.

You may create 5 RFQs per rolling 30 seconds. Past that, creation is refused with `RFQ_QUOTA_EXCEEDED`; the capabilities response reports `quotaRemaining` and `quotaRetryAfterMs`.

**2. Makers receive it and quote**

Every account RFQ is enabled for receives the request on the authenticated `rfq` WebSocket channel as an `RfqRequested` push. The requester's own wallet family never receives it, and the request never names the taker. Discovery is WebSocket only: there is no call that lists open RFQs to quote.

A maker quotes with `PUT /api/v1/rfqs/{rfqId}/quote`: a bid, an ask or both, each a price and a capacity.

* **Versions:** the first quote names `expectedVersion: 0`. Each replacement names the maker's current version (`ownLatestVersion` on `GET /api/v1/rfqs/{rfqId}`).
* **Withdrawing:** `DELETE /api/v1/rfqs/{rfqId}/quote` withdraws the current quote.
* **Reserved funds:** a quote reserves funds as a resting limit order does, each side on its own. A side that cannot be funded is refused, and the version holds only the funded sides.
* **Perpetuals:** leverage and margin mode are fixed while a side rests. To change them, withdraw and quote again.
* **Self-trade:** a maker cannot quote an RFQ from its own wallet family. Self-trade covers the whole family: a master and its sub-accounts.

**3. The taker watches the view**

The taker never sees individual quotes or makers. It sees an aggregated view, pushed as `RfqView` on the `rfq` channel and returned by `GET /api/v1/rfqs/{rfqId}`. For each side (`bid`, makers buying, and `ask`, makers selling) the view shows:

* `vwap`: the all-in volume-weighted price of the allocation that would fill the quantity, the taker's Monaco and builder fees included;
* `availableQuantity`;
* `grossVwap` and the estimated Monaco, builder and total fees on that quantity;
* optionally `book`: the same size priced on the public order book with the view's fee rates, for comparison. It is shown only from a fresh snapshot (at most 500 ms old) that fills the whole size. The book is read the way your own sweep would walk it: orders already past their deadline and your wallet family's resting orders are left out. That assumes a sweep trades around your orders, as `SKIP` and the default `CANCEL_MAKER` do. A book order of yours under `CANCEL_TAKER` or `CANCEL_BOTH` stops at your first own order instead, so it could fill less than shown. It is display only and never blocks an accept.

Allocation is price-time priority over at most 10 makers: the best price first, then the earliest quote. Each maker's part is rounded down to the market's size step, and a part below the market's minimum size is skipped. The view carries a `viewVersion`, which moves whenever any quote changes, is withdrawn or expires.

Follow the view on the `RfqView` pushes rather than polling `GET /api/v1/rfqs/{rfqId}`. Pushes can arrive out of order: keep the view with the highest `sequence`. When quotes change quickly, views in between may be skipped, but the last view pushed always reflects the latest change. Read the RFQ only when a push may have been missed: once after you subscribe (makers can quote before the create returns) and after a reconnect. In TypeScript, [`waitForRfqView`](https://docs.0xmonaco.com/sdk/typescript/rfq#following-the-view) does this for you.

**4. The taker accepts or cancels**

`POST /api/v1/rfqs/{rfqId}/accept` accepts one side (`BUY` or `SELL`) of the view, naming its `viewVersion`. Acceptance is binding. If the view has moved since, the accept is refused with `RFQ_STALE_VIEW` and nothing is substituted silently.

Before accepting, the taker may cancel with `POST /api/v1/rfqs/{rfqId}/cancel`. An RFQ nobody accepts within its 120-second request window expires.

**5. Selected makers get a 250 ms last look**

Each selected maker receives an `RfqSelected` push with its own selection: the quantity selected from its quote, its frozen price and capacity, its frozen maker fee rate, and the last-look deadline, 250 ms after acceptance. By then it may answer `POST /api/v1/rfqs/{rfqId}/last-look`:

* **Silence executes.** There is no accept call: a maker that does nothing trades at its quoted terms.
* **`REJECT`** takes the maker out of this RFQ.
* **`IMPROVE`** with `improvedTerms` gives a strictly better price or a higher capacity. An improvement never lowers the quoted capacity; a maker who wants to trade less must reject.

An answer counts by when it reaches Monaco: one that arrives by the deadline counts even if processing it ends later. One that arrives at or after the deadline is refused with `RFQ_DEADLINE_PASSED`, and the maker stays silent.

Last look closes as soon as every selected maker has answered, and otherwise at the deadline. If a selected maker's on-time answer is still being processed then, closing waits for it, up to a hard cap of 350 ms after acceptance. At the cap, an `IMPROVE` that has already re-priced the maker's quote counts and fills at the improved terms. Any other answer still in progress is refused with `RFQ_CONFLICT`, and that maker is silent at its original terms.

**6. Settlement**

When the last look closes, the allocation is computed again over the selected makers still in, at their final terms. A quote is firm for its whole quoted (or improved) capacity, so if one maker rejects, another selected maker can fill more than its first allocation, up to its capacity and never past the requested quantity. Makers the acceptance did not select never come in.

The trade settles as one sweep. Each maker's part fills at that maker's own price, or, if it can no longer fill as accepted, is skipped whole while the others fill. Partial fills are normal. Nothing falls back to the order book. The RFQ ends `SETTLED`, `REJECTED`, `CANCELLED` or `EXPIRED`, and the taker receives an `RfqOutcome` push.

## Timing

| | |
| - | - |
| Request window | 120 s from creation. No new quotes or replacements after it, and no quote outlives it. |
| Quote validity | 15 to 60 s, chosen per quote with `validityMs` (60 s if omitted), capped at the request deadline. |
| Last look | Answers count until 250 ms after acceptance. Last look closes by 350 ms after acceptance at the latest. |
| Creation quota | 5 new RFQs per rolling 30 s per account. |

The capabilities response returns these values (`requestWindowMs`, `minQuoteValidityMs`, `maxQuoteValidityMs`, `lastLookMs`, `maxNewRfqs`, `quotaWindowMs`).

## Disconnects

Before the taker accepts, a maker whose account has no live `rfq` WebSocket connection for 5 seconds has its quotes in RFQs still collecting withdrawn. The server pings `rfq` connections every 5 seconds and closes one that hasn't answered within 15 seconds. A maker that quotes without ever holding an `rfq` connection is not affected. After the taker accepts, a disconnect never cancels anything: the last look runs, and silence executes.

## Fees

Each side pays its own fee tier, and RFQ volume counts toward fee-tier volume.

* **Rates are frozen at acceptance.** If the taker's Monaco or builder rate changes before settlement, the trade is refused: nothing fills, and the RFQ ends `REJECTED`.
* **Taker:** the view's `vwap` is all-in, both fees included. `monacoFeeCbps` and `builderFeeCbps` give the rates in centi-bps.
* **Maker:** the selection shows the maker's frozen Monaco rate (`makerFeeCbps` on the `RfqSelected` push, `ownSelection.monacoFeeCbps` on `GET /api/v1/rfqs/{rfqId}`, negative for a rebate), its frontend's builder add-on as a maximum (`maxBuilderFeeCbps`), and `estimatedNet` at the selected size. The maximum is the rate stored on the selected quote: a fill of that quote charges it or nothing, never more. An `IMPROVE` re-places the quote at the add-on configured at that moment, so after an improvement the selection's maximum and `estimatedNet` no longer bound the fee.

## Receipts and history

* **Taker:** `GET /api/v1/rfqs/{rfqId}` returns:
  * `outcome`, once the RFQ ends: executed and unfilled quantity, the gross `vwap`, the all-in `allInVwap`, and each fill's price, quantity and fee (its Monaco and builder parts), with no maker identity;
  * `acceptance`, beside `outcome` (not inside it), once you accept: a record of what your view showed when you accepted.
* **Maker:** the same call returns `ownFills` once the RFQ settles: only your own fills, with the fees as charged.
* **Your RFQs:** `GET /api/v1/rfqs` lists them as the taker, newest first, in every state, including those that filled nothing. Page with `pageSize` (1 to 100, default 50): send the response's `nextPageToken` back as `pageToken` for the next page. A page token only marks where to resume; a malformed one is refused, and every page lists only your own RFQs.
* **Trade history:** RFQ fills appear in your trade history and in the public trade reads tagged `executionSource: "RFQ"`, and count in candles. They never print on the order-book `trades` channel.
* **Market volume:** RFQ fills count in market volume. The market reads also split it into order-book and RFQ parts: see [Screener and Stats](https://docs.0xmonaco.com/developers/markets.md#screener-and-stats).

## Public prints

Every RFQ fill is published anonymously on the public `rfq_trades:{trading_pair_id}` WebSocket channel: execution id, price, quantity, execution time and publication time, with no account, counterparty, quote or RFQ id. A print is published once the fill is durable, and a publish that fails is retried until it succeeds, so a print can arrive late and occasionally more than once. A repeated print carries the same values. WebSocket delivery itself is at-most-once, so a connection can still miss a print. Deduplicate on the execution id (`execution_id` on the wire, `executionId` in the TypeScript SDK), and recover gaps as below.

* **Snapshot:** subscribing to `rfq_trades` first returns the market's newest 50 RFQ prints, newest first.
* **Recovering a gap:** `GET /api/v1/trades/{tradingPairId}?executionSource=RFQ` (gRPC field `execution_source`) pages the market's RFQ fills newest first. Each trade's id is the print's execution id. It covers recent fills in the hot trade store; older RFQ fills stay on the unfiltered pages, tagged `executionSource: "RFQ"`. `executionSource` also accepts `CLOB` for order-book fills only.
* **TypeScript:** `sdk.trades.recoverRfqTrades(tradingPairId, { sinceExecutionId })` returns the RFQ fills newer than the last print you saw, oldest first, ready to replay. It also returns the other fills sharing that print's execution time, some of which you may already hold. Recovered trades are `TradeEvent`s, so their key is `data.tradeId`, which equals a live print's `data.executionId`: deduplicate on that. `complete: false` means the page budget (`maxPages`, default 10) ran out before recovery finished, either before reaching the last print you saw or before reading every fill that shares its execution time.

## Self-trade

Self-trade covers the whole wallet family, as on the order book: a master and its sub-accounts count as one trader. The taker's own wallet family never receives its RFQ and can never quote it.

## Interfaces

* **REST:** the [RFQ endpoints](https://docs.0xmonaco.com/api-reference) in the API reference.
* **gRPC:** [`RfqService`](https://docs.0xmonaco.com/grpc/protos/rfq).
* **WebSocket:** the authenticated `rfq` channel and the public `rfq_trades` channel, in the [WebSocket reference](https://docs.0xmonaco.com/reference/websockets.md).
* **TypeScript SDK:** [`sdk.rfq`](https://docs.0xmonaco.com/sdk/typescript/rfq).
* **MCP:** the [RFQ tools](https://docs.0xmonaco.com/sdk/mcp-server.md).

Every write takes an `idempotencyKey`: a retry with the same key and body returns the first result. The SDK and MCP tools generate one when omitted.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.