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

# Migrating from Deribit

> What a Deribit Block RFQ maker keeps, what it has to rebuild, and the one difference that costs money if it is ported over unchanged.

This page is for a desk already quoting Deribit **Block RFQ** — the
`private/*block_rfq*` methods and the `block_rfq.*` channels. It is not a
comparison against an order book, and the difference matters: a Block RFQ maker
never posts into a book. It subscribes, receives a broadcast request, and
responds. That is exactly the shape Joyride has, so the port is real and it is
much smaller than a book-to-RFQ rewrite.

Read the next section before anything else on this page.

## A delivered quote cannot be taken back

> A maker quote on Joyride cannot be cancelled, amended or replaced by any
> means, and there is no market maker protection to pull it automatically. Once
> delivered it is a firm, irrevocable two-sided price until `expires_at`, and
> the TTL chosen at signing time is the only risk control over it.

Both halves of a Deribit maker's intraday risk system are absent here.

**The manual half is gone.** `private/cancel_block_rfq_quote`,
`private/cancel_all_block_rfq_quotes` and `private/edit_block_rfq_quote` have no
counterpart at any layer — not in the gateway, not in the CLI, not in the SDK,
and none is planned. There is no cancel, no replace and no amend. A second
`rfq.maker.respond` on the same request is refused with `RFQ_STATE_CONFLICT`
rather than treated as an edit.

**The automatic half is gone too.** Deribit's Block RFQ market maker
protection — `private/set_mmp_config` with its Block-RFQ-only
`trade_count_limit`, which pulls your live quotes when fills breach a threshold
— has no counterpart. Nothing withdraws a quote on your behalf.

These two gaps compound. A quoting loop written for Deribit treats a stale price
as recoverable: send wide, tighten as confidence rises, pull when the reference
moves or the pricing feed goes stale. Ported unchanged, that loop finds the
retraction call absent and discovers on its first fast move that it wrote a free
option it cannot take back — and nothing pulled the rest of its book either.

What actually bounds the exposure is the TTL, and only the TTL:

* The TTL is the only control over how long you are committed. Choose one you
  would accept being held to on your worst tick, and treat lengthening it as a
  risk decision.
* TTL is capped at 3,600 seconds, and a quote can never outlive the nearest
  leg's close. The venue rejects an out-of-range expiry rather than clamping it.
* A quote reaching the venue with under two seconds of life left is refused: it
  would be firm but untakeable.
* The CLI and the runner both require the TTL explicitly, with no default, for
  this reason.
* Losing quotes get no push. Keep a local expiry timer per delivered quote so
  reserved risk is released on time.

Design for this before you write a line of code. Everything below assumes you
have.

## What ports across unchanged

The shape of the two venues genuinely matches, which is why the rest of this
page is a list of specifics rather than a rewrite.

* **Request-driven, maker responds.** You subscribe, the venue broadcasts an
  open request, you answer it. Nothing rests in a book.
* **Multi-leg option packages on one request.** Joyride takes 1 to 13 legs,
  sharing one underlying and one expiry.
* **Two-sided prices.** One answer carries both sides.
* **No last look on either venue.** Once both signatures exist on Joyride, the
  fill is submittable by anyone; there is no maker confirmation step.
* **Instrument names translate automatically, in both directions.**
  Deribit-style symbols are accepted wherever a Joyride symbol is, and the
  translation is lexical and total: a name that cannot be carried across exactly
  — a fractional strike, a perpetual, a future, a non-USDC quote leg, an unlisted
  asset — is **refused rather than guessed**, because a silently wrong instrument
  is a wrong trade.

```bash theme={null}
# Deribit-shaped symbols work as written; the translation is printed.
joyride rfq request \
  --leg BTC-2MAY26-77000-C:1 \
  --leg BTC-2MAY26-80000-C:-2
```

## What does not port

Every row here is a gateway or protocol difference. None of them can be closed
by an SDK, a wrapper, or a configuration flag.

| Gap                                 | Deribit has                                                                                                                                          | Joyride has                                                                                                                            | What it costs the desk                                                                                                                                                                                                |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Pull or amend a live quote**      | `private/cancel_block_rfq_quote`, `private/cancel_all_block_rfq_quotes`, `private/edit_block_rfq_quote` — three retraction paths, all hot-path calls | Nothing. A delivered quote stands until `expires_at`                                                                                   | A live quoting loop assumes a quote is a revocable offer. Here it is an irrevocable option written to the taker, and TTL is the only control over its firmness                                                        |
| **Market maker protection**         | Block-RFQ-specific MMP with a `trade_count_limit`, pulling quotes automatically on breach                                                            | Nothing                                                                                                                                | This is the automatic half of the same safety system as the row above. No manual brake and no automatic one                                                                                                           |
| **Maker competition**               | A real auction: several makers quote at once, quotes aggregate into price levels, the taker crosses a price against the aggregate                    | The first response to land wins the request; every later responder is refused with `RFQ_STATE_CONFLICT`. Exactly one quote ever exists | It inverts the incentive. On Deribit you price to win an auction; here you race on latency rather than price. Fill-rate models carried over from Deribit mis-forecast in both directions                              |
| **Time to price**                   | Deribit's own examples show minutes between creation and expiry, plus a documented five-second grace period before the taker can even see quotes     | A 5,000 ms request deadline, less a 2,000 ms landing haircut — roughly three usable seconds                                            | A pricing pipeline that takes ten seconds, normal for a multi-leg structure needing a vol-surface fit, cannot participate without re-architecting for latency. This one is invisible in a method-by-method comparison |
| **Partial and single-sided quotes** | `amount` and `direction` on `private/add_block_rfq_quote` quote a portion of the size on one side                                                    | Quotes carry no size and are always two-sided                                                                                          | A maker whose risk limit sits below the requested size can only decline with `size_limit`                                                                                                                             |
| **Taker-side controls**             | `private/cancel_block_rfq`; a resting `good_til_cancelled` acceptance cancellable via `private/cancel_block_rfq_trigger`                             | Neither. The taker waits out the deadline, and accept is immediate-or-error with no resting state                                      | A ported taker client calling either one gets a method-not-found                                                                                                                                                      |
| **Hedge legs**                      | One `hedge` leg per request                                                                                                                          | Calls and puts only; anything else is rejected                                                                                         | A delta-hedging desk hedges out of band and wears the basis risk between two unlinked fills                                                                                                                           |
| **Dealer targeting and anonymity**  | `makers[]` and `disclosed` on the request, plus `private/get_block_rfq_makers` and `private/get_block_rfq_user_info`                                 | Permanent broadcast to every subscribed quoter. Both sides always see the counterparty's subaccount; only balances are withheld        | Curated dealer lists are lost, and anonymity is not available at all — on-chain identities are structurally visible, not merely unimplemented                                                                         |
| **Public trade tape**               | `public/get_block_rfq_trades` and the `block_rfq.trades.{currency}` channel                                                                          | Fills are per-account only, read back with `GET /api/query/rfq-fills?role=maker`                                                       | A desk calibrating off observed block prints loses that input entirely                                                                                                                                                |
| **Self-match prevention**           | `private/set_self_trading_config` blocks execution between accounts of the same legal entity                                                         | Same-account only: maker and taker must differ on one account                                                                          | A desk running separate maker and taker accounts under one entity can cross itself. Raise this with compliance, not only with engineering                                                                             |
| **Counterparty reputation**         | `taker_rating`, a notional-weighted fill ratio over a rolling three months                                                                           | A `taker_funded` boolean on the open request                                                                                           | You cannot price adverse selection by counterparty quality                                                                                                                                                            |
| **Trade allocations**               | `trade_allocations[]`, pre-allocating a fill across sub-accounts                                                                                     | Nothing                                                                                                                                | Allocating desks split fills out of band                                                                                                                                                                              |

One consequence worth stating on its own, because it looks like a missing
feature and is not: there is no counterpart to `private/get_block_rfq_quotes`.
With one quote per request and no quote lifecycle, there is no plural to list.
Your own delivered quote comes back on `rfq.get`; record it locally as you send
it.

## Where Joyride is ahead

Two things Deribit has no equivalent for. Both are read after the risks above,
not instead of them.

**You can decline a request you will not price.** `rfq.maker.decline` ends an
unquoted request immediately with a stated reason — `unlisted_instrument`,
`near_expiry`, `size_limit`, `stale_data`, `low_confidence` or
`queue_overflow`. The taker stops waiting instead of timing out, and you have
said why. On Deribit, silence is the only way to pass.

**You are told your own economics before you sign.** Every open request arrives
personalized with your own fee and lock requirement for *both* taker sides, so
you know what a fill costs you and how much collateral it will tie up before you
commit to a price. Deribit has nothing equivalent; you find out after the fact.

Fee rates themselves are not restated here. The live schedule and the limits are
on [Fees and Limits](/market-makers/fees-and-limits).

## The concept map

| Deribit Block RFQ                           | Joyride                                                                                             |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| API key and secret via `public/auth`        | Sign-In with Solana: a wallet signs a server nonce                                                  |
| `block_rfq.maker.{currency}`                | `rfq_maker` channel via `rfq.maker.subscribe`, not currency-scoped — filter on the legs you receive |
| `block_rfq.maker.quotes.{currency}`         | `rfq_maker` state, fill and finality events. There is no quote lifecycle to stream                  |
| `block_rfq.taker.{currency}`                | `rfq` channel via `rfq.subscribe`                                                                   |
| `private/create_block_rfq`                  | `rfq.create`                                                                                        |
| `private/accept_block_rfq`, priced to cross | `rfq.accept`, committing to one specific quote id and one side                                      |
| `private/add_block_rfq_quote`               | `rfq.maker.respond`                                                                                 |
| `private/get_block_rfqs`                    | `rfq.list` and `rfq.get`                                                                            |
| *(no equivalent)*                           | `rfq.maker.poll` — cursor-paged catch-up after a reconnect                                          |
| *(no equivalent)*                           | `rfq.maker.decline`                                                                                 |

## The code path

Your alpha is the pricing function. Everything wrapped around it is
undifferentiated work, and you do not have to port it.

You supply one function that takes an open request and returns `{bid, ask}` —
or a decline. `MakerQuoteRunner` owns the rest: subscribing, paging the
catch-up poll, de-duplicating requests it has already answered, pacing inside
the account's rate budget, backing off, signing, persisting the quote nonce
before each send, confirming the returned `quote_id`, and reconciling fills
after a reconnect. You never touch a byte layout or an ed25519 call.

The same runner is what `joyride rfq maker watch` runs, with your pricing
program as a subprocess, so a desk can be quoting before it embeds anything:

```bash theme={null}
joyride rfq maker watch \
  --ttl 20 \
  --maker-subaccount <your-quoting-subaccount> \
  --price-command 'node price.js'
```

Your program gets one open request as JSON on stdin and writes one answer on
stdout. Porting a Deribit pricer means keeping the model and replacing the
output stage:

```js theme={null}
// price.js — one open request in, one whole-package answer out.
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
  input += chunk;
});
process.stdin.on('end', () => {
  const request = JSON.parse(input);

  // Your Deribit model, per leg, but stated in USDC per contract rather than
  // in the coin. This is the only place per-leg prices exist.
  const perContract = request.legs.map((leg) => priceOneLeg(leg));
  if (perContract.some((price) => price === null)) {
    process.stdout.write(JSON.stringify({ decline: 'low_confidence' }));
    return;
  }

  // Fold the vector into ONE package total. Legs are ordered and signed in
  // taker-buy orientation: quantity is positive for a leg the taker receives
  // when it buys the package, so the sum is the package value to the taker.
  // Credit structures make it negative, and that is a valid quote.
  const mid = request.legs.reduce(
    (total, leg, i) => total + perContract[i] * Number(leg.quantity),
    0,
  );
  const half = halfSpread(request.legs);

  process.stdout.write(
    JSON.stringify({
      bid: (mid - half).toFixed(6),
      ask: (mid + half).toFixed(6),
    }),
  );
});

/** Replace with your model, in USDC per contract. Returning null declines. */
function priceOneLeg(leg) {
  return null;
}

/** Replace with your width. Half the package spread, in USDC. */
function halfSpread(legs) {
  return 0.5;
}
```

`bid` is what you **pay** if the taker sells the package; `ask` is what you
**receive** if the taker buys it. A sign is not decoration: a negative `ask`
means the taker is paid to buy, which is cash leaving your vault.

## Five traps

These are the ones that bite a ported integration specifically. They are listed
in the order they will hurt you.

<Warning>
  **Your pricing output shape does not carry over.** A Deribit quote carries a
  **price per leg** inside `legs[]`, alongside `ratio` and `direction`, and
  Deribit ships `private/get_leg_prices` to decompose a strategy price into that
  vector. A Joyride quote carries **two signed whole-package totals in USDC** —
  one `bid`, one `ask` — never per leg, never per contract, never coin-denominated.
  The conversion therefore runs in the **opposite direction** to
  `private/get_leg_prices`: you fold a vector into a scalar pair rather than
  expanding a scalar into a vector. There is no per-leg price field anywhere in
  the signed bytes for a client library to fill on your behalf, so this rewrite is
  yours and it cannot be automated away. Totals are canonical six-decimal strings;
  do not compute them in floating point for production.
</Warning>

<Warning>
  **Expiry units differ silently.** Deribit's `expires_at` is **milliseconds since
  epoch**. Joyride's `expires_at` is **UNIX seconds**, and it requires `issued_at`
  alongside it. Nothing about a millisecond value is a type error — it is a number
  in range — so a ported expiry calculation does not fail loudly. It signs a quote
  whose life is off by a factor of a thousand, and the venue either refuses it for
  exceeding the TTL cap or, worse, accepts something you did not mean. The safe
  port is to stop computing the timestamp at all: give the CLI or the runner a
  TTL in seconds and let it derive `issued_at` and `expires_at`.
</Warning>

**A Deribit-shaped request payload is rejected outright, and that is the
feature.** `rfq.create` rejects unknown fields rather than dropping them, so a
payload still carrying `makers`, `disclosed`, `hedge` or `label` fails loudly on
the first call instead of trading on parameters that were silently discarded.
Expect that error during the port and read it as the venue doing its job: a
request that quietly lost its dealer list or its hedge leg would have executed
as something you did not intend.

**The maker subscription is per-socket and is not restored by session resume.**
Resuming a session brings back your authentication, not your subscription. After
every reconnect you must re-subscribe and page `rfq.maker.poll` to catch up on
requests that opened while you were away — paged, because a short page does not
prove you have reached the end. The runner does this for you; a hand-rolled
client must.

**Legs are signed verbatim, in the order you submitted them.** The order is
inside the signed bytes. Re-deriving the leg list, normalizing it, or sorting it
before signing produces a signature over a different package than the one the
venue rebuilt, and the quote is refused. Copy the legs through untouched.

## Onboarding is work, not a URL change

Three of the four items below have no Deribit analogue at all. Budget for them
before you schedule the port.

1. **Hold a Solana keypair.** It is your trading identity and your signing key.
   There is no API key and secret to rotate into.
2. **Fund a margin-vault subaccount on chain.** Collateral does not sit with the
   venue. An unfunded maker cannot quote: the venue cannot state your lock
   requirement and refuses with `RFQ_MAKER_UNFUNDED`.
3. **Be granted the quoter role.** It is provisioned by Joyride and cannot be
   enabled from the API. See
   [Becoming an RFQ Quoter](/market-makers/becoming-a-quoter).
4. **Persist a monotonic quote nonce across restarts.** The venue keeps a
   per-account high-water mark and it survives your process. A maker that seeds
   its nonce from memory on each boot collides with that mark and has its first
   quote refused. Persist before sending, never after.

## Where to go next

* [TypeScript Maker SDK](/agents/maker-sdk) — the runner, the signed package
  quote, the nonce, and fill reconciliation.
* [Quoting RFQs](/market-makers/rfq-quoting) — the wire-level request, quote and
  fill objects, and every error code.
* [The Signed Quote](/market-makers/quote-payload) — what is signed and how it
  is verified.
* [Connectivity for Bots](/market-makers/connectivity) — sessions, resume, and
  rate budgets.

Onboarding, the quoter role, and anything this page did not answer:
[support@joyride.exchange](mailto:support@joyride.exchange).
