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

# CLI

> Install, configure, and drive the Joyride RFQ venue from the terminal, including the leg grammar, output modes, consent, operator ceilings, and the maker commands.

The `joyride` CLI is the reference client for the RFQ venue and the only
installable surface that holds a signing key. It requests packages, accepts
quotes, reads RFQ state, and — with the quoter role — answers requests.

It also still carries order-book commands from the exchange's earlier shape.
They are out of scope here: the venue these pages document is RFQ, and every
sample below uses the RFQ path.

## Install

Node.js 20 or later is required.

```bash theme={null}
npm install -g joyride-cli
```

The package installs two binaries: `joyride`, and `joyride-mcp`, which is the
same program routed straight to `joyride mcp serve`.

```bash theme={null}
joyride --version
joyride rfq --help
```

Remove it with `npm uninstall -g joyride-cli`.

## Configure

The first command run on a fresh machine launches a setup wizard. Run it
directly, or drive it without prompts:

```bash theme={null}
joyride setup --non-interactive --new-wallet --default-output json
```

`--non-interactive` requires exactly one of `--new-wallet`, `--import-key`, or
`--skip-wallet`. Configuration and the encrypted keystore live under
`~/.joyride`.

Global flags override configuration for one invocation:

| Flag                    | Effect                                                                                                                                                |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-o, --output <format>` | `table` or `json`                                                                                                                                     |
| `--gateway <url>`       | Trading WebSocket origin override                                                                                                                     |
| `--http <url>`          | HTTP origin override                                                                                                                                  |
| `--wallet <address>`    | Act as a specific configured wallet                                                                                                                   |
| `--asset <asset>`       | Default asset for applicable commands. The CLI accepts `BTC`, `ETH`, or `SOL`; only BTC options are listed now. ETH, SOL, and others are coming soon. |
| `-v, --verbose`         | Verbose logging                                                                                                                                       |

## Authenticate

```bash theme={null}
joyride login
```

Sign-in is Sign-In with Solana against the trading WebSocket. The session token
is stored and resumed, so later commands do not re-sign. `joyride logout`
clears it. An account may hold three concurrent authenticated sessions; a
fourth evicts the oldest with WebSocket close code `4001` and reason
`session_limit`, and a client that receives that close must not reconnect on
its own. See [Connectivity for Bots](/market-makers/connectivity) for the
session mechanics.

Accounts are created in the web app. A wallet with no account cannot sign in.

## The rfq command group

| Command                  | What it does                                                        |
| ------------------------ | ------------------------------------------------------------------- |
| `joyride rfq subaccount` | Ask Joyride to provision this wallet's margin-vault subaccount      |
| `joyride rfq account`    | Vault subaccount balance and readiness                              |
| `joyride rfq request`    | Request a firm two-sided quote on a package                         |
| `joyride rfq accept`     | Verify, confirm, and sign one side of a live quote                  |
| `joyride rfq show`       | One RFQ: state, legs, package quote, funding                        |
| `joyride rfq list`       | This account's live RFQ requests                                    |
| `joyride rfq fills`      | RFQ fill history (the side shown is the taker's)                    |
| `joyride rfq maker`      | Quote side: watch, respond, decline. Requires the `rfq_quoter` role |

## The leg grammar

```
--leg SYMBOL:QUANTITY
```

Repeatable, one to thirteen legs.

* The quantity is signed. Positive means you receive that leg when you buy the
  package; negative means you deliver it. Up to six decimal places, never zero.
* Flag order is the submitted leg order and is never sorted. The venue signs
  the legs in the order you give them, and the taker re-verifies that order.
* Every leg must share one underlying and one expiry.
* Deribit-style symbols such as `BTC-2MAY26-77000-C` are translated
  automatically and the translation is printed. A symbol that cannot be carried
  across exactly is refused rather than guessed.
* `--instrument SYMBOL --size N` is shorthand for a single leg you receive.

```bash theme={null}
joyride rfq request \
  --leg BTC_USDC-2MAY26-77000-C:1 \
  --leg BTC_USDC-2MAY26-80000-C:-2
```

Instruments are 0DTE and refresh daily. List live symbols with
`joyride chain BTC` before building a package.

A refusal names the rule it broke. Under `--output json` the failure carries a
`rule` field and the offending leg index, so a scripted caller can branch
without matching prose.

## Output modes

Every command renders a table by default and a single JSON document under
`-o json`. The one exception is `joyride rfq maker watch`, which emits
newline-delimited JSON: a stream is not one document, and buffering it would
mean a quoter learns about a fill only after the loop ends.

Exit codes are part of the contract:

| Code | Meaning                                                                                                           |
| ---- | ----------------------------------------------------------------------------------------------------------------- |
| `0`  | The command answered. For `rfq request` that includes `unquoted` — nobody priced the package and nothing happened |
| `1`  | A refusal: a broken rule, a bound you set, or a declined confirmation                                             |
| `2`  | A system or configuration failure                                                                                 |
| `3`  | The outcome is unknown. Something may be live. Read it back before retrying                                       |

Exit 3 never prints "no quote". Telling a caller nothing happened when the
request may be live is how a package gets submitted twice.

## Consent and operator ceilings

`joyride rfq accept` is the CLI's autonomous-spend boundary. It re-reads the
RFQ, prints the legs, the side, the cash direction and amount, the fee and its
maximum, and the lock, and only then signs.

| Terminal attached | `--yes` | Behavior                     |
| ----------------- | ------- | ---------------------------- |
| Yes               | absent  | Prompt, sign on confirmation |
| Yes               | present | Sign, no prompt              |
| No                | absent  | Refuse, sign nothing         |
| No                | present | Sign, no prompt              |

Asking for `--output json` is a formatting choice, not a statement that nobody
is watching, so it changes the rendering and never the verdict.

Two kinds of limit apply:

* `--max-premium` and `--max-locked` are the caller's own bounds, passed on
  every accept.
* Two environment variables are the operator's. They are read together, and a
  caller flag may lower the effective limit but never raise it — a limit a
  caller sets for itself is not a limit on that caller.

`JOYRIDE_RFQ_MAX_PREMIUM_USDC` caps the total cash one accept may cost.&#x20;

`JOYRIDE_RFQ_MAX_LOCKED_USDC` caps the collateral one accept may lock.&#x20;

Both are handed to the SDK rather than checked locally, so they are measured
against the same funding read the signature is built from.

### The cost ceiling is premium plus fee

`--max-premium`, and the variable behind it, bound the
**premium plus the trading fee maximum the signature authorizes**, compared as
one number. The fee is real money leaving the account, and it is charged and
capped per leg rather than per package, so it grows with the number of legs
while the premium does not have to. A package can owe far more in fees than its premium
suggests; the worked example on
[Fees and Limits](/market-makers/fees-and-limits) shows one.

**A credit package is not automatically under the ceiling.** A credit pays you,
so its premium contributes nothing to the cost — but the fee is still owed, and
it is the whole cost. Those are exactly the structures a premium-only ceiling
never bounded at all.

<Warning>
  This changed the meaning of a flag and a variable you may already have set.
  A ceiling pitched at exactly what a package's premium costs
  now refuses that package, because the fee pushes the total over it. An accept
  that starts refusing with `max_premium` after an upgrade is hitting this: raise
  the ceiling to cover the premium and the fee together.
</Warning>

## Funding and readiness

```bash theme={null}
joyride rfq account --required 500
```

Readiness is `not_funded`, `unknown`, `ready`, or `short`. `unknown` means the
subaccount exists but its balance has not been projected yet; it is not zero,
and a funded account can report it. `not_funded` means there is no subaccount
row at all, and the next step is `joyride rfq subaccount`, not a deposit.

With `--required`, a short balance is named with its shortfall. The CLI never
moves money: deposits and withdrawals happen in the web app.

## Quoting

The maker commands need the `rfq_quoter` role on the authenticated account.
A request refused with `RFQ_WRONG_ROLE` is a permission, not a transient
failure, and nothing retries it. Roles are provisioned by Joyride; see
[Becoming an RFQ Quoter](/market-makers/becoming-a-quoter).

```bash theme={null}
joyride rfq maker watch --ttl 20 --decline-all --decline-reason stale_data
joyride rfq maker watch --ttl 20 --price-command 'node price.js'
joyride rfq maker respond --rfq-id <id> --bid 10.50 --ask 12.25 --ttl 20 --max-fee 0.50 --max-locked 900
joyride rfq maker decline --rfq-id <id> --reason stale_data
```

`watch` needs an explicit pricing source: either `--price-command`, or
`--decline-all` to answer every request with `--decline-reason`. There is no
default, because a loop that quotes and a loop that refuses are different
programs.

A `--price-command` receives the open request as JSON on stdin and answers with
JSON on stdout: `{"bid":"…","ask":"…"}` for a firm price,
`{"decline":"<reason>"}` to refuse this one, or nothing to decline with
`--decline-reason`. A command that fails or overruns `--price-timeout` is a
decline, never a late quote. A runnable `price.js` that answers every request,
ready to have a model dropped into it, is on
[TypeScript Maker SDK](/agents/maker-sdk#pricing-as-a-subprocess). To check the
connection before writing one, the first command above quotes nothing and
declines everything.

`watch` runs unattended, so it takes two operator ceilings of its own:

```bash theme={null}
joyride rfq maker watch --ttl 20 --price-command 'node price.js' \
  --max-locked 50000 --max-fee 25
```

`--max-locked` caps the post-fill locked balance any quote may sign for, and
`--max-fee` caps the maker fee any quote may sign for. A request that would
need more than either is declined with `size_limit` before anything is signed
and before a nonce is spent, and the NDJSON decline carries `bound`
(`locked` or `fee`), the limit, the figure and the overshoot. Both are
optional, and neither reaches the venue: they decide whether to quote at all.

`--fee-headroom-bps` and `--lock-headroom-bps` are multipliers over whatever
the venue states it needs, so they bound nothing on their own — a request
asking for ten times the collateral gets ten times the headroom. These two
flags are the ones that can say no.

Set the fee ceiling as well as the lock one. The fee is charged and capped per
leg, so a package with several legs can owe far more fee than a one-leg package
at the same lock, and it is paid whichever side the taker takes — including on
a credit package, where the premium comes towards you and the fee is the only
cash leaving.

`respond` has flags with the same two names and they mean the opposite thing:
there `--max-fee` and `--max-locked` are the maxima that one quote signs, they
go on the wire, and the venue enforces them on the fill. On `watch` they are
local refusals the venue is never told about.

Every signed quote carries your quoting subaccount address. It is not derived,
because a wrong one signs bytes the venue refuses. Pass
`--maker-subaccount <address>`, or set it in the environment:

`JOYRIDE_RFQ_MAKER_SUBACCOUNT` holds the quoting subaccount for the maker commands.&#x20;

Read the address from `joyride rfq account --output json`, where it is printed
in hex; hex and base58 are both accepted. `decline` signs nothing and needs
neither.

The venue keeps a per-account quote nonce high-water mark that survives
restarts. The CLI persists the nonce to `~/.joyride/rfq-maker-nonce.json`
before each quote is sent. The file location is overridable:

`JOYRIDE_RFQ_NONCE_FILE` moves the maker nonce store off its default path.&#x20;

## There is no cancel and no replace

A delivered quote is firm until its `expires_at`. Nothing pulls it back,
amends it, or replaces it. That is why `--ttl` is required on `watch` and
`respond` with no default.

The consequence is a risk decision, not a formality: the TTL you choose is the
only bound on how long the market can move against a price you have already
committed to. A quoting loop ported from a venue with cancel-and-replace will
size that exposure wrong on its first adverse move. Quote a TTL you are willing
to be held to, and let quotes expire rather than planning to withdraw them.

`watch` clamps a TTL down to the request's own ceiling (`max_quote_expires_at`),
never up; `respond` refuses an expiry past it rather than clamping, and the
venue itself rejects a later value rather than clamping it. Either way a
quote's life is capped at one hour.

## Fees

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

## Support

[support@joyride.exchange](mailto:support@joyride.exchange).
