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.joyride, and joyride-mcp, which is the
same program routed straight to joyride mcp serve.
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:--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:
Authenticate
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 for the
session mechanics.
Accounts are created in the web app. A wallet with no account cannot sign in.
The rfq command group
The leg grammar
- 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-Care translated automatically and the translation is printed. A symbol that cannot be carried across exactly is refused rather than guessed. --instrument SYMBOL --size Nis shorthand for a single leg you receive.
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:
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.
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-premiumand--max-lockedare 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.
JOYRIDE_RFQ_MAX_LOCKED_USDC caps the collateral one accept may lock.
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 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.
Funding and readiness
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 therfq_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.
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. 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:
--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.
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.
There is no cancel and no replace
A delivered quote is firm until itsexpires_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.