nexus — a command-line interface for the Nexus Exchange
API, built on the official nexus-exchange
Rust SDK.
Status: the full command surface is wired up — public market data, the authenticated account, order placement/cancellation, and live WebSocket streaming. It is a thin command/output layer over the SDK: every request goes through the SDK's
Client, which owns request signing, the HTTP/WebSocket transport, retries, rate-limit pacing, and the wire types. The CLI adds no transport of its own.
The quickest way — download and run the installer for the latest release:
# macOS + Linux
curl https://cli.nexus.xyz | sh# Windows
irm https://cli.nexus.xyz | iexcli.nexus.xyz serves the cargo-dist
installer for the most recent GitHub release; it picks the shell or PowerShell
variant automatically. The host itself lives in installer/.
Or pin the invocation to a specific GitHub release artifact:
Prebuilt, checksummed, signed binaries are published for every tagged release
(macOS arm64/x64, Linux x64/arm64, Windows x64) by
cargo-dist. A Windows .msi is also
attached to each release.
macOS / Linux — shell installer:
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/nexus-xyz/nexus-exchange-cli/releases/latest/download/nexus-exchange-cli-installer.sh | shWindows — PowerShell installer:
powershell -ExecutionPolicy Bypass -c "irm https://github.com/nexus-xyz/nexus-exchange-cli/releases/latest/download/nexus-exchange-cli-installer.ps1 | iex"Homebrew:
brew install nexus-xyz/tap/nexuscargo-binstall (prebuilt binary, no compile) — the crate isn't on crates.io yet, so point binstall at the repo directly:
cargo binstall --git https://github.com/nexus-xyz/nexus-exchange-cli nexus-exchange-cliFrom source:
cargo install --path .
# or, from a checkout:
cargo build --release # binary at target/release/nexusEvery release also ships per-artifact *.sha256 files and a combined
sha256.sum. Beyond checksums, each binary artifact carries two independent
signatures:
1. minisign signatures (.minisig, offline — no service to trust). Verify
an artifact against the project's public key:
minisign -Vm nexus-exchange-cli-x86_64-unknown-linux-gnu.tar.gz \
-P 'RWQ5th6qraoqAGncPLWGZthh5ObywWnTc8j0r1w8e0cX4kH9vuVc06ek'2. GitHub build-provenance attestations (proves the artifact was built by this repo's release workflow):
gh attestation verify nexus-exchange-cli-x86_64-unknown-linux-gnu.tar.gz \
--repo nexus-xyz/nexus-exchange-cliReleases are automated by release-please
(release-type: rust), complementary to the cargo-dist pipeline (see
EDR-003).
You do not bump the version or tag by hand.
- Land changes on
mainusing Conventional Commits (feat:,fix:,feat!:/BREAKING CHANGE:). - The
release-pleaseworkflow keeps a standing release PR that bumpsversioninCargo.toml+Cargo.lockand writesCHANGELOG.md. Semver follows commit type:feat:→ minor,fix:→ patch,feat!:/BREAKING CHANGE:→ major. - Merging that release PR creates the
v<version>tag. - The tag fires the
Releaseworkflow (generated bydist generate), which builds, checksums, attests, signs, and publishes the binaries and installers.
release-please only edits Cargo.toml / Cargo.lock / CHANGELOG.md / the
manifest — it never touches the generated release.yml, so it does not conflict
with the dist generate --check assertion in CI. The two pipelines stay
separate by design.
Do not edit
CHANGELOG.mdor bump the version manually — release-please owns both and will overwrite or conflict with hand edits.
Builds also run on every PR as a dry-run (pr-run-mode = "upload"), so the full
cross-platform build is a blocking check — no release is created and no publish
jobs run on PRs.
The very first cut (v0.1.0) needs two one-time nudges, because release-please
derives the next version by bumping the last released version — and there is
no prior release to bump from yet:
.release-please-manifest.jsonis seeded at0.0.0(the "nothing released yet" baseline), so release-please proposes a brand-new version rather than treating the0.1.0already inCargo.tomlas shipped.release-as: "0.1.0"inrelease-please-config.jsonpins that first proposal to exactly0.1.0, independent of how the conventional commits would otherwise bump a0.xbaseline.
To cut it, in order:
- Complete the signing setup below first. The minisign publish job fails
closed, so a release cut before the key/variable exist would publish an
unsigned
v0.1.0— defeating the point. VerifyMINISIGN_PUBLIC_KEY(the repo variable and the README key) plus theMINISIGN_SECRET_KEY/MINISIGN_PASSWORD/RELEASE_PLEASE_TOKEN/HOMEBREW_TAP_TOKENsecrets are all set. - Merge this bootstrap to
main. release-please opens arelease 0.1.0PR. - Merge that PR → it tags
v0.1.0and opens the draft Release →release.ymlbuilds, attests, signs (minisign), uploads into the draft, and undrafts it. - Remove
release-asimmediately afterwards. It is sticky: left in place it pins every future release to0.1.0, so the next release PR would be stuck at the same version. Open a one-line follow-up PR deleting the"release-as": "0.1.0"line. The manifest will by then read0.1.0, so subsequent releases bump from there normally. (Leaving it in is fail-safe — it blocks the next release loudly rather than mis-versioning, but remove it so the flow self-advances.)
After undrafting, confirm the install path end-to-end (this is the actual "done"):
# The cargo-dist "latest" installer asset now resolves (no longer 404s):
curl -fsSL https://github.com/nexus-xyz/nexus-exchange-cli/releases/latest/download/nexus-exchange-cli-installer.sh | head -5
# Once the cli.nexus.xyz Worker is deployed, the one-liner installs a working nexus:
curl https://cli.nexus.xyz | sh && nexus --versionConfigure these repository secrets before the first signed release. The minisign and Homebrew publish jobs fail closed — if their secret is missing they error rather than quietly shipping a release without the signatures/formula it promises. (dist's macOS/Windows code-signing steps are skipped when their secrets are absent; the artifacts are still produced, just unsigned.)
| Secret | Used for |
|---|---|
RELEASE_PLEASE_TOKEN |
PAT (or GitHub App token) for the release-please workflow. Required so the tag it pushes on release-PR merge triggers the tag-listening release.yml — the default GITHUB_TOKEN cannot trigger downstream workflows. Scope: contents:write + pull-requests:write on this repo. |
MINISIGN_SECRET_KEY |
minisign secret key contents (per-artifact .minisig) |
MINISIGN_PASSWORD |
password for the minisign secret key |
HOMEBREW_TAP_TOKEN |
push access to the nexus-xyz/homebrew-tap repo |
CODESIGN_CERTIFICATE, CODESIGN_CERTIFICATE_PASSWORD, CODESIGN_IDENTITY |
Apple Developer ID code-signing (macOS) |
SSLDOTCOM_USERNAME, SSLDOTCOM_PASSWORD, SSLDOTCOM_CREDENTIAL_ID, SSLDOTCOM_TOTP_SECRET |
SSL.com eSigner Windows Authenticode signing |
Set the MINISIGN_PUBLIC_KEY repository variable (public keys are not
secret) to the published public key — the same one in "Verifying downloads"
above. This is required: the release workflow verifies every signature
against it before uploading and fails closed if it is unset, so the first signed
release is gated until both this variable and the README key are populated.
Generate the minisign keypair offline and keep the secret key off CI:
minisign -G -p minisign.pub -s minisign.key
# store minisign.key contents in MINISIGN_SECRET_KEY, its password in MINISIGN_PASSWORD,
# and paste minisign.pub into the "Verifying downloads" section above + the var.macOS code-signing is currently disabled (
macos-sign = false). dist's macOS signing path does not gracefully skip a missing certificate — it fails the build — and since builds now run on every PR, enabling it without a cert would break all builds. To enable: add theCODESIGN_*secrets above and flipmacos-sign = trueindist-workspace.toml. macOS notarization is a separate, further step (dist signs but does not notarize, and a loose CLI binary cannot be stapled), so it only matters behind a.pkg/.dmginstaller.
Runnable, copy-pasteable recipes for each flow live in examples/.
nexus --help
# Public market data
nexus markets # tradable markets and their rules
nexus ticker BTC-USDX-PERP # ticker for one market
nexus tickers # tickers for every market
nexus summaries # per-market 24h summaries
nexus mark-price BTC-USDX-PERP # current mark price
nexus market-status BTC-USDX-PERP # lifecycle / halt status
nexus funding-rates BTC-USDX-PERP --limit 50
nexus orderbook BTC-USDX-PERP # bids/asks
nexus trades BTC-USDX-PERP --limit 50
nexus candles BTC-USDX-PERP --timeframe 1m --limit 100
nexus health # indexer health snapshot
# Per-market data
nexus market summary # 24h volume + halt state per market
nexus market status BTC-USDX-PERP # lifecycle / halt status
nexus market mark-price BTC-USDX-PERP # current mark price
# Authenticated account (see Credentials below)
nexus balance # balance, collateral, equity, margin
nexus account rate-limit # current rate-limit tier / remaining / reset
nexus positions # open positions
nexus fills --limit 50 # recent executions
nexus withdrawals --limit 50 # withdrawal history
nexus orders # open orders
nexus funding-payments --limit 50 # funding booked against the account
nexus withdrawals # withdrawal history
# Trading (prompts for confirmation; pass --yes to skip)
nexus order place --market BTC-USDX-PERP --side buy --type limit \
--price 84000 --quantity 0.01 --tif GTC
nexus order get <ORDER_ID> # fetch one order
nexus order amend <ORDER_ID> --price 85000 --quantity 0.02
nexus order batch orders.json # submit a JSON array of orders ('-' = stdin)
nexus order cancel <ORDER_ID>
nexus order cancel --all
# Account management (see Credentials below)
nexus account deposit 1000 # deposit collateral
nexus account credit --amount 500 # claim testnet USDX (omit --amount for the daily max)
nexus account rate-limit # rate-limit tier / remaining tokens
nexus account leverage BTC-USDX-PERP 10
nexus account margin-mode BTC-USDX-PERP isolated
# Wallet-signed auth (EVM key; see Credentials below)
nexus auth login # EIP-191 sign-in; prompts for the key,
# stores the session token (mode 0600)
nexus agents register --agent 0x<AGENT_ADDR> # EIP-712; prompts for the key
# API keys, agents, transfers, sub-accounts
nexus keys list
nexus keys create # secret is shown ONCE — store it now
nexus keys delete <KEY_ID>
nexus agents list
nexus agents revoke <AGENT_ADDRESS>
nexus transfers list
nexus transfers create --from <ACCT> --to <SUBACCT> --amount 100
nexus sub-accounts list
nexus sub-accounts create "trading-bot-1"
# Live streaming over WebSocket (Ctrl-C to stop)
nexus ws trades --market BTC-USDX-PERP # public channels need --market
nexus ws orders fills positions # account channels (need credentials)
# First-time setup (interactive)
nexus setupThe order batch file is a JSON array of order objects mirroring the
order place flags, with string amounts to preserve precision:
[
{"market": "BTC-USDX-PERP", "side": "buy", "type": "limit",
"price": "84000", "quantity": "0.01", "tif": "gtc"},
{"market": "ETH-USDX-PERP", "side": "sell", "type": "market", "quantity": "1"}
]Every subcommand supports --help.
By default the CLI targets the stable network. Override per-invocation:
nexus --network beta markets
nexus --network local markets
nexus --base-url http://127.0.0.1:9090 markets # any custom base URL| Flag | Env | Default |
|---|---|---|
--network <stable|beta|local> |
NEXUS_NETWORK |
stable |
--base-url <URL> |
NEXUS_BASE_URL |
— (overrides --network) |
By default commands print human-readable tables. Pass --output json (or set
NEXUS_OUTPUT=json) to emit pretty-printed JSON instead — handy for scripting
and piping into tools like jq. It works for every data command; nexus ws
emits one JSON object per line so it streams cleanly into jq.
nexus --output json markets
NEXUS_OUTPUT=json nexus ticker BTC-USDX-PERP
nexus --output json health | jq .
nexus --output json ws trades --market BTC-USDX-PERP | jq .payload| Flag | Env | Default |
|---|---|---|
--output <human|json> |
NEXUS_OUTPUT |
human |
Authenticated commands (balance, account …, positions, fills,
withdrawals, orders, order …, and account WebSocket channels) HMAC-sign
each request. Public market-data commands don't need credentials.
Credentials resolve in this order, highest priority first:
--api-key/--api-secretflagsNEXUS_API_KEY/NEXUS_API_SECRETenvironment variables- the config file written by
nexus setup
| Flag | Env |
|---|---|
--api-key <KEY> |
NEXUS_API_KEY |
--api-secret <SECRET> |
NEXUS_API_SECRET |
nexus setup # interactive; stores config at
# $XDG_CONFIG_HOME/nexus/config.json (mode 0600)
# …or per-shell:
export NEXUS_API_KEY=nx_...
export NEXUS_API_SECRET=...
nexus balancePrefer nexus setup or the environment variables over --api-secret: flags are
visible in your shell history and in the process list. The secret is never
echoed during setup, never printed back, and the config file is created
owner-read/write only (0600).
As an alternative to an HMAC key pair, you can authenticate with an EVM wallet.
nexus auth login reads a raw private key, signs the fixed sign-in challenge
(EIP-191), and exchanges it for a session token stored in the same config
file (mode 0600) under session_token. The session token authenticates
session-scoped routes; the HMAC pair, when present, takes precedence as the
request signer.
The private key is read from --private-key, the NEXUS_PRIVATE_KEY
environment variable, or — when neither is set and you're at a terminal — a
hidden interactive prompt. It is used only to produce the signature and is
never written to disk or echoed.
| Flag | Env |
|---|---|
--private-key <KEY> (on auth login / agents register) |
NEXUS_PRIVATE_KEY |
--session-token <TOKEN> |
NEXUS_SESSION_TOKEN |
export NEXUS_PRIVATE_KEY=0x<your-evm-key>
nexus auth login # stores the session token; prints the address
nexus balance # now authenticated via the stored token
# Register an agent key, authorized by an EIP-712 signature from your wallet
# (unauthenticated request — the signature is the authorization):
nexus agents register --agent 0x<agent-address> --label my-botagents register defaults the expiry to 30 days out, the nonce to the current
Unix-ms timestamp, and the EIP-712 chain-id to the exchange chain (393);
override any with --expires-at / --nonce / --chain-id.
Generate a completion script for your shell and source it:
# Bash
nexus completions bash > ~/.local/share/bash-completion/completions/nexus
# Zsh
nexus completions zsh > ~/.zfunc/_nexus # ensure ~/.zfunc is in $fpath
# Fish
nexus completions fish > ~/.config/fish/completions/nexus.fish
# PowerShell
nexus completions powershell >> $PROFILE
# Elvish
nexus completions elvish >> ~/.elvish/rc.elvcargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-featuresCI runs the same three checks on every push and pull request.
The CLI targets a specific released version of the Exchange API spec, pinned in
.api-version (currently v0.4.0, matching the wrapped
nexus-exchange SDK).
endpoints.txt lists the spec operations the CLI's commands
actually exercise, and scripts/check_spec_drift.py
verifies — in the spec-drift CI workflow — that:
- every endpoint in
endpoints.txtexists in the pinned spec (no rename/typo/ removal slips through), and - the set stays in sync with the SDK methods the CLI actually calls (parsed from
src/main.rs/src/wsclient.rs), modulo two documented allowlists in the script (ops that are ahead of the pinned spec, and the WebSocket upgrade).
The check also prints the coverage number the dashboard reads: the CLI currently exercises 31 of 40 spec operations (77.5%). Run it locally with a fetched spec:
curl -fsSL https://raw.githubusercontent.com/nexus-xyz/nexus-exchange-api/$(cat .api-version)/openapi.json -o openapi.pinned.json
python3 scripts/check_spec_drift.py openapi.pinned.jsonSee examples/ for copy-pasteable recipes covering each flow.
Dual-licensed under MIT or Apache-2.0, at your option.