# Introduction

Nexus is a blockchain built for programmable finance. Its architecture pairs a general-purpose smart-contract environment with a specialized, high-performance financial execution engine — under one consensus layer and a distributed proof system — so applications get the openness and composability of a public chain with the speed and depth of a centralized exchange.

At its center is the **Nexus Exchange**: a spot and perpetual-futures exchange built into the chain itself, not bolted on as a smart contract. It is **live on testnet today** and expands toward crypto, equities, FX, and commodities as it moves to production.

<figure><img src="/files/yNV3n1XZiLPSVNrSyz4M" alt="The Nexus stack: NexusEVM and NexusCore (with native co-processors) in the execution layer, the verification layer, and NexusBFT consensus, with applications on top."><figcaption><p>The Nexus stack — dual-core execution, verification, and consensus.</p></figcaption></figure>

## How it fits together

Nexus separates execution, verification, and consensus over one unified state:

* **Execution — dual-core.** NexusEVM runs general-purpose, EVM-compatible contracts (1-second blocks); NexusCore runs native financial co-processors — order matching, risk, oracles, liquidations — at 200 ms block intervals. They interoperate atomically, so any contract can compose with exchange liquidity.
* **Verification.** The Compute Network progressively proves execution, so anyone can verify it independently — without trusting the operator.
* **Consensus.** NexusBFT finalizes blocks with instant, single-slot finality.

See [Architecture](/architecture/architecture) for the full picture.

## Start here

* [Architecture](/architecture/architecture) — how the chain and the exchange work.
* [The Exchange](/exchange/introduction) — trading, perpetuals, and markets.
* [Build on Nexus](/network/building-on-nexus) — deploy with standard EVM tools.
* [Tokenomics](/overview/tokenomics) — NEX supply, utility, and distribution.

## Further reading

* [Original Nexus whitepaper](https://assets.nexus.xyz/whitepaper.pdf) — the foundational technical paper that introduced Nexus.
* [NEX MiCA whitepaper](https://assets.nexus.xyz/mica/mica-whitepaper.pdf) — the NEX token disclosure under the EU Markets in Crypto-Assets (MiCA) regulation.


# Economic Model

Nexus is designed as one integrated economic system — a purpose-built blockchain, a verifiable exchange, and a native dollar stablecoin — built to enable powerful financial activity at scale.

## The Nexus Trifecta

Three reinforcing primitives operate as a single economic machine rather than as separate products:

* **The Nexus blockchain** — a purpose-built, EVM-compatible chain with a high-performance trading engine built in and verifiable execution.
* **The Nexus Exchange** *(planned)* — a verifiable spot and perpetual futures venue running directly on the chain.
* **USDX** *(planned)* — a native dollar stablecoin, backed 1:1 by short-duration U.S. Treasury bills and cash equivalents, used as the Exchange's margin and settlement asset.

## Economic engines

Each part of Nexus is an economic engine in its own right, and the three are designed to compound into a single system:

### The Exchange

The Nexus Exchange will use a **maker–taker model** — makers who add liquidity earn rebates, takers who remove it pay fees. Trading fees will be the Exchange's primary economic output, scaling with traded volume and liquidity depth.

### The Stablecoin

USDX is designed to be the Exchange's margin and settlement asset. Dollar balances settled in USDX — rather than a third-party stablecoin such as USDC or USDT — are intended to generate **U.S. Treasury-bill yield** on the backing reserves: an economic flow that scales with the dollar value held and settled through the network.

### The Blockchain

The Nexus blockchain is fully **EVM-compatible and permissionless**, so its economy grows with everything built on it. Builders extend the venue directly — market deployers list new spot and perpetual markets, and frontend operators route order flow, earning a share of the fees they generate. Beyond the Exchange, EVM applications and integrations broaden network usage, and all of this activity consumes blockspace — deepening the chain's base-layer economy.

## How the engines compound

The three engines are not independent — they reinforce one another, so activity in one strengthens the others. This is the core of the model:

```mermaid
flowchart LR
  T[Trading volume] --> U[USDX demand & TVL]
  U --> L[Deeper liquidity]
  L --> E[Better execution]
  E --> T
  B[New markets & apps] --> T
```

* **The Exchange → liquidity.** More trading volume deepens order books and tightens spreads, which improves execution and attracts still more volume.
* **The Stablecoin → capital.** More value settling in USDX grows reserve yield *and* deepens USDX liquidity, which improves margining and settlement — feeding back into trading.
* **The Blockchain → platform.** Permissionless builders and EVM applications add markets, order flow, and usage, broadening coverage and volume — which in turn attracts more builders.

Each loop also consumes blockspace, so growth in any one engine deepens the chain's base-layer economy. **NEX is the unit of account that circulates through these flows** — see [Tokenomics › Utility](/overview/tokenomics) for the token's specific roles.

***

<sub>Forward-looking statement. The Nexus Exchange and USDX are in development and have not yet launched; the features, economics, and revenue described here are planned, are estimates only, are subject to change without notice, and may differ materially from actual outcomes.</sub>


# Tokenomics

NEX is the native token of the Nexus blockchain. This page covers its total supply, utility, distribution status, and vesting schedule.

## Token overview

| Property                          | Value                                                      |
| --------------------------------- | ---------------------------------------------------------- |
| Token name / ticker               | Nexus (NEX)                                                |
| Total supply                      | **100,000,000,000,000 NEX (100T)**                         |
| Token contract (Ethereum, ERC-20) | `0xf57D49646621F563b0B905aFc8336923AC569Ec5` (18 decimals) |
| Genesis / TGE                     | May 20, 2026                                               |

## Utility

NEX is the **native token of the Nexus blockchain** — the asset that powers computation, pays for network operation, and aligns participants across the ecosystem. Each utility is described in detail below.

### Gas for transactions

Every transaction on the Nexus blockchain — value transfers, contract calls, and, in the future, exchange operations — pays its fee in NEX. NEX is the **sole gas asset**: it is required to submit any transaction to the network, so demand for blockspace translates directly into demand for the token. Gas is priced by the computational resources a transaction consumes, so heavier operations cost proportionally more NEX.

### Smart-contract execution

The Nexus blockchain is fully EVM-compatible, and NEX **meters the execution of smart contracts** deployed to it. Each operation a contract performs consumes gas, paid in NEX and priced by the resources used. As programmable activity on the network grows — DeFi, integrations, and applications built by third parties — NEX is the settlement asset that underpins all of it.

### Consensus and compute

NEX is used to **interact with the network's consensus and compute layers** — the systems that order transactions, reach finality, and generate the cryptographic proofs that let anyone verify execution independently. NEX denominates the fees that compensate these operations, tying the token directly to the security, liveness, and verifiability of the network. (NEX is a fee and operating asset; it does not confer staking rewards, dividends, or governance rights — see the note on rights below.)

### Exchange incentives and alignment *(planned)*

Beyond gas, NEX is intended to be the asset that **incentivizes and aligns activity on the Nexus Exchange** — encouraging the behaviors that make a venue liquid and useful. Planned mechanisms include:

* **Trading incentives** — fee discounts are the first and most concrete example: holding NEX is designed to lower taker fees and improve maker rebates, stacking with standard volume-tier pricing.
* **Liquidity and deposits** — incentivizing the capital and order flow (TVL) that deepen markets and improve execution.
* **Builders** — aligning third parties who list markets and route order flow with the growth of the venue.

Fee discounts are planned for the Exchange launch; the broader incentive mechanisms will be documented as they are introduced. See the [Economic Model](/overview/economic-model) for how exchange activity reinforces network usage.

### Future applications

As the network matures, NEX may be used within **additional applications** on the Nexus blockchain as production features are introduced. Any such utility will be documented here as it becomes available.

## Distribution status

![NEX token breakdown — Treasury 60%, Team 20%, Investors 20%; total supply 100,000,000,000,000 NEX (100T).](/files/xVi7sK98FJVaQhqHmWit)

| Allocation | Share | Amount (NEX)       |
| ---------- | ----- | ------------------ |
| Treasury   | 60%   | 60,000,000,000,000 |
| Team       | 20%   | 20,000,000,000,000 |
| Investors  | 20%   | 20,000,000,000,000 |

Early backers include Dragonfly ($2.2M seed, 2022) and Lightspeed and Pantera ($25M Series A, 2024). See [Contributors & Partners](/overview/contributors-and-partners) for the full list.

## Vesting schedule

At genesis, the **60T Treasury is unlocked** and the **40T Team & Investor allocation is locked** on a single schedule: a one-year cliff (May 20, 2027) releases **33%**, followed by monthly linear unlocking over the next 24 months, reaching full supply by May 2029.

| Milestone      | Date         | Unlocked supply |
| -------------- | ------------ | --------------- |
| Genesis (TGE)  | May 20, 2026 | \~60T (60%)     |
| 12-month cliff | May 2027     | \~73T (73%)     |
| Full unlock    | May 2029     | 100T (100%)     |

***

<sub>Note on rights. Consistent with the MiCA whitepaper, NEX does not confer ownership rights, dividend rights, or automatic governance/voting rights. Protocol rules are changed through the network's consensus mechanisms and by validators during protocol upgrades.</sub>

<sub>Forward-looking statement. The unlocked-supply trajectory and all dates and figures are estimates only, subject to change without notice, and may differ materially from actual outcomes. Token release and unlock schedules may be adjusted, delayed, accelerated, or otherwise modified.</sub>


# Roadmap

Key milestones delivered to date, and what is planned next.

## Delivered

* **2022 · Founded.** Nexus begins research into high-performance verifiable computation.
* **Sep 2022 · Seed round.** Led by Dragonfly.
* **May 2024 · Series A.** Co-led by Lightspeed and Pantera.
* **Q1 2026 · Testnet matching engine.** First on-chain order-matching engine live on testnet.
* **May 20, 2026 · Mainnet & TGE.** Mainnet launch, NEX token generation event, and exchange listings.
* **Jun 2026 · Exchange testnet.** The Nexus Exchange opens for trading on testnet, following mainnet launch and TGE.

## Upcoming

* **H2 2026 · USDX.** Launch of the native dollar stablecoin, backed 1:1 by short-duration U.S. Treasury bills and cash equivalents.
* **H2 2026 · Exchange (production).** Production launch of the Nexus Exchange — verifiable spot and perpetual futures.

***

<sub>Forward-looking statement. Roadmap items, timelines, and dates are estimates only, subject to change without notice, and may differ materially from actual outcomes.</sub>


# Contributors & Partners

## Team

The Nexus team brings together deep expertise in financial markets, AI, and large-scale systems engineering — with academic backgrounds from Stanford, UC Berkeley, UCLA, University College London, and Brown, and operational experience across leading technology and finance organizations including Google, Amazon, Microsoft, PwC, and Mozilla.

## Advisors

Nexus is advised by distinguished cryptographers and leaders from institutions including NYU, MIT, Harvard, UC Berkeley, and Cornell, and from the Ethereum Foundation, Zcash, Chainlink, and Bitso.

## Investors

Nexus is backed by leading crypto investors — led by **Dragonfly**, **Lightspeed**, and **Pantera** — alongside Faction, SV Angel, Alliance, Blockchain Builders Fund, and the Stanford Blockchain Accelerator.

## Technology & protocol partners

* **M0** — collaborating on [USDX](/network/building-on-nexus/tokens-and-bridges), the native dollar stablecoin (in development).
* **Hyperlane** — cross-chain [bridging](/network/building-on-nexus/tokens-and-bridges/bridging) for NEX between Ethereum and BSC.

Nexus works with a growing ecosystem of partners across AI, zkVM, and crypto.

## Build on Nexus

Nexus is permissionless and EVM-compatible, so any team can build on it — and, over time, launch their own markets on the exchange. See [Build on Nexus](/network/building-on-nexus).


# Overview

Nexus is a blockchain built for programmable finance. Its architecture pairs a general-purpose smart-contract environment with a specialized, high-performance financial execution engine, under a single consensus layer and a distributed proof system — so applications get the composability of a public chain and the speed and depth of a centralized exchange.

<figure><img src="/files/yNV3n1XZiLPSVNrSyz4M" alt="The Nexus architecture stack: NexusEVM and NexusCore (with native co-processors) in the Execution layer, the Nexus zkVM verification layer, and NexusBFT consensus, with applications on top."><figcaption><p>The Nexus stack — dual-core execution, verification, and consensus.</p></figcaption></figure>

## The layers

Nexus separates **execution**, **verification**, and **consensus** while keeping a single, unified global state.

| Layer            | Components           | Role                                                                                           |
| ---------------- | -------------------- | ---------------------------------------------------------------------------------------------- |
| **Execution**    | NexusEVM · NexusCore | Runs general-purpose contracts (NexusEVM) and specialized financial co-processors (NexusCore). |
| **Verification** | Compute Network      | Progressively produces validity proofs of execution that anyone can verify independently.      |
| **Consensus**    | NexusBFT             | Orders transactions and finalizes blocks with single-slot finality.                            |

## Dual-core execution

The execution layer runs two synchronized engines:

* **NexusEVM** — an Ethereum-compatible virtual machine for general-purpose, composable smart contracts. It produces 1-second blocks and supports the standard Ethereum toolchain.
* **NexusCore** — a high-performance engine that hosts **enshrined co-processors**: native financial modules (order matching, risk, oracles, liquidations) that run deterministically at 200 ms block intervals. NexusCore powers the Nexus Exchange.

The two engines interoperate through atomic cross-core messaging, so an EVM contract and a NexusCore co-processor can act within a single, consistent transaction. This lets developers combine smart-contract flexibility with exchange-grade financial execution.

## Verification

The **Compute Network** generates validity proofs of execution using the Nexus zkVM and is live on Nexus Testnet. Proof coverage expands incrementally toward both NexusEVM and NexusCore, so any party can verify correctness without re-executing or trusting the operator.

## Consensus

**NexusBFT** is the consensus mechanism, built on CometBFT. Blocks are final when committed — there is no probabilistic finality window or reorgs.

## How execution flows

Today, a developer submits an EVM transaction; NexusEVM executes it and NexusBFT commits the block with single-slot finality. As the Nexus Exchange comes online, a trader will submit a signed order over the API; the sequencer will route it to NexusCore, which will match, update positions, and run risk checks at 200 ms intervals; NexusBFT will commit the block and the Compute Network will prove the batch.

## Design principles

* **Performance** — financial operations execute deterministically at 200 ms; the architecture targets centralized-exchange-grade throughput and latency.
* **Composability** — NexusEVM, NexusCore, and every co-processor interoperate within single transactions.
* **Extensibility** — new co-processors can be added without changing consensus.
* **Verifiability** — execution is provable and independently checkable.

Explore each component: [NexusCore](/architecture/nexuscore) · [NexusEVM](/architecture/nexusevm) · [NexusBFT](/architecture/nexusbft) · [Compute Network](/architecture/compute-network).


# NexusCore

NexusCore is the specialized, high-performance financial execution engine of the Nexus blockchain — the second core alongside [NexusEVM](/architecture/nexusevm). It runs **enshrined co-processors**: native financial modules built directly into the protocol rather than deployed as smart contracts, executing deterministically at 200 ms block intervals.

NexusCore powers the Nexus Exchange. The same co-processor framework is designed to host further financial primitives over time — vaults, oracles, lending, stablecoins, and more.

## The Exchange Kernel

The flagship co-processors form the **Exchange Kernel** — the matching, risk, funding, and liquidation primitives behind the Nexus Exchange:

<figure><img src="/files/1wGTjiahnB7jwVugbTxL" alt="NexusCore hosting the Exchange Kernel — CLOB, margining, risk, and liquidation engines — surrounded by per-market co-processors."><figcaption><p>NexusCore's Exchange Kernel and per-market co-processors.</p></figcaption></figure>

* **Central Limit Order Book (CLOB)** — native order matching with price-time priority.
* **Risk Engine** — margining and real-time position risk.
* **Oracle Engine** — mark and index pricing for every market.
* **Liquidation Engine** — deterministic liquidations and solvency protection.

Because these run in-protocol rather than as contracts, markets execute deterministically and settle with the chain's finality.

## Co-processors and parallelism

Each co-processor is an isolated, purpose-built state machine. They run in parallel — independent modules (matching, oracles, future vaults) execute concurrently, and individual co-processors parallelize internally across markets and accounts. The architecture targets near-linear scaling with validator hardware while preserving deterministic execution.

Co-processors expose **dual interfaces**:

* a **native API** (REST + WebSocket) for direct, low-latency access; and
* an **EVM interface** (precompiles) so NexusEVM contracts can compose with NexusCore — placing orders, reading market state, and managing margin atomically within a single transaction.

## Extensible markets

NexusCore is designed to make markets **permissionless to deploy**: in time, builders will be able to launch a perpetual market for any asset by supplying an oracle — spanning crypto, FX, commodities, equities, indices, and more exotic markets.

<figure><img src="/files/wh5dj2YdhnTpILAofNU8" alt="The Nexus Exchange vision — a CLOB at the center serving perps and spot across many asset classes: equities, FX, stables, indexes, options, commodities, information, and LSTs."><figcaption><p>One liquidity layer across every asset class.</p></figcaption></figure>

## Role in the system

NexusCore and NexusEVM operate as parallel execution domains with different block cadences — 200 ms for financial execution, 1 second for smart contracts. NexusBFT commits blocks for both with single-slot finality, and the [Compute Network](/architecture/compute-network) proves execution so anyone can verify it.


# NexusEVM

NexusEVM is the EVM execution layer of the Nexus blockchain — the general-purpose core alongside [NexusCore](/architecture/nexuscore). It produces 1-second blocks with single-slot finality.

## EVM compatibility

* Same bytecode — no Nexus-specific compiler flags
* Same gas semantics — standard opcodes cost the same gas as on Ethereum
* Standard JSON-RPC API (`eth_sendTransaction`, `eth_call`, etc.)
* Standard toolchains — Solidity, Vyper, Hardhat, Foundry, Remix, OpenZeppelin

## Connect

NEX is the native gas token. Add the network in any EVM wallet:

| Network | Chain ID | RPC                             | Explorer                             |
| ------- | -------- | ------------------------------- | ------------------------------------ |
| Mainnet | 3946     | `https://mainnet.rpc.nexus.xyz` | `https://explorer.nexus.xyz`         |
| Testnet | 3945     | `https://testnet.rpc.nexus.xyz` | `https://testnet.explorer.nexus.xyz` |

See [Building on Nexus](/network/building-on-nexus) and [Endpoints](/network/building-on-nexus/endpoints) to start deploying.

## Developer experience

Deploy using Hardhat, Foundry, or Remix, with standard libraries (OpenZeppelin, ethers.js, viem, web3.py). When the Exchange launches, EVM contracts will be able to call into Exchange functions atomically within a single transaction.

## Role in the system

NexusEVM handles programmable smart-contract execution; NexusCore handles specialized financial execution at a faster cadence. The two layers interoperate, so any EVM contract can compose with NexusCore — and, when the Exchange launches, with Exchange liquidity.


# NexusBFT

NexusBFT is the consensus mechanism of the Nexus blockchain, built on CometBFT.

## Single-slot finality

Blocks are final when committed. There is no probabilistic finality window. Once >2/3 of validators precommit a block, it is irreversible.

## Consensus rounds

1. **Propose** — A designated proposer assembles a block and broadcasts it
2. **Prevote** — Validators evaluate the block and broadcast a prevote (for the block or nil)
3. **Precommit** — Once >2/3 prevotes are seen for the same block, validators broadcast a precommit
4. **Commit** — Once >2/3 precommits are seen, the block is committed and final

If a round fails, the protocol advances to a new round with a new proposer. The system never forks.

## Validators

Validators run a Cosmos consensus node (`nexus-cosmos`) and an EVM execution node (`nexus-evm`).

**Responsibilities:**

* Participate in every consensus round
* Maintain high uptime
* Run both consensus and execution nodes

The validator set is permissioned at launch and will expand over time. Validators will stake NEX to join the active set in subsequent upgrades.

## Role in the system

NexusBFT orders transactions and commits blocks with finality — both NexusEVM blocks and NexusCore financial blocks, at their respective cadences.

After a block is committed, the Compute Network proves the execution batch. NexusBFT provides ordering and finality; the Compute Network provides verifiability.


# Compute Network

The Compute Network is what lets anyone verify Nexus markets without trusting the operator: it produces validity proofs of execution that any party can check independently. It is a distributed proof-generation network, live on Nexus Testnet since December 2024.

While the Nexus blockchain itself is live on mainnet today, the Compute Network remains in active testing: it is exercising and hardening its ability to prove Nexus blockchain blocks on testnet before that proving becomes part of mainnet operation. The components below describe the proving architecture as it is being proven out.

## Decoupled by design

Consensus and verification run as independent networks connected by a deterministic interface. NexusBFT finalizes and orders blocks; the Compute Network proves them. Separating the two lets proving scale and use specialized hardware without adding load to validators, and a proving delay never affects consensus liveness. Each layer scales and upgrades on its own.

## Proof lifecycle

```mermaid
flowchart LR
  F[Block finalized] --> A[Prover assigned]
  A --> P[Proof generated]
  P --> G[Aggregated]
  G --> V[Verified on-chain]
```

Finalized blocks are assigned to provers, which generate validity proofs. Proofs are recursively aggregated into a single succinct proof and verified on-chain, at which point the state is proven. The pipeline runs continuously, overlapping block production for near-real-time verifiability.

## Roles

* **Provers** — generate validity proofs for NexusCore and NexusEVM execution.
* **Aggregators** — recursively combine block proofs into compact epoch proofs.
* **Verifier** — the consensus-side check that validates aggregated proofs and commits the proven state.

## The zkVM

Every node in the network runs the **Nexus zkVM**, the verifiable processor that executes and proves Nexus computation. Its design is published in the [Nexus zkVM specification](https://specification.nexus.xyz/).

## Incentives

The Compute Network has no staking. Nodes are incentivized for the compute they contribute, which the network tracks continuously and aggregates into points. NEX-based incentives for compute activity are distributed on an ad-hoc basis; the first such distribution took place at mainnet genesis.

## Coverage

Proof coverage expands incrementally toward full coverage of both NexusCore and NexusEVM.


# Verifiable Computation

Every state transition on the Nexus blockchain can be checked with cryptographic proofs rather than trusted. That verifiability is produced by the **Nexus zkVM** — a virtual machine that generates succinct proofs attesting that a computation was executed correctly — together with a distributed proving network that produces and aggregates those proofs.

## Why it matters for the Exchange

The Exchange blockchain settles trades, margin, and liquidations on-chain. Verifiable computation lets anyone confirm that execution followed the rules — order matching, settlement, risk — without trusting the operator. Proof coverage expands incrementally as the network matures; the Nexus zkVM is one of several provers that can attest to mainnet execution.

## Proven at scale

This is not aspirational infrastructure. Nexus has operated proving infrastructure at public testnet scale since December 2024, backed by a distributed prover network and academic-grade specifications.

## Learn more

The Nexus zkVM has its own documentation space covering its architecture, SDK, and full specification:

* [Nexus zkVM documentation](https://docs.nexus.xyz/zkvm/overview/the-nexus-zkvm)


# Introduction

In Development

The Nexus Exchange is a verifiable perpetual futures exchange built directly into the Nexus blockchain. The order book, matching engine, and risk system run as native protocol components — not as a smart contract on a general-purpose chain — so the exchange settles at CEX-class speed while every fill, funding payment, and liquidation remains independently verifiable.

### What's live today

The exchange is **live on Nexus Testnet** as a development preview. You can connect a wallet, create API keys, fund a synthetic-USDX account from the faucet, and trade the live perpetual futures markets programmatically over REST and WebSocket.

* **Perpetual markets, all collateralized and quoted in synthetic USDX.** Which ones are listed moves as markets are listed and delisted, so `GET /markets` is the authority rather than a count here. The configured set expands to 32 — additional crypto, FX, commodities, and equity indices — across upcoming releases.
* **Leverage is configurable per market.**
* **API-first.** All trading happens over the REST and WebSocket APIs. The web frontend at [exchange.nexus.xyz](https://exchange.nexus.xyz) is a read-only market and portfolio view — order entry is via the API.
* **Non-custodial by design.** Balances and collateral are held by the exchange protocol, never by a private operator.
* **Synthetic test funds.** Each API key can claim test USDX from the faucet to trade against live, bot-seeded liquidity.

> This is a development preview on testnet. It is not a regulated venue and not an offer or solicitation. Markets are seeded with automated liquidity for testing; prices and balances have no real-world value.

### What's coming

Perpetual futures are the first market type. Spot, additional derivatives, and broader asset classes — equities, FX, commodities, RWAs — follow in later releases. The roadmap progresses through a sequence of release gates, each adding robustness, risk controls, and on-chain settlement guarantees before the next.


# Architecture

The Nexus Exchange runs a high-performance order book and matching engine, with execution settled on-chain and independently verifiable. It is live on Nexus Testnet today as a development preview; the production launch follows in a later release.

### Nexus Exchange components

* **Execution** — two synchronized execution environments process all transactions and state transitions:
  * **NexusCore** — the high-performance engine that hosts *enshrined co-processors* for specialized financial operations, with deterministic execution at 200 ms block intervals.
  * **NexusEVM** — an Ethereum-compatible virtual machine providing general-purpose programmability, composability, and full EVM tooling.
* **Consensus** — **NexusBFT**, a Byzantine Fault Tolerant protocol that finalizes dual-execution blocks, coordinates validators, and manages the on-chain registry of co-processors.
* **Verification** — the **Compute Network** progressively proves execution, so anyone can verify it independently without trusting the operator.

Together these let developers build and access institutional-grade financial infrastructure on-chain — with 200 ms block intervals, shared native liquidity, and verifiable execution — bringing the speed, depth, and reliability of traditional markets into an open, composable blockchain environment.


# Trading

The Nexus Exchange lets users trade on a central-limit order book (CLOB) on the Nexus blockchain. Orders are matched on NexusCore at 200 ms block intervals with instant finality. Trading is live on Nexus Testnet today; the production launch follows in a later release.

| Property        | Value                                                        |
| --------------- | ------------------------------------------------------------ |
| Execution       | 200 ms blocks, price-time priority CLOB                      |
| Finality        | Instant (NexusBFT)                                           |
| Custody         | Non-custodial — assets held on-chain                         |
| Margin currency | Synthetic USDX on testnet; USDX at production launch         |
| Verifiability   | Trades are progressively proven and independently verifiable |


# Quickstart

This walks you from zero to a live order on testnet. You need an Ethereum wallet to sign the login message. All requests go to the testnet gateway:

```
https://exchange.nexus.xyz/api/exchange
```

Monetary values are decimal **strings** throughout the API.

### 1. Sign in

Authenticate with an EIP-191 personal signature over the fixed string `Sign in to Nexus Exchange`. Your address is recovered from the signature. The session token is only used to manage API keys — not to trade — and expires in 24 hours.

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Sign in to Nexus Exchange",
    "signature": "0xSIGNATURE_HEX"
  }'
# → { "token": "sess_abc123...", "address": "0xYOUR_WALLET_ADDRESS" }
```

### 2. Create an API key

Use the session token to mint an HMAC key pair. **The secret is shown once — save it immediately.** A key is limited both by its own ceiling, recorded when it is created, and by your account's rate-limit tier; whichever binds first applies. `GET /account/rate-limit` reports the effective figure and costs nothing to poll — see [Rate Limits](/interfaces/rate-limits).

The key is bound to the network of the host you create it against — the base URL below is testnet, so this is a testnet key and will not authenticate anywhere else. See [Networks](/interfaces/networks) for what that means in practice.

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/keys' \
  -H 'Authorization: Bearer sess_abc123...' \
  -H 'Content-Type: application/json' \
  -d '{"label": "my-bot"}'
# → { "key_id": "nx_7f3a1b...", "secret": "e4d2c8f1...long_hex..." }
```

### 3. Sign requests

Every authenticated request carries three headers. Build a canonical string, HMAC-SHA256 it with your secret, and attach the result.

| Header        | Value                                                               |
| ------------- | ------------------------------------------------------------------- |
| `X-API-Key`   | your `key_id` (`nx_...`)                                            |
| `X-Timestamp` | current time in ms since epoch (must be within ±30s of server time) |
| `X-Signature` | HMAC-SHA256 hex of the canonical string                             |

Canonical string format (note the literal newlines):

```
timestamp\nMETHOD\npath\nquery\nsha256(body)
```

For a GET with no body, hash the empty string.

```bash
TIMESTAMP=$(date +%s%3N)
BODY_HASH=$(echo -n "" | shasum -a 256 | cut -d' ' -f1)
CANONICAL="$TIMESTAMP\nGET\n/markets\n\n$BODY_HASH"
SIGNATURE=$(echo -ne "$CANONICAL" | openssl dgst -sha256 -hmac "YOUR_SECRET" | cut -d' ' -f2)

curl 'https://exchange.nexus.xyz/api/exchange/markets' \
  -H "X-API-Key: nx_7f3a1b..." \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

### 4. Fund your account

Credit synthetic USDX from the faucet. Each API key can claim up to **500 USDX per day**; omit `amount` to claim the full remaining daily allowance. Returns `429` once the allowance is used up; it resets the next UTC day.

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/account/credit' \
  -H 'X-API-Key: nx_7f3a1b...' -H 'X-Timestamp: UNIX_MS' -H 'X-Signature: HMAC_HEX' \
  -H 'Content-Type: application/json' \
  -d '{"amount": "500"}'
# → { "amount": "500", "credited_today": "500", "daily_limit": "500" }
```

### 5. Browse markets

```bash
# All live markets
curl 'https://exchange.nexus.xyz/api/exchange/markets' -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'

# A single ticker
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/ticker' -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'
# → { "symbol": "BTC-USDX-PERP", "last": "84250.5", "bid": "84249.0", "ask": "84252.0", "volume": "1523.4", "change": "2.1" }
```

### 6. Place an order

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/orders' \
  -H 'X-API-Key: nx_7f3a1b...' -H 'X-Timestamp: UNIX_MS' -H 'X-Signature: HMAC_HEX' \
  -H 'Content-Type: application/json' \
  -d '{
    "market_id": "BTC-USDX-PERP",
    "side": "Buy",
    "order_type": "Limit",
    "price": "83000.00",
    "quantity": "0.01",
    "time_in_force": "GTC"
  }'
# → { "id": "ord_f82a...", "status": "open", "filled": "0.0", ... }
```

Use `"order_type": "Market"` to fill immediately at the best available price. `time_in_force` accepts `GTC`, `IOC`, or `FOK`; add `"reduce_only": true` to only reduce an existing position. Batch multiple orders in one `POST /orders/batch` call — they are processed sequentially and results preserve request order.

### 7. Monitor positions

```bash
curl 'https://exchange.nexus.xyz/api/exchange/positions' -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'
curl 'https://exchange.nexus.xyz/api/exchange/account'   -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'
# → { "balance": "500.0", "equity": "512.5", "margin_used": "83.0", "margin_ratio": "0.008", "positions": 1 }
```

Next: the full REST API Reference and WebSocket API Reference.


# Perpetuals

Perpetual futures are the first market type on the Nexus Exchange and are **live on testnet**. Which markets are listed moves as markets are listed and delisted, so `GET /markets` is the authority rather than a count here; the configured set expands to 32. A perpetual is a leveraged long or short position with no expiry; it tracks an underlying index price through a periodic funding payment between longs and shorts.

Perpetuals are implemented natively in the exchange engine — matching, margin checks, funding, and liquidation all run in-protocol and settle deterministically, rather than through a smart contract on a general-purpose VM.

### Market structure

* **Live markets are all quoted and collateralized in synthetic USDX.** `GET /markets` lists the set as it stands; the configured set expands to 32.
* Planned categories as the set expands: major crypto, altcoins, FX, commodities, and equity indices.
* Each market has its own contract specification — tick size, minimum/maximum order size, leverage, maintenance margin rate, funding interval, and open-interest cap. See [Market Specifications](/exchange/trading/perpetuals/market-specifications) for the full table.

### How a position works

1. **Fund collateral.** Deposit (or, on testnet, faucet) synthetic USDX into your account. Collateral is shared across all positions (cross-margin).
2. **Open a position.** Submit a buy (long) or sell (short) order via the API. The engine checks initial margin atomically before accepting the order — see Margining.
3. **Hold.** Your position accrues unrealized PnL as the mark price moves and pays or receives funding each interval.
4. **Close or get liquidated.** Close by submitting an opposing order. If your account equity falls below maintenance margin, the position is liquidated.

### Pricing

Each market has a **mark price** derived from an external index feed, used for margin and liquidation. Mark price is largely independent of the last trade price on the book, which prevents thin-book trades from triggering unfair liquidations. See Price Oracles.

**Funding is priced differently.** It measures the gap between the **perp reference price** — what the contract is actually trading at on our book — and the index. Using the mark there would understate the real deviation, because the mark is mostly the index by construction. See Funding Rates.

### Leverage and margin

Leverage is configurable per market. Margin is whole-account (cross): your entire USDX balance backs all open positions. The leverage and maintenance margin rate for each market are listed in [Market Specifications](/exchange/trading/perpetuals/market-specifications). See Margining for how initial and maintenance margin are calculated.

### Sub-pages

* Margining
* Order Types
* Positions
* Funding Rates
* Liquidations
* Price Oracles


# Market Specifications

Contract specifications for every perpetual market listed on the Nexus Exchange. Every value below is a per-market parameter the venue is configured with, not a platform-wide constant — a market's tick size, margin rates, funding cap, and position limits are set when it is listed and tuned between release gates.

{% hint style="warning" %}
**Some values below are not deployed yet.** The table specifies the configuration for the next release. As of the venue check on 2026-08-18, the deployed testnet venue still serves different values for the following:

Everything else on this page matches what the venue serves today.
{% endhint %}

| Instrument      | Parameter               | Live on testnet today | Next release |
| --------------- | ----------------------- | --------------------- | ------------ |
| `BTC-USDX-PERP` | Maintenance margin rate | 2%                    | 1%           |
| `ETH-USDX-PERP` | Maintenance margin rate | 2%                    | 1%           |
| `SOL-USDX-PERP` | Initial margin rate     | 2%                    | 5%           |
| `SOL-USDX-PERP` | Maintenance margin rate | 2%                    | 2.5%         |
| `SOL-USDX-PERP` | Maximum leverage        | 50×                   | 20×          |

### Instruments

| Instrument      | Description                                                 | Underlying      | Settlement | Trading hours    |
| --------------- | ----------------------------------------------------------- | --------------- | ---------- | ---------------- |
| `BTC-USDX-PERP` | Bitcoin, the largest crypto asset by market capitalization. | BTC / USD index | USDX       | 24/7, continuous |
| `ETH-USDX-PERP` | Ether, the native asset of the Ethereum network.            | ETH / USD index | USDX       | 24/7, continuous |
| `SOL-USDX-PERP` | Solana, the native asset of the Solana network.             | SOL / USD index | USDX       | 24/7, continuous |

All markets are quoted, margined, and settled in [USDX](/exchange/usdx), and trade continuously — there are no session breaks, daily settlement windows, or expiries. The underlying is an external index price, not the last trade on the Nexus book; see [Price Oracles](/exchange/trading/perpetuals/price-oracles) for how the index is constructed and how stale feeds are handled.

### Order and price increments

| Instrument      | Tick size | Lot size  | Minimum order size | Maximum order size |
| --------------- | --------- | --------- | ------------------ | ------------------ |
| `BTC-USDX-PERP` | 0.5 USDX  | 0.001 BTC | 0.001 BTC          | 100 BTC            |
| `ETH-USDX-PERP` | 0.10 USDX | 0.01 ETH  | 0.01 ETH           | 1,000 ETH          |
| `SOL-USDX-PERP` | 0.01 USDX | 0.1 SOL   | 0.1 SOL            | 10,000 SOL         |

**Tick size** is the smallest price increment an order may be priced at. **Lot size** is the smallest quantity increment. An order is rejected if its price is not a whole multiple of the tick size, if its quantity is not a whole multiple of the lot size, or if its quantity falls outside the minimum/maximum bounds. See [Order Types](/exchange/trading/perpetuals/order-types).

### Margin, leverage, and position limits

| Instrument      | Maximum leverage | Initial margin rate | Maintenance margin rate | Open-interest cap |
| --------------- | ---------------- | ------------------- | ----------------------- | ----------------- |
| `BTC-USDX-PERP` | 50×              | 2%                  | 1%                      | 10,000 BTC        |
| `ETH-USDX-PERP` | 50×              | 2%                  | 1%                      | 50,000 ETH        |
| `SOL-USDX-PERP` | 20×              | 5%                  | 2.5%                    | 200,000 SOL       |

**Initial margin rate** is the fraction of position notional your account equity must cover to open a position; **maintenance margin rate** is the fraction it must keep to avoid liquidation. Maximum leverage is the reciprocal of the initial margin rate. Margin is whole-account cross margin — your entire USDX balance backs every open position. See [Margining](/exchange/trading/perpetuals/margining) and [Liquidations](/exchange/trading/perpetuals/liquidations).

**Open interest is capped per market.** The cap bounds the total size of all open positions on one side of a market. Once a market is at its cap, orders that would increase open interest are rejected; orders that reduce it — closing or reducing an existing position — continue to be accepted. The cap is a venue risk control, not a per-account limit.

### Funding

| Instrument      | Funding interval | Premium sampling | Funding-rate cap |
| --------------- | ---------------- | ---------------- | ---------------- |
| `BTC-USDX-PERP` | Hourly           | Every 60s        | 0.1%             |
| `ETH-USDX-PERP` | Hourly           | Every 60s        | 0.1%             |
| `SOL-USDX-PERP` | Hourly           | Every 60s        | 0.1%             |

Funding settles at the interval above, with the mark/index premium sampled at the sampling interval and time-weighted across the period. The rate paid in any one interval is clamped to the funding-rate cap in both directions. See [Funding Rates](/exchange/trading/perpetuals/funding-rates).

### Fees

| Instrument      | Maker        | Taker |
| --------------- | ------------ | ----- |
| `BTC-USDX-PERP` | 2 bps rebate | 5 bps |
| `ETH-USDX-PERP` | 2 bps rebate | 5 bps |
| `SOL-USDX-PERP` | 2 bps rebate | 5 bps |

Maker rebates are credited and taker fees charged on filled notional. This is the venue's base tier; query the schedule in effect for your account at any time via `GET /account/fees`. See [Exchange REST](/exchange/apis-and-rates/exchange-rest).

***

<sub>Generated from the per-market config home, the configuration the exchange and oracle services boot from. Cross-checked against the venue's public</sub> <sub></sub><sub>`GET /markets`</sub> <sub></sub><sub>response of 2026-08-18. Where the two disagree, the difference is stated at the top of this page; an undocumented disagreement is a bug — the page is regenerated and diffed on every change to a market's configuration.</sub>


# Margining

Margin is enforced **natively inside the exchange engine** and verified atomically before any order is accepted. Margining is **whole-account (cross)**: your entire USDX balance is the collateral pool backing every open position. Per-market isolated margin is planned for a later release.

### Definitions

| Term                   | Meaning                                                                             |
| ---------------------- | ----------------------------------------------------------------------------------- |
| **Notional**           | `position_size × mark_price` — the value of the position at the current mark price. |
| **Equity**             | Account balance + unrealized PnL across all positions.                              |
| **Initial margin**     | Collateral required to *open or increase* a position.                               |
| **Maintenance margin** | Minimum collateral required to *keep* a position open.                              |

### Initial margin

Initial margin is the notional scaled by the market's initial margin rate:

```
initial_margin = position_size × mark_price × initial_margin_rate
```

Equivalently, `initial_margin_rate = 1 / max_leverage`. The engine sums the initial margin required across all positions and rejects an order if your equity cannot cover it.

### Maintenance margin

Each market defines its own **maintenance margin rate** — a configured fraction of notional, not a value derived from leverage at request time:

```
maintenance_margin = position_size × mark_price × maintenance_margin_rate
```

The maintenance margin rate is always **less than or equal to** the initial margin rate (the engine rejects any configuration where maintenance exceeds initial). A position becomes eligible for liquidation when account equity falls below the total maintenance margin across all positions:

```
liquidatable when:  equity < Σ maintenance_margin
```

> **Current testnet values.** Every market currently runs a deliberate **flat 2% margin profile** — `initial_margin_rate = maintenance_margin_rate = 0.02`. These rates are per-market configurable and will be tuned per market in later gates. See [Market Specifications](/exchange/trading/perpetuals/market-specifications) for the per-market rates, which flags any that are ratified but not yet deployed.

### Why mark price, not last price

All margin calculations use the mark price — an external index price — rather than the last trade on the book. This prevents a single thin-book trade from pushing an account into liquidation.


# Order Types

Orders are instructions to buy or sell assets at specified conditions. They define what, when, and how trades should execute on the exchange.

The exchange supports a range of order types and configurations.

### Available today

* **Market** — executes immediately against resting liquidity; any unfilled size is canceled.
* **Limit** — placed at a specific price and stays on the order book until filled or cancelled; fills at the selected limit price or better.
* **Time-in-force** for limit orders:
  * **GTC (Good-Till-Cancelled)** — stays live until filled or cancelled.
  * **IOC (Immediate-or-Cancel)** — fills what it can instantly, cancels the rest.
  * **FOK (Fill-or-Kill)** — fills the entire order immediately or cancels it.
* **Reduce-only** — set `reduce_only: true` on an order so it can only decrease your current position, never flip or increase it.

### Planned

* **GTD (Good-Till-Date)** — stays live until a chosen expiry or until the order fills.
* **Stop orders:**
  * **Stop Market** — a market order triggered when the mark price reaches the selected trigger price.
  * **Stop Limit** — a limit order that becomes active at the selected limit price once the mark price reaches the trigger price.
* **Market-close orders** — sized automatically to fully close your current position.


# Positions

Positions represent your active exposure in markets on the Nexus Exchange. Each position maintains a live state in the exchange's risk engine, tracking your entry price, size, collateral usage, funding, fees, and real-time profit and loss.

Positions update every block, giving near-instant feedback on risk, margin, and liquidation thresholds.

### Open positions

Your open-positions view shows continuously updated metrics from the real-time risk engine:

* **Unrealized PnL** — shown in both USDX value and percentage
* **Position Size** — total notional exposure (long or short)
* **Average Entry Price** — volume-weighted entry across fills
* **Margin Usage** — collateral allocated to maintain the position
* **Liquidation Price** — the estimated price at which the risk engine liquidates your position
* **Funding Payments** — funding paid or received since opening

All calculations follow the deterministic margin and funding rules enforced inside the exchange engine.

### Modifying positions

Active positions are managed through several methods to adjust exposure or exit trades. Updates are applied atomically by the matching engine.

* **Adjusting Position Size**
  * You can increase or decrease your position size via market or limit orders using the standard trading modal.
  * Orders in the opposite direction exceeding your current position size will automatically "flip" your positions.
    * For example, if you have 1 BTC active long and place a market sell for 2 BTC, you'll end up with 1 BTC active short.
* **Closing Positions**
  * Exit a position partially or in full from the Positions tab.
  * Closing executes a market or limit order on the opposite side and immediately realizes PnL after the fill.
* **Position History**
  * Track and review trading activity with transparent historical data through the Position History tab.


# Funding Rates

Funding keeps a perpetual's price anchored to its underlying index. At each funding interval, longs and shorts exchange a payment based on the gap between **where the perpetual is actually trading on our order book** and the index price. There is no expiry and no central counterparty — funding flows directly between position holders and nets to zero.

### The three prices

Funding involves three different prices, and it matters which one does what:

| Price                    | What it is                                                                                      | What it drives                                                |
| ------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| **Index price**          | The external reference price for the asset.                                                     | The anchor funding pulls the perpetual back toward.           |
| **Perp reference price** | What the perpetual is trading at on our book — the volume-weighted median of recent trades.     | **The funding premium.**                                      |
| **Mark price**           | A blend of the index and the perp reference (`w × index + (1−w) × perp reference`, `w = 0.95`). | Your margin, your unrealized PnL, and your liquidation price. |

The mark deliberately does **not** drive funding. Because the mark is mostly the index by construction, measuring the perpetual's deviation against the mark would measure it against a number that already contains the index — which would understate the real deviation by roughly 20×, leaving funding unable to exert the convergence pressure it exists for.

So: **the mark protects you from thin-book liquidations; the perp reference is what funding measures.** `GET /api/v1/markets/{market_id}/funding` publishes the premium and the rate history, but only one perp-side price: the field is named `mark_price` for historical reasons, but it actually carries the perp reference the premium is measured against, not the blended mark. The blended mark itself has no history endpoint — it is available only as the current value on `GET /markets/{market_id}/mark-price`.

### Mechanics

* **Interval.** Funding settles **hourly** (`funding_interval_s = 3600`). Per-market configurable.
* **Premium sampling.** The premium between the **perp reference and the index** is sampled every **60 seconds** (`funding_sample_interval_seconds = 60`) and time-weighted (TWAP) across the interval.
* **Interest component.** The rate carries a fixed interest term of **0.01% per 8 hours**, added to the average premium before the cap is applied. This is the standard perpetual-futures convention and reflects the cost of carry between the two sides.
* **Rate convention.** The quoted rate is **per 8 hours**, the industry standard. Because our interval is hourly, the amount actually charged each window is the 8-hour rate pro-rated to the window — i.e. divided by 8.
* **Direction.** The interest component shifts the flip point off zero: because the rate is `avg_premium + 0.0001` before clamping, it stays positive (longs pay shorts) until the average premium drops below **-0.01% per 8 hours**, not just below zero. A perpetual trading exactly at the index, or even trading slightly cheap to it, still has longs paying a small positive rate from the interest term alone.
* **Accrual.** Funding is not a lump charge at the boundary. Each sample accrues a pro-rated increment against every open position, so what settles is the accumulated total for the time the position was open during the interval. See [Payment](#payment).
* **Settlement.** At each hourly interval boundary, the accrued funding is applied to every open position. The sum across all positions is zero (no value is created or destroyed by funding).

Putting the last three together, the rate charged for one hourly window is:

```
rate_charged = clamp( (avg_premium + 0.0001) × 1/8 , ±funding_rate_cap )
```

### Thin markets

The perp reference is the volume-weighted median of the **last 5 trades**, whenever the market has any trade history at all. It falls back to the index price — reading a zero premium for that sample — when a market has **never traded**, since the median needs at least one print to compute from, and also when the oracle re-anchors on a large jump: a large enough move clears the recorded trade history so the reference has nothing to compute from until the market trades again. That second case runs on a market that has traded before, and it runs right after a large price move — exactly when funding matters most.

There is currently no time-based staleness check on those 5 trades: if a market trades a handful of times and then goes quiet, the reference keeps reflecting those prints — however old — until enough new trades push them out of the window. A market that has gone quiet does not automatically fall back to reading a zero premium while it is quiet.

### Per-window cap

Each market caps how large a single funding payment can be, to bound risk during volatile periods:

```
funding_rate_cap = 0.001   (0.1% per window — the crypto-perp value; FX, commodity, and index perps use 0.0005)
```

Because the cap applies **after** the interest term and the 8-hour pro-rating, it is worth knowing where it actually binds. At `funding_rate_cap = 0.001`:

```
(basis + 0.0001) × 1/8 > 0.001   →   clamps at a 0.79% basis
```

So a perpetual trading more than \~0.79% away from the index is paying the capped rate — for the crypto perps, whose `funding_rate_cap` is `0.001`. The cap is a per-market parameter and is not the same on every listed market: the FX, commodity, and index perps (seven markets, including NDQ) are configured at `funding_rate_cap = 0.0005`, which binds at a 0.39% basis instead. Which markets in each class are actually deployed on testnet or mainnet changes with the venue's rollout state — see [Market Specifications](/exchange/trading/perpetuals/market-specifications) for the live per-market set rather than a fixed snapshot here.

The cap is relaxed across release gates as the system is validated under wider conditions.

### Payment

Funding **accrues continuously and settles at the interval boundary**. Each premium sample advances a running total for every open position:

```
accrual = position_notional × rate_sample × (elapsed / funding_interval_s)
```

At the boundary, the interval's accumulated total is charged against (or credited to) account equity. So a position opened part-way through an interval pays only for the time it was actually open, at the rates that prevailed while it was open — not the settled rate applied to the whole interval. A position opened five minutes before the boundary on an hourly interval accrues roughly one twelfth of a full-interval payment.

Held for a complete interval at a steady rate, that reduces to the familiar closed form:

```
funding_payment = position_notional × rate_charged
```

where `position_notional = position_size × index_price`. Funding is independent of unrealized PnL — you can pay funding on a profitable position or receive it on a losing one.

> **Status:** funding is live on testnet with hourly settlement and the parameters above. Per-market rates and caps are configurable and subject to tuning between gates. See [Market Specifications](/exchange/trading/perpetuals/market-specifications) for the per-market intervals and caps.


# Liquidations

Liquidations protect the Nexus Exchange from insolvency by ensuring traders maintain sufficient margin to support their open positions.

When an account's equity falls below its required maintenance margin, the exchange's liquidation engine automatically closes positions at the current mark price, preventing negative balances and preserving system-wide solvency.

### Mark price and equity

The exchange uses a mark price derived from the oracle (see [Price Oracles](/exchange/trading/perpetuals/price-oracles)). Account equity is:

```
equity = collateral + unrealized_pnl(mark_price)
```

### Margin requirements

Margin follows the same rules as [Margining](/exchange/trading/perpetuals/margining) — notional valued at the mark price, scaled by each market's configured rates:

```
initial_margin     = position_size × mark_price × initial_margin_rate
maintenance_margin = position_size × mark_price × maintenance_margin_rate
```

### Liquidation trigger

A position becomes eligible for liquidation when account equity falls below the total maintenance margin across all positions:

```
liquidatable when:  equity < Σ maintenance_margin
```

### Liquidation process

1. **Trigger** — equity falls below maintenance margin.
2. **Execution** — a dedicated on-chain liquidator sub-account closes the position at or near the mark price.
3. **Bankruptcy protection** — a price cap prevents negative balances.
4. **Insurance fund** — covers any remaining shortfall if a position cannot be closed in time.


# Price Oracles

Every perpetual market is priced by an external oracle that produces a **mark price** and **index price**. These drive margin and liquidation — they are deliberately decoupled from the last trade on the order book so that thin-book activity cannot distort risk calculations.

Funding is the deliberate exception: it is priced off the **perp reference price** (the volume-weighted median of recent trades on our book) measured against the index, because measuring against the mark — which is 95% index by construction — would understate the perpetual's real deviation by roughly 20×. See Funding Rates.

### Sources

The oracle aggregates external reference prices for every live market (the configured set expands to 32):

* **Hyperliquid** index feeds, polled approximately **once per second**, are the primary source for the markets configured to use them.
* **Pyth** covers the rest, either as a market's single configured source or as one side of a failover pair.

Each market's mark price is published into the engine through an internal oracle service; the engine itself never makes outbound network calls on the trading path.

### Freshness and deviation guards

Each market is configured with two protective thresholds (per-market, in the engine config):

| Guard         | Current testnet value                    | Behavior                                                                     |
| ------------- | ---------------------------------------- | ---------------------------------------------------------------------------- |
| **Staleness** | `oracle_staleness_seconds = 30`          | A feed older than the threshold is treated as stale.                         |
| **Deviation** | `oracle_deviation_threshold = 0.1` (10%) | A single update that jumps more than the threshold is rejected as anomalous. |

A monotonic timestamp guard (`ts ≥ last_update_time`) rejects out-of-order updates, so a delayed packet cannot overwrite a newer price.

### Fail-closed direction

The oracle is being hardened to **fail closed**: when a feed is stale or fails its guards, the safe behavior is to pause new risk-increasing actions in that market rather than trade on a suspect price. Staleness handling and multi-source fallback are being strengthened across the current release gates.

> **Status:** oracle integration is live on testnet with the guards above. Formal multi-source aggregation and circuit-breaker policy are still being finalized; values and behavior may change between gates.


# Spot

In Development

Spot markets are planned. Perpetual futures are the first market type on the Nexus Exchange; spot trading, settled on the same central-limit order book and shared liquidity, follows in a later release. See the [Roadmap](/overview/roadmap).


# APIs & Rates

The Nexus Exchange is API-first. Everything a trader can do — fund an account, place and cancel orders, query positions, stream the order book — is available over REST and WebSocket. The web frontend is a read-only view; programmatic access is the primary interface.

### Base URL

```
https://exchange.nexus.xyz/api/exchange
```

This is the testnet preview gateway. The machine-readable OpenAPI specification is served at `/openapi.json` and versioned at [`nexus-xyz/nexus-exchange-api`](https://github.com/nexus-xyz/nexus-exchange-api); releases are tracked on [GitHub Releases](https://github.com/nexus-xyz/nexus-exchange-api/releases).

### Authentication model

There are two credential types:

1. **Session token (Bearer).** Created by signing a fixed message with your Ethereum wallet (EIP-191). Used **only** to create and manage API keys — never for trading. Expires after 24 hours.
2. **API key (HMAC).** A `key_id` + `secret` pair. Every trading and account request is signed with HMAC-SHA256 over a canonical request string. The secret is shown once at creation and cannot be retrieved later.

```
1. POST /auth/login   (wallet signature)        → session Bearer token
2. POST /keys         (Bearer)                   → API key_id + secret
3. sign each request  (HMAC over canonical str)  → trade
```

See the Quickstart for the full flow with runnable cURL.

### CCXT

CCXT-compatible **read** endpoints are available today (markets, tickers, order book, trades, balances, positions), tagged in the OpenAPI spec. Broader CCXT support, including order placement, is planned.

### Reference

* Quickstart: Your First Trade
* REST API Reference
* WebSocket API Reference
* Rate & Connection Limits
* [API Versioning](/exchange/apis-and-rates/api-versioning)

> **Status:** testnet preview. Credentials, sessions, and rate-limit state are not yet durable across gateway restarts — treat API keys as re-creatable. Production hardening is in progress.


# Exchange REST

Base URL: `https://exchange.nexus.xyz/api/exchange`

All routes **except** `/health`, `/openapi.json`, and `/auth/login` require HMAC authentication (see Quickstart → Sign requests). The complete machine-readable schema, including request/response bodies and CCXT method mappings, is at `/openapi.json`.

Conventions:

* All monetary values are decimal **strings**.
* On single-order routes, `market_id` is **required** as a query parameter — the engine routes directly to the owning market.
* Authenticated requests carry `X-API-Key`, `X-Timestamp`, `X-Signature`. The timestamp must be within ±30s of server time.

### Authentication & keys

| Method | Path          | Auth    | Description                                                     |
| ------ | ------------- | ------- | --------------------------------------------------------------- |
| POST   | `/auth/login` | —       | EIP-191 wallet signature → 24h session Bearer token             |
| POST   | `/keys`       | Session | Create an HMAC API key (`key_id` + `secret`; secret shown once) |
| GET    | `/keys`       | Session | List your API keys                                              |
| DELETE | `/keys/{id}`  | Session | Revoke an API key                                               |
| POST   | `/ws-tokens`  | HMAC    | Mint a 60s single-use token for a WebSocket subscription        |

### Account

| Method | Path                                        | Auth | Description                                                           |
| ------ | ------------------------------------------- | ---- | --------------------------------------------------------------------- |
| GET    | `/account`                                  | HMAC | Balance, equity, margin used, margin ratio                            |
| GET    | `/account/summary`                          | HMAC | Portfolio aggregates plus the withdrawable balance                    |
| GET    | `/account/state`                            | HMAC | Portfolio summary **and** all open positions from one coherent read   |
| GET    | `/account/fees`                             | HMAC | Effective maker/taker fee schedule for the account                    |
| GET    | `/account/portfolio-history?window=&limit=` | HMAC | Equity / cumulative PnL / cumulative volume time-series               |
| GET    | `/positions`                                | HMAC | Open positions with unrealized PnL and per-position risk detail       |
| POST   | `/account/credit`                           | HMAC | Faucet synthetic USDX (testnet; up to 500/day per key)                |
| GET    | `/account/{address}/adl-history?limit=`     | HMAC | Auto-deleveraging events where the account was target or counterparty |

The portfolio routes and the enriched position fields are documented in full — including the nullable-field convention, the `funding_paid` sign, and why `/account/state` should be preferred over two separate calls — in [Portfolio & Account State](/interfaces/portfolio).

### Orders

| Method | Path                      | Auth | Description                                                        |
| ------ | ------------------------- | ---- | ------------------------------------------------------------------ |
| POST   | `/orders`                 | HMAC | Place a limit or market order                                      |
| POST   | `/orders/batch`           | HMAC | Place multiple orders (sequential; results preserve request order) |
| GET    | `/orders`                 | HMAC | List open orders                                                   |
| GET    | `/orders/{id}?market_id=` | HMAC | Get a single order                                                 |
| PATCH  | `/orders/{id}?market_id=` | HMAC | Amend an order                                                     |
| DELETE | `/orders/{id}?market_id=` | HMAC | Cancel a single order                                              |
| DELETE | `/orders?market_id=`      | HMAC | Cancel all open orders in a market                                 |

Order body fields: `market_id`, `side` (`Buy`/`Sell`), `order_type` (`Limit`/`Market`), `quantity`, `price` (limit only), `time_in_force` (`GTC`/`IOC`/`FOK`), and optional `reduce_only`.

### Market data

| Method | Path                              | Description                                            |
| ------ | --------------------------------- | ------------------------------------------------------ |
| GET    | `/markets`                        | List all live markets and their status                 |
| GET    | `/markets/summary`                | Mark price, 24h volume, trade count, status per market |
| GET    | `/markets/{id}/ticker`            | Last / bid / ask / volume / change                     |
| GET    | `/markets/{id}/orderbook`         | L2 order book depth                                    |
| GET    | `/markets/{id}/trades`            | Recent trades                                          |
| GET    | `/markets/{id}/candles`           | OHLCV candles (1m / 5m / 1h)                           |
| GET    | `/markets/{id}/funding`           | Funding rate history                                   |
| GET    | `/markets/{id}/mark-price`        | Current mark price                                     |
| GET    | `/markets/{id}/status`            | Active/halted, reason, timestamp, ADL count            |
| GET    | `/markets/{id}/adl-events?limit=` | Per-market auto-deleveraging history                   |
| GET    | `/tickers`                        | All tickers in one call                                |
| GET    | `/stats`, `/stats/history`        | Exchange-wide stats                                    |

### Streaming

Real-time order book, trade, and account updates are delivered over WebSocket, not REST. See the WebSocket API Reference.

> **Status:** testnet preview. Endpoint set tracks the versioned OpenAPI spec at [`nexus-xyz/nexus-exchange-api`](https://github.com/nexus-xyz/nexus-exchange-api); consult `/openapi.json` for the authoritative, current schema.


# Exchange Websocket

The WebSocket API delivers real-time order book updates, trades, candles, engine status, and account events (orders, fills, positions, balances, liquidations). It is the recommended path for any latency-sensitive integration.

### Connecting

Browsers cannot attach custom auth headers to a WebSocket upgrade, so authentication uses a short-lived token rather than a header:

1. Call `POST /ws-tokens` over HMAC (a normal signed REST request). This mints a **60-second, single-use** opaque token. Your HMAC secret never crosses the WebSocket boundary.
2. Open a WebSocket to the `/stream` endpoint and present the token to subscribe.

Tokens are single-use and reaped after 60 seconds; mint a fresh one per connection.

### Connection limits

* Maximum **5 active connections per IP**, applied at upgrade time on every tier. A 6th connection is rejected with `HTTP 429` on the upgrade — nothing is evicted, and the single-use token is spent, so mint a fresh one before retrying.
* A per-account connection cap applies on top of that: **5** at the Pro tier, **100** at MarketMaker.
* Subscriptions are capped per connection *and* per account at the same number (Pro 50, MarketMaker 1,000), so more sockets do not buy more subscriptions.
* Inbound client frames are limited per connection (Pro 10/s, MarketMaker 50/s) with a 2× burst; sustained flooding closes the connection with code `1008`.
* **These ceilings are their own budget** — independent of the REST request budget and of the trading-action budget, not shared with them. See [Rate Limits](/interfaces/rate-limits).

### Channels

Subscriptions are per-market where noted. Available channels:

| Channel        | Scope      | Payload                      |
| -------------- | ---------- | ---------------------------- |
| `book`         | per market | Order book depth updates     |
| `trades`       | per market | Executed trades              |
| `candles`      | per market | OHLCV candles                |
| `engine`       | global     | Engine throughput and status |
| `orders`       | account    | Order lifecycle updates      |
| `fills`        | account    | Your executed fills          |
| `positions`    | account    | Position changes             |
| `balances`     | account    | Balance changes              |
| `liquidations` | account    | Liquidation events           |

Account-scoped channels (`orders`, `fills`, `positions`, `balances`, `liquidations`) require an authenticated subscription tied to your token.

### Delivery targets

The system targets low-latency fan-out; published latency goals tighten across release gates (order-book and fill delivery measured from the matching event to client receipt). Current testnet behavior is suitable for development and integration testing.

> **Status:** testnet preview. Exact subscription message framing and per-channel schemas are defined in the OpenAPI spec at `/openapi.json` and the live API reference at [exchange.nexus.xyz](https://exchange.nexus.xyz); use those as the authoritative source for message shapes.


# Rate & Connection Limits

The gateway budgets your traffic with a token-bucket limiter, plus a per-IP request limit and WebSocket connection and subscription caps. Limits are tiered to support both general programmatic use and high-throughput market-making.

**The budget is weight per second, not requests per second.** Most requests cost one unit, heavy aggregate and history reads cost five, and a batch submit scales with its size — so a Pro caller at 20/s gets 20 ticker reads per second but only 4 `/fills` reads. The full model, including the three independent budgets, the response headers, and the current per-tier ceilings, is documented once in [**Rate Limits**](/interfaces/rate-limits) under Interfaces. Read that page before building a client-side limiter; the per-operation costs are normative in the OpenAPI spec at `/openapi.json`.

When you exceed a limit the gateway returns **HTTP 429** with `retry-after`. `x-ratelimit-limit` and `x-ratelimit-remaining` accompany every authenticated response so you can pace without tripping the limiter; `x-ratelimit-reset` and `retry-after` appear on the 429 only.

### Other limits

| Limit                      | Value                          |
| -------------------------- | ------------------------------ |
| HMAC timestamp window      | ±30s of server time            |
| WebSocket token lifetime   | 60s, single-use                |
| Session token lifetime     | 24h                            |
| Faucet (`/account/credit`) | 500 USDX/day per key (testnet) |

> **Status:** testnet preview. Tier ceilings are current configuration and may change as the tier system is finalized across release gates. Rate-limit state is currently in-memory at the gateway and resets on restart.


# Market Maker Guide

Everything a market maker needs to integrate with the Nexus Exchange. The exchange is API-first — there is no MM-specific UI; you quote, cancel, and stream over the same REST + WebSocket surface every client uses, with a higher rate tier and a maker-only order type.

For the primitives, this guide points at the reference pages rather than restating them: [REST](/exchange/apis-and-rates/exchange-rest), [WebSocket](/exchange/apis-and-rates/exchange-websocket), [Rate Limits](/interfaces/rate-limits), [Rate & Connection Limits](/exchange/apis-and-rates/rate-limits), and the [Quickstart](/exchange/trading/quickstart).

## 1. Get MM-tier access

The **Market Maker** rate tier (**a budget of 2,000 weight per second**, of which a plain order submit costs 1) is admin-assigned, not self-serve — see [Rate Limits](/interfaces/rate-limits) for how requests are costed. Request the tier via your Nexus contact with your `key_id`. Until promoted, a key runs at the base tier. MM keys also get higher WebSocket ceilings (100 simultaneous connections vs. 5 at base, and 1,000 subscriptions vs. 50).

> Testnet-preview note: tier assignment and rate-limit state are currently in-memory at the gateway and reset on restart — after a gateway redeploy an MM key may briefly fall back to base until re-promoted. Note also that a key carries its own ceiling from the moment it was created (20/s by default) and a promotion does not rewrite it: check `GET /account/rate-limit` after being promoted, since the effective limit is whichever of the two binds first, and mint a fresh key if it still reports the base number.

## 2. Authenticate

Standard flow (details in the [section overview](/exchange/apis-and-rates)): wallet-signed session token (EIP-191) to create an **HMAC API key**, then sign every request with the key. The session token is for key management only — never for trading. Keep the HMAC secret safe; it's shown once.

## 3. The maker order surface

The order type that matters most for market making:

* **Post-only** — set `time_in_force: "PostOnly"` on a limit order. The engine **rejects** the order (rather than filling) if it would cross the book and take liquidity on entry, guaranteeing you rest as a maker. A crossing post-only order comes back as an `InvalidOrder` with code **`WouldTakeLiquidity`** — detect that and re-quote. This is the primary tool for never paying taker fees.
* Standard `GTC` / `IOC` / `FOK` are also available for the non-resting cases.
* **Batch + cancel-all.** Use batch order placement to refresh a quote ladder in one request, and market-scoped cancel-all to pull quotes on a market quickly. Amend is available for in-place price/size changes. (See [REST reference](/exchange/apis-and-rates/exchange-rest).)

## 4. Stream the market

Subscribe over [WebSocket](/exchange/apis-and-rates/exchange-websocket) rather than polling:

* **Order book** and **trades** for the market data you quote against.
* **Your fills / orders / positions** (private channels) to track quote state — mint a single-use ws-token via `POST /ws/token`, then connect.

Connection and subscription caps are per-tier (MM: 100 connections, 1,000 subscriptions) and form their own budget, separate from the REST one — see [Rate Limits](/interfaces/rate-limits). Subscriptions are capped per connection *and* across all of an account's connections at the same number, so spreading a quote ladder over more sockets does not raise the total. Design for reconnect: the client should re-subscribe and re-sync book state on reconnect, and a resubscribe storm is covered by the 2× inbound-frame burst allowance.

## 5. What to expect on latency & throughput

**We do not publish a latency figure for this venue yet.** Earlier revisions of this page carried an order-to-ack p95 and a WebSocket delivery p95 that were internal engineering targets, not measurements of the deployed testnet preview. Both have been removed rather than restated: a latency number a desk cannot reproduce is worse than no number at all.

What we can tell you today:

* **Rate ceiling, not a measured capacity.** The MM tier admits **2,000 weight per second** per key (see [Rate & Connection Limits](/exchange/apis-and-rates/rate-limits)). That is the configured admission ceiling — it is not a measured saturation point and it is not a throughput commitment. Pace against the `x-ratelimit-*` headers on every response and back off on `429` + `retry-after`, remembering two things about what those headers mean: `x-ratelimit-remaining` counts **unit-cost** requests, so it overstates how many heavy reads are left; and order writes draw on a **separate** budget from reads, so a healthy `remaining` on your last read says nothing about placement headroom.
* **Latency: measure it from your own client, and hold us to what you measure.** The numbers that matter to a quoting strategy — REST order-to-ack round trip, and matching-event-to-WebSocket-frame delivery — depend on your network path as much as on us, so your own measurement is the one worth acting on. Tell your Nexus contact what you get; a reproducible client-side number from a real desk is more useful to us than an internal one.
* **Latency goals tighten across release gates**, as noted in the [WebSocket reference](/exchange/apis-and-rates/exchange-websocket). When we publish a figure it will carry its conditions: environment, client location, network path, offered load, what the timer starts and stops on, and percentiles from a single run rather than averaged across runs.

## 6. Tooling

* **SDKs:** Rust (`nexus-exchange` on crates.io), Python (`nexus-exchange` on PyPI — the repository is `nexus-exchange-py`), and TypeScript (`@nexus-xyz/exchange-ts` on npm) all wrap this API, including request signing.
* **CLI:** `nexus-exchange-cli` (`nexus`) is a thin command layer over the Rust SDK.
* **CCXT:** the API exposes CCXT-compatible endpoints (tagged in the OpenAPI spec), so existing CCXT-based MM tooling connects with minimal changes.

## 7. Testnet status

This is a testnet preview: balances are testnet USDX (no real funds), state can reset on redeploy, and tier/rate-limit ceilings are configurable and may change as the tier system is finalized across release gates. Treat API keys as re-creatable.

***

*Doc note (ENG-2755): the per-tier ceilings now live in exactly one published place —* [*Rate Limits*](/interfaces/rate-limits) *— so this guide names only the MM figures it needs and links for the rest. The earlier Pro-tier inconsistency (ENG-4062: 200 req/s published vs. `DEFAULT_PRO_RPS` = 20 in code) is resolved; the published number is 20, matching the code.*


# API Versioning

The Nexus Exchange API is versioned with the OpenAPI specification published at [`nexus-xyz/nexus-exchange-api`](https://github.com/nexus-xyz/nexus-exchange-api). Each SDK release is pinned to a specific spec version, and the API declares a **minimum supported version** that it will accept. This page explains how to send your version, what happens when it falls below the minimum, and how deprecations are signalled.

## Sending your version

Send the spec tag your client was built against in a request header, in addition to (not instead of) your `User-Agent`:

```
X-Nexus-Api-Version: v0.6.2
```

The value is the released spec tag (a leading `v` is optional — `v0.6.2` and `0.6.2` are equivalent). The official SDKs are pinned to a spec version and send this header for you by default; you only need to set it manually if you build your own client.

The header is **optional during the current pre-1.0 grace period** — requests without it are accepted. This will tighten as the API approaches GA; send the header now so your integration is forward-compatible.

## Minimum supported version

To avoid stranding integrations on breaking changes — while not carrying permanent backward-compatibility baggage before there is a large external client base — the API accepts requests only at or above a published minimum version.

**Pre-1.0 support policy.** Until the 1.0 GA release, the minimum supported version may advance aggressively as breaking changes ship. Breaking changes are a minor bump (`0.X.0`); non-breaking changes are a patch bump. The operational practice when the minimum advances is to first move versions below it into a deprecation window (see below) before retiring them, so an active integration gets a signalled window to upgrade. Treat the `Deprecation` / `Sunset` response headers as your authoritative upgrade signal rather than assuming a fixed grace period.

You can always read the current threshold programmatically — see [Discovering the current version](#discovering-the-current-version).

## Below the minimum: `426 Upgrade Required`

A request whose version is below the minimum is rejected with **HTTP 426 Upgrade Required** and a machine-readable JSON body:

```json
{
  "code": "api_version_unsupported",
  "message": "Unsupported or missing API version. Fetch the current OpenAPI spec at spec_url, regenerate your client, and retry with the X-Nexus-Api-Version header.",
  "min_version": "0.6.0",
  "current_version": "0.6.2",
  "spec_url": "https://github.com/nexus-xyz/nexus-exchange-api/releases",
  "docs_url": "https://docs.nexus.xyz/exchange/apis-and-rates/api-versioning"
}
```

The response also carries an `X-Nexus-Api-Min-Version` header with the same floor, for clients that read headers without parsing the body.

**Self-healing for agents.** Because the body carries `spec_url` and both the minimum and current versions, an automated client can recover from version skew on its own: fetch the current spec, regenerate its client, and retry — no human in the loop. A version-skew error is a recoverable condition, not silent breakage.

## Deprecation and sunset

A version that is still accepted but scheduled for retirement is served normally, with standard advisory headers on every response:

* **`Deprecation`** ([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)) — signals that the version you sent is deprecated.
* **`Sunset`** ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)) — the date after which that version will be rejected with `426`.
* **`Link: <…>; rel="deprecation"`** — points to this page.

Treat a `Deprecation` header as a prompt to upgrade before the `Sunset` date. Nothing about the request fails while it is in the deprecation window; only the advisory headers are added.

## Header parsing details

A few specifics worth knowing if you build your own client rather than using an official SDK:

* **Pre-release tags compare by release lineage.** A pre-release such as `0.6.0-rc.1` is treated as `0.6.0` for the minimum check — everything from the first `-` or `+` is stripped. So a pre-release satisfies a floor equal to its release version.
* **An unparseable version is treated as missing.** During the grace period a malformed `X-Nexus-Api-Version` is accepted and logged, exactly like an absent header; once the header becomes required it is rejected the same way. Send a clean `major.minor.patch` tag (an optional leading `v`).
* **CORS preflight and WebSocket handshakes are never gated.** `OPTIONS` preflight and WebSocket upgrade handshakes are exempt — a browser cannot attach the header to either — so the gate never blocks them.
* **Discovery routes are never gated.** `/metadata`, `/openapi.json`, `/llms.txt`, and the `/.well-known/*` documents stay reachable at any version, so a stale client can always fetch what it needs to self-heal.

## Discovering the current version

The current minimum and latest versions are published at an unauthenticated metadata endpoint, so clients and agents can read the threshold without scraping docs:

```
GET /metadata
```

```json
{
  "api_version": {
    "header": "x-nexus-api-version",
    "min_supported": "0.6.0",
    "current": "0.6.2",
    "deprecated_below": null,
    "sunset": null,
    "spec_url": "https://github.com/nexus-xyz/nexus-exchange-api/releases",
    "docs_url": "https://docs.nexus.xyz/exchange/apis-and-rates/api-versioning",
    "policy": "pre-1.0: minimum-supported version may advance until GA; missing version header allowed during grace mode"
  }
}
```

`/metadata` is always reachable regardless of the version you send — otherwise an out-of-date client could never learn the value it needs to upgrade.

## Recommended client pattern

1. Pin your client to a released spec version and send it in `X-Nexus-Api-Version`.
2. On a `426 api_version_unsupported`, read `spec_url` from the body (or `GET /metadata`), regenerate against a version `>= min_supported`, and retry.
3. Watch for `Deprecation` / `Sunset` response headers and upgrade before the sunset date.


# Exchange Testnet

The Nexus Exchange is live on testnet at [exchange.nexus.xyz](https://exchange.nexus.xyz).

You can authenticate via the API, place orders, and trade across the live perpetual futures markets.

The frontend displays live exchange state in read-only mode. All trading is conducted via the REST and WebSocket APIs.

Full API documentation is hosted at [exchange.nexus.xyz/api-docs](https://exchange.nexus.xyz/api-docs).

### Getting Started

#### 1. Authenticate

Sign in with your Ethereum wallet using an EIP-191 personal signature:

```bash
POST /auth/login
```

Sign the message `"Sign in to Nexus Exchange"` to receive a session token (24-hour TTL). Your account is created automatically on first sign-in.

#### 2. Create an API Key

```bash
POST /keys
```

Returns an HMAC key pair (`key_id` + `secret`). The secret is shown once — store it immediately.

Manage keys with `GET /keys` (list) and `DELETE /keys/{key_id}` (revoke).

#### 3. Fund your account

Credit your testnet account with synthetic USDX from the faucet:

```bash
POST /account/credit
```

You need a USDX balance before you can place orders. Check your balance at any time via `GET /account`.

**Each API key can claim up to 500 test USDX per day.**

#### 4. Place an Order

Authenticate all trading requests with three headers: `X-API-Key`, `X-Timestamp`, `X-Signature`.

```bash
POST /orders
```

```json
{
  "market_id": "BTC-USDX-PERP",
  "side": "Buy",
  "order_type": "Limit",
  "price": "67000",
  "quantity": "0.01",
  "time_in_force": "GTC"
}
```

See the [API documentation](https://exchange.nexus.xyz/api-docs) for complete authentication examples and all available endpoints.

### Markets

The table below is the **configured** set: 32 perpetual futures pairs across crypto, FX, commodities, and indices, all denominated in USDX. Testnet lists a subset of it, and that subset moves as markets are listed and delisted — query `GET /markets` for what is live right now.

| Category     | Assets                                                                                                            |
| ------------ | ----------------------------------------------------------------------------------------------------------------- |
| Major Crypto | BTC, ETH, SOL                                                                                                     |
| Altcoins     | AVAX, DOT, ADA, ATOM, NEAR, SUI, APT, TIA, SEI, INJ, LINK, UNI, AAVE, MKR, SNX, CRV, FIL, WIF, DOGE, ARB, OP, POL |
| FX           | EUR, GBP, JPY                                                                                                     |
| Commodities  | GOLD, OIL                                                                                                         |
| Indices      | SPX, NDQ                                                                                                          |

All markets are denominated in USDX. Query all available markets via `GET /markets/summary`.

### Analytics Dashboard

Real-time exchange telemetry exists at [exchange.nexus.xyz/analytics](https://exchange.nexus.xyz/analytics):

* **Engine** — fills/second, throughput, HTTP latency, resource utilization
* **Microstructure** — cross-market spread, depth, imbalance, order book heatmap
* **Risk** — account risk distribution, insurance fund flow, liquidation monitoring
* **Oracle** — mark price divergence per market, feed status
* **Invariants** — continuous correctness monitoring (fund conservation, OI symmetry)


# USDX

**USDX is the Nexus Exchange's native margin and quote currency, pegged 1:1 to the US dollar.** Every market on the Exchange is denominated and margined in USDX, so traders hold a single collateral asset across spot and perpetual futures.

## What makes USDX different

* **Yield on idle capital.** USDX earns yield from US Treasury bills on balances that aren't actively deployed — not from new token issuance or inflationary rewards.
* **An exchange primitive.** USDX exists to make the Exchange work well for traders: one unified collateral and quote asset, composable with the EVM ecosystem on the Nexus blockchain.
* **Backed and pegged 1:1 to USD.**

## What USDX is not

USDX is not a standalone DeFi stablecoin protocol or a yield product. It is the collateral and quote layer of the Exchange.

## Learn more

USDX backs every position on the Exchange — see [Margining](/exchange/trading/perpetuals/margining) for how collateral and leverage work, and [Quickstart](/exchange/trading/quickstart) to fund an account and place your first order.


# Overview

The Nexus Exchange API is the primary interface to the Exchange. Everything a trader can do — sign in, mint credentials, place and amend orders, query positions and fills, stream the book — is available over HTTP and WebSocket. The web frontend is a view onto this API, not a privileged path around it.

This section mirrors the live API reference surface at [nexus.xyz/exchange/api-docs](https://nexus.xyz/exchange/api-docs), which is rendered directly from the machine-readable contract. Both are generated from the same source, so the endpoint set, field names, and examples here are the contract's own.

The Exchange is a verifiable spot and perpetual futures venue. **This contract covers the perpetual futures surface** — every market it describes is a perpetual (`BASE-USDX-PERP`). Spot endpoints are not part of version 0.7.2.

## Contract

|                      |                                                                                                                                                                             |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Spec version         | **0.7.2**                                                                                                                                                                   |
| OpenAPI              | 3.1                                                                                                                                                                         |
| Paths                | 85                                                                                                                                                                          |
| Operations           | 98 across 14 tags                                                                                                                                                           |
| Schemas              | 58                                                                                                                                                                          |
| In-repo contract     | `eng/apps/exchange/api/openapi.json`                                                                                                                                        |
| Served at runtime    | `GET /openapi.json` on the gateway                                                                                                                                          |
| Versioned externally | [`nexus-xyz/nexus-exchange-api`](https://github.com/nexus-xyz/nexus-exchange-api) — releases on [GitHub Releases](https://github.com/nexus-xyz/nexus-exchange-api/releases) |

The contract describes an exchange with HMAC API key authentication on trading endpoints, tiered rate limiting, and real-time WebSocket streaming across 32 configured markets. Configured is not the same as live — see [Exchange Testnet](/exchange/exchange-testnet) for the markets currently trading.

## Base URLs

The contract declares two servers at the document level:

| Server            | URL                                       | Use                                                                      |
| ----------------- | ----------------------------------------- | ------------------------------------------------------------------------ |
| Production        | `https://exchange.nexus.xyz/api/exchange` | The gateway. This is the base for every path documented in this section. |
| Local development | `http://localhost:9090`                   | A gateway running on your own machine.                                   |

Use the gateway base — `https://exchange.nexus.xyz/api/exchange`. It is the origin the SDKs, CLI, and MCP server resolve to by default, and the one that enforces rate limiting and tiering. It is also the only publicly routable base (see the warning below).

### Versioned (`/api/v1`) and unprefixed paths

33 of the 85 paths are `/api/v1/…` versioned siblings of the unprefixed paths — the same operations, reached through a versioned mount. Both forms work, and both live **under the gateway base**:

```
https://exchange.nexus.xyz/api/exchange/tickers          → 200
https://exchange.nexus.xyz/api/exchange/api/v1/tickers   → 200
```

**Prefer the `/api/v1/…` form.** It is the canonical, versioned surface; the unprefixed paths are the *legacy root mounts* and are on a migration path toward retirement, after which routes are reachable only under `/api/v1`. The contract marks neither variant `deprecated`, so this guidance comes from the implementation rather than from the spec.

**Sign the contract path, not the URL path.** The `/api/exchange` gateway mount is stripped before the request reaches the service that verifies your signature, so it is not part of the HMAC canonical string — but the `/api/v1` prefix is. Calling `…/api/exchange/api/v1/tickers` means signing `/api/v1/tickers`. See [Authentication](/exchange-api/exchange-api/authentication).

{% hint style="warning" %}
**Do not take the contract's `servers` override literally.** 29 of the 33 `/api/v1` paths declare a path-level override to `https://exchange.nexus.xyz` ("Production (direct service)"). That host is **not publicly routable for the API** — a request to `https://exchange.nexus.xyz/api/v1/tickers` returns the web app's 404 HTML page, not JSON. The override describes the service's own address behind the gateway, not a base that external integrators can call.

Prepend the gateway base to every path in this section, including the `/api/v1` ones. A generated client that honours the override verbatim will not reach the API.
{% endhint %}

## Authentication model

The contract declares exactly **three** security schemes. There is no scheme for agent keys; agent registration authenticates in-band with an EIP-712 signature in the request body.

| Scheme       | Type              | Transport                                              | What it is                                                                                                           | Where it is accepted                                                                                     |
| ------------ | ----------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `hmacAuth`   | `apiKey`          | `X-API-Key` header (plus `X-Timestamp`, `X-Signature`) | An HMAC-SHA256 API key pair. Every request is individually signed.                                                   | 61 operations — all trading, account, position, bridge, agent-management, and WebSocket-token operations |
| `bearerAuth` | `http` / `bearer` | `Authorization: Bearer <token>`                        | A 24-hour session token from `POST /auth/login`, obtained by signing a fixed message with your EVM wallet (EIP-191). | 3 operations — the `/keys` API-key management endpoints only                                             |
| `adminAuth`  | `http` / `bearer` | `Authorization: Bearer <secret>`                       | The operator admin secret (`ADMIN_SECRET`).                                                                          | 3 operations — tier management and service control under `/admin`                                        |

27 operations declare `security: []` — explicitly public. Four operations omit the `security` field entirely: `POST /auth/login` and `POST /agents/register`, which each carry their own signature in the request body, and the two WebSocket upgrade paths (`GET /ws`, `GET /stream`), which are unauthenticated at the HTTP layer and gate instead on a short-lived token minted over HMAC.

There are therefore four distinct credentials in practice:

1. **Session token** (`bearerAuth`) — wallet-derived, 24-hour lifetime, used *only* to create and manage API keys. Never used for trading.
2. **API key** (`hmacAuth`) — a `key_id` + `secret` pair. The secret is returned once at creation and is never retrievable again. Every authenticated request is HMAC-signed.
3. **Agent key** — an Ethereum-derived keypair that signs trading requests on your behalf without exposing your main wallet. Registered with an **EIP-712** signature from the owning wallet, so registration needs no session token and no API key.
4. **Admin secret** (`adminAuth`) — operator-only.

The normal path is: sign in with your wallet → mint an API key → sign every subsequent request. See [Authentication](/exchange-api/exchange-api/authentication) for the full flow and [Agents](/exchange-api/exchange-api/agents) for delegated signing.

### HMAC signing

```
X-API-Key:    your key ID, e.g. nx_a1b2c3d4e5f67890
X-Timestamp:  current time in Unix milliseconds
X-Signature:  hex(hmac_sha256(secret, timestamp + "\n" + METHOD + "\n" + path + "\n" + query + "\n" + sha256_hex(body)))
```

The timestamp must be within **30 seconds** of server time. For requests with no body, hash the empty string.

## Conventions

### Decimals are strings

Every monetary and quantity field typed as `Decimal` in the contract is:

> Arbitrary-precision decimal serialized as a string (lossless). **Parse with a decimal type, never a float.**

This is not a stylistic choice — prices, sizes, margin figures, and PnL are exact decimals, and round-tripping them through an IEEE-754 double loses value. Use `decimal.Decimal` (Python), `rust_decimal` / `BigDecimal` (Rust), `BigNumber`/`Decimal.js` (TypeScript) — not `float`, not `Number`. The `Decimal` schema is referenced in 81 places across the contract.

**Two families of exception use JSON&#x20;*****numbers*****&#x20;instead.**

1. **The CCXT-shaped schemas** — `Ticker`, `OrderBook`, `Trade`, and the OHLCV tuple returned by Candles. All four carry `x-ccxt: true`, and the CCXT unified structure specifies numbers. This divergence is deliberate.
2. **A few native schemas** — `MarketSummary` (`last_trade_price`, `volume_24h`), `EquityPoint` (`equity`), and `Position` (`leverage`, `max_leverage`). These are *not* CCXT surfaces; the contract itself notes the `EquityPoint`/`PortfolioPoint` mismatch and leaves the float on the wire.

Treat every number-typed value as an approximation and read the exact figure from a decimal-string field where one exists.

### Timestamps

| Form          | Type                                         | Where                                                                                                 |
| ------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `TimestampMs` | integer, int64 — Unix epoch **milliseconds** | The default across the contract (25 references): timestamps, expiries, funding times, fills, candles  |
| `datetime`    | string, RFC 3339 / ISO 8601                  | Only on the CCXT-shaped `Ticker`, `OrderBook`, and `Trade` schemas, alongside their `timestamp` field |

Client-supplied timestamps (`X-Timestamp`, agent `expires_at`, agent `nonce`) are all Unix milliseconds.

### Pagination

List endpoints paginate two ways, and some use both:

* **`limit`** (query, integer) — page size, on the endpoints that declare it. **Not every list endpoint does:** 11 array-returning GETs take no `limit` and return the full set — `/markets`, `/markets/summary`, `/orders`, `/positions`, `/stats/history`, `/agents`, `/api/v1/bridge/deposit-addresses`, and the `/api/v1` twins of the first five. `/stats/history` in particular returns thousands of rows. Where `limit` is accepted, the default and ceiling differ per endpoint (defaults 100–720; ceilings 100–1000) and values above the ceiling are clamped server-side, not rejected. Read the per-endpoint table.
* **`cursor`** (query, string) — an opaque forward cursor, used by 10 operations. Omit it for the first page. When more results exist, the response carries an **`X-Next-Cursor`** header; pass that value back as `cursor`. The header is absent on the last page.

Cursor semantics worth knowing before you build a backfill:

* The response body stays a **bare array** — pagination state rides only in the header.
* The token format is **not part of the contract** and may change. Treat it as opaque.
* Cursors **do not expire**.
* A malformed cursor is **not an error** — the server serves the first page.
* A well-formed cursor whose position has been evicted from the retained window is **not reset to the first page**; pagination resumes at the nearest surviving boundary. A resumed response is a continuation, not a fresh start.

Portfolio history uses a third parameter, `window` (`day` | `week` | `month` | `all`), which selects both the span and the server-side downsample cadence. See [Account](/exchange-api/exchange-api/account).

### Market identifiers

Markets are identified by a `market_id` string of the form `BASE-QUOTE-PERP` — for example **`BTC-USDX-PERP`**, `ETH-USDX-PERP`, `SOL-USDX-PERP`. `USDX` is the Exchange's native quote and margin currency.

`market_id` appears as a path parameter on market-scoped reads (18 operations) and as a **required query parameter** on single-order operations — the engine uses it to route directly to the owning market, so `GET`, `PATCH`, and `DELETE` on `/orders/{order_id}` all need `?market_id=…`.

### Advisory client headers

Every official client (SDK, CLI, MCP server) sends two headers on every request:

| Header                | Example                   | Purpose                                                                                                                                          |
| --------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `X-Nexus-Api-Version` | `v0.7.0`                  | The released spec tag the client was compiled or pinned against, format `vMAJOR.MINOR.PATCH`. Lets the edge attribute traffic to a spec version. |
| `User-Agent`          | `nexus-exchange-rs/0.5.1` | Client identifier, format `nexus-exchange-<lang>/<version>`, for per-client usage metering.                                                      |

Both are **advisory and optional**. The server accepts requests when either is missing, malformed, or names an unknown tag, and treats a missing value as an unknown or legacy client. Neither is part of the HMAC canonical signing string, so both are unauthenticated and can be altered in transit. They are for observability and usage metering only — never for authentication, authorization, or routing.

### API version support

The version identifier is the released spec tag of this contract. The edge publishes the versions it accepts at `/metadata` — served by the edge, **not** an operation in this contract — returning `current_api_version` (latest tag served) and `min_api_version` (oldest tag still accepted), so clients and agents can discover the support window programmatically.

Pre-1.0 (`v0.x.y`), breaking changes are frequent and `min_api_version` may advance with any breaking release. A released tag stays supported for at least **14 days** after the release that supersedes it; that window widens after 1.0. A request whose `X-Nexus-Api-Version` names a recognized tag older than `min_api_version` receives a machine-readable `426 Upgrade Required` (`api_version_unsupported`) with a link to the current spec, so tooling can detect the skew and upgrade. Because the header is unauthenticated, this gate is a compatibility courtesy, not a security control — spoofing the value only relaxes it.

See also [API Versioning](/exchange/apis-and-rates/api-versioning).

## Error model

| Status | Meaning                        | Notes                                                                                                                                                                                                                                                                                                 |
| ------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | Success                        | 92 operations                                                                                                                                                                                                                                                                                         |
| `201`  | Created                        | 4 operations — resource creation on the bridge surface                                                                                                                                                                                                                                                |
| `101`  | Switching Protocols            | The two WebSocket upgrade paths                                                                                                                                                                                                                                                                       |
| `400`  | Validation error               | 14 operations. Body carries a machine-readable `code` where the contract defines one — e.g. `invalid_window`, `bad_wallet`, `bad_agent`, `expiry_out_of_range`, `invalid_json`. Order rejections cover insufficient margin, invalid tick size, non-amendable orders, and margin breach.               |
| `401`  | Authentication failed          | 66 operations. **All 401 responses return the same opaque body — `{"code":"unauthorized"}` — deliberately, to prevent information leakage.** A 401 does not tell you *why*: bad key, bad signature, stale timestamp, and expired session are indistinguishable. Check your clock skew first.          |
| `403`  | Forbidden                      | 5 operations — the admin endpoints (admin secret required) and `POST /account/credit` when crediting is administratively frozen.                                                                                                                                                                      |
| `404`  | Not found, or not owned by you | 17 operations. Ownership failures are reported as 404, not 403 — you cannot probe for other accounts' resources.                                                                                                                                                                                      |
| `409`  | Conflict                       | 1 operation — `duplicate_agent` on agent registration.                                                                                                                                                                                                                                                |
| `429`  | Rate limit exceeded            | 58 operations. See below.                                                                                                                                                                                                                                                                             |
| `502`  | Upstream unavailable           | 4 operations. `authoritative_margin_unavailable` — the engine-authoritative margin view is temporarily unavailable, and endpoints that derive balances from it **fail closed**, returning the error rather than a locally-estimated, potentially unsafe figure. Transient; retry after a short delay. |

### 429 and the rate-limit headers

A `429` body looks like `{"code":"RateLimitExceeded","tier":"Pro"}` and the response carries:

| Header                  | Meaning                                   |
| ----------------------- | ----------------------------------------- |
| `X-RateLimit-Limit`     | Requests per second allowed for your tier |
| `X-RateLimit-Remaining` | Requests remaining in the current window  |
| `X-RateLimit-Reset`     | Unix timestamp when the limit resets      |
| `Retry-After`           | Seconds to wait before retrying           |

Pace from the `X-RateLimit-*` headers rather than retrying blindly. Two endpoints reuse `429` for a non-rate-limit meaning: `POST /account/credit` (daily credit allowance exhausted, resets at midnight UTC) and `POST /faucet` (cooldown not elapsed, or cumulative cap reached).

For tier ceilings and connection caps, see [Rate-Limits](/exchange/apis-and-rates/rate-limits).

## CCXT compatibility

The contract carries CCXT hints at two levels.

**Four tags are marked `x-ccxt: true`** — every operation in them returns a CCXT unified structure:

| Tag        | Section                                             |
| ---------- | --------------------------------------------------- |
| Tickers    | [Tickers](/exchange-api/exchange-api/tickers)       |
| Order Book | [Order Book](/exchange-api/exchange-api/order-book) |
| Trades     | [Trades](/exchange-api/exchange-api/trades)         |
| Candles    | [Candles](/exchange-api/exchange-api/candles)       |

**Fourteen operations carry an `x-ccxt-method`**, naming the CCXT method they satisfy. These extend past the four CCXT tags into Markets, Trading, Account, and Positions:

| CCXT method       | Operation                            | Tag        |
| ----------------- | ------------------------------------ | ---------- |
| `fetchMarkets`    | `GET /markets`                       | Markets    |
| `fetchTicker`     | `GET /markets/{market_id}/ticker`    | Tickers    |
| `fetchTickers`    | `GET /tickers`                       | Tickers    |
| `fetchOrderBook`  | `GET /markets/{market_id}/orderbook` | Order Book |
| `fetchTrades`     | `GET /markets/{market_id}/trades`    | Trades     |
| `fetchOHLCV`      | `GET /markets/{market_id}/candles`   | Candles    |
| `createOrder`     | `POST /orders`                       | Trading    |
| `editOrder`       | `PATCH /orders/{order_id}`           | Trading    |
| `cancelOrder`     | `DELETE /orders/{order_id}`          | Trading    |
| `cancelAllOrders` | `DELETE /orders`                     | Trading    |
| `fetchOpenOrders` | `GET /orders`                        | Account    |
| `fetchOrder`      | `GET /orders/{order_id}`             | Account    |
| `fetchBalance`    | `GET /account`                       | Account    |
| `fetchPositions`  | `GET /positions`                     | Positions  |

What this means for an integrator:

* The **read** surface — markets, tickers, order book, trades, OHLCV, balances, positions, open orders — maps onto CCXT's unified methods today, so an exchange adapter is mostly a transport shim.
* **Order lifecycle** operations (`createOrder`, `editOrder`, `cancelOrder`, `cancelAllOrders`) carry the CCXT method name but are **not** in a `x-ccxt: true` tag: they take and return the Exchange's native shapes (`OrderRequest`, `OrderResponse`), with decimal **strings**, not the CCXT unified order structure. Map them yourself.
* CCXT-shaped responses use JSON numbers and carry both `timestamp` (Unix ms) and `datetime` (ISO 8601). Native responses use decimal strings and `timestamp` only.
* Every CCXT-shaped schema keeps an `info` object for the raw upstream payload, per CCXT convention.

## In this section

| Page                                                        | Contents                                                                                         |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| [Quickstart](/exchange-api/exchange-api/quickstart)         | The guided path from a wallet to a filled order, in cURL, Rust, Python, TypeScript, and the CLI  |
| [Authentication](/exchange-api/exchange-api/authentication) | Wallet sign-in and API key lifecycle — 4 operations                                              |
| [Agents](/exchange-api/exchange-api/agents)                 | EIP-712 agent key registration and revocation — 3 operations                                     |
| [Markets](/exchange-api/exchange-api/markets)               | Market parameters, summaries, status, risk params, ADL history — 14 operations                   |
| [Tickers](/exchange-api/exchange-api/tickers)               | 24-hour price statistics (CCXT) — 4 operations                                                   |
| [Order Book](/exchange-api/exchange-api/order-book)         | Live order book depth (CCXT) — 2 operations                                                      |
| [Trades](/exchange-api/exchange-api/trades)                 | Recent trade history (CCXT) — 2 operations                                                       |
| [Candles](/exchange-api/exchange-api/candles)               | OHLCV candlestick data (CCXT) — 2 operations                                                     |
| [Funding](/exchange-api/exchange-api/funding)               | Funding rate history and samples — 5 operations                                                  |
| [Trading](/exchange-api/exchange-api/trading)               | Order submission, amendment, cancellation, preview — 12 operations                               |
| [Positions](/exchange-api/exchange-api/positions)           | Open and closed position queries — 4 operations                                                  |
| [Account](/exchange-api/exchange-api/account)               | Balances, equity, fees, fills, portfolio history, deposits, cancel-on-disconnect — 34 operations |
| [WebSocket](/exchange-api/exchange-api/websocket)           | Real-time streaming and short-lived token minting — 4 operations                                 |
| [Bridge](/exchange-api/exchange-api/bridge)                 | Cross-chain deposits: bridgeable assets, deposit addresses, deposit tracking — 5 operations      |
| [Admin](/exchange-api/exchange-api/admin)                   | Tier management, operator-only — 3 operations                                                    |
| [Schemas](/exchange-api/exchange-api/schemas)               | All 58 component schemas                                                                         |

## For AI agents

The contract is the interface. Discovery documents, in preference order:

* `https://exchange.nexus.xyz/api/exchange/openapi.json` — the full OpenAPI 3.1 contract.
* `https://exchange.nexus.xyz/llms.txt` — a compact, prose description of the surface.
* `/metadata` on the edge — the accepted API version window.
* [`@nexus-xyz/exchange-mcp`](https://www.npmjs.com/package/@nexus-xyz/exchange-mcp) — an MCP server exposing the API as tools ([source](https://github.com/nexus-xyz/nexus-exchange-mcp)). It is on npm, so adding it is one line, and it runs over stdio so your API key never leaves your machine:

```bash
claude mcp add nexus-exchange -- npx -y @nexus-xyz/exchange-mcp
```

Its public market-data and demo tools need no credentials, so the command is useful before you have a key. A hosted MCP endpoint is planned; its DNS is not live yet, so there is no remote URL to add today.

> **Status:** testnet preview. Credentials, sessions, and rate-limit state are not yet durable across gateway restarts — treat API keys as re-creatable. Mainnet is scheduled for 2026-05-20; nothing in this section describes live mainnet trading.


# Quickstart

A seven-step walkthrough from a cold start to an open position on the Nexus Exchange: sign in with your wallet, mint an HMAC API key, sign requests, fund the account, browse markets, place an order, and monitor positions.

Every step is shown in five clients — cURL, Rust, Python, TypeScript, and the Exchange CLI. Pick a tab per step.

This page mirrors the **Getting Started** tab of the interactive API docs served by the Exchange app at its `/api-docs` route. The interactive version adds live "Try it" buttons; the content below is the same.

## Before you start

* **You need an Ethereum wallet** to sign the login message. Nothing else is required to begin.
* **Base URL.** The API is served at the Exchange deployment's origin plus `/api/exchange` — the gateway. Examples below use `https://exchange.nexus.xyz/api/exchange`. Running locally, the base is `http://localhost:9090`. Substitute the base for the deployment you are actually calling. Paths are shown with the `/api/v1` prefix where a versioned route exists, since that is the canonical surface — so a full URL reads `https://exchange.nexus.xyz/api/exchange/api/v1/…`. See [Base URLs](/exchange-api/exchange-api#base-urls) for why the contract's own `servers` override is not usable directly.
* **Authentication.** Two schemes: a **session token** (Bearer) used only to create and manage API keys, and **HMAC-SHA256** API-key signing used for everything else, including all trading.
* **Two surfaces.** The Exchange app is deployed twice from one codebase: a **testnet** surface, which is live and funds accounts from a synthetic-credit faucet, and a **mainnet** real-funds surface, which will be funded by bridging USDX from Ethereum Mainnet. Mainnet is **2026-05-20**. Step 4 below differs between the two — both variants are documented.
* **Placeholders.** Values such as `0xSIGNATURE_HEX`, `nx_7f3a1b...`, `sess_abc123...`, and `0x<wallet-private-key>` are placeholders. Substitute your own; never commit a secret.

### Language coverage

cURL is the canonical, always-populated baseline. The four SDK/CLI clients do not yet cover every step. Where a client has no example for a step, the tab says so and the cURL example above it is the one to follow — the interactive docs behave the same way, falling back to cURL with a note.

### Machine-readable entry points

Everything an agent needs in order to start without reading this page:

* `llms.txt` — <https://exchange.nexus.xyz/llms.txt>
* `openapi.json` — the OpenAPI contract, at `https://exchange.nexus.xyz/api/exchange/openapi.json`
* `/metadata` — the Exchange app's machine-readable metadata route
* **MCP server** — published to npm, so adding it is one line. It runs locally over stdio, which means your API key stays on your machine:

```bash
claude mcp add nexus-exchange -- npx -y @nexus-xyz/exchange-mcp
```

Its public market-data and demo tools need no credentials, so the command is useful before you have a key. A hosted MCP endpoint is planned; its DNS is not live yet, so there is no remote URL to add today.

The OpenAPI spec and changelog are versioned at [nexus-xyz/nexus-exchange-api](https://github.com/nexus-xyz/nexus-exchange-api); releases are tracked on [GitHub Releases](https://github.com/nexus-xyz/nexus-exchange-api/releases).

***

## Authentication

Steps 1–3 get you from a wallet to a signed request.

## 1. Sign in

`POST /auth/login` — no authentication required.

Authenticate with an EIP-191 personal signature. The message to sign is always the fixed string “Sign in to Nexus Exchange” — your wallet address is recovered from the signature.

**Notes**

* Session token expires in 24 hours.
* You only need the session to create and manage API keys — not for trading.

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Sign in to Nexus Exchange",
    "signature": "0xSIGNATURE_HEX"
  }'
```

{% endtab %}

{% tab title="Rust" %}

```rust
use nexus_exchange::{Client, Config, EthSigner, Network};

// EIP-191 personal_sign of the fixed login message. The signer
// holds your key only to sign — nothing is written to disk.
let signer = EthSigner::from_hex("0x<wallet-private-key>")?;
let client = Client::new(Config::new(Network::Stable));

let session = client.sign_in(&signer).await?;
println!("signed in as {}", session.address);
// session.token is a SecretString — hand it to
// Config::session_token to authenticate the /keys endpoints.
```

{% endtab %}

{% tab title="Python" %}

```python
from nexus_exchange import Client, EthSigner

signer = EthSigner.from_hex("0x<wallet-private-key>")

with Client() as client:
    session = client.sign_in(signer)  # EIP-191 personal_sign
    print(session.address, session.token)
```

{% endtab %}

{% tab title="TypeScript" %}
Not available in TypeScript yet — use the cURL example.
{% endtab %}

{% tab title="CLI" %}

```bash
export NEXUS_PRIVATE_KEY=0x<your-evm-key>
nexus auth login   # signs EIP-191, stores the session token (mode 0600)
```

{% endtab %}
{% endtabs %}

**Request body**

```json
{
  "message": "Sign in to Nexus Exchange",
  "signature": "0xSIGNATURE_HEX"
}
```

**Response**

```json
{
  "token": "sess_abc123...",
  "address": "0xYOUR_WALLET_ADDRESS"
}
```

## 2. Create API key

`POST /keys` — **Session token required.**

Use the session token to create an HMAC key pair. The secret is shown once — save it immediately.

**Notes**

* You cannot retrieve the secret after this response.
* Keys inherit your account’s tier; per-tier rate limits are reported in the `X-RateLimit-*` response headers.

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/keys' \
  -H 'Authorization: Bearer sess_abc123...' \
  -H 'Content-Type: application/json' \
  -d '{"label": "my-bot"}'
```

{% endtab %}

{% tab title="Rust" %}

```rust
use nexus_exchange::{Client, Config, Network};

// /keys endpoints authenticate with the session token from step 1.
let client =
    Client::new(Config::new(Network::Stable).session_token("sess_..."));

let key = client.create_api_key().await?;
// key.secret is returned once — persist it immediately.
println!("key_id: {}", key.key_id);
```

{% endtab %}

{% tab title="Python" %}
Not available in Python yet — use the cURL example.
{% endtab %}

{% tab title="TypeScript" %}
Not available in TypeScript yet — use the cURL example.
{% endtab %}

{% tab title="CLI" %}

```bash
nexus keys create   # secret is shown ONCE — store it now
```

{% endtab %}
{% endtabs %}

**Request body**

```json
{
  "label": "my-bot"
}
```

**Response**

```json
{
  "key_id": "nx_7f3a1b...",
  "secret": "e4d2c8f1...long_hex..."
}
```

## 3. Sign requests

`GET /markets` — **HMAC API key required.**

Every authenticated request needs three headers. Build a canonical string, HMAC-SHA256 it with your secret, and attach the result.

**Required headers**

| Header        | Required | Description                             |
| ------------- | -------- | --------------------------------------- |
| `X-API-Key`   | yes      | Your key ID (`nx_...`)                  |
| `X-Timestamp` | yes      | Current time in ms since epoch          |
| `X-Signature` | yes      | HMAC-SHA256 hex of the canonical string |

**Notes**

* Canonical format: `timestamp\nMETHOD\npath\nquery\nsha256(body)`
* `path` is the path **as written in the contract** — include the `/api/v1` prefix when the route has one, but **not** the `/api/exchange` gateway mount, which is stripped before your signature is verified. Calling `…/api/exchange/markets` means signing `/markets`; calling `…/api/exchange/api/v1/tickers` means signing `/api/v1/tickers`.
* Timestamp must be within ±30 seconds of server time (milliseconds since epoch).
* For GET requests with no body, hash the empty string.

{% tabs %}
{% tab title="cURL" %}

```bash
# Build the HMAC signature
TIMESTAMP=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(echo -n "" | shasum -a 256 | cut -d' ' -f1)
CANONICAL="$TIMESTAMP\nGET\n/markets\n\n$BODY_HASH"
SIGNATURE=$(echo -ne "$CANONICAL" | \
  openssl dgst -sha256 -mac HMAC -macopt "hexkey:$NEXUS_API_SECRET" -hex | awk '{print $NF}')

curl 'https://exchange.nexus.xyz/api/exchange/markets' \
  -H "X-API-Key: nx_7f3a1b..." \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

{% endtab %}

{% tab title="Rust" %}

```rust
use nexus_exchange::{Client, Config, Network};

// The SDK builds the canonical string and HMAC-signs every request.
let client = Client::new(Config::new(Network::Stable).api_key(
    std::env::var("NEXUS_API_KEY")?,
    std::env::var("NEXUS_API_SECRET")?,
));

let markets = client.fetch_markets().await?;
println!("{} markets", markets.len());
```

{% endtab %}

{% tab title="Python" %}

```python
import os
from nexus_exchange import Client

# The SDK builds the canonical string and HMAC-signs every request.
client = Client(
    api_key=os.environ["NEXUS_API_KEY"],
    api_secret=os.environ["NEXUS_API_SECRET"],
)

print(len(client.fetch_markets()), "markets")
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Client, Network } from "@nexus-xyz/exchange-ts";

// The SDK builds the canonical string and HMAC-signs every request.
const client = new Client({
  network: Network.Stable,
  apiKey: process.env.NEXUS_API_KEY,
  apiSecret: process.env.NEXUS_API_SECRET,
});

console.log((await client.fetchMarketSummaries()).length, "markets");
```

{% endtab %}

{% tab title="CLI" %}

```bash
nexus setup            # interactive; stores credentials (mode 0600)
# ...or per shell:
export NEXUS_API_KEY=nx_...
export NEXUS_API_SECRET=...
nexus balance          # every request is HMAC-signed for you
```

{% endtab %}
{% endtabs %}

**Response**

```json
[
  { "id": "BTC-USDX-PERP", "base": "BTC", "quote": "USDX", "status": "active" },
  { "id": "ETH-USDX-PERP", "base": "ETH", "quote": "USDX", "status": "active" }
]
```

{% hint style="info" %}
In the interactive docs this step has a live "Try it" button against `GET /markets`.
{% endhint %}

***

## Trading

Steps 4–7 fund the account and put a position on.

## 4. Fund account

`POST /account/credit` — **HMAC API key required.** Testnet surface.

Credit synthetic USDX to start trading. Each API key can claim up to 500 USDX per day — omit `"amount"` to claim the full remaining daily allowance.

{% hint style="warning" %}
The credit faucet is a **testnet** affordance. On the mainnet real-funds surface this endpoint returns `403` and funding is bridge-only — see the mainnet variant below.
{% endhint %}

**Notes**

* Amounts are decimal strings, like all monetary values in the API.
* Returns 429 once the daily allowance is used up; it resets the next UTC day.

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/api/v1/account/credit' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: UNIX_MS' \
  -H 'X-Signature: HMAC_HEX' \
  -H 'Content-Type: application/json' \
  -d '{"amount": "500"}'
```

{% endtab %}

{% tab title="Rust" %}

```rust
use nexus_exchange::types::Decimal;

// `client` is the HMAC-credentialed client from step 3.
// Pass None to claim the full remaining daily allowance.
let credit = client
    .claim_credit(Some("500".parse::<Decimal>()?))
    .await?;
println!("credited {} (today: {})", credit.amount, credit.credited_today);
```

{% endtab %}

{% tab title="Python" %}

```python
# `client` is the HMAC-credentialed client from step 3.
credit = client.claim_credit("500")  # omit the amount for the daily max
print(credit.amount, credit.credited_today)
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
// `client` is the HMAC-credentialed client from step 3.
const credit = await client.claimCredit({ amount: "500" }); // {} = daily max
console.log(credit.amount, credit.credited_today);
```

{% endtab %}

{% tab title="CLI" %}

```bash
nexus account credit --amount 500   # omit --amount for the daily max
```

{% endtab %}
{% endtabs %}

**Request body**

```json
{
  "amount": "500"
}
```

**Response**

```json
{
  "amount": "500",
  "credited_today": "500",
  "daily_limit": "500"
}
```

### Mainnet surface variant — step 4: Fund via bridge

On the mainnet real-funds surface (mainnet: **2026-05-20**), step 4 is replaced by a bridge deposit. Funding differs fundamentally per surface: the testnet play surface has a synthetic-credit faucet; the mainnet real-funds surface will be bridge-only.

`POST /bridge/deposit-addresses` — **HMAC API key required.**

Real funds: there is no synthetic credit on mainnet. Get your per-account deposit address, bridge USDX from Ethereum Mainnet to it, and your exchange account is credited once the deposit confirms.

**Notes**

* `POST /account/credit` is testnet-only — it returns 403 on mainnet; the bridge is the sole funding path.
* Idempotent per (account, chain): repeated calls return the same address.
* `GET /bridge/assets` lists depositable assets per chain with minimum amounts, required confirmations, and fees — check it before sending.
* Track crediting with `GET /bridge/deposits` (or `/bridge/deposits/{id}`): status moves to `credited` once required confirmations are reached.
* On-chain transfers are irreversible — send only supported assets to this address, from a wallet you control.
* Round trip: fund here → trade (next steps) → withdrawals: `GET /withdrawals` lists your records; withdrawal initiation is not yet exposed through the public API.

{% tabs %}
{% tab title="cURL" %}

```bash
# 1) Get (or create) your deposit address on Ethereum Mainnet
curl -X POST 'https://exchange.nexus.xyz/api/exchange/api/v1/bridge/deposit-addresses' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: UNIX_MS' \
  -H 'X-Signature: HMAC_HEX' \
  -H 'Content-Type: application/json' \
  -d '{"chain": "ethereum"}'

# 2) Send USDX to the returned address (from your own wallet), then track it:
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/bridge/deposits' \
  -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'
```

{% endtab %}

{% tab title="Rust" %}
Not available in Rust yet — use the cURL example. The white-glove client wrap for the bridge step is still in progress.
{% endtab %}

{% tab title="Python" %}
Not available in Python yet — use the cURL example.
{% endtab %}

{% tab title="TypeScript" %}
Not available in TypeScript yet — use the cURL example.
{% endtab %}

{% tab title="CLI" %}
Not available in the CLI yet — use the cURL example.
{% endtab %}
{% endtabs %}

**Request body**

```json
{
  "chain": "ethereum"
}
```

**Response**

```json
{
  "address": "0xDEPOSIT_ADDRESS",
  "chain": "ethereum",
  "accepts": ["USDC", "USDX"],
  "account_id": "0xYOUR_WALLET_ADDRESS",
  "created_at": 1779225381000
}
```

## 5. Browse markets

`GET /markets/{market_id}/ticker` — **HMAC API key required.**

List available perpetual futures markets and check current prices.

{% tabs %}
{% tab title="cURL" %}

```bash
# List markets (4 trading on testnet today; 32 configured)
curl 'https://exchange.nexus.xyz/api/exchange/markets' \
  -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'

# Get a single ticker
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/ticker' \
  -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'
```

{% endtab %}

{% tab title="Rust" %}

```rust
let markets = client.fetch_markets().await?;
println!("{} markets", markets.len());

let ticker = client.fetch_ticker("BTC-USDX-PERP").await?;
println!(
    "{}: last={:?} mark={:?}",
    ticker.symbol, ticker.last, ticker.mark_price
);
```

{% endtab %}

{% tab title="Python" %}

```python
for market in client.fetch_markets():
    print(market.market_id)

ticker = client.fetch_ticker("BTC-USDX-PERP")
print(ticker.last, ticker.mark_price)
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
for (const market of await client.fetchMarketSummaries()) {
  console.log(market.market_id);
}

const ticker = await client.fetchTicker("BTC-USDX-PERP");
console.log(ticker.last, ticker.markPrice);
```

{% endtab %}

{% tab title="CLI" %}

```bash
nexus markets                 # tradable markets and their rules
nexus ticker BTC-USDX-PERP    # ticker for one market
```

{% endtab %}
{% endtabs %}

**Response**

```json
{
  "symbol": "BTC-USDX-PERP",
  "last": 84250.5,
  "bid": 84249.0,
  "ask": 84252.0,
  "volume": 1523.4,
  "change": 2.1
}
```

{% hint style="info" %}
The source comment for this step reads `# List markets (all 32)`. That is the configured-market count, not the live one — the testnet is currently trading **4** markets, so the comment above has been corrected. See [Exchange Testnet](/exchange/exchange-testnet) for the current set.

On the mainnet surface the market-set copy differs: mainnet launches with 3 markets — `BTC-USDX-PERP`, `ETH-USDX-PERP`, `SOL-USDX-PERP` — expanding to 32+. The cURL comment there reads `# List markets (3 at internal launch: BTC, ETH, SOL — expanding to 32+)`.

In the interactive docs this step has a live "Try it" button against `GET /markets/BTC-USDX-PERP/ticker`.
{% endhint %}

## 6. Place an order

`POST /orders` — **HMAC API key required.**

Submit a limit or market order. The response confirms acceptance.

**Notes**

* Use `"type": "market"` to fill immediately at best available price.
* Batch multiple orders in a single `POST /orders/batch` call — processed sequentially, results preserve request order.

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/api/v1/orders' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: UNIX_MS' \
  -H 'X-Signature: HMAC_HEX' \
  -H 'Content-Type: application/json' \
  -d '{
    "market_id": "BTC-USDX-PERP",
    "side": "buy",
    "type": "limit",
    "size": 0.01,
    "price": 83000.00
  }'
```

{% endtab %}

{% tab title="Rust" %}

```rust
use nexus_exchange::types::{OrderRequest, Side, TimeInForce};

let order = OrderRequest::limit(
    "BTC-USDX-PERP",
    Side::Buy,
    "83000".parse()?,
    "0.01".parse()?,
    TimeInForce::Gtc,
);
let placed = client.create_order(&order).await?;
println!("placed {} — {}", placed.order.id, placed.order.status);
```

{% endtab %}

{% tab title="Python" %}

```python
from decimal import Decimal
from nexus_exchange import OrderRequest

order = OrderRequest.limit(
    "BTC-USDX-PERP", "Buy", Decimal("83000"), Decimal("0.01")
)
placed = client.create_order(order)
print(placed.order.id, placed.order.status)
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
const { order } = await client.placeOrder({
  market_id: "BTC-USDX-PERP",
  side: "Buy",
  order_type: "Limit",
  price: "83000",
  quantity: "0.01",
  time_in_force: "GTC",
});
console.log(order.id, order.status);
```

{% endtab %}

{% tab title="CLI" %}

```bash
# Prompts for confirmation; pass --yes to skip
nexus order place --market BTC-USDX-PERP --side buy --type limit \
  --price 83000 --quantity 0.01 --tif GTC
```

{% endtab %}
{% endtabs %}

**Request body**

```json
{
  "market_id": "BTC-USDX-PERP",
  "side": "buy",
  "type": "limit",
  "size": 0.01,
  "price": 83000.0
}
```

**Response**

```json
{
  "id": "ord_f82a...",
  "status": "open",
  "filled": 0.0,
  "market_id": "BTC-USDX-PERP",
  "side": "buy",
  "type": "limit",
  "size": 0.01,
  "price": 83000.0
}
```

## 7. Monitor positions

`GET /positions` — **HMAC API key required.**

Check open positions, unrealized PnL, and account health.

{% tabs %}
{% tab title="cURL" %}

```bash
# Open positions
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/positions' \
  -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'

# Account summary
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account' \
  -H 'X-API-Key: ...' -H 'X-Timestamp: ...' -H 'X-Signature: ...'
```

{% endtab %}

{% tab title="Rust" %}

```rust
let account = client.fetch_balance().await?;
println!("equity {}", account.equity);

for p in client.fetch_positions().await? {
    println!(
        "{} {} size {} | uPnL {}",
        p.market_id, p.side, p.size, p.unrealized_pnl
    );
}
```

{% endtab %}

{% tab title="Python" %}

```python
account = client.fetch_balance()
print(account.equity)

for p in client.fetch_positions():
    print(p.market_id, p.side, p.size, p.unrealized_pnl)
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
const account = await client.getAccount();
console.log(account.equity);

for (const p of await client.getPositions()) {
  console.log(p.market_id, p.side, p.size, p.unrealized_pnl);
}
```

{% endtab %}

{% tab title="CLI" %}

```bash
nexus positions   # open positions with PnL
nexus balance     # balance, collateral, equity, margin
```

{% endtab %}
{% endtabs %}

**Response**

```json
{
  "balance": 10000.0,
  "equity": 10012.5,
  "margin_used": 83.0,
  "margin_ratio": 0.008,
  "positions": 1
}
```

{% hint style="info" %}
In the interactive docs this step has a live "Try it" button against `GET /account`.
{% endhint %}

***

## Clients

The snippets above use these packages:

| Client     | Package / crate          |
| ---------- | ------------------------ |
| Rust       | `nexus_exchange`         |
| Python     | `nexus_exchange`         |
| TypeScript | `@nexus-xyz/exchange-ts` |
| CLI        | the `nexus` command      |

The CLI and the language SDKs are distributed outside the Exchange monorepo. The guided walkthrough does not state their repositories, so install instructions are not reproduced here.

## Next steps

* [Overview](/exchange-api/exchange-api) — what the Exchange API covers and how it is organised
* [Authentication](/exchange-api/exchange-api/authentication) — session tokens, HMAC canonicalisation, and API-key management in full
* [Trading](/exchange-api/exchange-api/trading) — orders, batching, positions, and account endpoints
* [WebSocket](/exchange-api/exchange-api/websocket) — token minting, channels, message envelopes, and reconnection


# Authentication

EVM wallet sign-in and API key management. Four operations.

Two credentials are involved, and they are not interchangeable:

1. **Session token** — obtained by signing a fixed message with your EVM wallet (EIP-191 `personal_sign`). It is a `Bearer` token, it lasts **24 hours**, and it authenticates the `/keys` endpoints **only**. You cannot trade with it.
2. **API key** — a `key_id` + `secret` pair minted with the session token. Every trading and account request is HMAC-SHA256 signed with the secret. The secret is returned **once** at creation and is never stored or shown again.

```
POST /auth/login   (wallet signature)         → session Bearer token
POST /keys         (Bearer)                    → key_id + secret
sign each request  (HMAC over canonical str)   → trade
```

For delegated signing that avoids exposing your main wallet, see [Agents](/exchange-api/exchange-api/agents).

Base URL for every example below: `https://exchange.nexus.xyz/api/exchange`.

***

## `POST /auth/login`

Sign in with EVM wallet.

Submit an EIP-191 `personal_sign` signature to receive a session token. The session token is used to create and manage API keys via `/keys` endpoints. For trading, use HMAC API keys instead. Session tokens expire after 24 hours.

**Authentication:** None — the request authenticates itself with the wallet signature it carries.

### Request body

`LoginRequest` — `application/json`, required.

| Field       | Type   | Required | Description                                         |
| ----------- | ------ | -------- | --------------------------------------------------- |
| `message`   | string | Yes      | Must be exactly: `Sign in to Nexus Exchange`        |
| `signature` | string | Yes      | EIP-191 `personal_sign` hex (0x-prefixed, 65 bytes) |

The message is a fixed string, not a nonce challenge — there is no separate "request a challenge" call. Your wallet address is *recovered* from the signature, so it is not sent.

### Responses

#### `200`

Session created. `LoginResponse`.

| Field     | Type   | Description                                                               |
| --------- | ------ | ------------------------------------------------------------------------- |
| `token`   | string | Session token (64-char hex). Use as `Bearer` token for `/keys` endpoints. |
| `address` | string | Recovered Ethereum address (0x-prefixed)                                  |

#### `401`

Signature verification failed.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Sign in to Nexus Exchange",
    "signature": "0x1234...abcd"
  }'
```

Response:

```json
{
  "token": "a1b2c3d4e5f6...",
  "address": "0xAbCdEf0123456789..."
}
```

***

## `POST /keys`

Create an API key.

Create a new HMAC API key for the authenticated wallet. Returns the secret once — it is never stored or shown again. Requires a session token (Bearer) from `POST /auth/login`.

**Authentication:** `bearerAuth` — session token.

### Request body

None declared in the contract. Send the request with no body.

> **Gap:** the guided quickstart on the live API-docs page sends an optional `{"label": "my-bot"}` body to this endpoint, but spec 0.7.2 declares no `requestBody` for `createApiKey`. Treat `label` as undocumented until the contract carries it.

### Responses

#### `200`

Key created.

| Field    | Type   | Description                                                                                  |
| -------- | ------ | -------------------------------------------------------------------------------------------- |
| `key_id` | string | The key identifier. Send it as `X-API-Key` on signed requests.                               |
| `secret` | string | The HMAC secret, hex. **Returned once.** Persist it immediately — there is no recovery path. |

The contract gives this response an example but no named schema.

#### `401`

Valid session token required.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/keys' \
  -H 'Authorization: Bearer a1b2c3d4e5f6...'
```

Response:

```json
{
  "key_id": "nx_a1b2c3d4e5f67890",
  "secret": "deadbeef..."
}
```

New keys are assigned the **Pro** tier by default. Tier assignment is an operator action — see [Admin](/exchange-api/exchange-api/admin) — and per-tier ceilings are reported on every response in the `X-RateLimit-*` headers.

***

## `GET /keys`

List your API keys.

Returns key IDs and tiers for all keys owned by the authenticated wallet. Secrets are not included.

**Authentication:** `bearerAuth` — session token.

### Responses

#### `200`

The session's API keys. A bare array; the contract gives an example but no named schema.

| Field    | Type   | Description                                                      |
| -------- | ------ | ---------------------------------------------------------------- |
| `key_id` | string | The key identifier                                               |
| `tier`   | string | Rate-limit tier assigned to this key, e.g. `Pro`, `Market Maker` |

#### `401`

Valid session token required.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/keys' \
  -H 'Authorization: Bearer a1b2c3d4e5f6...'
```

Response:

```json
[
  {
    "key_id": "nx_a1b2c3d4e5f67890",
    "tier": "Pro"
  }
]
```

***

## `DELETE /keys/{key_id}`

Delete an API key.

Delete a key you own. Cannot delete keys owned by other wallets.

**Authentication:** `bearerAuth` — session token.

### Parameters

| Name     | In   | Type   | Required | Description                  |
| -------- | ---- | ------ | -------- | ---------------------------- |
| `key_id` | path | string | Yes      | The key identifier to delete |

### Responses

#### `200`

Key deleted.

#### `401`

Valid session token required.

#### `404`

Key not found or not owned by you. Ownership failures are reported as `404`, not `403` — you cannot probe for other wallets' key IDs.

### Example

```bash
curl -X DELETE 'https://exchange.nexus.xyz/api/exchange/keys/nx_a1b2c3d4e5f67890' \
  -H 'Authorization: Bearer a1b2c3d4e5f6...'
```

***

## Signing a request with the key

Once you hold a `key_id` and `secret`, every `hmacAuth` operation needs three headers:

| Header        | Value                                   |
| ------------- | --------------------------------------- |
| `X-API-Key`   | Your key ID, e.g. `nx_a1b2c3d4e5f67890` |
| `X-Timestamp` | Current time in Unix **milliseconds**   |
| `X-Signature` | `hex(hmac_sha256(secret, canonical))`   |

The canonical string is five newline-separated fields:

```
<timestamp>\n<METHOD>\n<path>\n<query>\n<sha256_hex(body)>
```

* `path` is the path **as written in the contract** — *not* the full URL path you called. The `/api/exchange` gateway mount is stripped before the request reaches the service that verifies your signature, so it must **not** be signed. Do include the `/api/v1` prefix when calling a versioned path, because that part is forwarded.

  | You call                                                 | You sign          |
  | -------------------------------------------------------- | ----------------- |
  | `https://exchange.nexus.xyz/api/exchange/markets`        | `/markets`        |
  | `https://exchange.nexus.xyz/api/exchange/api/v1/tickers` | `/api/v1/tickers` |

  Signing the `/api/exchange` prefix is the second most common cause of an opaque `401`, after clock skew.
* `query` is the raw query string, empty for none.
* For requests with no body, hash the empty string.
* The timestamp must be within **30 seconds** of server time. A skewed clock is the most common cause of an opaque `401`.

The advisory `X-Nexus-Api-Version` and `User-Agent` headers are **excluded** from the canonical string.

```bash
TIMESTAMP=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)
CANONICAL="$TIMESTAMP\nGET\n/markets\n\n$BODY_HASH"
SIGNATURE=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$NEXUS_API_SECRET" -hex | awk '{print $NF}')

curl 'https://exchange.nexus.xyz/api/exchange/markets' \
  -H "X-API-Key: nx_a1b2c3d4e5f67890" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

All `401` responses return the same opaque body — `{"code":"unauthorized"}` — so the response will not tell you whether the key, the signature, or the clock was at fault. Check clock skew first, then the canonical string, then the key.

> **Status:** testnet preview. Credentials, sessions, and rate-limit state are not yet durable across gateway restarts — treat API keys as re-creatable.


# Agents

Agent key registration and management. Three operations.

An **agent** is an Ethereum-derived keypair that can sign trading requests on your behalf without exposing your main wallet. You register the agent's address once, authorized by a signature from the owning wallet, and from then on the agent key carries the trading authority — while the wallet key stays offline.

**No session token is required for any operation in this section.** Registration is authorized in-band by an **EIP-712** signature in the request body, so it needs no credential at all. The two management operations (`GET /agents`, `DELETE /agents/{address}`) authenticate with your HMAC API key.

| Operation                  | Authorization                                                        |
| -------------------------- | -------------------------------------------------------------------- |
| `POST /agents/register`    | EIP-712 signature in the request body. No session token, no API key. |
| `GET /agents`              | `hmacAuth`                                                           |
| `DELETE /agents/{address}` | `hmacAuth`                                                           |

Base URL for every example below: `https://exchange.nexus.xyz/api/exchange`.

## The EIP-712 payload

Registration is authorized by a typed-data signature from the wallet that will own the agent.

**Domain**

```json
{
  "name": "Nexus Exchange",
  "version": "1",
  "chainId": "<testnet chain id>"
}
```

**Type**

```
RegisterAgent {
  address agent
  uint64  expiresAt
  uint64  nonce
}
```

Notes on getting this right:

* The signature covers **three** fields — `agent`, `expiresAt`, `nonce`. The owning `wallet` is **not** part of the typed data; it is sent alongside in the request body and the server checks that the recovered signer matches it.
* The typed-data field names are **camelCase** (`expiresAt`), while the JSON request body uses **snake\_case** (`expires_at`). Sign the camelCase names; send the snake\_case ones.
* If you let `expires_at` default server-side, you have nothing to sign. Compute the expiry yourself, sign it, and send it explicitly.
* The contract writes the domain's `chainId` as the placeholder `<testnet chain id>` and does not pin a value. Published Nexus chain IDs are listed under [Developer Environment Setup](/network/building-on-nexus/developer-environment-setup); confirm the value your target gateway expects before signing, since a domain mismatch produces `signer_mismatch`, not a descriptive error.

***

## `POST /agents/register`

Register an agent key.

Register a new agent key for your wallet. An agent is an Ethereum-derived keypair that can sign trading requests on your behalf without exposing your main wallet. The registration is authorized by an EIP-712 signature from the wallet that will own the agent — no session token required.

EIP-712 domain: `{ name: 'Nexus Exchange', version: '1', chainId: <testnet chain id> }`. Typed data type: `RegisterAgent { address agent, uint64 expiresAt, uint64 nonce }`.

**Authentication:** None — the request authorizes itself with the EIP-712 signature it carries.

### Request body

`AgentRegistrationRequest` — `application/json`, required.

| Field        | Type            | Required | Description                                                                                               |
| ------------ | --------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `wallet`     | string          | Yes      | Owner wallet address (0x-prefixed, 20 bytes)                                                              |
| `agent`      | string          | Yes      | Agent Ethereum address (0x-prefixed, 20 bytes) derived from the agent keypair                             |
| `expires_at` | integer (int64) | No       | Expiry as Unix ms. Optional — defaults to now+30 d. Must be in `[now+1d, now+90d]`.                       |
| `nonce`      | integer (int64) | Yes      | Monotonic nonce. Use the current Unix timestamp in ms as a safe starting value.                           |
| `signature`  | string          | Yes      | EIP-712 signature over `RegisterAgent{agent, expiresAt, nonce}` from the wallet private key (0x-prefixed) |
| `label`      | string          | No       | Optional human-readable label for the agent (e.g. `my-bot`)                                               |

### Responses

#### `200`

Agent registered. The contract gives this response an example but no named schema.

| Field           | Type            | Description                                                                             |
| --------------- | --------------- | --------------------------------------------------------------------------------------- |
| `agent_address` | string          | The registered agent address (0x-prefixed)                                              |
| `expires_at`    | integer (int64) | Effective expiry, Unix ms — the value you sent, or the server default if you omitted it |

#### `400`

Bad request: `bad_wallet`, `bad_agent`, `expiry_out_of_range` (`[1 d, 90 d]` from now), or `invalid_json`.

#### `401`

`signature_invalid` or `signer_mismatch` — the EIP-712 signature did not recover to the claimed wallet.

#### `409`

`duplicate_agent` — the agent address is already registered to this wallet.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/agents/register' \
  -H 'Content-Type: application/json' \
  -d '{
    "wallet": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01",
    "agent": "0x1234567890AbCdEf1234567890AbCdEf12345678",
    "expires_at": 1782000000000,
    "nonce": 1,
    "signature": "0xdeadbeef..."
  }'
```

Response:

```json
{
  "agent_address": "0x1234567890AbCdEf1234567890AbCdEf12345678",
  "expires_at": 1782000000000
}
```

***

## `GET /agents`

List your agents.

Returns all non-expired agent keys registered to the authenticated wallet. Expired agents are filtered out server-side, so an agent disappearing from this list is the expected end of its lifecycle, not an error.

**Authentication:** `hmacAuth` — HMAC API key.

### Responses

#### `200`

Array of agent records. Items are `AgentInfo`.

| Field          | Type            | Description                 |
| -------------- | --------------- | --------------------------- |
| `address`      | string          | Agent address (0x-prefixed) |
| `expiresAt`    | integer (int64) | Expiry, Unix ms             |
| `registeredAt` | integer (int64) | Registration time, Unix ms  |
| `label`        | string \| null  | Optional label              |

Response field names are **camelCase** here, unlike the snake\_case registration request body.

#### `401`

HMAC authentication required.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/agents' \
  -H "X-API-Key: nx_a1b2c3d4e5f67890" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

Response:

```json
[
  {
    "address": "0x1234567890AbCdEf1234567890AbCdEf12345678",
    "expiresAt": 1782000000000,
    "registeredAt": 1779000000000,
    "label": "my-bot"
  }
]
```

See [Authentication](/exchange-api/exchange-api/authentication#signing-a-request-with-the-key) for how to build `X-Timestamp` and `X-Signature`.

***

## `DELETE /agents/{address}`

Revoke an agent.

Immediately revoke an agent key. Any in-flight requests signed by the revoked agent will be rejected after this call returns.

**Authentication:** `hmacAuth` — HMAC API key.

### Parameters

| Name      | In   | Type   | Required | Description                           |
| --------- | ---- | ------ | -------- | ------------------------------------- |
| `address` | path | string | Yes      | Agent address to revoke (0x-prefixed) |

### Responses

#### `200`

Agent revoked.

#### `401`

HMAC authentication required.

#### `404`

Agent not found or not owned by you. Ownership failures are reported as `404`, not `403` — you cannot probe for other wallets' agents.

### Example

```bash
curl -X DELETE 'https://exchange.nexus.xyz/api/exchange/agents/0x1234567890AbCdEf1234567890AbCdEf12345678' \
  -H "X-API-Key: nx_a1b2c3d4e5f67890" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

***

## Operating notes

* **Expiry is bounded.** An agent must expire between 1 and 90 days from registration; omitting `expires_at` gives you 30 days. There is no renewal operation in version 0.7.2 — re-register a fresh agent before the old one lapses.
* **Nonces are monotonic per wallet.** Using the current Unix millisecond timestamp as the nonce satisfies that ordering without tracking state.
* **Revocation is immediate**, and it applies to in-flight requests: a request signed by a revoked agent is rejected once the `DELETE` returns.
* **Registration is unauthenticated at the transport layer.** Anyone can submit a registration; only a valid EIP-712 signature from the claimed wallet is accepted. Guard the wallet key accordingly — a signature over `RegisterAgent` grants trading authority for up to 90 days.

> **Status:** testnet preview. Agent registrations are not yet durable across gateway restarts — treat them as re-creatable alongside API keys.


# Markets

Market parameters and summaries. These operations describe the tradeable universe — per-market tick and lot sizes, margin rates and maximum leverage, halt state, mark price, and the rolling volume summary — plus venue-wide statistics and aggregate service health used by status pages.

Full field-by-field definitions for every schema named on this page live in the [Schema Reference](/exchange-api/exchange-api/schemas).

## Base URLs and the two path surfaces

Most operations on this page are served twice — a versioned path and a legacy root path:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers and the same authentication and rate-limit layers back both mounts, the bodies are identical, and the bare root paths are the legacy form kept for compatibility. Prefer `/api/v1` for new integrations. The contract marks neither variant `deprecated` — both are live. Whichever you choose, **sign the path the service sees** — include the `/api/v1` prefix, but not the `/api/exchange` gateway mount, which is stripped before your signature is verified.

| Canonical (`/api/v1`)                        | Legacy alias                          | Operation                       | `operationId`                                   |
| -------------------------------------------- | ------------------------------------- | ------------------------------- | ----------------------------------------------- |
| `GET /api/v1/markets/summary`                | `GET /markets/summary`                | Market summaries with volume    | `fetchMarketsSummaryV1` / `fetchMarketsSummary` |
| `GET /api/v1/markets/{market_id}/mark-price` | `GET /markets/{market_id}/mark-price` | Get mark price                  | `fetchMarkPriceV1` / `fetchMarkPrice`           |
| `GET /api/v1/markets/{market_id}/status`     | `GET /markets/{market_id}/status`     | Get market status and halt info | `fetchMarketStatusV1` / `fetchMarketStatus`     |
| `GET /api/v1/stats`                          | `GET /stats`                          | Venue statistics                | `fetchStatsV1` / `fetchStats`                   |
| `GET /api/v1/stats/history`                  | `GET /stats/history`                  | Venue throughput history        | `fetchStatsHistoryV1` / `fetchStatsHistory`     |

Four operations in this group exist **only** at the bare gateway path — the contract declares no `/api/v1` twin for `GET /markets`, `GET /markets/{market_id}/adl-events`, `GET /markets/{market_id}/risk-params`, or `GET /status`.

## Decimal values are strings

Prices, sizes, and margin rates (`tick_size`, `lot_size`, `min_order_size`, `max_order_size`, `initial_margin_rate`, `maintenance_margin_rate`, `mark_price`, and every ADL amount) are arbitrary-precision decimals serialized as JSON **strings** so nothing is lost on the wire. **Parse them with a decimal type, never a float.**

The volume-oriented fields on `MarketSummary` are the exception: `last_trade_price` and `volume_24h` are JSON numbers.

## Error responses

Most operations on this page can return `429`. Rate-limited responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers, with a body of `{"code": "RateLimitExceeded", "tier": "Pro"}`. The four venue-level reads — `GET /stats`, `GET /stats/history`, `GET /status`, and their `/api/v1` twins — declare no `429` response in the contract, even though they are subject to the same tiered rate limiting.

The two authenticated operations can return `401`. Every `401` returns the same opaque body — `{"code": "unauthorized"}` — regardless of cause, so it never leaks which part of the credential was wrong. See [Authentication](/exchange-api/exchange-api/authentication) for the HMAC signing scheme.

## `GET /markets`

List all markets.

Returns market parameters for all perpetual futures markets including tick size, lot size, margin rates, and maximum leverage.

CCXT method: `fetchMarkets`.

**Authentication:** `hmacAuth` — HMAC API key (`X-API-Key`, `X-Timestamp`, `X-Signature`). This and `GET /markets/{market_id}/adl-events` are the only two operations on this page that require credentials; the rest are public.

### Responses

#### `200`

Array of market parameters. Each element is a `Market`:

| Field                     | Type             | Description                               |
| ------------------------- | ---------------- | ----------------------------------------- |
| `market_id`               | string           | Market identifier, e.g. `BTC-USDX-PERP`   |
| `base_asset`              | string           | Base asset symbol                         |
| `quote_asset`             | string           | Quote asset symbol                        |
| `tick_size`               | string (decimal) | Minimum price increment                   |
| `lot_size`                | string (decimal) | Minimum size increment                    |
| `min_order_size`          | string (decimal) | Smallest accepted order size              |
| `max_order_size`          | string (decimal) | Largest accepted order size               |
| `initial_margin_rate`     | string (decimal) | Initial margin requirement as a ratio     |
| `maintenance_margin_rate` | string (decimal) | Maintenance margin requirement as a ratio |
| `max_leverage`            | integer          | Maximum leverage allowed for this market  |

Example body:

```json
[
  {
    "market_id": "BTC-USDX-PERP",
    "base_asset": "BTC",
    "quote_asset": "USDX",
    "tick_size": "0.5",
    "lot_size": "0.001",
    "min_order_size": "0.001",
    "max_order_size": "100",
    "initial_margin_rate": "0.05",
    "maintenance_margin_rate": "0.025",
    "max_leverage": 20
  }
]
```

#### `401`

Authentication failed. All `401` responses return the same opaque body to prevent information leakage.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
TIMESTAMP=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(echo -n "" | shasum -a 256 | cut -d' ' -f1)
CANONICAL="$TIMESTAMP\nGET\n/markets\n\n$BODY_HASH"
SIGNATURE=$(echo -ne "$CANONICAL" | \
  openssl dgst -sha256 -mac HMAC -macopt "hexkey:$NEXUS_API_SECRET" -hex | awk '{print $NF}')

curl 'https://exchange.nexus.xyz/api/exchange/markets' \
  -H "X-API-Key: $NEXUS_API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

## `GET /markets/summary`

Market summaries with volume.

Returns last trade price, 24h volume, and trade count for all markets.

Legacy alias of `GET /api/v1/markets/summary`. Prefer the versioned path for new integrations.

**Authentication:** None — public read.

### Responses

#### `200`

Volume and price summaries for all markets. Each element is a `MarketSummary`:

| Field              | Type                          | Description                                                                                                         |
| ------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `market_id`        | string                        | Market identifier                                                                                                   |
| `last_trade_price` | number or null                | Last trade price ("what the market is trading at"). **Not** the mark; the engine-derived mark is exposed separately |
| `volume_24h`       | number                        | Rolling 24h volume                                                                                                  |
| `trade_count`      | integer                       | Trade count                                                                                                         |
| `status`           | string (`active` \| `halted`) | v0.21: `halted` when the ADL pool is exhausted                                                                      |
| `halt_reason`      | string or null                | Reason the market was halted                                                                                        |
| `halted_at`        | integer or null               | Unix ms timestamp when the market was halted                                                                        |
| `adl_event_count`  | integer                       | Cumulative ADL settlement events for this market                                                                    |

Example body:

```json
[
  {
    "market_id": "BTC-USDX-PERP",
    "last_trade_price": 48850,
    "volume_24h": 19530020.08,
    "trade_count": 45230
  }
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/summary'
```

## `GET /markets/{market_id}/status`

Get market status and halt info (v0.21).

Returns current market status including halt state from ADL exhaustion. Halted markets reject new orders with `ExchangeError::MarketHalted`.

Legacy alias of `GET /api/v1/markets/{market_id}/status`. Prefer the versioned path for new integrations.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Market status and halt information (`MarketStatus` — per-market halt status, v0.21):

| Field             | Type                          | Description                                      |
| ----------------- | ----------------------------- | ------------------------------------------------ |
| `market_id`       | string                        | Market identifier                                |
| `status`          | string (`active` \| `halted`) | Current market status                            |
| `halt_reason`     | string or null                | Reason the market was halted                     |
| `halted_at`       | integer or null               | Unix ms timestamp when the market was halted     |
| `adl_event_count` | integer                       | Cumulative ADL settlement events for this market |

#### `404`

Market not found.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/status'
```

## `GET /markets/{market_id}/adl-events`

Get ADL settlement history for a market (v0.21).

Returns up to `limit` ADL settlement events for the market, most recent first. Populated when the insurance fund is depleted and auto-deleveraging closes opposite-side positions to absorb bad debt.

**Authentication:** `hmacAuth` — HMAC API key (`X-API-Key`, `X-Timestamp`, `X-Signature`).

### Parameters

| Name        | In    | Type                                | Required | Description                                               |
| ----------- | ----- | ----------------------------------- | -------- | --------------------------------------------------------- |
| `market_id` | path  | string                              | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |
| `limit`     | query | integer (default `100`, max `1000`) | No       | Maximum events to return                                  |

### Responses

#### `200`

ADL settlement history for the market. Each element is an `AdlEventRecord` — a single ADL settlement (insurance fund depleted, so counterparty positions were closed):

| Field                       | Type                        | Description                                             |
| --------------------------- | --------------------------- | ------------------------------------------------------- |
| `market_id`                 | string                      | Market identifier                                       |
| `target_account`            | string                      | 0x-prefixed bankrupt account                            |
| `bankruptcy_price`          | string (decimal)            | Price at which the target account went bankrupt         |
| `bad_debt_absorbed_by_fund` | string (decimal)            | Bad debt the insurance fund absorbed before ADL engaged |
| `counterparty_closures`     | array of `AdlClosureRecord` | Forced closures that absorbed the remainder             |
| `sequence`                  | integer                     | Engine event sequence number                            |
| `timestamp`                 | integer (Unix ms)           | When the settlement occurred                            |

Each `counterparty_closures` entry — one counterparty's forced closure within an ADL settlement:

| Field               | Type             | Description                                                       |
| ------------------- | ---------------- | ----------------------------------------------------------------- |
| `account_id`        | string           | 0x-prefixed address of the counterparty whose position was closed |
| `position_closed`   | string (decimal) | Quantity closed                                                   |
| `settlement_amount` | string (decimal) | Amount charged to the counterparty                                |

#### `401`

Authentication failed. All `401` responses return the same opaque body to prevent information leakage.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
TIMESTAMP=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(echo -n "" | shasum -a 256 | cut -d' ' -f1)
CANONICAL="$TIMESTAMP\nGET\n/markets/BTC-USDX-PERP/adl-events\nlimit=100\n$BODY_HASH"
SIGNATURE=$(echo -ne "$CANONICAL" | \
  openssl dgst -sha256 -mac HMAC -macopt "hexkey:$NEXUS_API_SECRET" -hex | awk '{print $NF}')

curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/adl-events?limit=100' \
  -H "X-API-Key: $NEXUS_API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

## `GET /markets/{market_id}/mark-price`

Get mark price.

Legacy alias of `GET /api/v1/markets/{market_id}/mark-price`. Prefer the versioned path for new integrations.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Current mark price for the market. The contract gives this response by example only — no named schema — so treat the shape below as the documented form:

| Field        | Type             | Description                                                  |
| ------------ | ---------------- | ------------------------------------------------------------ |
| `market_id`  | string           | Market identifier                                            |
| `mark_price` | string (decimal) | Current mark price, as an arbitrary-precision decimal string |

Example body:

```json
{
  "market_id": "BTC-USDX-PERP",
  "mark_price": "50011.60"
}
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/mark-price'
```

## `GET /markets/{market_id}/risk-params`

Get market risk parameters.

Returns per-market risk parameters including margin requirements and maximum leverage. Populated by the indexer's `market_params_poller` from the engine's market registry.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Market risk parameters (`MarketRiskParams`):

| Field                     | Type             | Description                                                             |
| ------------------------- | ---------------- | ----------------------------------------------------------------------- |
| `market_id`               | string           | Market identifier                                                       |
| `max_leverage`            | integer          | Maximum leverage allowed for this market                                |
| `initial_margin_rate`     | string (decimal) | Initial margin requirement as a decimal ratio (e.g. `0.05` = 5%)        |
| `maintenance_margin_rate` | string (decimal) | Maintenance margin requirement as a decimal ratio (e.g. `0.025` = 2.5%) |

Example body:

```json
{
  "market_id": "BTC-USDX-PERP",
  "max_leverage": 20,
  "initial_margin_rate": "0.05",
  "maintenance_margin_rate": "0.025"
}
```

#### `404`

Market not found.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/risk-params'
```

## `GET /stats`

Venue statistics.

Aggregate venue statistics plus rolling unique-trader counts. Public — no authentication required.

Legacy alias of `GET /api/v1/stats`. Prefer the versioned path for new integrations.

**Authentication:** None — public read.

### Responses

#### `200`

Venue statistics snapshot (`StatsSnapshot`). `/stats` augments the base snapshot with rolling unique-trader counts:

| Field                   | Type                      | Description                                                       |
| ----------------------- | ------------------------- | ----------------------------------------------------------------- |
| `events_received`       | integer (int64)           | Engine events the indexer has ingested                            |
| `fills_total`           | integer (int64)           | Cumulative fills                                                  |
| `liquidations_total`    | integer (int64)           | Cumulative liquidations                                           |
| `gap_count`             | integer (int64)           | Detected sequence gaps                                            |
| `connected`             | boolean                   | Whether the indexer is connected to the engine stream             |
| `last_event_ms`         | integer (Unix ms) or null | Timestamp of the most recent event                                |
| `uptime_seconds`        | integer (int64)           | Indexer uptime                                                    |
| `events_per_sec`        | number                    | Current event throughput                                          |
| `health`                | string                    | Health classification (e.g. `Healthy` / `Degraded` / `Unhealthy`) |
| `highest_sequence_seen` | integer (int64)           | Highest engine sequence number observed                           |
| `unique_traders_24h`    | integer (int64)           | Rolling 24h unique traders (DAU). Present on `/stats`             |
| `unique_traders_7d`     | integer (int64)           | Rolling 7d unique traders (WAU). Present on `/stats`              |
| `unique_traders_30d`    | integer (int64)           | Rolling 30d unique traders (MAU). Present on `/stats`             |

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/stats'
```

## `GET /stats/history`

Venue throughput history.

Per-second throughput ring buffer (up to 3600 points). Public — no authentication required.

Legacy alias of `GET /api/v1/stats/history`. Prefer the versioned path for new integrations.

**Authentication:** None — public read.

### Responses

#### `200`

Throughput samples, oldest first. Each element is a `ThroughputSample` — one point in the venue throughput ring buffer (1s cadence, capped at 3600 points):

| Field       | Type            | Description                   |
| ----------- | --------------- | ----------------------------- |
| `timestamp` | integer (int64) | Unix **seconds**              |
| `fills`     | integer (int64) | Fills recorded in that second |

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/stats/history'
```

## `GET /status`

Aggregate service health.

Aggregate health of indexer/engine/oracle/bots for status pages. Public — no authentication required.

**Authentication:** None — public read.

### Responses

#### `200`

Service health summary (`ServiceHealth`). Consumed by `status.nexus.xyz`. The `services` object carries per-component detail; only the common fields are part of the contract:

| Field          | Type                                                | Description                                                                                                                                           |
| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`       | string (`ok` \| `degraded` \| `down` \| `starting`) | Worst-of across all components                                                                                                                        |
| `timestamp_ms` | integer (Unix ms)                                   | When the snapshot was taken                                                                                                                           |
| `services`     | object                                              | Per-component status (indexer, engine, oracle, bots). Component detail is informational and may evolve; clients should rely on the top-level `status` |

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/status'
```

## `GET /api/v1/markets/summary`

Market summaries with volume.

Returns last trade price, 24h volume, and trade count for all markets.

Canonical versioned form. The bare `GET /markets/summary` is a legacy alias that returns the same body.

**Authentication:** None — public read.

### Responses

#### `200`

Volume and price summaries for all markets. Each element is a `MarketSummary`:

| Field              | Type                          | Description                                                                                                         |
| ------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `market_id`        | string                        | Market identifier                                                                                                   |
| `last_trade_price` | number or null                | Last trade price ("what the market is trading at"). **Not** the mark; the engine-derived mark is exposed separately |
| `volume_24h`       | number                        | Rolling 24h volume                                                                                                  |
| `trade_count`      | integer                       | Trade count                                                                                                         |
| `status`           | string (`active` \| `halted`) | v0.21: `halted` when the ADL pool is exhausted                                                                      |
| `halt_reason`      | string or null                | Reason the market was halted                                                                                        |
| `halted_at`        | integer or null               | Unix ms timestamp when the market was halted                                                                        |
| `adl_event_count`  | integer                       | Cumulative ADL settlement events for this market                                                                    |

Example body:

```json
[
  {
    "market_id": "BTC-USDX-PERP",
    "last_trade_price": 48850,
    "volume_24h": 19530020.08,
    "trade_count": 45230
  }
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/summary'
```

## `GET /api/v1/markets/{market_id}/mark-price`

Get mark price.

Canonical versioned form. The bare `GET /markets/{market_id}/mark-price` is a legacy alias that returns the same body.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Current mark price for the market. The contract gives this response by example only — no named schema:

| Field        | Type             | Description                                                  |
| ------------ | ---------------- | ------------------------------------------------------------ |
| `market_id`  | string           | Market identifier                                            |
| `mark_price` | string (decimal) | Current mark price, as an arbitrary-precision decimal string |

Example body:

```json
{
  "market_id": "BTC-USDX-PERP",
  "mark_price": "50011.60"
}
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/mark-price'
```

## `GET /api/v1/markets/{market_id}/status`

Get market status and halt info (v0.21).

Returns current market status including halt state from ADL exhaustion. Halted markets reject new orders with `ExchangeError::MarketHalted`.

Canonical versioned form. The bare `GET /markets/{market_id}/status` is a legacy alias that returns the same body.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Market status and halt information (`MarketStatus`):

| Field             | Type                          | Description                                      |
| ----------------- | ----------------------------- | ------------------------------------------------ |
| `market_id`       | string                        | Market identifier                                |
| `status`          | string (`active` \| `halted`) | Current market status                            |
| `halt_reason`     | string or null                | Reason the market was halted                     |
| `halted_at`       | integer or null               | Unix ms timestamp when the market was halted     |
| `adl_event_count` | integer                       | Cumulative ADL settlement events for this market |

#### `404`

Market not found.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/status'
```

## `GET /api/v1/stats`

Venue statistics.

Aggregate venue statistics plus rolling unique-trader counts. Public — no authentication required.

Canonical versioned form. The bare `GET /stats` is a legacy alias that returns the same body.

**Authentication:** None — public read.

### Responses

#### `200`

Venue statistics snapshot (`StatsSnapshot`):

| Field                   | Type                      | Description                                                       |
| ----------------------- | ------------------------- | ----------------------------------------------------------------- |
| `events_received`       | integer (int64)           | Engine events the indexer has ingested                            |
| `fills_total`           | integer (int64)           | Cumulative fills                                                  |
| `liquidations_total`    | integer (int64)           | Cumulative liquidations                                           |
| `gap_count`             | integer (int64)           | Detected sequence gaps                                            |
| `connected`             | boolean                   | Whether the indexer is connected to the engine stream             |
| `last_event_ms`         | integer (Unix ms) or null | Timestamp of the most recent event                                |
| `uptime_seconds`        | integer (int64)           | Indexer uptime                                                    |
| `events_per_sec`        | number                    | Current event throughput                                          |
| `health`                | string                    | Health classification (e.g. `Healthy` / `Degraded` / `Unhealthy`) |
| `highest_sequence_seen` | integer (int64)           | Highest engine sequence number observed                           |
| `unique_traders_24h`    | integer (int64)           | Rolling 24h unique traders (DAU)                                  |
| `unique_traders_7d`     | integer (int64)           | Rolling 7d unique traders (WAU)                                   |
| `unique_traders_30d`    | integer (int64)           | Rolling 30d unique traders (MAU)                                  |

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/stats'
```

## `GET /api/v1/stats/history`

Venue throughput history.

Per-second throughput ring buffer (up to 3600 points). Public — no authentication required.

Canonical versioned form. The bare `GET /stats/history` is a legacy alias that returns the same body.

**Authentication:** None — public read.

### Responses

#### `200`

Throughput samples, oldest first. Each element is a `ThroughputSample`:

| Field       | Type            | Description                   |
| ----------- | --------------- | ----------------------------- |
| `timestamp` | integer (int64) | Unix **seconds**              |
| `fills`     | integer (int64) | Fills recorded in that second |

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/stats/history'
```


# Tickers

24h price statistics. A ticker bundles the rolling 24-hour high/low/open/close, the current best bid and ask with their sizes, volume in both base and quote terms, and the engine-derived mark price for one market — or for every market in a single call.

This tag is **CCXT-compatible** (`x-ccxt: true`): the response objects match CCXT's unified ticker structure, so a CCXT client can consume them without a custom parser.

Full field-by-field definitions for every schema named on this page live in the [Schema Reference](/exchange-api/exchange-api/schemas).

## Base URLs and the two path surfaces

Both operations on this page are served twice, and the two surfaces have **different base URLs** — every `/api/v1` path carries its own path-level server override in the contract:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers and the same authentication and rate-limit layers back both mounts, the bodies are identical, and the bare root paths are the legacy form kept for compatibility. Prefer `/api/v1` for new integrations. The contract marks neither variant `deprecated` — both are live. Whichever you choose, **sign the path the service sees** — include the `/api/v1` prefix, but not the `/api/exchange` gateway mount, which is stripped before your signature is verified.

| Canonical (`/api/v1`)                    | Legacy alias                      | Operation                   | `operationId`                     | CCXT method    |
| ---------------------------------------- | --------------------------------- | --------------------------- | --------------------------------- | -------------- |
| `GET /api/v1/markets/{market_id}/ticker` | `GET /markets/{market_id}/ticker` | Get ticker for a market     | `fetchTickerV1` / `fetchTicker`   | `fetchTicker`  |
| `GET /api/v1/tickers`                    | `GET /tickers`                    | Get tickers for all markets | `fetchTickersV1` / `fetchTickers` | `fetchTickers` |

The `x-ccxt-method` binding is declared on the unversioned operation only; the versioned twin returns the identical CCXT-shaped body.

## Numbers, not decimal strings

Native Nexus surfaces serialize prices and quantities as arbitrary-precision decimal **strings**. The CCXT-compatible ticker uses JSON **numbers** instead, because that is what the CCXT unified structure specifies — so ticker prices and volumes are subject to double-precision rounding.

If you need a lossless price, read a native endpoint such as `GET /api/v1/markets/{market_id}/mark-price`, which returns a decimal string.

## Rate limiting

Both operations can return `429`, carrying `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers with a body of `{"code": "RateLimitExceeded", "tier": "Pro"}`.

## `GET /markets/{market_id}/ticker`

Get ticker for a market.

Legacy alias of `GET /api/v1/markets/{market_id}/ticker`. Prefer the versioned path for new integrations.

CCXT method: `fetchTicker`.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Current ticker for the market. A `Ticker` — CCXT-compatible ticker with 24h statistics:

| Field         | Type               | Description                                                                                                                                                       |
| ------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbol`      | string             | Market identifier                                                                                                                                                 |
| `timestamp`   | integer (Unix ms)  | When the ticker was produced                                                                                                                                      |
| `datetime`    | string (date-time) | ISO 8601 rendering of `timestamp`                                                                                                                                 |
| `high`        | number or null     | 24h high                                                                                                                                                          |
| `low`         | number or null     | 24h low                                                                                                                                                           |
| `bid`         | number or null     | Best bid price                                                                                                                                                    |
| `bidVolume`   | number or null     | Size at the best bid                                                                                                                                              |
| `ask`         | number or null     | Best ask price                                                                                                                                                    |
| `askVolume`   | number or null     | Size at the best ask                                                                                                                                              |
| `open`        | number or null     | 24h open                                                                                                                                                          |
| `close`       | number or null     | 24h close                                                                                                                                                         |
| `last`        | number or null     | Last trade price                                                                                                                                                  |
| `change`      | number or null     | Absolute change over the window                                                                                                                                   |
| `percentage`  | number or null     | Percentage change over the window                                                                                                                                 |
| `baseVolume`  | number or null     | 24h volume in base units                                                                                                                                          |
| `quoteVolume` | number or null     | 24h volume in quote units                                                                                                                                         |
| `markPrice`   | number or null     | Engine-derived mark price (oracle + premium-index), falling back to the last trade until the first mark-price poll lands. The raw last trade is carried by `last` |
| `indexPrice`  | number or null     | Index price. Typed nullable, and the contract's own example returns `null`                                                                                        |
| `info`        | object             | Raw venue payload passed through for CCXT consumers                                                                                                               |

Example body:

```json
{
  "symbol": "BTC-USDX-PERP",
  "timestamp": 1776033911836,
  "datetime": "2026-04-12T22:45:11.836Z",
  "high": 50500,
  "low": 49200,
  "bid": 50100.5,
  "bidVolume": 1.4,
  "ask": 50102,
  "askVolume": 0.8,
  "open": 49800,
  "close": 50100,
  "last": 50100,
  "change": 300,
  "percentage": 0.602,
  "baseVolume": 1250.5,
  "quoteVolume": 62525000,
  "markPrice": 50101.5,
  "indexPrice": null,
  "info": {}
}
```

#### `404`

Market not found.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/ticker'
```

## `GET /tickers`

Get tickers for all markets.

Legacy alias of `GET /api/v1/tickers`. Prefer the versioned path for new integrations.

CCXT method: `fetchTickers`.

**Authentication:** None — public read.

### Responses

#### `200`

Object keyed by `market_id`. Every value is a `Ticker` with the same fields as `GET /markets/{market_id}/ticker`:

| Field         | Type     | Description                                                             |
| ------------- | -------- | ----------------------------------------------------------------------- |
| `<market_id>` | `Ticker` | One entry per market, keyed by market identifier (e.g. `BTC-USDX-PERP`) |

`Ticker` fields:

| Field         | Type               | Description                                                                                                              |
| ------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `symbol`      | string             | Market identifier                                                                                                        |
| `timestamp`   | integer (Unix ms)  | When the ticker was produced                                                                                             |
| `datetime`    | string (date-time) | ISO 8601 rendering of `timestamp`                                                                                        |
| `high`        | number or null     | 24h high                                                                                                                 |
| `low`         | number or null     | 24h low                                                                                                                  |
| `bid`         | number or null     | Best bid price                                                                                                           |
| `bidVolume`   | number or null     | Size at the best bid                                                                                                     |
| `ask`         | number or null     | Best ask price                                                                                                           |
| `askVolume`   | number or null     | Size at the best ask                                                                                                     |
| `open`        | number or null     | 24h open                                                                                                                 |
| `close`       | number or null     | 24h close                                                                                                                |
| `last`        | number or null     | Last trade price                                                                                                         |
| `change`      | number or null     | Absolute change over the window                                                                                          |
| `percentage`  | number or null     | Percentage change over the window                                                                                        |
| `baseVolume`  | number or null     | 24h volume in base units                                                                                                 |
| `quoteVolume` | number or null     | 24h volume in quote units                                                                                                |
| `markPrice`   | number or null     | Engine-derived mark price (oracle + premium-index), falling back to the last trade until the first mark-price poll lands |
| `indexPrice`  | number or null     | Index price                                                                                                              |
| `info`        | object             | Raw venue payload passed through for CCXT consumers                                                                      |

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/tickers'
```

## `GET /api/v1/markets/{market_id}/ticker`

Get ticker for a market.

Canonical versioned form. The bare `GET /markets/{market_id}/ticker` is a legacy alias that returns the same body. Equivalent to CCXT's `fetchTicker`.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Current ticker for the market (`Ticker`):

| Field         | Type               | Description                                                                                                                                                       |
| ------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbol`      | string             | Market identifier                                                                                                                                                 |
| `timestamp`   | integer (Unix ms)  | When the ticker was produced                                                                                                                                      |
| `datetime`    | string (date-time) | ISO 8601 rendering of `timestamp`                                                                                                                                 |
| `high`        | number or null     | 24h high                                                                                                                                                          |
| `low`         | number or null     | 24h low                                                                                                                                                           |
| `bid`         | number or null     | Best bid price                                                                                                                                                    |
| `bidVolume`   | number or null     | Size at the best bid                                                                                                                                              |
| `ask`         | number or null     | Best ask price                                                                                                                                                    |
| `askVolume`   | number or null     | Size at the best ask                                                                                                                                              |
| `open`        | number or null     | 24h open                                                                                                                                                          |
| `close`       | number or null     | 24h close                                                                                                                                                         |
| `last`        | number or null     | Last trade price                                                                                                                                                  |
| `change`      | number or null     | Absolute change over the window                                                                                                                                   |
| `percentage`  | number or null     | Percentage change over the window                                                                                                                                 |
| `baseVolume`  | number or null     | 24h volume in base units                                                                                                                                          |
| `quoteVolume` | number or null     | 24h volume in quote units                                                                                                                                         |
| `markPrice`   | number or null     | Engine-derived mark price (oracle + premium-index), falling back to the last trade until the first mark-price poll lands. The raw last trade is carried by `last` |
| `indexPrice`  | number or null     | Index price. Typed nullable, and the contract's own example returns `null`                                                                                        |
| `info`        | object             | Raw venue payload passed through for CCXT consumers                                                                                                               |

Example body:

```json
{
  "symbol": "BTC-USDX-PERP",
  "timestamp": 1776033911836,
  "datetime": "2026-04-12T22:45:11.836Z",
  "high": 50500,
  "low": 49200,
  "bid": 50100.5,
  "bidVolume": 1.4,
  "ask": 50102,
  "askVolume": 0.8,
  "open": 49800,
  "close": 50100,
  "last": 50100,
  "change": 300,
  "percentage": 0.602,
  "baseVolume": 1250.5,
  "quoteVolume": 62525000,
  "markPrice": 50101.5,
  "indexPrice": null,
  "info": {}
}
```

#### `404`

Market not found.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/ticker'
```

## `GET /api/v1/tickers`

Get tickers for all markets.

Canonical versioned form. The bare `GET /tickers` is a legacy alias that returns the same body. Equivalent to CCXT's `fetchTickers`.

**Authentication:** None — public read.

### Responses

#### `200`

Object keyed by `market_id`, where every value is a `Ticker`:

| Field         | Type     | Description                                                             |
| ------------- | -------- | ----------------------------------------------------------------------- |
| `<market_id>` | `Ticker` | One entry per market, keyed by market identifier (e.g. `BTC-USDX-PERP`) |

`Ticker` fields are as listed under `GET /api/v1/markets/{market_id}/ticker` above: `symbol`, `timestamp`, `datetime`, `high`, `low`, `bid`, `bidVolume`, `ask`, `askVolume`, `open`, `close`, `last`, `change`, `percentage`, `baseVolume`, `quoteVolume`, `markPrice`, `indexPrice`, `info`.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/tickers'
```


# Order Book

Live order book depth. A single call returns the current aggregated bid and ask ladders for one market, each level expressed as a `[price, amount]` pair, together with a monotonic `nonce` so a consumer can tell one snapshot from the next.

This tag is **CCXT-compatible** (`x-ccxt: true`): the response matches CCXT's unified order-book structure, so a CCXT client can consume it without a custom parser.

For continuous depth updates, stream the `book:{market_id}` channel over the WebSocket surface rather than polling this endpoint.

Full field-by-field definitions for every schema named on this page live in the [Schema Reference](/exchange-api/exchange-api/schemas).

## Base URLs and the two path surfaces

The operation on this page is served twice, and the two surfaces have **different base URLs** — every `/api/v1` path carries its own path-level server override in the contract:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers and the same authentication and rate-limit layers back both mounts, the bodies are identical, and the bare root paths are the legacy form kept for compatibility. Prefer `/api/v1` for new integrations. The contract marks neither variant `deprecated` — both are live. Whichever you choose, **sign the path the service sees** — include the `/api/v1` prefix, but not the `/api/exchange` gateway mount, which is stripped before your signature is verified.

| Canonical (`/api/v1`)                       | Legacy alias                         | Operation      | `operationId`                         | CCXT method      |
| ------------------------------------------- | ------------------------------------ | -------------- | ------------------------------------- | ---------------- |
| `GET /api/v1/markets/{market_id}/orderbook` | `GET /markets/{market_id}/orderbook` | Get order book | `fetchOrderBookV1` / `fetchOrderBook` | `fetchOrderBook` |

The `x-ccxt-method` binding is declared on the unversioned operation only; the versioned twin returns the identical CCXT-shaped body.

## Numbers, not decimal strings

Native Nexus surfaces serialize prices and quantities as arbitrary-precision decimal **strings**. The CCXT-compatible order book uses JSON **numbers** for both price and amount instead, because that is what the CCXT unified structure specifies — so book levels are subject to double-precision rounding.

If you need a lossless price, read a native endpoint such as `GET /api/v1/markets/{market_id}/mark-price`, which returns a decimal string.

## Rate limiting

The operation can return `429`, carrying `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers with a body of `{"code": "RateLimitExceeded", "tier": "Pro"}`.

## `GET /markets/{market_id}/orderbook`

Get order book.

Legacy alias of `GET /api/v1/markets/{market_id}/orderbook`. Prefer the versioned path for new integrations.

CCXT method: `fetchOrderBook`.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Current order book for the market. An `OrderBook` — CCXT-compatible order book where bids and asks are `[price, amount]` arrays:

| Field       | Type                        | Description                                                         |
| ----------- | --------------------------- | ------------------------------------------------------------------- |
| `symbol`    | string                      | Market identifier                                                   |
| `bids`      | array of `[number, number]` | Bid levels as `[price, amount]`, best bid first                     |
| `asks`      | array of `[number, number]` | Ask levels as `[price, amount]`, best ask first                     |
| `timestamp` | integer (Unix ms)           | When the snapshot was taken                                         |
| `datetime`  | string (date-time)          | ISO 8601 rendering of `timestamp`                                   |
| `nonce`     | integer (int64)             | Snapshot sequence number; use it to order or de-duplicate snapshots |

Example body:

```json
{
  "symbol": "BTC-USDX-PERP",
  "bids": [
    [50100.5, 1.4],
    [50099, 2.1]
  ],
  "asks": [
    [50102, 0.8],
    [50103.5, 1.2]
  ],
  "timestamp": 1776033930898,
  "datetime": "2026-04-12T22:45:30.898Z",
  "nonce": 1651
}
```

#### `404`

Market not found.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/orderbook'
```

## `GET /api/v1/markets/{market_id}/orderbook`

Get order book.

Canonical versioned form. The bare `GET /markets/{market_id}/orderbook` is a legacy alias that returns the same body. Equivalent to CCXT's `fetchOrderBook`.

**Authentication:** None — public read.

### Parameters

| Name        | In   | Type   | Required | Description                                               |
| ----------- | ---- | ------ | -------- | --------------------------------------------------------- |
| `market_id` | path | string | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |

### Responses

#### `200`

Current order book for the market (`OrderBook`):

| Field       | Type                        | Description                                                         |
| ----------- | --------------------------- | ------------------------------------------------------------------- |
| `symbol`    | string                      | Market identifier                                                   |
| `bids`      | array of `[number, number]` | Bid levels as `[price, amount]`, best bid first                     |
| `asks`      | array of `[number, number]` | Ask levels as `[price, amount]`, best ask first                     |
| `timestamp` | integer (Unix ms)           | When the snapshot was taken                                         |
| `datetime`  | string (date-time)          | ISO 8601 rendering of `timestamp`                                   |
| `nonce`     | integer (int64)             | Snapshot sequence number; use it to order or de-duplicate snapshots |

Example body:

```json
{
  "symbol": "BTC-USDX-PERP",
  "bids": [
    [50100.5, 1.4],
    [50099, 2.1]
  ],
  "asks": [
    [50102, 0.8],
    [50103.5, 1.2]
  ],
  "timestamp": 1776033930898,
  "datetime": "2026-04-12T22:45:30.898Z",
  "nonce": 1651
}
```

#### `404`

Market not found.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/orderbook'
```


# Trades

Recent trade history. Returns the public tape for one market — every fill, with price, size, notional cost, aggressor side, and a flag marking trades that came from a liquidation. Results are cursor-paginated so you can walk back through the retained window.

This tag is **CCXT-compatible** (`x-ccxt: true`): the response objects match CCXT's unified trade structure, so a CCXT client can consume them without a custom parser.

Full field-by-field definitions for every schema named on this page live in the [Schema Reference](/exchange-api/exchange-api/schemas).

## Base URLs and the two path surfaces

The operation on this page is served twice, and the two surfaces have **different base URLs** — every `/api/v1` path carries its own path-level server override in the contract:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers and the same authentication and rate-limit layers back both mounts, the bodies are identical, and the bare root paths are the legacy form kept for compatibility. Prefer `/api/v1` for new integrations. The contract marks neither variant `deprecated` — both are live. Whichever you choose, **sign the path the service sees** — include the `/api/v1` prefix, but not the `/api/exchange` gateway mount, which is stripped before your signature is verified.

| Canonical (`/api/v1`)                    | Legacy alias                      | Operation         | `operationId`                   | CCXT method   |
| ---------------------------------------- | --------------------------------- | ----------------- | ------------------------------- | ------------- |
| `GET /api/v1/markets/{market_id}/trades` | `GET /markets/{market_id}/trades` | Get recent trades | `fetchTradesV1` / `fetchTrades` | `fetchTrades` |

The `x-ccxt-method` binding is declared on the unversioned operation only; the versioned twin returns the identical CCXT-shaped body.

## Pagination

The response body is always a bare array — pagination state rides only in the `X-Next-Cursor` response header. That header is present only when more results exist beyond the current response, and absent on the last page. Pass its value back as the `cursor` query parameter to fetch the next page.

Treat the token as opaque: its format is not part of the contract and may change. Cursors do not expire. A malformed (unparseable) cursor is **not** an error — the server serves the first page. A well-formed cursor whose exact position has since been evicted from the retained window is **not** reset to the first page either: pagination resumes at the nearest surviving boundary, so a resumed response is a continuation, not a fresh first page.

## Numbers, not decimal strings

Native Nexus surfaces serialize prices and quantities as arbitrary-precision decimal **strings**. The CCXT-compatible trade record uses JSON **numbers** for `price`, `amount`, and `cost` instead, because that is what the CCXT unified structure specifies — so trade prices and sizes are subject to double-precision rounding.

If you need a lossless price, read a native endpoint such as `GET /api/v1/markets/{market_id}/mark-price`, which returns a decimal string.

## Rate limiting

The operation can return `429`, carrying `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers with a body of `{"code": "RateLimitExceeded", "tier": "Pro"}`.

## `GET /markets/{market_id}/trades`

Get recent trades.

Legacy alias of `GET /api/v1/markets/{market_id}/trades`. Prefer the versioned path for new integrations.

CCXT method: `fetchTrades`.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                                | Required | Description                                                                                                                                                                            |
| ----------- | ----- | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `market_id` | path  | string                              | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`)                                                                                                                              |
| `limit`     | query | integer (default `100`, max `1000`) | No       | Number of trades to return                                                                                                                                                             |
| `cursor`    | query | string                              | No       | Opaque pagination cursor returned in the previous response's `X-Next-Cursor` header. Omit to fetch the first page. See **Pagination** above for eviction and malformed-token behaviour |

### Responses

#### `200`

Recent trades for the market. Each element is a `Trade` — a CCXT-compatible trade record:

| Field            | Type                     | Description                                         |
| ---------------- | ------------------------ | --------------------------------------------------- |
| `id`             | string (uuid)            | Trade identifier                                    |
| `symbol`         | string                   | Market identifier                                   |
| `price`          | number                   | Execution price                                     |
| `amount`         | number                   | Executed quantity in base units                     |
| `cost`           | number                   | Notional value of the trade in quote units          |
| `side`           | string (`buy` \| `sell`) | Aggressor side                                      |
| `timestamp`      | integer (Unix ms)        | Execution time                                      |
| `datetime`       | string (date-time)       | ISO 8601 rendering of `timestamp`                   |
| `takerOrMaker`   | string or null           | Taker/maker role, when known                        |
| `is_liquidation` | boolean                  | Whether the trade came from a liquidation           |
| `info`           | object                   | Raw venue payload passed through for CCXT consumers |

Response headers:

| Header          | Type   | Description                                                                                                         |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `X-Next-Cursor` | string | Opaque cursor for the next page. Present only when more results exist beyond this response; absent on the last page |

Example body:

```json
[
  {
    "id": "cf72c7f3-4c59-4d3c-85c8-99d92bc1fda7",
    "symbol": "BTC-USDX-PERP",
    "price": 50100.5,
    "amount": 0.033,
    "cost": 1653.32,
    "side": "buy",
    "timestamp": 1776033942331,
    "datetime": "2026-04-12T22:45:42.331Z",
    "takerOrMaker": null,
    "is_liquidation": false,
    "info": {}
  }
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl -i 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/trades?limit=100'
```

Use `-i` (or `-D -`) so you can read the `X-Next-Cursor` header, then pass it back:

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/trades?limit=100&cursor=<X-Next-Cursor>'
```

## `GET /api/v1/markets/{market_id}/trades`

Get recent trades.

Canonical versioned form. The bare `GET /markets/{market_id}/trades` is a legacy alias that returns the same body. Equivalent to CCXT's `fetchTrades`.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                                | Required | Description                                                                                                                                                                            |
| ----------- | ----- | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `market_id` | path  | string                              | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`)                                                                                                                              |
| `limit`     | query | integer (default `100`, max `1000`) | No       | Number of trades to return                                                                                                                                                             |
| `cursor`    | query | string                              | No       | Opaque pagination cursor returned in the previous response's `X-Next-Cursor` header. Omit to fetch the first page. See **Pagination** above for eviction and malformed-token behaviour |

### Responses

#### `200`

Recent trades for the market. Each element is a `Trade`:

| Field            | Type                     | Description                                         |
| ---------------- | ------------------------ | --------------------------------------------------- |
| `id`             | string (uuid)            | Trade identifier                                    |
| `symbol`         | string                   | Market identifier                                   |
| `price`          | number                   | Execution price                                     |
| `amount`         | number                   | Executed quantity in base units                     |
| `cost`           | number                   | Notional value of the trade in quote units          |
| `side`           | string (`buy` \| `sell`) | Aggressor side                                      |
| `timestamp`      | integer (Unix ms)        | Execution time                                      |
| `datetime`       | string (date-time)       | ISO 8601 rendering of `timestamp`                   |
| `takerOrMaker`   | string or null           | Taker/maker role, when known                        |
| `is_liquidation` | boolean                  | Whether the trade came from a liquidation           |
| `info`           | object                   | Raw venue payload passed through for CCXT consumers |

Response headers:

| Header          | Type   | Description                                                                                                         |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `X-Next-Cursor` | string | Opaque cursor for the next page. Present only when more results exist beyond this response; absent on the last page |

Example body:

```json
[
  {
    "id": "cf72c7f3-4c59-4d3c-85c8-99d92bc1fda7",
    "symbol": "BTC-USDX-PERP",
    "price": 50100.5,
    "amount": 0.033,
    "cost": 1653.32,
    "side": "buy",
    "timestamp": 1776033942331,
    "datetime": "2026-04-12T22:45:42.331Z",
    "takerOrMaker": null,
    "is_liquidation": false,
    "info": {}
  }
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl -i 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/trades?limit=100'
```


# Candles

OHLCV candlestick data. Returns aggregated bars for one market at a chosen timeframe, in CCXT's positional array form rather than as named objects — one array per bar, `[timestamp, open, high, low, close, volume]`.

This tag is **CCXT-compatible** (`x-ccxt: true`): the response matches CCXT's unified OHLCV structure, so a CCXT client can consume it without a custom parser.

Full field-by-field definitions for every schema named on this page live in the [Schema Reference](/exchange-api/exchange-api/schemas).

## Base URLs and the two path surfaces

The operation on this page is served twice, and the two surfaces have **different base URLs** — every `/api/v1` path carries its own path-level server override in the contract:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers and the same authentication and rate-limit layers back both mounts, the bodies are identical, and the bare root paths are the legacy form kept for compatibility. Prefer `/api/v1` for new integrations. The contract marks neither variant `deprecated` — both are live. Whichever you choose, **sign the path the service sees** — include the `/api/v1` prefix, but not the `/api/exchange` gateway mount, which is stripped before your signature is verified.

| Canonical (`/api/v1`)                     | Legacy alias                       | Operation         | `operationId`                 | CCXT method  |
| ----------------------------------------- | ---------------------------------- | ----------------- | ----------------------------- | ------------ |
| `GET /api/v1/markets/{market_id}/candles` | `GET /markets/{market_id}/candles` | Get OHLCV candles | `fetchOHLCVV1` / `fetchOHLCV` | `fetchOHLCV` |

The `x-ccxt-method` binding is declared on the unversioned operation only; the versioned twin returns the identical CCXT-shaped body.

## Timeframes

The contract's `timeframe` enum is `1s`, `1m` (the default), `5m`, and `1h`. Any other value is outside the enum.

## Numbers, not decimal strings

Native Nexus surfaces serialize prices and quantities as arbitrary-precision decimal **strings**. The CCXT-compatible OHLCV tuple uses JSON **numbers** for the four prices and the volume instead, because that is what the CCXT unified structure specifies — so bar values are subject to double-precision rounding.

If you need a lossless price, read a native endpoint such as `GET /api/v1/markets/{market_id}/mark-price`, which returns a decimal string.

## Rate limiting

The operation can return `429`, carrying `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers with a body of `{"code": "RateLimitExceeded", "tier": "Pro"}`.

## `GET /markets/{market_id}/candles`

Get OHLCV candles.

Returns candlestick data as arrays: `[timestamp, open, high, low, close, volume]`.

Legacy alias of `GET /api/v1/markets/{market_id}/candles`. Prefer the versioned path for new integrations.

CCXT method: `fetchOHLCV`.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                                                    | Required | Description                                               |
| ----------- | ----- | ------------------------------------------------------- | -------- | --------------------------------------------------------- |
| `market_id` | path  | string                                                  | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |
| `timeframe` | query | string enum `1s` \| `1m` \| `5m` \| `1h` (default `1m`) | No       | Bar interval                                              |
| `limit`     | query | integer (default `200`, max `1000`)                     | No       | Number of bars to return                                  |

### Responses

#### `200`

OHLCV candles for the market: an array of fixed-position arrays. Each inner array has six elements, in this order:

| Position | Field     | Type    | Description                      |
| -------- | --------- | ------- | -------------------------------- |
| `0`      | timestamp | integer | Bar open time, Unix milliseconds |
| `1`      | open      | number  | Open price                       |
| `2`      | high      | number  | High price                       |
| `3`      | low       | number  | Low price                        |
| `4`      | close     | number  | Close price                      |
| `5`      | volume    | number  | Volume over the bar              |

Example body:

```json
[
  [1776033900000, 48062, 51903, 44992, 51903, 27.123]
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/candles?timeframe=1m&limit=200'
```

## `GET /api/v1/markets/{market_id}/candles`

Get OHLCV candles.

Returns candlestick data as arrays: `[timestamp, open, high, low, close, volume]`.

Canonical versioned form. The bare `GET /markets/{market_id}/candles` is a legacy alias that returns the same body. Equivalent to CCXT's `fetchOHLCV`.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                                                    | Required | Description                                               |
| ----------- | ----- | ------------------------------------------------------- | -------- | --------------------------------------------------------- |
| `market_id` | path  | string                                                  | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |
| `timeframe` | query | string enum `1s` \| `1m` \| `5m` \| `1h` (default `1m`) | No       | Bar interval                                              |
| `limit`     | query | integer (default `200`, max `1000`)                     | No       | Number of bars to return                                  |

### Responses

#### `200`

OHLCV candles for the market: an array of fixed-position arrays. Each inner array has six elements, in this order:

| Position | Field     | Type    | Description                      |
| -------- | --------- | ------- | -------------------------------- |
| `0`      | timestamp | integer | Bar open time, Unix milliseconds |
| `1`      | open      | number  | Open price                       |
| `2`      | high      | number  | High price                       |
| `3`      | low       | number  | Low price                        |
| `4`      | close     | number  | Close price                      |
| `5`      | volume    | number  | Volume over the bar              |

Example body:

```json
[
  [1776033900000, 48062, 51903, 44992, 51903, 27.123]
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/candles?timeframe=1m&limit=200'
```


# Funding

Funding rate history. Three things live under this tag: the per-market funding rate series, the denser per-tick premium-index sample series behind it, and the authenticated per-account record of funding actually paid or received.

Full field-by-field definitions for every schema named on this page live in the [Schema Reference](/exchange-api/exchange-api/schemas).

## Base URLs and the two path surfaces

The two market-level reads on this page are served twice, and the two surfaces have **different base URLs** — every `/api/v1` path carries its own path-level server override in the contract:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers and the same authentication and rate-limit layers back both mounts, the bodies are identical, and the bare root paths are the legacy form kept for compatibility. Prefer `/api/v1` for new integrations. The contract marks neither variant `deprecated` — both are live. Whichever you choose, **sign the path the service sees** — include the `/api/v1` prefix, but not the `/api/exchange` gateway mount, which is stripped before your signature is verified.

| Canonical (`/api/v1`)                             | Legacy alias                               | Operation                     | `operationId`                                   |
| ------------------------------------------------- | ------------------------------------------ | ----------------------------- | ----------------------------------------------- |
| `GET /api/v1/markets/{market_id}/funding`         | `GET /markets/{market_id}/funding`         | Get funding rate history      | `fetchFundingV1` / `fetchFunding`               |
| `GET /api/v1/markets/{market_id}/funding-samples` | `GET /markets/{market_id}/funding-samples` | Funding premium-index samples | `fetchFundingSamplesV1` / `fetchFundingSamples` |

The account-level `GET /funding` exists only at the bare gateway path — the contract declares no `/api/v1` twin for it.

## Decimal values are strings

Every rate, price, and amount in this group — `funding_rate`, `premium_index`, `mark_price`, `oracle_price`, `amount`, `position_size` — is an arbitrary-precision decimal serialized as a JSON **string**, lossless on the wire. **Parse them with a decimal type, never a float.**

Funding rates are very small numbers (`"0.000000016"` in the contract's own example), which is exactly where float parsing loses precision.

## Error responses

Where the contract declares it, a rate-limited response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers with a body of `{"code": "RateLimitExceeded", "tier": "Pro"}`. Note that `GET /markets/{market_id}/funding-samples` and `GET /funding` declare no `429` response in the contract, even though both are subject to the same tiered rate limiting.

`GET /funding` can return `401`. Every `401` returns the same opaque body — `{"code": "unauthorized"}` — regardless of cause. See [Authentication](/exchange-api/exchange-api/authentication) for the HMAC signing scheme.

## `GET /markets/{market_id}/funding`

Get funding rate history.

Legacy alias of `GET /api/v1/markets/{market_id}/funding`. Prefer the versioned path for new integrations.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                                | Required | Description                                               |
| ----------- | ----- | ----------------------------------- | -------- | --------------------------------------------------------- |
| `market_id` | path  | string                              | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |
| `limit`     | query | integer (default `300`, max `1000`) | No       | Maximum samples to return                                 |

### Responses

#### `200`

Funding rate history for the market. Each element is a `FundingSample`:

| Field           | Type              | Description                                                           |
| --------------- | ----------------- | --------------------------------------------------------------------- |
| `timestamp`     | integer (Unix ms) | When the sample was taken                                             |
| `funding_rate`  | string (decimal)  | Funding rate at that sample                                           |
| `premium_index` | string (decimal)  | Premium index — the mark-versus-oracle basis the rate is derived from |
| `mark_price`    | string (decimal)  | Mark price at that sample                                             |
| `oracle_price`  | string (decimal)  | Oracle price at that sample                                           |

Example body:

```json
[
  {
    "timestamp": 1776033960368,
    "funding_rate": "0.000000016",
    "premium_index": "0.004192",
    "mark_price": "49756.75",
    "oracle_price": "49549.0"
  }
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/funding?limit=300'
```

## `GET /markets/{market_id}/funding-samples`

Funding premium-index samples.

Dense per-tick premium-index samples (60s cadence, up to 480 points = 8h). Public — no authentication required.

Legacy alias of `GET /api/v1/markets/{market_id}/funding-samples`. Prefer the versioned path for new integrations.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                               | Required | Description                               |
| ----------- | ----- | ---------------------------------- | -------- | ----------------------------------------- |
| `market_id` | path  | string                             | Yes      | Market identifier                         |
| `limit`     | query | integer (default `480`, max `480`) | No       | Maximum samples to return (capped at 480) |

### Responses

#### `200`

Premium-index samples. Each element is a `FundingSample` — the same object the funding rate history returns, at a denser cadence:

| Field           | Type              | Description                                  |
| --------------- | ----------------- | -------------------------------------------- |
| `timestamp`     | integer (Unix ms) | When the sample was taken                    |
| `funding_rate`  | string (decimal)  | Funding rate at that sample                  |
| `premium_index` | string (decimal)  | Premium index — the mark-versus-oracle basis |
| `mark_price`    | string (decimal)  | Mark price at that sample                    |
| `oracle_price`  | string (decimal)  | Oracle price at that sample                  |

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/markets/BTC-USDX-PERP/funding-samples?limit=480'
```

## `GET /funding`

Account funding payments.

Funding payment history for the authenticated account, newest first.

**Authentication:** `hmacAuth` — HMAC API key (`X-API-Key`, `X-Timestamp`, `X-Signature`).

### Parameters

| Name    | In    | Type                                | Required | Description               |
| ------- | ----- | ----------------------------------- | -------- | ------------------------- |
| `limit` | query | integer (default `100`, max `1000`) | No       | Maximum records to return |

### Responses

#### `200`

Funding payments, newest first. Each element is an `AccountFunding` — a funding payment for the account:

| Field           | Type                          | Description                           |
| --------------- | ----------------------------- | ------------------------------------- |
| `market_id`     | string                        | Market the payment relates to         |
| `amount`        | string (decimal)              | Signed funding amount                 |
| `direction`     | string (`paid` \| `received`) | Whether the account paid or received  |
| `funding_rate`  | string (decimal)              | Rate applied                          |
| `position_size` | string (decimal)              | Position size the rate was applied to |
| `timestamp`     | integer (Unix ms)             | When the payment was applied          |

#### `401`

Authentication failed. All `401` responses return the same opaque body to prevent information leakage.

### Example

```bash
TIMESTAMP=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(echo -n "" | shasum -a 256 | cut -d' ' -f1)
CANONICAL="$TIMESTAMP\nGET\n/funding\nlimit=100\n$BODY_HASH"
SIGNATURE=$(echo -ne "$CANONICAL" | \
  openssl dgst -sha256 -mac HMAC -macopt "hexkey:$NEXUS_API_SECRET" -hex | awk '{print $NF}')

curl 'https://exchange.nexus.xyz/api/exchange/funding?limit=100' \
  -H "X-API-Key: $NEXUS_API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

## `GET /api/v1/markets/{market_id}/funding`

Get funding rate history.

Canonical versioned form. The bare `GET /markets/{market_id}/funding` is a legacy alias that returns the same body.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                                | Required | Description                                               |
| ----------- | ----- | ----------------------------------- | -------- | --------------------------------------------------------- |
| `market_id` | path  | string                              | Yes      | Market identifier (e.g. `BTC-USDX-PERP`, `ETH-USDX-PERP`) |
| `limit`     | query | integer (default `300`, max `1000`) | No       | Maximum samples to return                                 |

### Responses

#### `200`

Funding rate history for the market. Each element is a `FundingSample`:

| Field           | Type              | Description                                                           |
| --------------- | ----------------- | --------------------------------------------------------------------- |
| `timestamp`     | integer (Unix ms) | When the sample was taken                                             |
| `funding_rate`  | string (decimal)  | Funding rate at that sample                                           |
| `premium_index` | string (decimal)  | Premium index — the mark-versus-oracle basis the rate is derived from |
| `mark_price`    | string (decimal)  | Mark price at that sample                                             |
| `oracle_price`  | string (decimal)  | Oracle price at that sample                                           |

Example body:

```json
[
  {
    "timestamp": 1776033960368,
    "funding_rate": "0.000000016",
    "premium_index": "0.004192",
    "mark_price": "49756.75",
    "oracle_price": "49549.0"
  }
]
```

#### `429`

Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/funding?limit=300'
```

## `GET /api/v1/markets/{market_id}/funding-samples`

Funding premium-index samples.

Dense per-tick premium-index samples (60s cadence, up to 480 points = 8h). Public — no authentication required.

Canonical versioned form. The bare `GET /markets/{market_id}/funding-samples` is a legacy alias that returns the same body.

**Authentication:** None — public read.

### Parameters

| Name        | In    | Type                               | Required | Description                               |
| ----------- | ----- | ---------------------------------- | -------- | ----------------------------------------- |
| `market_id` | path  | string                             | Yes      | Market identifier                         |
| `limit`     | query | integer (default `480`, max `480`) | No       | Maximum samples to return (capped at 480) |

### Responses

#### `200`

Premium-index samples. Each element is a `FundingSample`:

| Field           | Type              | Description                                  |
| --------------- | ----------------- | -------------------------------------------- |
| `timestamp`     | integer (Unix ms) | When the sample was taken                    |
| `funding_rate`  | string (decimal)  | Funding rate at that sample                  |
| `premium_index` | string (decimal)  | Premium index — the mark-versus-oracle basis |
| `mark_price`    | string (decimal)  | Mark price at that sample                    |
| `oracle_price`  | string (decimal)  | Oracle price at that sample                  |

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/markets/BTC-USDX-PERP/funding-samples?limit=480'
```


# Trading

Order submission and cancellation. Twelve operations: submit, batch-submit, preview, amend, cancel one, and cancel all — each served on two path surfaces.

Every operation on this page requires HMAC API key authentication (`hmacAuth`). See [Authentication](/exchange-api/exchange-api/authentication) for the signing scheme.

## Base URLs and the two path surfaces

Each of the six trading operations is served twice, and the two surfaces have **different base URLs**:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers, the same authentication layer, and the same rate-limit layer back both mounts, and the bare root paths are the legacy form kept for compatibility while clients migrate off the gateway proxy. Prefer `/api/v1` for new integrations.

The contract does **not** mark either variant `deprecated`, so both are live today and behave identically. Whichever you choose, **sign the path as written in the contract** — the HMAC canonical string includes the `/api/v1` prefix but **not** the `/api/exchange` gateway mount, which is stripped before your signature is verified (see [Authentication](/exchange-api/exchange-api/authentication)).

| Canonical (`/api/v1`)              | Legacy alias                | Operation              | `operationId`                               | CCXT method       |
| ---------------------------------- | --------------------------- | ---------------------- | ------------------------------------------- | ----------------- |
| `POST /api/v1/orders`              | `POST /orders`              | Submit an order        | `createOrderV1` / `createOrder`             | `createOrder`     |
| `POST /api/v1/orders/batch`        | `POST /orders/batch`        | Submit multiple orders | `createOrdersBatchV1` / `createOrdersBatch` | —                 |
| `POST /api/v1/orders/preview`      | `POST /orders/preview`      | Preview an order       | `previewOrderV1` / `previewOrder`           | —                 |
| `PATCH /api/v1/orders/{order_id}`  | `PATCH /orders/{order_id}`  | Amend an order         | `editOrderV1` / `editOrder`                 | `editOrder`       |
| `DELETE /api/v1/orders/{order_id}` | `DELETE /orders/{order_id}` | Cancel an order        | `cancelOrderV1` / `cancelOrder`             | `cancelOrder`     |
| `DELETE /api/v1/orders`            | `DELETE /orders`            | Cancel all orders      | `cancelAllOrdersV1` / `cancelAllOrders`     | `cancelAllOrders` |

The `x-ccxt-method` bindings are declared on the unversioned operations only; the versioned twins take the same request body and return the same response body.

## Prices and quantities are decimal strings

Every monetary and size field on this page — `price`, `quantity`, `trigger_price`, `stop_price`, `filled_qty`, `fee`, and every field in a preview response — is an arbitrary-precision **decimal serialized as a string** (the `Decimal` schema), so no precision is lost on the wire. **Parse them with a decimal type, never a float.** Sending `50000` as a JSON number rather than `"50000"` is not the contract's shape.

The two basis-point fields, `trailing_offset_bps` and `limit_offset_bps`, are the exception: they are JSON **integers**, not strings.

## Order placement schema

`OrderRequest` is the body of `POST /orders`, `POST /orders/batch` (as array elements), and `POST /orders/preview`, plus their `/api/v1` twins.

It supports plain `Limit` and `Market` orders and six conditional order types (`StopLimit`, `StopMarket`, `TakeProfitLimit`, `TakeProfitMarket`, `TrailingStop`, `TrailingLimit`). Field requirements depend on `order_type`:

* **Limit-family** (`Limit`, `StopLimit`, `TakeProfitLimit`) require a limit `price`.
* **Triggerable, non-trailing** orders (`StopLimit`, `StopMarket`, `TakeProfitLimit`, `TakeProfitMarket`) require a `trigger_price` (the legacy `stop_price` field is accepted as a fallback when `trigger_price` is absent).
* **`TrailingStop`** is market-only — it fires as a market order — and requires `trailing_offset_bps`. It does not take a limit `price` or a `trigger_price` (the trigger anchor is derived from the mark price and the offset).
* **`TrailingLimit`** trails like `TrailingStop` but fires a limit order instead of a market order. It requires both `trailing_offset_bps` (the trailing trigger) and `limit_offset_bps` (the fire-time limit offset). It does not take `price`, `trigger_price`, or `stop_price`; the limit price is computed at fire time from the mark that crossed the offset.

### Required fields by order type

`market_id`, `side`, `order_type`, `quantity`, and `time_in_force` are required for **every** order type. The four conditional fields resolve as follows:

| `order_type`       | `price`      | `trigger_price` | `trailing_offset_bps` | `limit_offset_bps` | Fires as                 |
| ------------------ | ------------ | --------------- | --------------------- | ------------------ | ------------------------ |
| `Limit`            | **Required** | Not used        | Ignored               | Ignored            | — (rests immediately)    |
| `Market`           | Omit         | Not used        | Ignored               | Ignored            | — (executes immediately) |
| `StopLimit`        | **Required** | **Required**    | Ignored               | Ignored            | Limit                    |
| `StopMarket`       | Omit         | **Required**    | Ignored               | Ignored            | Market                   |
| `TakeProfitLimit`  | **Required** | **Required**    | Ignored               | Ignored            | Limit                    |
| `TakeProfitMarket` | Omit         | **Required**    | Ignored               | Ignored            | Market                   |
| `TrailingStop`     | Not taken    | Not taken       | **Required**          | Ignored            | Market                   |
| `TrailingLimit`    | Not taken    | Not taken       | **Required**          | **Required**       | Limit                    |

Reading the matrix:

* **Required** — the request is rejected without it.
* **Omit** — the field is not part of a market-family order; there is no limit price to set.
* **Not used** / **Not taken** — the contract states the field is not used by this order type; the trailing types derive their trigger from the mark price and the offset instead.
* **Ignored** — the field may be present but has no effect for this order type.

The four triggerable, non-trailing types differ only in trigger direction: `StopLimit` and `StopMarket` fire when the mark price crosses `trigger_price` in the **adverse** direction; `TakeProfitLimit` and `TakeProfitMarket` fire on the **favorable** direction.

### `stop_price` is deprecated

`stop_price` is **deprecated in favour of `trigger_price`**. The rules are exact:

* `trigger_price` is the canonical trigger threshold.
* `stop_price` is accepted **only as a fallback**, when `trigger_price` is absent.
* When **both** are supplied, **`trigger_price` wins** and `stop_price` is disregarded.
* Both are ignored entirely for `Limit`, `Market`, `TrailingStop`, and `TrailingLimit` orders.

New integrations should send `trigger_price` and never `stop_price`.

### Time in force

`time_in_force` is required on every order and takes one of four policies:

| Value      | Meaning                                                                                                   |
| ---------- | --------------------------------------------------------------------------------------------------------- |
| `GTC`      | Good-till-cancelled — rests on the book until filled or cancelled.                                        |
| `IOC`      | Immediate-or-cancel — fills what it can immediately, cancels the remainder.                               |
| `FOK`      | Fill-or-kill — fills in full immediately or is cancelled entirely.                                        |
| `PostOnly` | Rejects the order if it would take liquidity (cross the book) on entry, guaranteeing it rests as a maker. |

`PostOnly` is the only value the contract describes in detail; the description above for `GTC`, `IOC`, and `FOK` states the standard meaning of each acronym, which the contract itself does not spell out.

### `OrderRequest` fields

| Field                 | Type                                                                                                                        | Required            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `market_id`           | string                                                                                                                      | Yes                 | Market identifier, e.g. `BTC-USDX-PERP`.                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `side`                | `Buy` / `Sell`                                                                                                              | Yes                 | Order side.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `order_type`          | `Limit` / `Market` / `StopLimit` / `StopMarket` / `TakeProfitLimit` / `TakeProfitMarket` / `TrailingStop` / `TrailingLimit` | Yes                 | Order type. `Limit` and `Market` are unconditional. The remaining six are conditional: `StopLimit` / `StopMarket` fire when the mark price crosses `trigger_price` in the adverse direction; `TakeProfitLimit` / `TakeProfitMarket` fire on the favorable direction; `TrailingStop` fires as a market order when the mark retraces from its best-seen extreme by `trailing_offset_bps`; `TrailingLimit` fires the same way but rests a limit order priced off the fire price by `limit_offset_bps`. |
| `price`               | decimal string                                                                                                              | Conditional         | Limit price. Required for limit-family orders (`Limit`, `StopLimit`, `TakeProfitLimit`); omit for market-family and trailing orders.                                                                                                                                                                                                                                                                                                                                                                |
| `quantity`            | decimal string                                                                                                              | Yes                 | Order size. Arbitrary-precision decimal serialized as a string (lossless) — parse with a decimal type, never a float.                                                                                                                                                                                                                                                                                                                                                                               |
| `time_in_force`       | `GTC` / `IOC` / `FOK` / `PostOnly`                                                                                          | Yes                 | Time-in-force policy. `PostOnly` rejects the order if it would take liquidity (cross the book) on entry, guaranteeing it rests as a maker.                                                                                                                                                                                                                                                                                                                                                          |
| `reduce_only`         | boolean                                                                                                                     | No                  | The contract declares the field with no description; see [Open questions](#open-questions).                                                                                                                                                                                                                                                                                                                                                                                                         |
| `stop_price`          | decimal string or null                                                                                                      | No — **deprecated** | **Deprecated** — use `trigger_price` instead. Legacy trigger threshold for the stop / take-profit family. Accepted as a fallback only when `trigger_price` is absent; when both are supplied, `trigger_price` wins. Ignored for `Limit`, `Market`, `TrailingStop`, and `TrailingLimit` orders.                                                                                                                                                                                                      |
| `trigger_price`       | decimal string or null                                                                                                      | Conditional         | Canonical trigger threshold for triggerable, non-trailing orders (`StopLimit`, `StopMarket`, `TakeProfitLimit`, `TakeProfitMarket`), which require it (the legacy `stop_price` field is accepted as a fallback when this is omitted). Not used by `Limit`, `Market`, `TrailingStop`, or `TrailingLimit` orders.                                                                                                                                                                                     |
| `trailing_offset_bps` | integer (minimum 0) or null                                                                                                 | Conditional         | Trailing offset in basis points (1 bp = 0.01%). Required for `TrailingStop` and `TrailingLimit` orders; ignored for all other order types. The trailing trigger fires once the mark price retraces from its best-seen extreme by this many basis points: `TrailingStop` fires a market order, `TrailingLimit` fires a limit order priced by `limit_offset_bps`. A value of `0` is accepted and fires the trigger at the first mark-price evaluation after placement (no retracement required).      |
| `limit_offset_bps`    | integer (0–9999) or null                                                                                                    | Conditional         | Offset in basis points for the fired limit price (`TrailingLimit` only; required together with `trailing_offset_bps`). When the trailing trigger fires at `fire_price`, the injected limit order rests at `fire_price` × (1 + offset) for buys / × (1 − offset) for sells, tick-rounded toward the tighter bound. A value of `0` rests the limit exactly at `fire_price`. Ignored for other order types.                                                                                            |

### Order admission is governed by the margin inequality

Order admission is not a free-form validation: an order is accepted only if the account still satisfies the initial-margin requirement once the order's own reservation is included. That is the order-admission inequality **(M.13)** in the Exchange's margin model — see [Margin math](/math-engine/margin-math).

A `400` on `POST /orders` with an insufficient-margin reason is that inequality failing. Use `POST /orders/preview` to evaluate it without submitting.

## `POST /orders`

Submit an order.

**Legacy path.** Prefer the canonical `POST /api/v1/orders`.

CCXT method: `createOrder`.

**Authentication:** `hmacAuth` — HMAC API key (`X-API-Key`, `X-Timestamp`, `X-Signature`).

### Request body

`OrderRequest` — `application/json`, required. Field table and the per-order-type requirement matrix are in [Order placement schema](#order-placement-schema).

### Responses

#### `201`

Order accepted. `OrderResponse`:

| Field   | Type            | Description                                                                         |
| ------- | --------------- | ----------------------------------------------------------------------------------- |
| `order` | `Order`         | The resting or terminal order the engine created.                                   |
| `fills` | array of `Fill` | Executions that occurred on entry. Empty for an order that rested without crossing. |

`Order`:

| Field              | Type                                                                         | Description                                                                                                                                                      |
| ------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`               | string (uuid)                                                                | Order identifier.                                                                                                                                                |
| `market_id`        | string                                                                       | Market the order is on.                                                                                                                                          |
| `account_id`       | string                                                                       | Owning account.                                                                                                                                                  |
| `side`             | `Buy` / `Sell`                                                               | Order side.                                                                                                                                                      |
| `order_type`       | string                                                                       | Echoed order type.                                                                                                                                               |
| `limit_offset_bps` | integer or null                                                              | Fire-time limit offset in basis points, echoed for `TrailingLimit` orders (see the `OrderRequest.limit_offset_bps` placement field); null for other order types. |
| `price`            | decimal string                                                               | Limit price.                                                                                                                                                     |
| `quantity`         | decimal string                                                               | Original quantity.                                                                                                                                               |
| `filled_qty`       | decimal string                                                               | Quantity filled so far.                                                                                                                                          |
| `status`           | `Open` / `PartiallyFilled` / `Filled` / `Cancelled` / `Expired` / `Rejected` | Order status.                                                                                                                                                    |
| `time_in_force`    | string                                                                       | Echoed time-in-force policy.                                                                                                                                     |
| `created_at`       | integer (Unix ms)                                                            | Creation timestamp.                                                                                                                                              |
| `updated_at`       | integer (Unix ms)                                                            | Last-update timestamp.                                                                                                                                           |

`Fill` — a single trade execution for the authenticated account:

| Field            | Type              | Description                                                                    |
| ---------------- | ----------------- | ------------------------------------------------------------------------------ |
| `id`             | string (uuid)     | Fill ID.                                                                       |
| `order_id`       | string            | Parent order ID.                                                               |
| `market_id`      | string            | Market (e.g. `BTC-USDX-PERP`).                                                 |
| `side`           | `buy` / `sell`    | Fill side — **lower-case here**, unlike `Order.side`, which is `Buy` / `Sell`. |
| `price`          | decimal string    | Executed price.                                                                |
| `size`           | decimal string    | Executed quantity.                                                             |
| `fee`            | decimal string    | Fee charged in USDX. Negative values are maker rebates.                        |
| `taker_or_maker` | `taker` / `maker` | Liquidity role for this fill.                                                  |
| `timestamp`      | integer (Unix ms) | Execution time.                                                                |
| `is_liquidation` | boolean           | Whether the fill came from a liquidation.                                      |

#### `400`

Validation error (insufficient margin, invalid tick size, etc.). An insufficient-margin rejection is the order-admission inequality **(M.13)** failing — see [Margin math](/math-engine/margin-math).

#### `401`

Authentication failed. All `401` responses return the same opaque body (`{"code": "unauthorized"}`) to prevent information leakage.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Body: `{"code": "RateLimitExceeded", "tier": "Pro"}`.

### Example

Plain limit order:

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/orders' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{
  "market_id": "BTC-USDX-PERP",
  "side": "Buy",
  "order_type": "Limit",
  "price": "50000",
  "quantity": "0.1",
  "time_in_force": "GTC"
}'
```

Stop-limit — requires `trigger_price` and a limit price:

```json
{
  "market_id": "BTC-USDX-PERP",
  "side": "Sell",
  "order_type": "StopLimit",
  "trigger_price": "48000",
  "price": "47900",
  "quantity": "0.1",
  "time_in_force": "GTC"
}
```

Trailing stop — market-only, requires `trailing_offset_bps`:

```json
{
  "market_id": "BTC-USDX-PERP",
  "side": "Sell",
  "order_type": "TrailingStop",
  "trailing_offset_bps": 250,
  "quantity": "0.1",
  "time_in_force": "IOC"
}
```

## `DELETE /orders`

Cancel all orders.

**Legacy path.** Prefer the canonical `DELETE /api/v1/orders`.

CCXT method: `cancelAllOrders`.

**Authentication:** `hmacAuth`.

### Parameters

| Name        | In    | Type   | Required | Description                                                            |
| ----------- | ----- | ------ | -------- | ---------------------------------------------------------------------- |
| `market_id` | query | string | No       | Cancel only orders on this market. Omit to cancel across every market. |

### Responses

#### `200`

Cancelled orders returned. The contract declares no response schema for this operation — the body shape is undefined by the spec; see [Open questions](#open-questions).

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X DELETE 'https://exchange.nexus.xyz/api/exchange/orders?market_id=BTC-USDX-PERP' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## `POST /orders/batch`

Submit multiple orders.

**Legacy path.** Prefer the canonical `POST /api/v1/orders/batch`.

Submit multiple orders in one request. Orders are processed **sequentially and non-atomically**: an early order consuming margin can cause a later order in the same batch to fail, and per-order failures do not abort the batch. The response array preserves request order with a per-order success or error result.

So the batch is **partial-success, not all-or-nothing**. The HTTP status is `201` for the batch as a whole even when individual entries failed — you must inspect each entry.

**Authentication:** `hmacAuth`.

### Request body

An **array** of `OrderRequest` — `application/json`, required. Each element is exactly the object documented in [Order placement schema](#order-placement-schema), including the same per-order-type requirement matrix.

The contract does not state a maximum batch length or how a batch is weighted against the rate limit; see [Open questions](#open-questions).

### Responses

#### `201`

Per-order results, in request order. Returned with status `201` for the batch as a whole even when individual entries failed; inspect each entry's `error`.

The body is an array of `OrderResult`. `OrderResult` is internally tagged by `outcome`, with a discriminator mapping `ok` → `OrderResultOk` and `err` → `OrderResultErr`. `ok` carries the same `{ order, fills }` shape as `POST /orders`; `err` carries the same `{ error, message }` shape as the global error envelope.

`OrderResultOk` — a placed order in a batch result:

| Field     | Type            | Required | Description                                                        |
| --------- | --------------- | -------- | ------------------------------------------------------------------ |
| `outcome` | `"ok"`          | Yes      | Discriminator.                                                     |
| `order`   | `Order`         | Yes      | The order the engine created — same shape as under `POST /orders`. |
| `fills`   | array of `Fill` | No       | Executions that occurred on entry.                                 |

`OrderResultErr` — a rejected order in a batch result. Mirrors the global error envelope:

| Field     | Type    | Required | Description                   |
| --------- | ------- | -------- | ----------------------------- |
| `outcome` | `"err"` | Yes      | Discriminator.                |
| `error`   | string  | Yes      | Machine-readable error code.  |
| `message` | string  | Yes      | Human-readable error message. |

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/orders/batch' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "market_id": "BTC-USDX-PERP",
    "side": "Buy",
    "order_type": "Limit",
    "price": "50000",
    "quantity": "0.1",
    "time_in_force": "GTC"
  },
  {
    "market_id": "ETH-USDX-PERP",
    "side": "Sell",
    "order_type": "Limit",
    "price": "3200",
    "quantity": "1.5",
    "time_in_force": "PostOnly"
  }
]'
```

## `DELETE /orders/{order_id}`

Cancel an order.

**Legacy path.** Prefer the canonical `DELETE /api/v1/orders/{order_id}`.

CCXT method: `cancelOrder`.

**Authentication:** `hmacAuth`.

### Parameters

| Name        | In    | Type          | Required | Description                                       |
| ----------- | ----- | ------------- | -------- | ------------------------------------------------- |
| `order_id`  | path  | string (uuid) | Yes      | Order to cancel.                                  |
| `market_id` | query | string        | Yes      | Market the order rests on (required for routing). |

`market_id` is **required** on this operation — an order id alone is not enough to route the cancel.

### Responses

#### `200`

Cancelled order returned. The contract declares no response schema for this operation — the body shape is undefined by the spec; see [Open questions](#open-questions).

#### `401`

Authentication failed.

#### `404`

Order not found.

### Example

```bash
curl -X DELETE 'https://exchange.nexus.xyz/api/exchange/orders/8f2c1e40-9a3b-4d5e-8c7f-1a2b3c4d5e6f?market_id=BTC-USDX-PERP' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## `PATCH /orders/{order_id}`

Amend an order.

**Legacy path.** Prefer the canonical `PATCH /api/v1/orders/{order_id}`.

Atomic cancel-replace amend of a resting order: changes the price and/or size in a single operation. At least one of `price` or `size` must be supplied. Liquidation orders are not amendable, and a pre-trade margin check is applied to the projected replacement before it is accepted.

The replacement carries a **fresh order id** — the amend is a cancel-replace, not an in-place mutation, so persist the returned `id`.

CCXT method: `editOrder`.

**Authentication:** `hmacAuth`.

### Parameters

| Name        | In    | Type          | Required | Description                                       |
| ----------- | ----- | ------------- | -------- | ------------------------------------------------- |
| `order_id`  | path  | string (uuid) | Yes      | Order to amend.                                   |
| `market_id` | query | string        | Yes      | Market the order rests on (required for routing). |

### Request body

`AmendOrderRequest` — `application/json`, required. At least one property must be present (`minProperties: 1`); an empty body is rejected with `InvalidAmend`.

| Field   | Type           | Required    | Description      |
| ------- | -------------- | ----------- | ---------------- |
| `price` | decimal string | Conditional | New limit price. |
| `size`  | decimal string | Conditional | New quantity.    |

Note the field is `size` here, whereas placement uses `quantity` for the same concept.

### Responses

#### `200`

Amended order (the replacement, with a fresh id). An `Order` — same shape as the `order` field under `POST /orders`:

| Field              | Type                                                                         | Description                                                                        |
| ------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `id`               | string (uuid)                                                                | **New** order identifier for the replacement.                                      |
| `market_id`        | string                                                                       | Market the order is on.                                                            |
| `account_id`       | string                                                                       | Owning account.                                                                    |
| `side`             | `Buy` / `Sell`                                                               | Order side.                                                                        |
| `order_type`       | string                                                                       | Echoed order type.                                                                 |
| `limit_offset_bps` | integer or null                                                              | Fire-time limit offset in basis points for `TrailingLimit` orders; null otherwise. |
| `price`            | decimal string                                                               | Limit price after the amend.                                                       |
| `quantity`         | decimal string                                                               | Quantity after the amend.                                                          |
| `filled_qty`       | decimal string                                                               | Quantity filled so far.                                                            |
| `status`           | `Open` / `PartiallyFilled` / `Filled` / `Cancelled` / `Expired` / `Rejected` | Order status.                                                                      |
| `time_in_force`    | string                                                                       | Echoed time-in-force policy.                                                       |
| `created_at`       | integer (Unix ms)                                                            | Creation timestamp.                                                                |
| `updated_at`       | integer (Unix ms)                                                            | Last-update timestamp.                                                             |

#### `400`

Invalid amend (empty body, invalid price/size, order not amendable, or margin breach). The margin-breach case is the pre-trade check on the projected replacement — the same order-admission inequality **(M.13)** that governs placement; see [Margin math](/math-engine/margin-math).

#### `401`

Authentication failed.

#### `404`

Order not found.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X PATCH 'https://exchange.nexus.xyz/api/exchange/orders/8f2c1e40-9a3b-4d5e-8c7f-1a2b3c4d5e6f?market_id=BTC-USDX-PERP' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{
  "price": "50100",
  "size": "0.2"
}'
```

## `POST /orders/preview`

Preview an order.

**Legacy path.** Prefer the canonical `POST /api/v1/orders/preview`.

Pre-trade preview: projects the margin/equity/fee impact of an order without submitting it. This is the read-only way to evaluate the order-admission inequality **(M.13)** before committing — see [Margin math](/math-engine/margin-math).

**Authentication:** `hmacAuth`.

### Request body

`OrderRequest` — `application/json`, required. The same object as `POST /orders`, including the per-order-type requirement matrix; see [Order placement schema](#order-placement-schema).

### Responses

#### `200`

Projected pre-trade impact. `PreviewResponse`:

| Field                                    | Type                   | Description                                                                                   |
| ---------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------- |
| `accepted`                               | boolean                | Whether the order would be admitted as submitted.                                             |
| `reject_reason`                          | string or null         | Reason the projection would be rejected; null when `accepted` is true.                        |
| `required_initial_margin`                | decimal string         | Initial margin the order would require.                                                       |
| `projected_post_trade_equity`            | decimal string         | Account equity after the projected fill.                                                      |
| `projected_post_trade_liquidation_price` | decimal string or null | Liquidation price after the projected fill; null when it is not defined.                      |
| `projected_post_trade_leverage`          | decimal string         | Account leverage after the projected fill.                                                    |
| `expected_fill_vwap`                     | decimal string or null | Volume-weighted average fill price the order would achieve; null when it cannot be projected. |
| `projected_fees`                         | decimal string         | Fees the projected fill would incur.                                                          |

The contract declares these fields with types but **without per-field descriptions**; the descriptions above state what each name denotes and are not carried from the spec. See [Open questions](#open-questions).

#### `400`

Validation error.

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/orders/preview' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{
  "market_id": "BTC-USDX-PERP",
  "side": "Buy",
  "order_type": "Limit",
  "price": "50000",
  "quantity": "0.1",
  "time_in_force": "GTC"
}'
```

## `POST /api/v1/orders`

Submit an order.

**Canonical versioned form.** The bare `POST /orders` is the legacy alias; both take the same body and return the same response. `operationId`: `createOrderV1`. Equivalent to CCXT's `createOrder`.

**Authentication:** `hmacAuth`. Sign the full path including the `/api/v1` prefix.

### Request body

`OrderRequest` — `application/json`, required. Identical to `POST /orders`; see [Order placement schema](#order-placement-schema) for the field table and the per-order-type requirement matrix, and `POST /orders` for the worked examples.

### Responses

#### `201`

Order accepted. `OrderResponse` — `{ order, fills }`, with `Order` and `Fill` exactly as documented under `POST /orders`.

#### `400`

Validation error (insufficient margin, invalid tick size, etc.). The insufficient-margin case is the order-admission inequality **(M.13)** failing — see [Margin math](/math-engine/margin-math).

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/api/v1/orders' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{
  "market_id": "BTC-USDX-PERP",
  "side": "Buy",
  "order_type": "Limit",
  "price": "50000",
  "quantity": "0.1",
  "time_in_force": "GTC"
}'
```

## `DELETE /api/v1/orders`

Cancel all orders.

**Canonical versioned form.** The bare `DELETE /orders` is the legacy alias. `operationId`: `cancelAllOrdersV1`. Equivalent to CCXT's `cancelAllOrders`.

**Authentication:** `hmacAuth`.

### Parameters

| Name        | In    | Type   | Required | Description                                                            |
| ----------- | ----- | ------ | -------- | ---------------------------------------------------------------------- |
| `market_id` | query | string | No       | Cancel only orders on this market. Omit to cancel across every market. |

### Responses

#### `200`

Cancelled orders returned. No response schema is declared — the body shape is undefined by the spec.

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X DELETE 'https://exchange.nexus.xyz/api/exchange/api/v1/orders?market_id=BTC-USDX-PERP' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## `POST /api/v1/orders/batch`

Submit multiple orders.

**Canonical versioned form.** The bare `POST /orders/batch` is the legacy alias. `operationId`: `createOrdersBatchV1`.

Submit multiple orders in one request. Orders are processed **sequentially and non-atomically**: an early order consuming margin can cause a later order in the same batch to fail, and per-order failures do not abort the batch. The response array preserves request order with a per-order success or error result — the batch is partial-success, not all-or-nothing.

**Authentication:** `hmacAuth`.

### Request body

An **array** of `OrderRequest` — `application/json`, required. Each element is the object in [Order placement schema](#order-placement-schema).

The contract does not state a maximum batch length or the rate-limit weighting of a batch.

### Responses

#### `201`

Per-order results, in request order. Returned with status `201` for the batch as a whole even when individual entries failed; inspect each entry's `error`.

An array of `OrderResult` — `outcome: "ok"` carries `{ order, fills }`, `outcome: "err"` carries `{ error, message }`, exactly as documented under `POST /orders/batch`.

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/api/v1/orders/batch' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "market_id": "BTC-USDX-PERP",
    "side": "Buy",
    "order_type": "Limit",
    "price": "50000",
    "quantity": "0.1",
    "time_in_force": "GTC"
  },
  {
    "market_id": "ETH-USDX-PERP",
    "side": "Sell",
    "order_type": "Limit",
    "price": "3200",
    "quantity": "1.5",
    "time_in_force": "PostOnly"
  }
]'
```

## `POST /api/v1/orders/preview`

Preview an order.

**Canonical versioned form.** The bare `POST /orders/preview` is the legacy alias. `operationId`: `previewOrderV1`.

Pre-trade preview: projects the margin/equity/fee impact of an order without submitting it. The read-only way to evaluate the order-admission inequality **(M.13)** — see [Margin math](/math-engine/margin-math).

**Authentication:** `hmacAuth`.

### Request body

`OrderRequest` — `application/json`, required. See [Order placement schema](#order-placement-schema).

### Responses

#### `200`

Projected pre-trade impact. `PreviewResponse` — `accepted`, `reject_reason`, `required_initial_margin`, `projected_post_trade_equity`, `projected_post_trade_liquidation_price`, `projected_post_trade_leverage`, `expected_fill_vwap`, `projected_fees`, as documented under `POST /orders/preview`.

#### `400`

Validation error.

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/api/v1/orders/preview' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{
  "market_id": "BTC-USDX-PERP",
  "side": "Buy",
  "order_type": "Limit",
  "price": "50000",
  "quantity": "0.1",
  "time_in_force": "GTC"
}'
```

## `DELETE /api/v1/orders/{order_id}`

Cancel an order.

**Canonical versioned form.** The bare `DELETE /orders/{order_id}` is the legacy alias. `operationId`: `cancelOrderV1`. Equivalent to CCXT's `cancelOrder`.

**Authentication:** `hmacAuth`.

### Parameters

| Name        | In    | Type          | Required | Description                                       |
| ----------- | ----- | ------------- | -------- | ------------------------------------------------- |
| `order_id`  | path  | string (uuid) | Yes      | Order to cancel.                                  |
| `market_id` | query | string        | Yes      | Market the order rests on (required for routing). |

### Responses

#### `200`

Cancelled order returned. No response schema is declared — the body shape is undefined by the spec.

#### `401`

Authentication failed.

#### `404`

Order not found.

### Example

```bash
curl -X DELETE 'https://exchange.nexus.xyz/api/exchange/api/v1/orders/8f2c1e40-9a3b-4d5e-8c7f-1a2b3c4d5e6f?market_id=BTC-USDX-PERP' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## `PATCH /api/v1/orders/{order_id}`

Amend an order.

**Canonical versioned form.** The bare `PATCH /orders/{order_id}` is the legacy alias. `operationId`: `editOrderV1`.

Atomic cancel-replace amend of a resting order: changes the price and/or size in a single operation. At least one of `price` or `size` must be supplied. Liquidation orders are not amendable, and a pre-trade margin check is applied to the projected replacement before it is accepted. The replacement carries a fresh order id.

**Authentication:** `hmacAuth`.

### Parameters

| Name        | In    | Type          | Required | Description                                       |
| ----------- | ----- | ------------- | -------- | ------------------------------------------------- |
| `order_id`  | path  | string (uuid) | Yes      | Order to amend.                                   |
| `market_id` | query | string        | Yes      | Market the order rests on (required for routing). |

### Request body

`AmendOrderRequest` — `application/json`, required. At least one property must be present (`minProperties: 1`); an empty body is rejected with `InvalidAmend`.

| Field   | Type           | Required    | Description      |
| ------- | -------------- | ----------- | ---------------- |
| `price` | decimal string | Conditional | New limit price. |
| `size`  | decimal string | Conditional | New quantity.    |

### Responses

#### `200`

Amended order (the replacement, with a fresh id). An `Order` — same shape as documented under `PATCH /orders/{order_id}`.

#### `400`

Invalid amend (empty body, invalid price/size, order not amendable, or margin breach). See [Margin math](/math-engine/margin-math) for the admission inequality the projected replacement must satisfy.

#### `401`

Authentication failed.

#### `404`

Order not found.

#### `429`

Rate limit exceeded.

### Example

```bash
curl -X PATCH 'https://exchange.nexus.xyz/api/exchange/api/v1/orders/8f2c1e40-9a3b-4d5e-8c7f-1a2b3c4d5e6f?market_id=BTC-USDX-PERP' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{
  "price": "50100",
  "size": "0.2"
}'
```

## Related

* [Positions](/exchange-api/exchange-api/positions) — open and closed position state, including per-position risk detail.
* [Authentication](/exchange-api/exchange-api/authentication) — the HMAC canonical string and API key lifecycle.
* [Margin math](/math-engine/margin-math) — the order-admission inequality **(M.13)** that governs whether an order is accepted.
* [Schemas](/exchange-api/exchange-api/schemas) — every component schema in the contract.

## Open questions

Gaps in the contract, flagged rather than filled:

* **Cancel response bodies.** `DELETE /orders`, `DELETE /orders/{order_id}`, and both `/api/v1` twins document a `200` with a prose description ("Cancelled orders returned" / "Cancelled order returned") but **no response schema**. The body shape is not part of the contract.
* **Batch limits.** No maximum batch length is declared for `POST /orders/batch`, and the contract does not say how a batch is weighted against the rate limit (one request, or one per element).
* **`reduce_only`.** Declared as a boolean with no description. Its interaction with the conditional and trailing order types is unspecified.
* **`PreviewResponse` field semantics.** The eight fields are typed but carry no descriptions in the contract.
* **Trigger direction.** "Adverse" and "favorable" are not formally defined per `side` for the stop and take-profit families.
* **Time-in-force × conditional types.** The contract does not state which `time_in_force` values are valid for the six conditional order types (for example `PostOnly` on a `StopMarket`).
* **Amendability.** Beyond "liquidation orders are not amendable", the contract does not say whether conditional or trailing orders can be amended, or whether an amend may change `time_in_force`.
* **`stop_price` removal.** Marked deprecated with no stated removal version or date.
* **No client order id or idempotency key.** `OrderRequest` has no `client_order_id`, and no idempotency header is declared, so a retried submission after a timeout is not deduplicated by the contract.
* **`Order.price` on market orders.** `Order.price` is typed as a non-nullable decimal string, while `OrderHistoryEntry.price` for the same concept is explicitly nullable ("null for market orders"). What `Order.price` carries for a market order is not stated.


# Positions

Open position queries. Four operations: list open positions and list closed positions, each served on two path surfaces.

Every operation on this page requires HMAC API key authentication (`hmacAuth`). See [Authentication](/exchange-api/exchange-api/authentication) for the signing scheme.

## Base URLs and the two path surfaces

Each of the two operations is served twice, and the two surfaces have **different base URLs**:

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface: the same handlers and the same authentication and rate-limit layers back both mounts, and the bare root paths are the legacy form kept for compatibility while clients migrate off the gateway proxy. Prefer `/api/v1` for new integrations. The contract does not mark either variant `deprecated` — both are live and return identical bodies. Whichever you choose, sign the path you actually call.

| Canonical (`/api/v1`)          | Legacy alias            | Operation             | `operationId`                                     | CCXT method      |
| ------------------------------ | ----------------------- | --------------------- | ------------------------------------------------- | ---------------- |
| `GET /api/v1/positions`        | `GET /positions`        | List open positions   | `fetchPositionsV1` / `fetchPositions`             | `fetchPositions` |
| `GET /api/v1/positions/closed` | `GET /positions/closed` | List closed positions | `fetchClosedPositionsV1` / `fetchClosedPositions` | —                |

The `x-ccxt-method` binding is declared on the unversioned `fetchPositions` only; the versioned twin returns the identical body.

## Monetary fields are decimal strings

`size`, `entry_price`, `exit_price`, `unrealized_pnl`, `realized_pnl`, `liquidation_price`, `notional_value`, `roe`, `margin_used`, and `funding_paid` are all arbitrary-precision **decimals serialized as strings** (the `Decimal` schema) — lossless on the wire. **Parse them with a decimal type, never a float.**

The leverage fields are the exception: `leverage` is a JSON **number** and `max_leverage` is a JSON **integer**.

## Null-with-reason, not fabricated numbers

The enriched risk fields on an open position are derived strictly from indexer-mirrored state — no engine round-trip, to stay on the low-latency read path. When an input is not mirrored, the field is `null` and its companion `<field>_error` carries a machine-readable reason **rather than a fabricated number**.

There are five such pairs: `leverage` / `leverage_error`, `notional_value` / `notional_value_error`, `roe` / `roe_error`, `margin_used` / `margin_used_error`, and `max_leverage` / `max_leverage_error`. In every pair the `_error` field is `null` when the value is populated.

Read the pair, not the value alone: a `null` is a stated absence with a cause, not a zero.

## `GET /positions`

List open positions.

**Legacy path.** Prefer the canonical `GET /api/v1/positions`.

CCXT method: `fetchPositions`.

**Authentication:** `hmacAuth` — HMAC API key (`X-API-Key`, `X-Timestamp`, `X-Signature`).

### Responses

#### `200`

The account's open positions. An array of `Position` — an open position with per-position risk detail. Enriched risk fields are derived strictly from indexer-mirrored state (no engine round-trip, to stay on the low-latency read path): when an input is not mirrored, the field is `null` and its companion `<field>_error` carries a machine-readable reason rather than a fabricated number. Monetary fields are lossless decimal strings; leverage fields are JSON numbers.

| Field                  | Type                   | Description                                                                                                                                                                                                                                                                                                                                                                                                  |
| ---------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `market_id`            | string                 | Market the position is on.                                                                                                                                                                                                                                                                                                                                                                                   |
| `side`                 | `Long` / `Short`       | Position direction.                                                                                                                                                                                                                                                                                                                                                                                          |
| `size`                 | decimal string         | Absolute position size.                                                                                                                                                                                                                                                                                                                                                                                      |
| `entry_price`          | decimal string         | Average entry price.                                                                                                                                                                                                                                                                                                                                                                                         |
| `unrealized_pnl`       | decimal string         | Unrealized profit and loss.                                                                                                                                                                                                                                                                                                                                                                                  |
| `realized_pnl`         | decimal string         | Realized profit and loss on this position.                                                                                                                                                                                                                                                                                                                                                                   |
| `liquidation_price`    | decimal string         | Price at which the position would be liquidated.                                                                                                                                                                                                                                                                                                                                                             |
| `leverage`             | number or null         | Position leverage (the account's leverage multiplier for this position). Currently always `null`: deriving it needs the user's leverage setting or account equity/allocated margin, which the indexer does not mirror; when `null`, `leverage_error` carries the reason. Do not infer leverage from `margin_used` — that collapses to `1/initial_margin_rate`, a per-market constant, not the real leverage. |
| `leverage_error`       | string or null         | Machine-readable reason `leverage` is `null`, or `null` when `leverage` is populated. Currently always `margin_state_not_mirrored`.                                                                                                                                                                                                                                                                          |
| `notional_value`       | decimal string or null | Position notional value (\|size\| × mark price). `null` when the mark price is unavailable — see `notional_value_error`.                                                                                                                                                                                                                                                                                     |
| `notional_value_error` | string or null         | Machine-readable reason `notional_value` is `null` (e.g. `mark_price_unavailable`), or `null` when populated.                                                                                                                                                                                                                                                                                                |
| `roe`                  | decimal string or null | Return on equity: `unrealized_pnl / margin_used` (return on initial margin). `null` when a required input is unavailable or margin is zero — see `roe_error`.                                                                                                                                                                                                                                                |
| `roe_error`            | string or null         | Machine-readable reason `roe` is `null` (e.g. `mark_price_unavailable`, `margin_rate_unavailable`, `margin_used_zero`), or `null` when populated.                                                                                                                                                                                                                                                            |
| `margin_used`          | decimal string or null | Initial-margin requirement held against this position (`notional_value × initial_margin_rate`, under the engine's cross-margin model). Isolated/custom margin allocations are not mirrored by the indexer. `null` when a required input is unavailable — see `margin_used_error`.                                                                                                                            |
| `margin_used_error`    | string or null         | Machine-readable reason `margin_used` is `null` (e.g. `mark_price_unavailable`, `margin_rate_unavailable`), or `null` when populated.                                                                                                                                                                                                                                                                        |
| `max_leverage`         | integer or null        | Maximum leverage allowed for this market (from market risk params), matching `max_leverage` on `GET /markets/{market_id}/risk-params`. `null` when market params are unavailable — see `max_leverage_error`.                                                                                                                                                                                                 |
| `max_leverage_error`   | string or null         | Machine-readable reason `max_leverage` is `null` (e.g. `market_params_unavailable`), or `null` when populated.                                                                                                                                                                                                                                                                                               |
| `funding_paid`         | decimal string         | Cumulative funding paid on this position. Sign is **paid-positive**: a positive value means the position has paid funding, a negative value means it has received funding. Always present: `"0"` when no funding has accrued. Bounded by the funding history the indexer retains.                                                                                                                            |

Example body:

```json
[
  {
    "market_id": "BTC-USDX-PERP",
    "side": "Long",
    "size": "0.5",
    "entry_price": "49500.00",
    "unrealized_pnl": "250.50",
    "realized_pnl": "0.00",
    "liquidation_price": "42000.00",
    "notional_value": "25000.50",
    "notional_value_error": null,
    "roe": "0.2004",
    "roe_error": null,
    "margin_used": "1250.03",
    "margin_used_error": null,
    "max_leverage": 20,
    "max_leverage_error": null,
    "funding_paid": "12.50",
    "leverage": null,
    "leverage_error": "margin_state_not_mirrored"
  }
]
```

#### `401`

Authentication failed. All `401` responses return the same opaque body (`{"code": "unauthorized"}`) to prevent information leakage.

#### `429`

Rate limit exceeded. Check the `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Body: `{"code": "RateLimitExceeded", "tier": "Pro"}`.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/positions' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## `GET /positions/closed`

List closed positions.

**Legacy path.** Prefer the canonical `GET /api/v1/positions/closed`.

**Authentication:** `hmacAuth`.

### Parameters

| Name     | In    | Type                                   | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| -------- | ----- | -------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`  | query | integer (default `100`, maximum `200`) | No       | Maximum records to return.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `cursor` | query | string                                 | No       | Opaque pagination cursor returned in the previous response's `X-Next-Cursor` header. Omit to fetch the first page. Treat the token as opaque: its format is not part of the contract and may change. Cursors do not expire. A malformed (unparseable) cursor is not an error — the server serves the first page. A well-formed cursor whose exact position has since been evicted from the retained window is not reset to the first page: pagination resumes at the nearest surviving boundary, so a resumed response is a continuation, not a fresh first page. |

### Responses

#### `200`

Closed positions, newest first. An array of `ClosedPosition`:

| Field          | Type              | Description                                 |
| -------------- | ----------------- | ------------------------------------------- |
| `market_id`    | string            | Market the position was on.                 |
| `side`         | `Long` / `Short`  | The side the position was before it closed. |
| `size`         | decimal string    | Absolute size at close.                     |
| `entry_price`  | decimal string    | Average entry price.                        |
| `exit_price`   | decimal string    | Price at which the position closed.         |
| `realized_pnl` | decimal string    | Realized profit and loss on close.          |
| `closed_at_ms` | integer (Unix ms) | When the position closed.                   |

Response headers:

| Header          | Type   | Description                                                                                                                                                                                                                                                                   |
| --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Next-Cursor` | string | Opaque cursor for the next page. Present only when more results exist beyond this response; absent on the last page. Pass it back via the `cursor` query parameter to fetch the next page. The response body stays a bare array — pagination state rides only in this header. |

`ClosedPosition` carries none of the enriched risk fields that `Position` does — no `notional_value`, `roe`, `margin_used`, `leverage`, or `funding_paid`, and therefore no `_error` companions.

#### `401`

Authentication failed.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/positions/closed?limit=50' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## `GET /api/v1/positions`

List open positions.

**Canonical versioned form.** The bare `GET /positions` is the legacy alias; both return the same body. `operationId`: `fetchPositionsV1`. Equivalent to CCXT's `fetchPositions`.

**Authentication:** `hmacAuth`. Sign the full path including the `/api/v1` prefix.

### Responses

#### `200`

The account's open positions. An array of `Position`, with the same fields and the same null-with-reason semantics as `GET /positions`:

| Field                  | Type                   | Description                                                                                                                                                                                                                                                      |
| ---------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `market_id`            | string                 | Market the position is on.                                                                                                                                                                                                                                       |
| `side`                 | `Long` / `Short`       | Position direction.                                                                                                                                                                                                                                              |
| `size`                 | decimal string         | Absolute position size.                                                                                                                                                                                                                                          |
| `entry_price`          | decimal string         | Average entry price.                                                                                                                                                                                                                                             |
| `unrealized_pnl`       | decimal string         | Unrealized profit and loss.                                                                                                                                                                                                                                      |
| `realized_pnl`         | decimal string         | Realized profit and loss on this position.                                                                                                                                                                                                                       |
| `liquidation_price`    | decimal string         | Price at which the position would be liquidated.                                                                                                                                                                                                                 |
| `leverage`             | number or null         | Position leverage. Currently always `null` — the indexer does not mirror the margin state needed to derive it; `leverage_error` carries the reason. Do not infer leverage from `margin_used`, which collapses to `1/initial_margin_rate`, a per-market constant. |
| `leverage_error`       | string or null         | Reason `leverage` is `null`. Currently always `margin_state_not_mirrored`.                                                                                                                                                                                       |
| `notional_value`       | decimal string or null | \|size\| × mark price. `null` when the mark price is unavailable.                                                                                                                                                                                                |
| `notional_value_error` | string or null         | Reason `notional_value` is `null` (e.g. `mark_price_unavailable`).                                                                                                                                                                                               |
| `roe`                  | decimal string or null | `unrealized_pnl / margin_used` — return on initial margin.                                                                                                                                                                                                       |
| `roe_error`            | string or null         | Reason `roe` is `null` (e.g. `mark_price_unavailable`, `margin_rate_unavailable`, `margin_used_zero`).                                                                                                                                                           |
| `margin_used`          | decimal string or null | `notional_value × initial_margin_rate` under the engine's cross-margin model. Isolated/custom margin allocations are not mirrored.                                                                                                                               |
| `margin_used_error`    | string or null         | Reason `margin_used` is `null` (e.g. `mark_price_unavailable`, `margin_rate_unavailable`).                                                                                                                                                                       |
| `max_leverage`         | integer or null        | Maximum leverage for this market, matching `GET /markets/{market_id}/risk-params`.                                                                                                                                                                               |
| `max_leverage_error`   | string or null         | Reason `max_leverage` is `null` (e.g. `market_params_unavailable`).                                                                                                                                                                                              |
| `funding_paid`         | decimal string         | Cumulative funding, **paid-positive**. `"0"` when none has accrued.                                                                                                                                                                                              |

Example body:

```json
[
  {
    "market_id": "BTC-USDX-PERP",
    "side": "Long",
    "size": "0.5",
    "entry_price": "49500.00",
    "unrealized_pnl": "250.50",
    "realized_pnl": "0.00",
    "liquidation_price": "42000.00",
    "notional_value": "25000.50",
    "notional_value_error": null,
    "roe": "0.2004",
    "roe_error": null,
    "margin_used": "1250.03",
    "margin_used_error": null,
    "max_leverage": 20,
    "max_leverage_error": null,
    "funding_paid": "12.50",
    "leverage": null,
    "leverage_error": "margin_state_not_mirrored"
  }
]
```

#### `401`

Authentication failed.

#### `429`

Rate limit exceeded.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/positions' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## `GET /api/v1/positions/closed`

List closed positions.

**Canonical versioned form.** The bare `GET /positions/closed` is the legacy alias. `operationId`: `fetchClosedPositionsV1`.

**Authentication:** `hmacAuth`.

### Parameters

| Name     | In    | Type                                   | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| -------- | ----- | -------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`  | query | integer (default `100`, maximum `200`) | No       | Maximum records to return.                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `cursor` | query | string                                 | No       | Opaque pagination cursor from the previous response's `X-Next-Cursor` header. Omit for the first page. The token's format is not part of the contract; cursors do not expire. A malformed cursor is not an error — the server serves the first page. A well-formed cursor whose position has been evicted from the retained window resumes at the nearest surviving boundary, so a resumed response is a continuation, not a fresh first page. |

### Responses

#### `200`

Closed positions, newest first. An array of `ClosedPosition`:

| Field          | Type              | Description                                 |
| -------------- | ----------------- | ------------------------------------------- |
| `market_id`    | string            | Market the position was on.                 |
| `side`         | `Long` / `Short`  | The side the position was before it closed. |
| `size`         | decimal string    | Absolute size at close.                     |
| `entry_price`  | decimal string    | Average entry price.                        |
| `exit_price`   | decimal string    | Price at which the position closed.         |
| `realized_pnl` | decimal string    | Realized profit and loss on close.          |
| `closed_at_ms` | integer (Unix ms) | When the position closed.                   |

Response headers:

| Header          | Type   | Description                                                                                                                                                                                          |
| --------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Next-Cursor` | string | Opaque cursor for the next page. Present only when more results exist; absent on the last page. Pass it back via `cursor`. The body stays a bare array — pagination state rides only in this header. |

#### `401`

Authentication failed.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/positions/closed?limit=50' \
  -H 'X-API-Key: nx_7f3a1b...' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## Related

* [Trading](/exchange-api/exchange-api/trading) — order submission, amendment, and cancellation.
* [Margin math](/math-engine/margin-math) — how initial margin, maintenance margin, and the liquidation price are defined.
* [Schemas](/exchange-api/exchange-api/schemas) — every component schema in the contract.

## Open questions

Gaps in the contract, flagged rather than filled:

* **`leverage` is permanently `null` today.** The contract states it is "currently always `null`" with `leverage_error: "margin_state_not_mirrored"`, and gives no target for populating it. Clients cannot read per-position leverage from this surface.
* **No `market_id` filter on closed positions.** `GET /positions/closed` accepts only `limit` and `cursor`; there is no way to scope the query to a single market.
* **Retention window unstated.** `funding_paid` is documented as "bounded by the funding history the indexer retains" and cursor pagination as bounded by "the retained window", but no retention period is given for either.
* **`429` missing on closed positions.** `GET /positions/closed` and its `/api/v1` twin declare only `200` and `401`; the `429` response the other position operation declares is absent from the contract even though the same rate-limit layer applies.
* **Cross-margin only.** `margin_used` is defined "under the engine's cross-margin model", and the contract notes isolated/custom margin allocations are not mirrored by the indexer — so an isolated-margin position's true allocation is not readable here.
* **No open-position count or total.** The response is a bare array with no envelope and no pagination on `GET /positions`, so there is no documented bound on how many positions a single response may carry.


# Account

Balance and order management — the largest tag in the contract, with **34 operations**. Everything an authenticated caller needs to read its own state: collateral and equity, portfolio time-series, effective fee schedule, rate-limit budget, open orders and order history, fills, the cancel-on-disconnect dead man's switch, collateral movement, testnet funding, and auto-deleveraging events that touched the account.

Every operation on this page requires HMAC API key authentication (`hmacAuth`). The account is identified by the credentials, not by a parameter — with one exception (`GET /account/{address}/adl-history`, which takes an explicit address). See [Authentication](/exchange-api/exchange-api/authentication) for the signing scheme.

## On this page

| Sub-group                                                                            | Operations | What it covers                                                         |
| ------------------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------- |
| [Account snapshot](#account-snapshot-balances-collateral-and-consolidated-state)     | 6          | Balances, collateral, equity, portfolio aggregates, consolidated state |
| [Portfolio history](#portfolio-history)                                              | 4          | Equity and portfolio time-series                                       |
| [Fees and rate-limit status](#fees-and-rate-limit-status)                            | 4          | Effective fee schedule, tier, remaining request budget                 |
| [Orders](#orders-open-orders-and-order-history)                                      | 5          | Open orders, single order lookup, terminal-status history              |
| [Fills](#fills)                                                                      | 2          | Trade executions for the account                                       |
| [Cancel-on-disconnect](#cancel-on-disconnect)                                        | 4          | Read and set the per-account dead man's switch                         |
| [Collateral movement](#collateral-movement-deposits-withdrawals-and-isolated-margin) | 5          | Deposits, withdrawal records, isolated-margin adjustment               |
| [Testnet funding](#testnet-funding-synthetic-credit-and-faucet)                      | 3          | Synthetic USDX credit and the faucet                                   |
| [ADL history](#adl-history)                                                          | 1          | Auto-deleveraging settlements touching the account                     |

Order submission, amendment, and cancellation live on [Trading](/exchange-api/exchange-api/trading). Position queries live on [Positions](/exchange-api/exchange-api/positions).

## Base URLs and the two path surfaces

Thirteen of the 34 operations are served twice: once under a versioned `/api/v1/…` path and once under a bare path. The two surfaces have **different base URLs**, and the contract declares them separately — a document-level server list plus a per-path override on every `/api/v1` path.

| Surface                   | Path form       | Notes                                                  |
| ------------------------- | --------------- | ------------------------------------------------------ |
| Versioned — **canonical** | `/api/v1/…`     | The versioned surface; prefer it for new integrations. |
| Root — **legacy**         | bare root paths | Kept for compatibility; slated for retirement.         |

**Both forms are served on the same base: `https://exchange.nexus.xyz/api/exchange`** (locally, `http://localhost:9090`). The contract declares a path-level `servers` override to `https://exchange.nexus.xyz` for `/api/v1` paths, but that host is **not publicly routable** — a request there returns the web app's 404 HTML. See [Base URLs](/exchange-api/exchange-api#base-urls).

The versioned `/api/v1` prefix is the canonical surface — the same operations addressed on the service directly rather than through the gateway proxy. The bare paths are the legacy form, retained while clients migrate off the gateway. **Prefer `/api/v1` for new integrations.**

Two qualifications, stated plainly because they matter for integration:

* The contract marks **neither** variant `deprecated`. Both are live and return identical bodies. "Legacy" here is the migration status described by the platform's own architecture, not a `deprecated: true` flag in version 0.7.2.
* The `/api/v1` mirror is **partial**. Of the 34 operations on this page, 26 (13 canonical/legacy pairs) exist on both surfaces and 8 are gateway-only. In particular `GET /orders/{order_id}` has no versioned twin, so a client that reads a single order by ID cannot stay entirely on `/api/v1`.

Whichever surface you call, **sign the path as written in the contract** — the HMAC canonical string includes the `/api/v1` prefix but **not** the `/api/exchange` gateway mount, which is stripped before your signature is verified (see [Authentication](/exchange-api/exchange-api/authentication)).

## Monetary fields are decimal strings

Every balance, collateral, equity, price, size, fee, and volume field on this page is an arbitrary-precision **decimal serialized as a string** (the [`Decimal`](/exchange-api/exchange-api/schemas#decimal) schema) — lossless on the wire. **Parse them with a decimal type, never a float.** A JSON float cannot represent these values exactly, and rounding a collateral or margin figure is how an integration ends up disagreeing with the venue about whether an order is affordable.

There is one deliberate exception in this tag: `EquityPoint.equity` on the equity-history endpoints is a JSON **number**, while the same underlying value on `PortfolioPoint.equity` is a decimal string. Compare the two by decimal value, not by wire representation. Integer fields — `maker_fee_bps`, `taker_fee_bps`, `max_leverage`, `limit`, `remaining`, `grace_secs`, and all `*_at_ms` timestamps — are JSON integers.

Timestamps are Unix epoch **milliseconds** ([`TimestampMs`](/exchange-api/exchange-api/schemas#timestampms)).

## Common error responses

These are shared components in the contract. They are described once here and referenced by code from each operation below.

| Code                                       | Meaning                                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`                                      | Authentication failed. All `401` responses return the same opaque body — `{"code": "unauthorized"}` — to prevent information leakage.                                                                                                                                                                                        |
| `429`                                      | Rate limit exceeded. Carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Body: `{"code": "RateLimitExceeded", "tier": "Pro"}`.                                                                                                                                             |
| `400` (`invalid_window`)                   | The `window` query parameter was present but not one of `day`, `week`, `month`, or `all`. Body: `{"code": "invalid_window"}`.                                                                                                                                                                                                |
| `502` (`authoritative_margin_unavailable`) | The engine-authoritative margin view is temporarily unavailable. Endpoints that derive balances from it (e.g. `withdrawable`) **fail closed** — returning this error rather than a locally-estimated, potentially unsafe figure. Transient; retry after a short delay. Body: `{"code": "authoritative_margin_unavailable"}`. |

`POST /account/credit` overrides `429` with its own daily-allowance meaning, and `POST /faucet` overrides it with a cooldown meaning. Both are documented at those operations.

## Pagination

Five operations on this page paginate with an opaque cursor: the two fills operations, the two equity-history operations, and order history (plus its versioned twin).

| Name     | In    | Type   | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| -------- | ----- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cursor` | query | string | No       | Opaque pagination cursor returned in the previous response's `X-Next-Cursor` header. Omit to fetch the first page. Treat the token as opaque: its format is not part of the contract and may change. Cursors do not expire. A malformed (unparseable) cursor is not an error — the server serves the first page. A well-formed cursor whose exact position has since been evicted from the retained window is not reset to the first page: pagination resumes at the nearest surviving boundary, so a resumed response is a continuation, not a fresh first page. |

The response body stays a bare array — pagination state rides only in the response header:

| Header          | Type   | Description                                                                                                                                                                                |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `X-Next-Cursor` | string | Opaque cursor for the next page. Present only when more results exist beyond this response; absent on the last page. Pass it back via the `cursor` query parameter to fetch the next page. |

## Account snapshot: balances, collateral, and consolidated state

Three snapshot shapes, each served on both path surfaces:

| Operation                  | Canonical                     | Legacy                 | Returns                                                                                                                             |
| -------------------------- | ----------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Get account summary        | `GET /api/v1/account`         | `GET /account`         | Balances plus embedded positions ([`AccountSummary`](/exchange-api/exchange-api/schemas#accountsummary))                            |
| Account portfolio summary  | `GET /api/v1/account/summary` | `GET /account/summary` | Aggregates only, including `withdrawable` ([`AccountPortfolioSummary`](/exchange-api/exchange-api/schemas#accountportfoliosummary)) |
| Consolidated account state | `GET /api/v1/account/state`   | `GET /account/state`   | `{ summary, positions }` in one coherent read ([`AccountState`](/exchange-api/exchange-api/schemas#accountstate))                   |

`GET /account` and `GET /account/summary` are different shapes, not aliases: the former is the CCXT-style balance object with an embedded `positions` array; the latter is the portfolio aggregate that adds `total_unrealized_pnl`, 24-hour realized PnL and volume, open counts, `margin_used`, and the engine-authoritative `withdrawable`. `GET /account/state` returns the portfolio summary *and* the full position list, so a client does not have to pair two calls.

### `GET /api/v1/account`

Get account summary.

**Canonical versioned form.** The bare `GET /account` is the legacy alias; both return the same body. `operationId`: `fetchBalanceV1`.

**Authentication:** `hmacAuth` — HMAC API key (`X-API-Key`, `X-Timestamp`, `X-Signature`). Sign the full path including the `/api/v1` prefix.

#### Responses

**`200`**

Account summary for the authenticated caller. [`AccountSummary`](/exchange-api/exchange-api/schemas#accountsummary):

| Field              | Type                                                               | Description                                                                                                                                       |
| ------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `balance`          | decimal string                                                     | Collateral balance.                                                                                                                               |
| `collateral`       | decimal string                                                     | Collateral posted to the account.                                                                                                                 |
| `equity`           | decimal string                                                     | Balance plus unrealized PnL.                                                                                                                      |
| `available_margin` | decimal string                                                     | Free margin available for new orders.                                                                                                             |
| `positions`        | array of [`Position`](/exchange-api/exchange-api/schemas#position) | Open positions, with the per-position risk detail and null-with-reason semantics documented on [Positions](/exchange-api/exchange-api/positions). |

Example body:

```json
{
  "balance": "100000.00",
  "collateral": "100000.00",
  "equity": "102500.50",
  "available_margin": "85000.00",
  "positions": []
}
```

**`401`**

Authentication failed. See [Common error responses](#common-error-responses).

**`429`**

Rate limit exceeded. See [Common error responses](#common-error-responses).

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account`

Get account summary.

**Legacy path.** Prefer the canonical `GET /api/v1/account`. `operationId`: `fetchBalance`. Bound to CCXT's `fetchBalance` via `x-ccxt-method` — the binding is declared on this unversioned operation only.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Account summary for the authenticated caller. Identical body and identical example to `GET /api/v1/account` — [`AccountSummary`](/exchange-api/exchange-api/schemas#accountsummary).

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /api/v1/account/summary`

Account portfolio summary.

**Canonical versioned form.** The bare `GET /account/summary` is the legacy alias. `operationId`: `fetchAccountSummaryV1`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Portfolio summary. [`AccountPortfolioSummary`](/exchange-api/exchange-api/schemas#accountportfoliosummary) — portfolio summary for the authenticated account (aggregate equity, PnL, volume, open counts):

| Field                    | Type           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `collateral`             | decimal string | Collateral posted to the account.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `total_equity`           | decimal string | Aggregate account equity.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `total_unrealized_pnl`   | decimal string | Sum of unrealized PnL across open positions.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `total_realized_pnl_24h` | decimal string | Realized PnL over the trailing 24 hours.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `total_volume_24h`       | decimal string | Traded notional over the trailing 24 hours.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `open_positions_count`   | integer        | Number of open positions.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `open_orders_count`      | integer        | Number of resting orders.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `margin_used`            | decimal string | Initial margin held against open positions.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `available_margin`       | decimal string | Free margin.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `withdrawable`           | decimal string | Wallet-withdrawable balance: engine-authoritative free margin floored at zero (`max(0, available_margin)`). Free margin already nets each position's initial margin and pre-trade order reservations out of equity, so this is exactly what can leave the account. A negative free margin (an underwater account) is clamped to `"0"` and never surfaced negative. Derived from the authoritative margin view — the endpoint fails closed with `502` rather than reporting a local estimate when that view is unavailable. Example: `"8500.00"`. |
| `early_access_allowed`   | boolean        | Present **only** when the early-access gate is active. Absent otherwise — treat absence as "gate not active", not as `false`.                                                                                                                                                                                                                                                                                                                                                                                                                    |

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

**`502`**

`authoritative_margin_unavailable` — the engine-authoritative margin view is temporarily unavailable and `withdrawable` cannot be derived safely. See [Common error responses](#common-error-responses).

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account/summary' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account/summary`

Account portfolio summary.

**Legacy path.** Prefer the canonical `GET /api/v1/account/summary`. `operationId`: `fetchAccountSummary`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Portfolio summary — [`AccountPortfolioSummary`](/exchange-api/exchange-api/schemas#accountportfoliosummary), the same fields as `GET /api/v1/account/summary`.

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

**`502`**

`authoritative_margin_unavailable`.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/summary' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /api/v1/account/state`

Consolidated account state.

Full account state in a single call: the portfolio summary aggregates plus all open positions (`{ summary, positions }`). Saves clients from pairing `/account/summary` with `/positions`, matching Hyperliquid `clearinghouseState` ergonomics. Both parts are built from one coherent read, so `summary.open_positions_count` always matches the `positions` length, and the embedded `summary` is identical to the standalone `/account/summary` response. Fails closed with `502` when the engine-authoritative margin view is unavailable.

**Canonical versioned form.** The bare `GET /account/state` is the legacy alias. `operationId`: `fetchAccountStateV1`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Consolidated account state (summary aggregates + open positions). [`AccountState`](/exchange-api/exchange-api/schemas#accountstate):

| Field       | Type                                                                                    | Description                                                                                        |
| ----------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `summary`   | [`AccountPortfolioSummary`](/exchange-api/exchange-api/schemas#accountportfoliosummary) | Required. Identical to the standalone `/account/summary` response.                                 |
| `positions` | array of [`Position`](/exchange-api/exchange-api/schemas#position)                      | Required. All open positions for the account. Length always equals `summary.open_positions_count`. |

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

**`502`**

`authoritative_margin_unavailable`.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account/state' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account/state`

Consolidated account state.

Full account state in a single call: the portfolio summary aggregates plus all open positions (`{ summary, positions }`), built from one coherent read.

**Legacy path.** Prefer the canonical `GET /api/v1/account/state`. `operationId`: `fetchAccountState`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

[`AccountState`](/exchange-api/exchange-api/schemas#accountstate) — same fields as `GET /api/v1/account/state`.

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

**`502`**

`authoritative_margin_unavailable`.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/state' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## Portfolio history

Two time-series, each on both path surfaces. They are not alternatives so much as different resolutions of the same underlying equity value.

| Operation                     | Canonical                               | Legacy                           | Series                                    | Window                           |
| ----------------------------- | --------------------------------------- | -------------------------------- | ----------------------------------------- | -------------------------------- |
| Account equity history        | `GET /api/v1/account/equity-history`    | `GET /account/equity-history`    | Equity only                               | 5s cadence, \~1h                 |
| Account portfolio time-series | `GET /api/v1/account/portfolio-history` | `GET /account/portfolio-history` | Equity, cumulative PnL, cumulative volume | `day` / `week` / `month` / `all` |

Both derive equity from the same source, so the two series never disagree — but note the wire types differ: equity history serializes `equity` as a JSON number, portfolio history as a decimal string.

### `GET /api/v1/account/portfolio-history`

Account portfolio time-series.

Portfolio time-series for the authenticated account — equity, cumulative trading PnL, and cumulative traded volume — downsampled over the selected `window` (`day`, `week`, `month`, or `all`), oldest first. Extends `/account/equity-history` (equity only, \~1h window) with PnL and volume series across multiple windows; both endpoints derive equity from the same source, so the series never disagree.

**Canonical versioned form.** The bare `GET /account/portfolio-history` is the legacy alias. `operationId`: `fetchPortfolioHistoryV1`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name     | In    | Type                                                                                                                          | Required | Description                                                                                                                                                                                                                                 |
| -------- | ----- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `window` | query | [`PortfolioWindow`](/exchange-api/exchange-api/schemas#portfoliowindow) — `day` \| `week` \| `month` \| `all` (default `day`) | No       | Time window to return. Also selects the server-side downsample cadence and point capacity (see the table below). A value outside this set is rejected with `400` (`invalid_window`). If the parameter is repeated, the first value is used. |
| `limit`  | query | integer (minimum `1`, maximum `366`)                                                                                          | No       | Maximum number of points to return. Capped server-side at the selected window's capacity (`day` 288, `week` 168, `month` 120, `all` 366); a larger value is **clamped, not rejected**. Omit to return the full window.                      |

The `window` value drives cadence, capacity, and span together:

| `window` | Cadence | Max points | Span  |
| -------- | ------- | ---------- | ----- |
| `day`    | 5 min   | 288        | 24 h  |
| `week`   | 1 h     | 168        | 7 d   |
| `month`  | 6 h     | 120        | 30 d  |
| `all`    | 1 d     | 366        | \~1 y |

#### Responses

**`200`**

Portfolio time-series for the window, oldest first. [`PortfolioHistory`](/exchange-api/exchange-api/schemas#portfoliohistory):

| Field        | Type                                                                           | Description                                                                                                             |
| ------------ | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `window`     | [`PortfolioWindow`](/exchange-api/exchange-api/schemas#portfoliowindow)        | Required. The window that was served — echoes the `window` query parameter, or its `day` default.                       |
| `cadence_ms` | integer (int64)                                                                | Required. Downsample interval between adjacent points, in milliseconds (e.g. `300000` for `day`, `86400000` for `all`). |
| `points`     | array of [`PortfolioPoint`](/exchange-api/exchange-api/schemas#portfoliopoint) | Required. Samples for the window, oldest first. Length is bounded by the window's capacity and by `limit`.              |

Each [`PortfolioPoint`](/exchange-api/exchange-api/schemas#portfoliopoint) — monetary fields are lossless decimal strings, never floats:

| Field          | Type              | Description                                                                                                                                                                                                                                                                                                        |
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `timestamp_ms` | integer (Unix ms) | Required. Sample time.                                                                                                                                                                                                                                                                                             |
| `equity`       | decimal string    | Required. Account equity at sample time (collateral balance + Σ unrealized PnL). Derived from the same underlying value as `EquityPoint.equity`; note `EquityPoint` serializes equity as a JSON number, whereas this is a lossless decimal string, so compare by decimal value rather than by wire representation. |
| `pnl`          | decimal string    | Required. Cumulative trading PnL up to this sample: Σ realized PnL on position close (including liquidation and ADL closes) + Σ funding (signed) + current unrealized PnL. **Deposit-neutral** — wallet deposits and withdrawals never move it — so the curve reflects trading performance only.                   |
| `volume`       | decimal string    | Required. Cumulative traded notional (Σ price × size) up to this sample, across taker and maker fills; a self-trade is counted once. Monotonically non-decreasing.                                                                                                                                                 |

**`400`**

`invalid_window`. See [Common error responses](#common-error-responses).

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account/portfolio-history?window=week&limit=168' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account/portfolio-history`

Account portfolio time-series.

Equity, cumulative trading PnL, and cumulative traded volume, downsampled over the selected `window`, oldest first.

**Legacy path.** Prefer the canonical `GET /api/v1/account/portfolio-history`. `operationId`: `fetchPortfolioHistory`.

**Authentication:** `hmacAuth`.

#### Parameters

Identical to `GET /api/v1/account/portfolio-history`: `window` (`day` | `week` | `month` | `all`, default `day`) and `limit` (1–366, clamped to the window's capacity).

#### Responses

**`200`**

[`PortfolioHistory`](/exchange-api/exchange-api/schemas#portfoliohistory) — same shape as the canonical form.

**`400`**

`invalid_window`.

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/portfolio-history?window=day' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /api/v1/account/equity-history`

Account equity history.

Equity time-series for the authenticated account (5s cadence, \~1h window), oldest first.

**Canonical versioned form.** The bare `GET /account/equity-history` is the legacy alias. `operationId`: `fetchEquityHistoryV1`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name     | In    | Type                                   | Required | Description                                               |
| -------- | ----- | -------------------------------------- | -------- | --------------------------------------------------------- |
| `limit`  | query | integer (default `720`, maximum `720`) | No       | Maximum points to return (capped at 720).                 |
| `cursor` | query | string                                 | No       | Opaque pagination cursor — see [Pagination](#pagination). |

#### Responses

**`200`**

Equity samples, oldest first. An array of [`EquityPoint`](/exchange-api/exchange-api/schemas#equitypoint) — one equity sample (balance + unrealized PnL) for the account, 5s cadence:

| Field          | Type              | Description                                                                                                            |
| -------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `timestamp_ms` | integer (Unix ms) | Sample time.                                                                                                           |
| `equity`       | **number**        | Account equity at sample time. Serialized as a JSON number here, unlike the decimal string on `PortfolioPoint.equity`. |

Response headers:

| Header          | Type   | Description                                               |
| --------------- | ------ | --------------------------------------------------------- |
| `X-Next-Cursor` | string | Opaque cursor for the next page; absent on the last page. |

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account/equity-history?limit=720' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account/equity-history`

Account equity history.

Equity time-series for the authenticated account (5s cadence, \~1h window), oldest first.

**Legacy path.** Prefer the canonical `GET /api/v1/account/equity-history`. `operationId`: `fetchEquityHistory`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name     | In    | Type                                   | Required | Description                                               |
| -------- | ----- | -------------------------------------- | -------- | --------------------------------------------------------- |
| `limit`  | query | integer (default `720`, maximum `720`) | No       | Maximum points to return (capped at 720).                 |
| `cursor` | query | string                                 | No       | Opaque pagination cursor — see [Pagination](#pagination). |

#### Responses

**`200`**

Equity samples, oldest first — an array of [`EquityPoint`](/exchange-api/exchange-api/schemas#equitypoint), same fields as the canonical form. Carries `X-Next-Cursor` when more pages exist.

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/equity-history?limit=720' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## Fees and rate-limit status

Two status reads, each on both path surfaces. Note the word "tier" means two different things here: `AccountFees.tier` is a **fee** tier (currently always `base`), while `RateLimitStatus.tier` is a **rate-limit** tier (`pro`, `marketmaker`, `unlimited`). They are unrelated.

| Operation             | Canonical                        | Legacy                    |
| --------------------- | -------------------------------- | ------------------------- |
| Account fee schedule  | `GET /api/v1/account/fees`       | `GET /account/fees`       |
| Get rate limit status | `GET /api/v1/account/rate-limit` | `GET /account/rate-limit` |

### `GET /api/v1/account/fees`

Account fee schedule.

The authenticated account's effective fee schedule — maker/taker rate (bps), fee tier, rolling 30-day traded volume, and active discounts — for parity with Hyperliquid `userFees`. This is the forward-looking *schedule* rate, not a realized average: the venue charges a per-fill fee but does not emit a realized per-fill rate. The reported rate's scope is given by `schedule` (see the schema); the account id is taken from the authenticated credentials, not a parameter.

**Canonical versioned form.** The bare `GET /account/fees` is the legacy alias. `operationId`: `fetchAccountFeesV1`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Account fee schedule. [`AccountFees`](/exchange-api/exchange-api/schemas#accountfees) — mirrors Hyperliquid `userFees`. Reports what the venue charges today: there are no per-account fee tiers or discounts yet (fee model still a draft), so `tier` is `base` and `discounts` is empty. All fields are required:

| Field                  | Type                                                                     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `maker_fee_bps`        | integer                                                                  | Effective maker fee in basis points. **Negative means the maker is paid a rebate** — e.g. `-2` is a 0.02% rebate.                                                                                                                                                                                                                                                                                                                                                                                            |
| `taker_fee_bps`        | integer                                                                  | Effective taker fee in basis points — e.g. `5` is a 0.05% fee.                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `tier`                 | string                                                                   | Fee tier for the account. Currently always `base`: there are no per-account fee tiers yet (distinct from rate-limit tiers). New values may appear when the fee model lands, so treat this as an open string.                                                                                                                                                                                                                                                                                                 |
| `schedule`             | string                                                                   | Scope of the reported rate. Currently always `standard`. The venue charges a per-market schedule (standard crypto, mid-cap crypto, FX, commodities/indices all differ, and the split varies by deploy config), but this endpoint takes no market parameter, so it reports the standard crypto-group schedule and marks it here. Treat the rate as scoped by this value, **not a venue-wide guarantee**; per-market effective rates are a planned follow-up. Treat as an open string — new scopes may appear. |
| `volume_30d`           | decimal string                                                           | Rolling 30-day traded notional for the account. Best-effort — see `volume_30d_estimated`.                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `volume_30d_estimated` | boolean                                                                  | `true` when `volume_30d` may undercount: the source fill buffer was at capacity, so some older in-window fills may have been evicted. `false` when the full 30-day window is covered.                                                                                                                                                                                                                                                                                                                        |
| `discounts`            | array of [`FeeDiscount`](/exchange-api/exchange-api/schemas#feediscount) | Active fee discounts applied to the account. Currently always empty — no discount program exists yet. The concrete `FeeDiscount` shape is provisional and finalizes with the fee model; no properties are guaranteed, and additional ones may be added additively.                                                                                                                                                                                                                                           |

Example body:

```json
{
  "maker_fee_bps": -2,
  "taker_fee_bps": 5,
  "tier": "base",
  "schedule": "standard",
  "volume_30d": "101005.00",
  "volume_30d_estimated": false,
  "discounts": []
}
```

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account/fees' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account/fees`

Account fee schedule.

The authenticated account's effective fee schedule — maker/taker rate (bps), fee tier, rolling 30-day traded volume, and active discounts. The forward-looking *schedule* rate, not a realized average; scoped by `schedule`.

**Legacy path.** Prefer the canonical `GET /api/v1/account/fees`. `operationId`: `fetchAccountFees`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

[`AccountFees`](/exchange-api/exchange-api/schemas#accountfees) — same fields and same example as `GET /api/v1/account/fees`.

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/fees' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /api/v1/account/rate-limit`

Get rate limit status.

Returns the authenticated caller's rate limit tier, effective request ceiling, remaining requests, and reset timestamp — the same data surface as the `X-RateLimit-*` response headers, exposed as a queryable resource. **This endpoint does not consume a rate limit token**, so it can be polled freely to self-manage request pacing without depleting the caller's budget. For HMAC API keys with a per-key rate, the reported values are the binding minimum of the key bucket and the owner bucket. For unlimited-tier callers (gateway keys), `limit`, `remaining`, and `reset_at_ms` are `null`.

**Canonical versioned form.** The bare `GET /account/rate-limit` is the legacy alias. `operationId`: `fetchRateLimitStatusV1`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Current rate-limit status for the caller. [`RateLimitStatus`](/exchange-api/exchange-api/schemas#ratelimitstatus) — all fields required:

| Field         | Type            | Description                                                                                                                                                                  |
| ------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tier`        | string          | Rate limit tier name (e.g. `pro`, `marketmaker`, `unlimited`).                                                                                                               |
| `limit`       | integer or null | Maximum requests per second. Also the burst capacity — the token bucket holds one second's worth of tokens — so `remaining` never exceeds it. `null` for the unlimited tier. |
| `remaining`   | integer or null | Requests that can be made right now before throttling (tokens currently in the bucket). `null` for the unlimited tier.                                                       |
| `reset_at_ms` | integer or null | Unix timestamp in milliseconds when the bucket refills back to `limit`; `0` when it is already full. `null` for the unlimited tier.                                          |

Example body:

```json
{
  "tier": "pro",
  "limit": 20,
  "remaining": 17,
  "reset_at_ms": 1765432100123
}
```

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account/rate-limit' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account/rate-limit`

Get rate limit status.

The caller's rate limit tier, effective request ceiling, remaining requests, and reset timestamp — the same data as the `X-RateLimit-*` headers, as a queryable resource. Does not consume a rate limit token, so it can be polled freely.

**Legacy path.** Prefer the canonical `GET /api/v1/account/rate-limit`. `operationId`: `fetchRateLimitStatus`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

[`RateLimitStatus`](/exchange-api/exchange-api/schemas#ratelimitstatus) — same fields and same example as `GET /api/v1/account/rate-limit`.

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/rate-limit' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## Orders: open orders and order history

Read-side order operations. Placement, amendment, and cancellation are on [Trading](/exchange-api/exchange-api/trading).

| Operation        | Canonical                    | Legacy                   | Returns                                                                              |
| ---------------- | ---------------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
| List open orders | `GET /api/v1/orders`         | `GET /orders`            | Array of [`Order`](/exchange-api/exchange-api/schemas#order)                         |
| Order history    | `GET /api/v1/orders/history` | `GET /orders/history`    | Array of [`OrderHistoryEntry`](/exchange-api/exchange-api/schemas#orderhistoryentry) |
| Get order by ID  | —                            | `GET /orders/{order_id}` | A single [`Order`](/exchange-api/exchange-api/schemas#order)                         |

`GET /orders/{order_id}` is **gateway-only** — the contract declares no `/api/v1` twin for it in version 0.7.2.

Open orders and order history are different schemas, not different views of one: `Order` uses capitalised side and status enums (`Buy`/`Sell`, `Open`/`PartiallyFilled`/…) and `created_at`/`updated_at`; `OrderHistoryEntry` uses lowercase sides (`buy`/`sell`), only terminal statuses, and `created_at_ms`/`completed_at_ms`.

### `GET /api/v1/orders`

List open orders.

**Canonical versioned form.** The bare `GET /orders` is the legacy alias. `operationId`: `fetchOpenOrdersV1`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

The account's open orders. An array of [`Order`](/exchange-api/exchange-api/schemas#order):

| Field              | Type                                                                         | Description                                                                                                                                                        |
| ------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`               | string (uuid)                                                                | Order ID.                                                                                                                                                          |
| `market_id`        | string                                                                       | Market the order rests on.                                                                                                                                         |
| `account_id`       | string                                                                       | Owning account.                                                                                                                                                    |
| `side`             | `Buy` / `Sell`                                                               | Order side.                                                                                                                                                        |
| `order_type`       | string                                                                       | Order type.                                                                                                                                                        |
| `limit_offset_bps` | integer or null                                                              | Fire-time limit offset in basis points, echoed for `TrailingLimit` orders (see the `OrderRequest.limit_offset_bps` placement field); `null` for other order types. |
| `price`            | decimal string                                                               | Limit price.                                                                                                                                                       |
| `quantity`         | decimal string                                                               | Original order quantity.                                                                                                                                           |
| `filled_qty`       | decimal string                                                               | Quantity filled so far.                                                                                                                                            |
| `status`           | `Open` / `PartiallyFilled` / `Filled` / `Cancelled` / `Expired` / `Rejected` | Order status.                                                                                                                                                      |
| `time_in_force`    | string                                                                       | Time-in-force policy.                                                                                                                                              |
| `created_at`       | integer (Unix ms)                                                            | Creation time.                                                                                                                                                     |
| `updated_at`       | integer (Unix ms)                                                            | Last update time.                                                                                                                                                  |

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/orders' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /orders`

List open orders.

**Legacy path.** Prefer the canonical `GET /api/v1/orders`. `operationId`: `fetchOpenOrders`. Bound to CCXT's `fetchOpenOrders` via `x-ccxt-method` — declared on this unversioned operation only.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

The account's open orders — an array of [`Order`](/exchange-api/exchange-api/schemas#order), same fields as `GET /api/v1/orders`.

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/orders' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /orders/{order_id}`

Get order by ID.

**Gateway-only.** The contract declares no `/api/v1` twin for this operation. `operationId`: `fetchOrder`. Bound to CCXT's `fetchOrder` via `x-ccxt-method`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name        | In    | Type          | Required | Description                                       |
| ----------- | ----- | ------------- | -------- | ------------------------------------------------- |
| `order_id`  | path  | string (uuid) | Yes      | The order's ID.                                   |
| `market_id` | query | string        | **Yes**  | Market the order rests on (required for routing). |

`market_id` is a **required query parameter**, not optional: the order ID alone does not identify the shard the order lives on.

#### Responses

**`200`**

The requested order — a single [`Order`](/exchange-api/exchange-api/schemas#order) object (fields as listed under `GET /api/v1/orders`).

**`401`**

Authentication failed.

**`404`**

Order not found.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/orders/cf72c7f3-1234-5678-abcd-ef0123456789?market_id=BTC-USDX-PERP' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /api/v1/orders/history`

Order history.

Terminal-status order history (filled / cancelled / rejected / expired) for the authenticated account, newest first.

**Canonical versioned form.** The bare `GET /orders/history` is the legacy alias. `operationId`: `fetchOrderHistoryV1`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name     | In    | Type                                   | Required | Description                                               |
| -------- | ----- | -------------------------------------- | -------- | --------------------------------------------------------- |
| `limit`  | query | integer (default `100`, maximum `500`) | No       | Maximum records to return.                                |
| `cursor` | query | string                                 | No       | Opaque pagination cursor — see [Pagination](#pagination). |

#### Responses

**`200`**

Order history, newest first. An array of [`OrderHistoryEntry`](/exchange-api/exchange-api/schemas#orderhistoryentry) — a terminal-status order (filled / cancelled / rejected / expired):

| Field                 | Type                                            | Description                                                                                |
| --------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `id`                  | string (uuid)                                   | Order ID.                                                                                  |
| `market_id`           | string                                          | Market the order rested on.                                                                |
| `side`                | `buy` / `sell`                                  | Order side (lowercase here, unlike `Order.side`).                                          |
| `order_type`          | string                                          | `limit` \| `market` \| `stop_*` \| `take_profit_*` \| `trailing_stop` \| `trailing_limit`. |
| `price`               | decimal string or null                          | Limit price; `null` for market orders.                                                     |
| `size`                | decimal string                                  | Original quantity.                                                                         |
| `filled_qty`          | decimal string                                  | Quantity filled.                                                                           |
| `status`              | `Filled` / `Cancelled` / `Rejected` / `Expired` | Terminal status only — no `Open` or `PartiallyFilled`.                                     |
| `cancellation_reason` | string or null                                  | Why the order was cancelled, when applicable.                                              |
| `created_at_ms`       | integer (Unix ms)                               | Creation time.                                                                             |
| `completed_at_ms`     | integer (Unix ms)                               | Time the order reached its terminal status.                                                |

Response headers:

| Header          | Type   | Description                                               |
| --------------- | ------ | --------------------------------------------------------- |
| `X-Next-Cursor` | string | Opaque cursor for the next page; absent on the last page. |

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/orders/history?limit=100' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /orders/history`

Order history.

Terminal-status order history (filled / cancelled / rejected / expired) for the authenticated account, newest first.

**Legacy path.** Prefer the canonical `GET /api/v1/orders/history`. `operationId`: `fetchOrderHistory`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name     | In    | Type                                   | Required | Description                                               |
| -------- | ----- | -------------------------------------- | -------- | --------------------------------------------------------- |
| `limit`  | query | integer (default `100`, maximum `500`) | No       | Maximum records to return.                                |
| `cursor` | query | string                                 | No       | Opaque pagination cursor — see [Pagination](#pagination). |

#### Responses

**`200`**

Order history, newest first — an array of [`OrderHistoryEntry`](/exchange-api/exchange-api/schemas#orderhistoryentry), same fields as `GET /api/v1/orders/history`. Carries `X-Next-Cursor` when more pages exist.

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/orders/history?limit=100' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## Fills

One operation on both path surfaces. Fills are trade executions resulting from order matches — each fill carries the matched price, quantity, fee, and whether it was the taker or maker side.

| Operation       | Canonical           | Legacy       |
| --------------- | ------------------- | ------------ |
| List your fills | `GET /api/v1/fills` | `GET /fills` |

### `GET /api/v1/fills`

List your fills.

Returns up to 1000 fills for the authenticated account, newest first. Fills are trade executions resulting from order matches — each fill carries the matched price, quantity, fee, and whether it was the taker or maker side.

**Canonical versioned form.** The bare `GET /fills` is the legacy alias. `operationId`: `fetchFillsV1`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name     | In    | Type                                    | Required | Description                                               |
| -------- | ----- | --------------------------------------- | -------- | --------------------------------------------------------- |
| `limit`  | query | integer (default `100`, maximum `1000`) | No       | Maximum fills to return.                                  |
| `cursor` | query | string                                  | No       | Opaque pagination cursor — see [Pagination](#pagination). |

#### Responses

**`200`**

Array of fills, newest first. Each entry is a [`Fill`](/exchange-api/exchange-api/schemas#fill) — a single trade execution for the authenticated account:

| Field            | Type              | Description                               |
| ---------------- | ----------------- | ----------------------------------------- |
| `id`             | string (uuid)     | Fill ID.                                  |
| `order_id`       | string            | Parent order ID.                          |
| `market_id`      | string            | Market (e.g. `BTC-USDX-PERP`).            |
| `side`           | `buy` / `sell`    | Side of the fill.                         |
| `price`          | decimal string    | Executed price.                           |
| `size`           | decimal string    | Executed quantity.                        |
| `fee`            | decimal string    | Fee charged in USDX.                      |
| `taker_or_maker` | `taker` / `maker` | Which side of the match this fill was.    |
| `timestamp`      | integer (Unix ms) | Execution time.                           |
| `is_liquidation` | boolean           | Whether the fill came from a liquidation. |

Example body:

```json
[
  {
    "id": "cf72c7f3-1234-5678-abcd-ef0123456789",
    "order_id": "ord_a1b2c3d4e5f6...",
    "market_id": "BTC-USDX-PERP",
    "side": "buy",
    "price": "84250.00",
    "size": "0.01",
    "fee": "0.84",
    "taker_or_maker": "taker",
    "timestamp": 1779225381434,
    "is_liquidation": false
  }
]
```

Response headers:

| Header          | Type   | Description                                               |
| --------------- | ------ | --------------------------------------------------------- |
| `X-Next-Cursor` | string | Opaque cursor for the next page; absent on the last page. |

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/fills?limit=100' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /fills`

List your fills.

Returns up to 1000 fills for the authenticated account, newest first.

**Legacy path.** Prefer the canonical `GET /api/v1/fills`. `operationId`: `fetchFills`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name     | In    | Type                                    | Required | Description                                               |
| -------- | ----- | --------------------------------------- | -------- | --------------------------------------------------------- |
| `limit`  | query | integer (default `100`, maximum `1000`) | No       | Maximum fills to return.                                  |
| `cursor` | query | string                                  | No       | Opaque pagination cursor — see [Pagination](#pagination). |

#### Responses

**`200`**

Array of fills, newest first — [`Fill`](/exchange-api/exchange-api/schemas#fill) objects with the same fields and the same example as `GET /api/v1/fills`. Carries `X-Next-Cursor` when more pages exist.

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/fills?limit=100' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## Cancel-on-disconnect

Cancel-on-disconnect (COD) is an opt-in, per-account **dead man's switch**: when the account's last authenticated `/ws` connection drops and does not reconnect within the grace window, the exchange automatically cancels all of the account's resting orders, so a crashed client cannot leave orders exposed.

Opt-in is per account and **off by default**: someone who deliberately leaves a passive resting order while offline should not have a brief blip cancel it. Enable it when you want the guarantee that a dead client cannot keep orders resting — typical for market makers and algorithmic traders.

Two important limits:

* **REST-only clients are not covered.** Clients that trade purely over REST and never open a `/ws` connection have no connection to lose, so COD never fires for them.
* **`enabled` is not `active`.** `enabled` is the account's own opt-in; `active` additionally requires the exchange-side feature switch. When `enabled` is `true` but `active` is `false`, the exchange has the feature switched off and no cancel fires on disconnect. Check `active`.

| Operation                       | Canonical                                  | Legacy                              |
| ------------------------------- | ------------------------------------------ | ----------------------------------- |
| Get cancel-on-disconnect status | `GET /api/v1/account/cancel-on-disconnect` | `GET /account/cancel-on-disconnect` |
| Set cancel-on-disconnect        | `PUT /api/v1/account/cancel-on-disconnect` | `PUT /account/cancel-on-disconnect` |

See also the [WebSocket](/exchange-api/exchange-api/websocket) page for the connection whose loss arms the switch.

### `GET /api/v1/account/cancel-on-disconnect`

Get cancel-on-disconnect status.

Returns the authenticated account's COD status. `enabled` is the account's own opt-in; `active` additionally requires the exchange-side feature switch, so it reflects whether COD will actually fire; `grace_secs` is the exchange-configured reconnect window in seconds (`null` when the feature is unavailable). Clients that trade purely over REST and never open a `/ws` connection are not covered.

**Canonical versioned form.** The bare `GET /account/cancel-on-disconnect` is the legacy alias. `operationId`: `fetchCancelOnDisconnectV1`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Current cancel-on-disconnect status for the account. [`CancelOnDisconnectStatus`](/exchange-api/exchange-api/schemas#cancelondisconnectstatus):

| Field        | Type            | Description                                                                                                                                                                                                                                        |
| ------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`    | boolean         | Required. The account's own COD opt-in setting.                                                                                                                                                                                                    |
| `active`     | boolean         | Required. Whether COD will actually fire for this account: the account opt-in **AND** the exchange-side feature switch. When `enabled` is true but `active` is false, the exchange has the feature switched off and no cancel fires on disconnect. |
| `grace_secs` | integer or null | Seconds the exchange waits after the last `/ws` disconnect before cancelling; a reconnect within the window disarms the cancel. `null` when the feature is unavailable on this deployment.                                                         |

Example body:

```json
{
  "enabled": true,
  "active": true,
  "grace_secs": 10
}
```

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/api/v1/account/cancel-on-disconnect' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /account/cancel-on-disconnect`

Get cancel-on-disconnect status.

Returns the authenticated account's COD status: the account opt-in, whether COD will actually fire, and the reconnect grace window.

**Legacy path.** Prefer the canonical `GET /api/v1/account/cancel-on-disconnect`. `operationId`: `fetchCancelOnDisconnect`.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

[`CancelOnDisconnectStatus`](/exchange-api/exchange-api/schemas#cancelondisconnectstatus) — same fields and same example as the canonical form.

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/cancel-on-disconnect' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `PUT /api/v1/account/cancel-on-disconnect`

Set cancel-on-disconnect.

Enables or disables COD for the authenticated account. Opt-in is per account and off by default. Returns the resulting COD status (same shape as the GET).

**Canonical versioned form.** The bare `PUT /account/cancel-on-disconnect` is the legacy alias. `operationId`: `setCancelOnDisconnectV1`.

**Authentication:** `hmacAuth`.

#### Request body

Required. [`SetCancelOnDisconnectRequest`](/exchange-api/exchange-api/schemas#setcancelondisconnectrequest) — a COD opt-in change for the authenticated account:

| Field     | Type    | Required | Description                                               |
| --------- | ------- | -------- | --------------------------------------------------------- |
| `enabled` | boolean | Yes      | `true` to enable COD for the account, `false` to disable. |

```json
{
  "enabled": true
}
```

#### Responses

**`200`**

The resulting cancel-on-disconnect status for the account — [`CancelOnDisconnectStatus`](/exchange-api/exchange-api/schemas#cancelondisconnectstatus), identical shape to the GET.

```json
{
  "enabled": true,
  "active": true,
  "grace_secs": 10
}
```

Setting `enabled: true` does not guarantee `active: true` — the exchange-side feature switch also has to be on. Read the returned `active`.

**`400`**

Malformed request body.

**`401`**

Authentication failed.

#### Example

```bash
curl -X PUT 'https://exchange.nexus.xyz/api/exchange/api/v1/account/cancel-on-disconnect' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{"enabled": true}'
```

### `PUT /account/cancel-on-disconnect`

Set cancel-on-disconnect.

Enables or disables COD for the authenticated account. Returns the resulting COD status.

**Legacy path.** Prefer the canonical `PUT /api/v1/account/cancel-on-disconnect`. `operationId`: `setCancelOnDisconnect`.

**Authentication:** `hmacAuth`.

#### Request body

Required. [`SetCancelOnDisconnectRequest`](/exchange-api/exchange-api/schemas#setcancelondisconnectrequest):

| Field     | Type    | Required | Description                                               |
| --------- | ------- | -------- | --------------------------------------------------------- |
| `enabled` | boolean | Yes      | `true` to enable COD for the account, `false` to disable. |

```json
{
  "enabled": true
}
```

#### Responses

**`200`**

[`CancelOnDisconnectStatus`](/exchange-api/exchange-api/schemas#cancelondisconnectstatus) — the resulting status.

**`400`**

Malformed request body.

**`401`**

Authentication failed.

#### Example

```bash
curl -X PUT 'https://exchange.nexus.xyz/api/exchange/account/cancel-on-disconnect' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{"enabled": true}'
```

## Collateral movement: deposits, withdrawals, and isolated margin

Five operations, all **gateway-only** — none has a `/api/v1` twin in version 0.7.2.

| Operation                     | Path                    | What it does                                         |
| ----------------------------- | ----------------------- | ---------------------------------------------------- |
| Deposit USDX collateral       | `POST /account/deposit` | Direct collateral deposit against the account        |
| Submit a deposit              | `POST /deposits`        | Deposit through the funds ledger                     |
| List deposits                 | `GET /deposits`         | Deposit ledger entries                               |
| List your withdrawals         | `GET /withdrawals`      | Withdrawal records                                   |
| Add or remove isolated margin | `POST /account/margin`  | Adjust allocated margin on an open isolated position |

Cross-chain deposits are a separate surface — see [Bridge](/exchange-api/exchange-api/bridge). Note the four bridge deposit paths carry the `/api/v1` prefix but, unusually, no per-path server override, so they resolve against the gateway base like the operations here.

### `POST /account/deposit`

Deposit USDX collateral.

**Gateway-only.** `operationId`: `deposit`.

**Authentication:** `hmacAuth`.

#### Request body

Required. The contract declares an example but **no schema** for this body:

| Field    | Type           | Required | Description                                                                         |
| -------- | -------------- | -------- | ----------------------------------------------------------------------------------- |
| `amount` | decimal string | Yes      | Amount of USDX collateral to deposit. Shown in the contract's example as `"10000"`. |

```json
{
  "amount": "10000"
}
```

#### Responses

**`200`**

The deposit result. The contract declares an example but no schema:

| Field     | Type           | Description           |
| --------- | -------------- | --------------------- |
| `balance` | decimal string | Post-deposit balance. |

```json
{
  "balance": "110000.00"
}
```

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/account/deposit' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{"amount": "10000"}'
```

### `POST /deposits`

Submit a deposit.

Submit a (testnet/synthetic) deposit for the authenticated account.

**Gateway-only.** `operationId`: `createDeposit`.

**Authentication:** `hmacAuth`.

#### Request body

Required. [`DepositRequest`](/exchange-api/exchange-api/schemas#depositrequest):

| Field    | Type                    | Required | Description                               |
| -------- | ----------------------- | -------- | ----------------------------------------- |
| `amount` | decimal string          | Yes      | Deposit amount (positive decimal string). |
| `asset`  | string (default `USDX`) | No       | Asset symbol; defaults to `USDX`.         |

```json
{
  "amount": "1000",
  "asset": "USDX"
}
```

#### Responses

**`200`**

Deposit acknowledged. [`DepositResponse`](/exchange-api/exchange-api/schemas#depositresponse) — an engine deposit acknowledgement, forwarded, including the updated authoritative balance. The schema sets `additionalProperties: true`, so the engine may include fields beyond the one named below; do not treat the shape as closed.

| Field     | Type           | Description                         |
| --------- | -------------- | ----------------------------------- |
| `balance` | decimal string | Authoritative post-deposit balance. |

**`400`**

Invalid amount.

**`401`**

Authentication failed.

#### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/deposits' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{"amount": "1000", "asset": "USDX"}'
```

### `GET /deposits`

List deposits.

**Gateway-only.** `operationId`: `fetchDeposits`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name    | In    | Type                                   | Required | Description                |
| ------- | ----- | -------------------------------------- | -------- | -------------------------- |
| `limit` | query | integer (default `100`, maximum `100`) | No       | Maximum records to return. |

#### Responses

**`200`**

Deposit ledger entries, newest first. An array of [`FundsEntry`](/exchange-api/exchange-api/schemas#fundsentry) — a deposit or withdrawal ledger entry:

| Field       | Type                                | Description                                                    |
| ----------- | ----------------------------------- | -------------------------------------------------------------- |
| `id`        | integer (int64)                     | Ledger entry ID.                                               |
| `kind`      | `deposit` / `withdrawal` / `faucet` | What moved the funds. Faucet claims appear in this ledger too. |
| `account`   | string                              | 0x-prefixed account address.                                   |
| `amount`    | decimal string                      | Amount moved.                                                  |
| `asset`     | string                              | Asset symbol.                                                  |
| `timestamp` | integer (Unix ms)                   | When the entry was recorded.                                   |
| `status`    | `pending` / `confirmed` / `failed`  | Entry lifecycle status.                                        |
| `tx_hash`   | string or null                      | On-chain transaction hash, when there is one.                  |

Note the status vocabulary differs from `Withdrawal.status` (`pending` / `settled` / `failed`) — the ledger entry uses `confirmed` where the withdrawal record uses `settled`.

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/deposits?limit=100' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `GET /withdrawals`

List your withdrawals.

Returns up to 100 withdrawal records for the authenticated account, newest first.

**Gateway-only.** `operationId`: `fetchWithdrawals`.

**Authentication:** `hmacAuth`.

#### Parameters

| Name    | In    | Type                                   | Required | Description                |
| ------- | ----- | -------------------------------------- | -------- | -------------------------- |
| `limit` | query | integer (default `100`, maximum `100`) | No       | Maximum records to return. |

#### Responses

**`200`**

Array of withdrawal records. Each is a [`Withdrawal`](/exchange-api/exchange-api/schemas#withdrawal) — a single withdrawal record for the authenticated account. All four fields are required:

| Field       | Type                             | Description                       |
| ----------- | -------------------------------- | --------------------------------- |
| `id`        | string                           | Withdrawal ID.                    |
| `amount`    | decimal string                   | Withdrawn amount in USDX.         |
| `timestamp` | integer (Unix ms)                | When the withdrawal was recorded. |
| `status`    | `pending` / `settled` / `failed` | Withdrawal lifecycle status.      |

Example body:

```json
[
  {
    "id": "wd_a1b2c3d4e5f6",
    "amount": "500.00",
    "timestamp": 1779225381434,
    "status": "settled"
  }
]
```

**`401`**

Authentication failed.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/withdrawals?limit=100' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

### `POST /account/margin`

Add or remove isolated margin on an open position.

**Gateway-only.** `operationId`: `adjustMargin`.

This operation applies only to positions in **isolated** margin mode — a position in cross mode is rejected with `400` (`MarginModeNotIsolated`).

**Authentication:** `hmacAuth`.

#### Request body

Required. The contract declares an example but **no schema** for this body:

| Field       | Type           | Required | Description                                                                                                                                                                                                                         |
| ----------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `market_id` | string         | Yes      | Market of the open position (e.g. `BTC-USDX-PERP`).                                                                                                                                                                                 |
| `amount`    | decimal string | Yes      | Amount of margin to move.                                                                                                                                                                                                           |
| `direction` | string         | Yes      | Direction of the adjustment. The contract's example shows `"add"`; the summary and the `400` description cover removal as well, but the accepted values are not enumerated in the contract — see [Open questions](#open-questions). |

```json
{
  "market_id": "BTC-USDX-PERP",
  "amount": "100",
  "direction": "add"
}
```

#### Responses

**`200`**

Updated allocated margin and account collateral after the adjustment. The contract declares an example but no schema:

| Field              | Type           | Description                                    |
| ------------------ | -------------- | ---------------------------------------------- |
| `market_id`        | string         | Market the adjustment applied to.              |
| `allocated_margin` | decimal string | Margin now allocated to the isolated position. |
| `collateral`       | decimal string | Account collateral after the adjustment.       |

```json
{
  "market_id": "BTC-USDX-PERP",
  "allocated_margin": "350.00",
  "collateral": "9900.00"
}
```

**`400`**

Invalid amount, position not in isolated mode (`MarginModeNotIsolated`), or removal breaches the withdrawal floor / exceeds collateral (`InsufficientMargin` / `InsufficientBalance`).

**`401`**

Authentication failed.

**`404`**

No open position for the account in this market (`NoOpenPosition`).

**`429`**

Rate limit exceeded.

#### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/account/margin' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{"market_id": "BTC-USDX-PERP", "amount": "100", "direction": "add"}'
```

## Testnet funding: synthetic credit and faucet

{% hint style="info" %}
**Testnet affordances.** These three operations mint **synthetic USDX** that carries no real-world value. They exist so that testnet integrators can fund an account without moving real assets. Nexus testnet is live; mainnet is scheduled for **2026-05-20**, and nothing on this page should be read as mainnet availability. Do not build a mainnet funding path on these endpoints — see [Bridge](/exchange-api/exchange-api/bridge) for the cross-chain deposit surface.
{% endhint %}

Two mechanisms, three operations:

| Mechanism        | Operations                                            | Allowance                                                             |
| ---------------- | ----------------------------------------------------- | --------------------------------------------------------------------- |
| Synthetic credit | `POST /api/v1/account/credit`, `POST /account/credit` | Per-API-key daily allowance, default 500 USDX, resets at midnight UTC |
| Faucet           | `POST /faucet`                                        | Fixed amount, per-wallet cooldown and cumulative cap                  |

They are separately metered: the credit allowance is per **API key** and resets daily; the faucet is per **wallet** with a cooldown and a lifetime cap. Faucet claims show up in the funds ledger as `FundsEntry.kind = "faucet"`.

### `POST /api/v1/account/credit`

Claim synthetic USDX credit.

Credit synthetic USDX to the authenticated account, up to a per-API-key daily allowance (default 500 USDX, resets at midnight UTC). `amount` is a decimal string; omit it to claim the full remaining daily allowance. Returns `429` with code `daily_limit_exceeded` once the allowance is used up, and `403` with code `credits_frozen` while crediting is administratively frozen. This is a testnet faucet: the credited USDX is synthetic and carries no real-world value.

**Canonical versioned form.** The bare `POST /account/credit` is the legacy alias. `operationId`: `creditV1`.

**Authentication:** `hmacAuth`.

#### Request body

**Optional** (`required: false`). [`CreditRequest`](/exchange-api/exchange-api/schemas#creditrequest):

| Field    | Type           | Required | Description                                                                                               |
| -------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `amount` | decimal string | No       | Synthetic USDX to credit. Omit — or omit the body entirely — to claim the full remaining daily allowance. |

```json
{
  "amount": "500"
}
```

#### Responses

**`200`**

Credit applied. [`CreditResponse`](/exchange-api/exchange-api/schemas#creditresponse) — all three fields required:

| Field            | Type           | Description                                       |
| ---------------- | -------------- | ------------------------------------------------- |
| `amount`         | decimal string | USDX credited by **this request**.                |
| `credited_today` | decimal string | Total USDX credited to this API key so far today. |
| `daily_limit`    | decimal string | Per-API-key daily credit allowance in USDX.       |

```json
{
  "amount": "500",
  "credited_today": "500",
  "daily_limit": "500"
}
```

**`401`**

Authentication failed.

**`403`**

Crediting is administratively frozen.

```json
{
  "code": "credits_frozen",
  "message": "USDX crediting is temporarily frozen by an administrator. Existing balances remain tradeable."
}
```

**`429`**

Daily credit allowance exhausted (resets at midnight UTC). Note this overrides the shared `RateLimited` meaning of `429` — it is an allowance error, not a request-pacing error.

```json
{
  "code": "daily_limit_exceeded",
  "message": "daily USDX credit allowance reached for this API key",
  "credited_today": "500",
  "daily_limit": "500"
}
```

#### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/api/v1/account/credit' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{"amount": "500"}'
```

### `POST /account/credit`

Claim synthetic USDX credit.

Credit synthetic USDX to the authenticated account, up to a per-API-key daily allowance (default 500 USDX, resets at midnight UTC). `amount` is a decimal string; omit it to claim the full remaining daily allowance. This is a testnet faucet: the credited USDX is synthetic and carries no real-world value.

**Legacy path.** Prefer the canonical `POST /api/v1/account/credit`. `operationId`: `credit`. This is the form the in-repo agent quickstart (`llms.txt`) still shows.

**Authentication:** `hmacAuth`.

#### Request body

**Optional.** [`CreditRequest`](/exchange-api/exchange-api/schemas#creditrequest) — one field, `amount` (decimal string, optional). Omit to claim the full remaining allowance.

```json
{
  "amount": "500"
}
```

#### Responses

**`200`**

Credit applied — [`CreditResponse`](/exchange-api/exchange-api/schemas#creditresponse), same fields and same example as `POST /api/v1/account/credit`.

**`401`**

Authentication failed.

**`403`**

Crediting is administratively frozen (`credits_frozen`). Existing balances remain tradeable.

**`429`**

Daily credit allowance exhausted (`daily_limit_exceeded`), resets at midnight UTC.

#### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/account/credit' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>' \
  -H 'Content-Type: application/json' \
  -d '{"amount": "500"}'
```

### `POST /faucet`

Claim testnet faucet.

Credit a fixed testnet faucet amount of synthetic USDX to the authenticated account, subject to a per-wallet cooldown and cumulative cap.

**Gateway-only.** `operationId`: `claimFaucet`. Takes no request body and no parameters.

**Authentication:** `hmacAuth`.

#### Responses

**`200`**

Faucet credited. [`FaucetResponse`](/exchange-api/exchange-api/schemas#faucetresponse) — testnet faucet credit result:

| Field             | Type              | Description                                    |
| ----------------- | ----------------- | ---------------------------------------------- |
| `amount`          | decimal string    | Amount credited.                               |
| `available_at_ms` | integer (Unix ms) | Earliest time the faucet may be claimed again. |

**`401`**

Authentication failed.

**`429`**

Cooldown not elapsed or cumulative cap reached. As with the credit endpoint, this `429` is an allowance error rather than the shared request-pacing one.

#### Example

```bash
curl -X POST 'https://exchange.nexus.xyz/api/exchange/faucet' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## ADL history

One operation. Auto-deleveraging (ADL) is what happens when a bankrupt account's loss exceeds the insurance fund: the engine closes counterparty positions to absorb the remainder. This endpoint returns the settlements where a given address was involved on either side.

The market-scoped counterpart, `GET /markets/{market_id}/adl-events`, is on [Markets](/exchange-api/exchange-api/markets).

### `GET /account/{address}/adl-history`

Get ADL events touching an account (v0.21).

Returns ADL settlements where the specified address was either the bankrupt target or one of the counterparties whose position was closed.

**Gateway-only.** `operationId`: `fetchAdlHistory`. Unlike every other operation in this tag, the account is named by an explicit path parameter rather than taken from the credentials.

**Authentication:** `hmacAuth`.

#### Parameters

| Name      | In    | Type                                    | Required | Description                        |
| --------- | ----- | --------------------------------------- | -------- | ---------------------------------- |
| `address` | path  | string                                  | Yes      | Account address (0x-prefixed hex). |
| `limit`   | query | integer (default `100`, maximum `1000`) | No       | Maximum events to return.          |

#### Responses

**`200`**

ADL events touching the account. An array of [`AdlEventRecord`](/exchange-api/exchange-api/schemas#adleventrecord) — a single ADL settlement (insurance fund depleted → counterparty closures), introduced in v0.21:

| Field                       | Type                                                                               | Description                                                          |
| --------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `market_id`                 | string                                                                             | Market the settlement occurred on.                                   |
| `target_account`            | string                                                                             | 0x-prefixed bankrupt account.                                        |
| `bankruptcy_price`          | decimal string                                                                     | Price at which the target account went bankrupt.                     |
| `bad_debt_absorbed_by_fund` | decimal string                                                                     | Loss the insurance fund absorbed before counterparties were touched. |
| `counterparty_closures`     | array of [`AdlClosureRecord`](/exchange-api/exchange-api/schemas#adlclosurerecord) | The forced closures that covered the remainder.                      |
| `sequence`                  | integer                                                                            | Engine event sequence number.                                        |
| `timestamp`                 | integer (Unix ms)                                                                  | Settlement time.                                                     |

Each [`AdlClosureRecord`](/exchange-api/exchange-api/schemas#adlclosurerecord) — one counterparty's forced closure within an ADL settlement:

| Field               | Type           | Description                                                        |
| ------------------- | -------------- | ------------------------------------------------------------------ |
| `account_id`        | string         | 0x-prefixed address of the counterparty whose position was closed. |
| `position_closed`   | decimal string | Decimal quantity closed.                                           |
| `settlement_amount` | decimal string | Decimal amount charged to the counterparty.                        |

**`401`**

Authentication failed.

**`429`**

Rate limit exceeded.

#### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/account/0x1234567890AbCdEf1234567890AbCdEf12345678/adl-history?limit=100' \
  -H 'X-API-Key: nx_a1b2c3d4e5f67890' \
  -H 'X-Timestamp: 1776033911836' \
  -H 'X-Signature: <hmac-sha256>'
```

## The v1 / legacy pairs at a glance

Thirteen pairs. Neither variant is flagged `deprecated` in version 0.7.2; both are live and return identical bodies.

| Canonical (`/api/v1`)                      | Legacy (root path)                  | `operationId` pair                                      |
| ------------------------------------------ | ----------------------------------- | ------------------------------------------------------- |
| `GET /api/v1/account`                      | `GET /account`                      | `fetchBalanceV1` / `fetchBalance`                       |
| `GET /api/v1/account/summary`              | `GET /account/summary`              | `fetchAccountSummaryV1` / `fetchAccountSummary`         |
| `GET /api/v1/account/state`                | `GET /account/state`                | `fetchAccountStateV1` / `fetchAccountState`             |
| `GET /api/v1/account/equity-history`       | `GET /account/equity-history`       | `fetchEquityHistoryV1` / `fetchEquityHistory`           |
| `GET /api/v1/account/portfolio-history`    | `GET /account/portfolio-history`    | `fetchPortfolioHistoryV1` / `fetchPortfolioHistory`     |
| `GET /api/v1/account/fees`                 | `GET /account/fees`                 | `fetchAccountFeesV1` / `fetchAccountFees`               |
| `GET /api/v1/account/rate-limit`           | `GET /account/rate-limit`           | `fetchRateLimitStatusV1` / `fetchRateLimitStatus`       |
| `GET /api/v1/account/cancel-on-disconnect` | `GET /account/cancel-on-disconnect` | `fetchCancelOnDisconnectV1` / `fetchCancelOnDisconnect` |
| `PUT /api/v1/account/cancel-on-disconnect` | `PUT /account/cancel-on-disconnect` | `setCancelOnDisconnectV1` / `setCancelOnDisconnect`     |
| `GET /api/v1/orders`                       | `GET /orders`                       | `fetchOpenOrdersV1` / `fetchOpenOrders`                 |
| `GET /api/v1/orders/history`               | `GET /orders/history`               | `fetchOrderHistoryV1` / `fetchOrderHistory`             |
| `POST /api/v1/account/credit`              | `POST /account/credit`              | `creditV1` / `credit`                                   |
| `GET /api/v1/fills`                        | `GET /fills`                        | `fetchFillsV1` / `fetchFills`                           |

The remaining eight operations in this tag are **gateway-only**: `GET /orders/{order_id}`, `POST /account/deposit`, `POST /account/margin`, `POST /deposits`, `GET /deposits`, `GET /withdrawals`, `POST /faucet`, and `GET /account/{address}/adl-history`.

The `x-ccxt-method` bindings (`fetchBalance`, `fetchOpenOrders`, `fetchOrder`) are declared on the **unversioned** operations only. The versioned twins return identical bodies but carry no CCXT annotation.

## Related

* [Authentication](/exchange-api/exchange-api/authentication) — the HMAC signing scheme and API key lifecycle.
* [Trading](/exchange-api/exchange-api/trading) — order placement, amendment, and cancellation.
* [Positions](/exchange-api/exchange-api/positions) — open and closed position queries, and the full `Position` field reference.
* [WebSocket](/exchange-api/exchange-api/websocket) — the connection whose loss arms cancel-on-disconnect, and push updates for orders, fills, and account state.
* [Admin](/exchange-api/exchange-api/admin) — operator-only rate-limit tier management.
* [Schemas](/exchange-api/exchange-api/schemas) — every component schema in the contract.
* [Margin math](/math-engine/margin-math) — how initial margin, maintenance margin, and the liquidation price are defined.

## Open questions

Gaps and inconsistencies in the contract, flagged rather than filled:

* **`direction` is not enumerated.** `POST /account/margin` declares no request schema at all. Its example shows `"direction": "add"`, and the summary and `400` description imply removal is supported, but the contract never states the value for removal. Nor does it name the field types or mark any field required.
* **Three operations declare no schema.** `POST /account/deposit` (request and response) and `POST /account/margin` (request and response) carry examples only. Clients generated from the contract get untyped bodies for these.
* **Two overlapping deposit paths.** `POST /account/deposit` and `POST /deposits` both deposit collateral, return different shapes (`{balance}` vs `DepositResponse`), and the contract does not say which to prefer or how they differ operationally.
* **Status vocabularies disagree.** `Withdrawal.status` is `pending` / `settled` / `failed`; `FundsEntry.status` is `pending` / `confirmed` / `failed`. The same lifecycle is named two ways.
* **`GET /orders/{order_id}` has no `/api/v1` twin.** A client standardising on the versioned surface must fall back to the gateway path for single-order lookup.
* **Rate-limit tier casing is inconsistent.** `RateLimitStatus.tier` is documented lowercase (`pro`, `marketmaker`, `unlimited`); the `429` body example returns `"tier": "Pro"`; the admin tier-management operations use `MarketMaker` and `Pro`. The contract does not state a canonical casing.
* **`429` coverage is uneven.** Several operations that are subject to the same rate-limit layer declare only `200` and `401` — both equity-history operations, both order-history operations, both cancel-on-disconnect GETs and PUTs, `GET /withdrawals`, `GET /deposits`, `POST /deposits`, `GET /orders/{order_id}`, and both rate-limit-status operations.
* **Retention windows unstated.** Cursor pagination is bounded by "the retained window" and `volume_30d` may undercount when the fill buffer is at capacity, but no retention period or buffer size is given.
* **`limit` maximums are lower than the prose in two places.** `GET /withdrawals` and `GET /deposits` both cap `limit` at 100 with a default of 100, so the parameter can only reduce the page — there is no way to page past 100 records (neither operation accepts a `cursor`).
* **`EquityPoint.equity` is a JSON number.** Every other monetary field in this tag is a lossless decimal string. The contract itself notes the mismatch against `PortfolioPoint.equity` and tells clients to compare by decimal value, but the float representation remains on the wire.
* **`early_access_allowed` is present-or-absent, not true-or-false.** The contract says it appears "only when the early-access gate is active" without stating what governs the gate or what its absence means for a caller.
* **Fee model is a draft.** `AccountFees.tier` is always `base`, `schedule` is always `standard`, `discounts` is always empty, and `FeeDiscount` has no defined properties. Per-market effective rates are described as "a planned follow-up" with no date.
* **`Position.leverage` is permanently `null` today** on every response that embeds a position (`AccountSummary`, `AccountState`), with `leverage_error: "margin_state_not_mirrored"`. See [Positions](/exchange-api/exchange-api/positions).


# WebSocket

Real-time streaming via short-lived tokens. One connection carries both public market data (`trades`, `book`, `candles`) and per-account private channels (`orders`, `fills`, `positions`, `balances`), and every message on the wire is a JSON envelope tagged with an `op` field.

Browsers cannot attach custom headers to a WebSocket upgrade, so the stream is not authenticated with HMAC headers. Instead you mint a short-lived, single-use token over an ordinary signed REST call and present it as a query parameter on the upgrade. Your HMAC secret never crosses the WebSocket boundary.

There are two generations of endpoints in the contract:

| Generation  | Mint              | Connect       | Channels                   |
| ----------- | ----------------- | ------------- | -------------------------- |
| **Current** | `POST /ws/token`  | `GET /ws`     | Public **and** per-account |
| Legacy      | `POST /ws-tokens` | `GET /stream` | Public only (plus `stats`) |

Prefer the current pair. The legacy pair is documented below because it is still served and uses a different, incompatible subscribe message format.

## Bases

REST operations on this page are served from the API base:

```
https://exchange.nexus.xyz/api/exchange
http://localhost:9090
```

The `/ws/token` and `/ws` routes are also mounted under the versioned `/api/v1` surface the indexer serves directly, but the WebSocket quickstarts and the API docs page both use the unversioned `/api/exchange` base, so that is the base shown throughout this page.

> **Not stated in the contract:** the OpenAPI document does not publish a canonical `wss://` origin for the stream. The WebSocket host is configured per deployment (the frontend reads it from `NEXT_PUBLIC_INDEXER_WS_URL`), so examples below use `wss://<indexer-host>` — substitute the host your environment publishes. Testnet is live; mainnet is scheduled for **2026-05-20**.

## The flow end to end

```
1. POST /auth/login        (wallet signature)   → session Bearer token
2. POST /keys              (Bearer)             → API key_id + secret
3. POST /ws/token          (HMAC-signed)        → 60s single-use token
4. GET  wss://<host>/ws?token=TOKEN             → 101 Switching Protocols
5. {"op": "subscribe", ...}                     → {"op": "subscribed", ...}
6. {"op": "event", ...}                         → stream
```

Steps 1 and 2 are one-time setup and are covered on the [Authentication](/exchange-api/exchange-api/authentication) page. Steps 3–6 are this page.

### 1. Mint a token

`POST /ws/token`, signed with your API key. Returns `{"token": "..."}`. The token is **single-use** and **expires after 60 seconds**, so mint one per connection — including on every reconnect.

The token encodes the account identity of the credential that minted it. That is what makes the per-account channels work: you never send an account id when subscribing to `orders`, `fills`, `positions`, or `balances` — they are scoped automatically to the wallet behind the token.

### 2. Connect

Open a standard WebSocket to `/ws` with the token as a query parameter:

```
wss://<indexer-host>/ws?token=YOUR_TOKEN
```

The token is required to upgrade, for public channels as well as private ones.

### 3. Subscribe

Every client→server and server→client message is a JSON envelope tagged with an `op` field. Subscribe to **one channel per message**. Market channels take a `market` field; per-account channels do not.

```json
{"op": "subscribe", "channel": "fills"}
{"op": "subscribe", "channel": "trades", "market": "BTC-USDX-PERP"}
```

Unsubscribe with the same shape:

```json
{"op": "unsubscribe", "channel": "trades", "market": "BTC-USDX-PERP"}
```

## Channels

| Channel     | `market` field | Scope       | Description                                      |
| ----------- | -------------- | ----------- | ------------------------------------------------ |
| `trades`    | required       | Public      | Trades on a market                               |
| `book`      | required       | Public      | Order book updates on a market                   |
| `candles`   | required       | Public      | Candle updates on a market                       |
| `orders`    | —              | Per-account | Your open order updates (new, filled, cancelled) |
| `fills`     | —              | Per-account | Your trade executions                            |
| `positions` | —              | Per-account | Your open position updates                       |
| `balances`  | —              | Per-account | Your account balance updates                     |

Public channels still require a token to upgrade the connection. Per-account channels are scoped to the wallet that minted the token.

The `stats` channel exists only on the legacy `GET /stream` endpoint.

## Server messages

| Message                                                                          | When sent                                                                                                                          |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `{"op": "subscribed", "channel": "fills", "market": null, "seq_at_join": 42}`    | Ack for a successful subscribe. `seq_at_join` is the channel's current sequence at attach time — keep it as your reconnect cursor. |
| `{"op": "unsubscribed", "channel": "fills", "market": null}`                     | Ack for an unsubscribe.                                                                                                            |
| `{"op": "event", "channel": "fills", "market": null, "seq": 43, "payload": {…}}` | A delivered event. `seq` is monotonic per channel.                                                                                 |
| `{"op": "out_of_sync", "channel": "fills", "market": null, "oldest_seq": 100}`   | The `since` cursor you requested fell behind the replay buffer. Refetch state over REST and resubscribe from a fresh cursor.       |
| `{"op": "error", "message": "…"}`                                                | Invalid op, unknown channel, or bad message format.                                                                                |

## Message payloads

Events arrive in the `op: "event"` envelope with `channel`, `market`, `seq`, and `payload`. `seq` is monotonic per channel.

The contract does **not** declare a schema for `payload` — it is documented by example. The examples below are the ones the API docs page publishes; treat the REST schemas as the authoritative field reference for the equivalent resources ([Fill](/exchange-api/exchange-api/schemas#fill), [Order](/exchange-api/exchange-api/schemas#order), [Position](/exchange-api/exchange-api/schemas#position), [AccountSummary](/exchange-api/exchange-api/schemas#accountsummary), [Trade](/exchange-api/exchange-api/schemas#trade), [OrderBook](/exchange-api/exchange-api/schemas#orderbook)).

Event envelope (all channels):

```json
{
  "op": "event",
  "channel": "fills",
  "market": null,
  "seq": 42,
  "payload": {
    "id": "cf72c7f3-...",
    "order_id": "ord_a1b2...",
    "market_id": "BTC-USDX-PERP",
    "side": "buy",
    "price": "84250.00",
    "size": "0.01",
    "fee": "0.84",
    "taker_or_maker": "taker",
    "timestamp": 1779225381434,
    "is_liquidation": false
  }
}
```

`orders`:

```json
{
  "op": "event",
  "channel": "orders",
  "market": null,
  "seq": 7,
  "payload": {
    "id": "ord_a1b2c3d4...",
    "market_id": "BTC-USDX-PERP",
    "side": "Buy",
    "order_type": "Limit",
    "price": "83000.00",
    "quantity": "0.01",
    "filled_qty": "0.00",
    "status": "Open",
    "time_in_force": "GTC",
    "created_at": 1779225381000,
    "updated_at": 1779225381000
  }
}
```

`positions`:

```json
{
  "op": "event",
  "channel": "positions",
  "market": null,
  "seq": 15,
  "payload": {
    "market_id": "BTC-USDX-PERP",
    "side": "Long",
    "size": "0.5",
    "entry_price": "83000.00",
    "unrealized_pnl": "612.50",
    "realized_pnl": "0.00",
    "liquidation_price": "72000.00"
  }
}
```

`balances`:

```json
{
  "op": "event",
  "channel": "balances",
  "market": null,
  "seq": 3,
  "payload": {
    "balance": "9999.16",
    "collateral": "9999.16",
    "equity": "10611.66",
    "available_margin": "9584.16"
  }
}
```

`trades`:

```json
{
  "op": "event",
  "channel": "trades",
  "market": "BTC-USDX-PERP",
  "seq": 1651,
  "payload": {
    "id": "cf72c7f3-...",
    "symbol": "BTC-USDX-PERP",
    "price": 84250.5,
    "amount": 0.033,
    "cost": 2780.27,
    "side": "buy",
    "timestamp": 1779033942331,
    "is_liquidation": false
  }
}
```

`book`:

```json
{
  "op": "event",
  "channel": "book",
  "market": "BTC-USDX-PERP",
  "seq": 3001,
  "payload": {
    "symbol": "BTC-USDX-PERP",
    "bids": [[84249.0, 1.4], [84248.0, 2.1]],
    "asks": [[84251.0, 0.8], [84252.0, 1.2]],
    "timestamp": 1779033950000,
    "nonce": 3001
  }
}
```

Note the two naming families: per-account payloads use `snake_case` keys with decimal **strings** (`"84250.00"`), while the CCXT-shaped public `trades` and `book` payloads use CCXT keys and JSON numbers. Parse decimal strings with a decimal type, never a float — see the [schema conventions](/exchange-api/exchange-api/schemas#conventions).

## Reconnection and gap recovery

Tokens are single-use, so a reconnect always starts with a fresh `POST /ws/token`.

To resume a channel without gaps, pass the last `seq` you received (or the `seq_at_join` from your original subscribe ack) as `since`:

```json
{"op": "subscribe", "channel": "fills", "since": 42}
```

If the requested cursor has already fallen out of the server's replay buffer you receive `{"op": "out_of_sync", …, "oldest_seq": N}`. Recover by refetching current state over REST and resubscribing from a fresh cursor rather than assuming continuity.

> **Not stated in the contract.** The OpenAPI document declares the reconnect *mechanism* (single-use tokens, `since` cursors, `out_of_sync`) but not the operational policy around it. It does **not** specify: a reconnect backoff or retry schedule, a heartbeat / ping-pong interval or idle timeout, the depth of the per-channel replay buffer behind `out_of_sync`, a maximum number of subscriptions per connection, or a maximum number of concurrent connections. Do not assume values for these — they are open questions against the spec, not defaults.

## Cancel-on-disconnect

Cancel-on-disconnect is keyed off `/ws` disconnects: when armed for an account, the exchange waits a grace period after the last `/ws` disconnect and then cancels the account's resting orders, and a reconnect inside the window disarms the cancel. The account opt-in and the exchange-side feature switch are reported separately — see [CancelOnDisconnectStatus](/exchange-api/exchange-api/schemas#cancelondisconnectstatus) and [SetCancelOnDisconnectRequest](/exchange-api/exchange-api/schemas#setcancelondisconnectrequest).

***

## `POST /ws/token`

Mint a WebSocket token.

Returns a short-lived (60s), single-use token bound to the authenticated account. Pass it as `?token=TOKEN` when upgrading to `GET /ws`. Supports HMAC keys, registered agent keys, and session tokens (Bearer). The token encodes the account identity so per-account channels (orders, fills, positions, balances) are automatically scoped to the connected wallet.

**Authentication:** `hmacAuth` — `X-API-Key`, `X-Timestamp`, `X-Signature`. The operation description additionally states that registered agent keys and Bearer session tokens are accepted; the contract's `security` block for this operation lists only `hmacAuth`.

**Operation id:** `createWsTokenLegacy` (note: the contract's `operationId` values for the two mint endpoints read as transposed relative to their summaries — `POST /ws/token` carries `createWsTokenLegacy` and the legacy `POST /ws-tokens` carries `createWsToken`).

### Responses

| Status | Description                                |
| ------ | ------------------------------------------ |
| `200`  | Token minted. `{"token": "a1b2c3d4e5f6…"}` |
| `401`  | Authentication required.                   |

### Example

```bash
API_BASE="https://exchange.nexus.xyz/api/exchange"
KEY_ID="nx_a1b2c3..."
SECRET_HEX="..."          # hex-encoded secret, shown once at key creation

TS=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)
CANONICAL=$(printf '%s\nPOST\n/ws/token\n\n%s' "$TS" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$SECRET_HEX" -hex | awk '{print $NF}')

curl -sS -X POST "$API_BASE/ws/token" \
  -H "X-API-Key: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG"
```

```json
{"token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6"}
```

## `GET /ws`

Per-account WebSocket stream.

WebSocket endpoint for both public market data and per-account private channels. Requires a token from `POST /ws/token`. Tokens are single-use and expire after 60 seconds. The subscribe / event / control message shapes are documented in [Channels](#channels), [Server messages](#server-messages), and [Message payloads](#message-payloads) above.

**Authentication:** None on the upgrade itself — the `token` query parameter carries the authenticated identity.

### Parameters

| Name    | In    | Type     | Required | Description                              |
| ------- | ----- | -------- | -------- | ---------------------------------------- |
| `token` | query | `string` | Yes      | Short-lived token from `POST /ws/token`. |

### Responses

| Status | Description                              |
| ------ | ---------------------------------------- |
| `101`  | Switching Protocols — WebSocket upgrade. |

### Example

```bash
TOKEN=$(curl -sS -X POST "$API_BASE/ws/token" \
  -H "X-API-Key: $KEY_ID" -H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')

# websocat sends each stdin line as a frame.
printf '%s\n%s\n' \
  '{"op":"subscribe","channel":"trades","market":"BTC-USDX-PERP"}' \
  '{"op":"subscribe","channel":"fills"}' \
  | websocat "wss://<indexer-host>/ws?token=$TOKEN"
```

## `POST /ws-tokens`

Mint a WebSocket token (legacy).

Legacy endpoint. Prefer `POST /ws/token`, which supports both HMAC keys and registered agents. Returns a short-lived (60s), single-use token for the public `/stream` endpoint.

**Authentication:** `hmacAuth` — `X-API-Key`, `X-Timestamp`, `X-Signature`.

### Responses

| Status | Description                                                           |
| ------ | --------------------------------------------------------------------- |
| `200`  | A minted WebSocket authentication token. `{"token": "a1b2c3d4e5f6…"}` |
| `401`  | HMAC authentication required.                                         |

### Example

```bash
TS=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)
CANONICAL=$(printf '%s\nPOST\n/ws-tokens\n\n%s' "$TS" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$SECRET_HEX" -hex | awk '{print $NF}')

curl -sS -X POST "$API_BASE/ws-tokens" \
  -H "X-API-Key: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG"
```

## `GET /stream`

Public WebSocket stream (legacy).

Legacy public-only WebSocket endpoint. Prefer `GET /ws`, which also supports per-account channels. Requires a token from `POST /ws-tokens`.

Unlike `/ws`, the protocol is a **single untagged subscribe message** listing channels — there is no `op` envelope:

```json
{"subscribe": ["trades:*", "book:BTC-USDX-PERP", "stats"]}
```

Channels: `trades:*` / `trades:{market_id}`, `book:*` / `book:{market_id}`, and `stats`. `stats` is the only channel that exists on `/stream` and not on `/ws`; the contract does not declare its payload shape here, but the equivalent REST resource is [StatsSnapshot](/exchange-api/exchange-api/schemas#statssnapshot).

**Authentication:** None on the upgrade itself — the `token` query parameter carries the identity.

### Parameters

| Name    | In    | Type     | Required | Description                               |
| ------- | ----- | -------- | -------- | ----------------------------------------- |
| `token` | query | `string` | Yes      | Short-lived token from `POST /ws-tokens`. |

### Responses

| Status | Description                              |
| ------ | ---------------------------------------- |
| `101`  | Switching Protocols — WebSocket upgrade. |

### Example

```bash
TOKEN=$(curl -sS -X POST "$API_BASE/ws-tokens" \
  -H "X-API-Key: $KEY_ID" -H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')

printf '%s\n' '{"subscribe":["trades:*","stats"]}' \
  | websocat "wss://<indexer-host>/stream?token=$TOKEN"
```

***

## Browser clients: the gateway's `/api/ws-token` helper

A browser has no API secret, so it cannot sign `POST /ws/token` itself. For that case the gateway app exposes its own helper route, a **sibling of** `/api/exchange` rather than a path underneath it:

```
GET https://exchange.nexus.xyz/api/ws-token
```

It takes no request body and no credentials from the caller. Server-side it HMAC-signs `POST /ws/token` with the deployment's own operator key and returns the resulting `{"token": "…"}` to the browser, which then opens `wss://<indexer-host>/ws?token=…`. No secret ever reaches the browser.

Two things to keep straight:

* **It is not part of the OpenAPI contract.** It is a gateway route handler, not an operation in `openapi.json`. Programmatic clients that hold a key should call the contract operation `POST /ws/token` under the `/api/exchange` base instead.
* **The path is derived, not fixed.** The API docs page computes it from its API base by swapping the trailing segment — `…/api/exchange` → `…/api/ws-token` — so on a deployment whose API base is not `/api/exchange`, the helper moves with it.

## See also

* [Authentication](/exchange-api/exchange-api/authentication) — session login, API keys, and the HMAC canonical string
* [Schema Reference](/exchange-api/exchange-api/schemas) — shared field definitions and wire conventions


# Bridge

Cross-chain deposits: bridgeable assets, per-account deposit addresses, and deposit tracking. **Phase A** of the bridge surface covers USDC and USDX deposits; USDT is out of scope for this cut.

The model is deposit-address based. You ask the exchange for a deposit address on a source chain, send a supported asset to it, and a watcher detects the transfer, waits for the chain's required confirmations, and credits your Nexus account. The watcher owns the deposit records — the endpoints on this page are a read model over them, plus the one call that allocates an address.

> **Status.** These four operations are declared in the API contract (v0.7.2) and are documented here as the contract reference. They are **not yet served on the testnet gateway** — a request to `/api/v1/bridge/assets` there currently returns `NOT_FOUND`. Treat this page as the interface Phase A will expose, not as a surface you can call today. Withdrawal endpoints are a later phase and are not in the contract at all, although `withdraw_assets` in the asset catalog already lists the eventual capability. Nexus testnet is live; **mainnet is scheduled for 2026-05-20**, and nothing on this page should be read as mainnet availability.

## Base

The bridge paths are on the versioned `/api/v1` surface, appended to the gateway base like every other path in this section:

```
https://exchange.nexus.xyz/api/exchange
http://localhost:9090
```

So `GET /api/v1/bridge/assets` resolves to `https://exchange.nexus.xyz/api/exchange/api/v1/bridge/assets`. Examples on this page use `$API_BASE` for the base.

Unlike the other 29 `/api/v1` paths, these four do not declare a path-level `servers` override in the contract. That makes no practical difference: as [the section overview](/exchange-api/exchange-api#base-urls) explains, the override names the service's own address behind the gateway and is not publicly routable, so the gateway base is correct for every path either way.

## The deposit flow

```
1. GET  /api/v1/bridge/assets                → which chains and assets are supported
2. POST /api/v1/bridge/deposit-addresses     → your address on a chain (idempotent)
3. send USDC or USDX to that address on the source chain
4. GET  /api/v1/bridge/deposits              → watch the record advance
```

Step 1 is a public catalog and needs no credentials. Steps 2 and 4 are authenticated and scoped to the calling account.

## Deposit lifecycle

A [BridgeDeposit](/exchange-api/exchange-api/schemas#bridgedeposit) record advances through exactly these states, as declared by the contract:

```
detected → confirming → credited
                     ↘ failed
```

| Status       | Meaning                                                                                     |
| ------------ | ------------------------------------------------------------------------------------------- |
| `detected`   | The watcher has seen the transfer and opened a record.                                      |
| `confirming` | The transfer is accumulating block confirmations.                                           |
| `credited`   | The required confirmations were reached and the account was credited. `credited_at` is set. |
| `failed`     | Terminal failure.                                                                           |

Progress is observable on the record itself: `confirmations` counts what has been observed so far (`null` before the transaction is seen on chain) against `required_confirmations`, and `tx_hash` is `null` until detection. The per-chain, per-asset confirmation requirement is also published up front in the asset catalog as `BridgeAsset.confirmations`.

Deposits are listed newest first, and the list endpoint filters on `chain`, `asset`, and `status`.

## Errors

All non-2xx responses on the `/v1/bridge` surface return the [BridgeError](/exchange-api/exchange-api/schemas#bridgeerror) envelope: an `error` object with a stable machine-readable `code` (snake\_case, e.g. `unsupported_chain`, `amount_below_minimum`, `deposit_not_found`), a human-readable `message` that is not intended for programmatic matching, and optional structured `details`. Match on `code`, never on `message`.

`401` responses are the one exception — authentication failures return the same opaque `{"code": "unauthorized"}` body across the whole API so that nothing leaks about why the credential was rejected.

***

## `GET /api/v1/bridge/assets`

List bridgeable chains and assets.

Returns the supported chains and, per chain, the depositable assets (USDC, USDX) and withdrawable assets (USDX) with their decimals, minimum amounts, required confirmations, and fees. Public catalog — no authentication required.

**Authentication:** None.

### Responses

| Status | Body                                                                            | Description                                                               |
| ------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `200`  | [BridgeAssetsResponse](/exchange-api/exchange-api/schemas#bridgeassetsresponse) | Supported bridge chains and assets.                                       |
| `429`  | `{"code": "RateLimitExceeded", "tier": "…"}`                                    | Rate limit exceeded. Check the `X-RateLimit-*` headers and `Retry-After`. |

### Example

```bash
API_BASE="https://exchange.nexus.xyz/api/exchange"

curl -sS "$API_BASE/api/v1/bridge/assets"
```

## `POST /api/v1/bridge/deposit-addresses`

Get or create a deposit address.

Get-or-create a deposit address for the authenticated account on a chain. Idempotent per `(account, chain)`: repeated calls return the same address rather than allocating a new one.

**Authentication:** `hmacAuth` — `X-API-Key`, `X-Timestamp`, `X-Signature`.

### Request body

Required. [CreateBridgeDepositAddressRequest](/exchange-api/exchange-api/schemas#createbridgedepositaddressrequest):

| Field   | Type     | Required | Description                                                                                                                              |
| ------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `chain` | `string` | Yes      | Chain to get-or-create a deposit address on. The operation is idempotent per `(account, chain)`: repeated calls return the same address. |

```json
{"chain": "ethereum"}
```

### Responses

| Status | Body                                                                            | Description                                                                      |
| ------ | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `200`  | [BridgeDepositAddress](/exchange-api/exchange-api/schemas#bridgedepositaddress) | The account's deposit address for the chain (existing or newly created).         |
| `400`  | [BridgeError](/exchange-api/exchange-api/schemas#bridgeerror)                   | The request was invalid (e.g. unsupported chain or asset, amount below minimum). |
| `401`  | `{"code": "unauthorized"}`                                                      | Authentication failed.                                                           |
| `429`  | `{"code": "RateLimitExceeded", "tier": "…"}`                                    | Rate limit exceeded.                                                             |

### Example

```bash
API_BASE="https://exchange.nexus.xyz/api/exchange"
KEY_ID="nx_a1b2c3..."
SECRET_HEX="..."          # hex-encoded secret, shown once at key creation

PATH_ONLY="/api/v1/bridge/deposit-addresses"
BODY='{"chain":"ethereum"}'
TS=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
CANONICAL=$(printf '%s\nPOST\n%s\n\n%s' "$TS" "$PATH_ONLY" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$SECRET_HEX" -hex | awk '{print $NF}')

curl -sS -X POST "$API_BASE$PATH_ONLY" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG" \
  -d "$BODY"
```

## `GET /api/v1/bridge/deposit-addresses`

List deposit addresses.

List the authenticated account's deposit addresses across chains.

**Authentication:** `hmacAuth` — `X-API-Key`, `X-Timestamp`, `X-Signature`.

### Responses

| Status | Body                                                                                     | Description                      |
| ------ | ---------------------------------------------------------------------------------------- | -------------------------------- |
| `200`  | array of [BridgeDepositAddress](/exchange-api/exchange-api/schemas#bridgedepositaddress) | The account's deposit addresses. |
| `401`  | `{"code": "unauthorized"}`                                                               | Authentication failed.           |
| `429`  | `{"code": "RateLimitExceeded", "tier": "…"}`                                             | Rate limit exceeded.             |

### Example

```bash
PATH_ONLY="/api/v1/bridge/deposit-addresses"
TS=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)
CANONICAL=$(printf '%s\nGET\n%s\n\n%s' "$TS" "$PATH_ONLY" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$SECRET_HEX" -hex | awk '{print $NF}')

curl -sS "$API_BASE$PATH_ONLY" \
  -H "X-API-Key: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG"
```

## `GET /api/v1/bridge/deposits`

List bridge deposits.

List the authenticated account's cross-chain deposits, newest first. The watcher creates and advances these records.

**Authentication:** `hmacAuth` — `X-API-Key`, `X-Timestamp`, `X-Signature`.

### Parameters

| Name     | In    | Type      | Required | Description                                                                      |
| -------- | ----- | --------- | -------- | -------------------------------------------------------------------------------- |
| `limit`  | query | `integer` | No       | Maximum records to return. Default `100`, maximum `100`.                         |
| `chain`  | query | `string`  | No       | Filter by source chain.                                                          |
| `asset`  | query | `string`  | No       | Filter by deposited asset. One of `USDC`, `USDX`.                                |
| `status` | query | `string`  | No       | Filter by deposit status. One of `detected`, `confirming`, `credited`, `failed`. |

### Responses

| Status | Body                                                                       | Description                    |
| ------ | -------------------------------------------------------------------------- | ------------------------------ |
| `200`  | array of [BridgeDeposit](/exchange-api/exchange-api/schemas#bridgedeposit) | Bridge deposits, newest first. |
| `401`  | `{"code": "unauthorized"}`                                                 | Authentication failed.         |
| `429`  | `{"code": "RateLimitExceeded", "tier": "…"}`                               | Rate limit exceeded.           |

### Example

```bash
PATH_ONLY="/api/v1/bridge/deposits"
QUERY="status=confirming&limit=50"
TS=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)
CANONICAL=$(printf '%s\nGET\n%s\n%s\n%s' "$TS" "$PATH_ONLY" "$QUERY" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$SECRET_HEX" -hex | awk '{print $NF}')

curl -sS "$API_BASE$PATH_ONLY?$QUERY" \
  -H "X-API-Key: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG"
```

The query string is part of the HMAC canonical string — sign the exact query you send, byte for byte.

## `GET /api/v1/bridge/deposits/{id}`

Get a bridge deposit.

Fetch a single cross-chain deposit by id. Only deposits owned by the authenticated account are returned.

**Authentication:** `hmacAuth` — `X-API-Key`, `X-Timestamp`, `X-Signature`.

### Parameters

| Name | In   | Type     | Required | Description                            |
| ---- | ---- | -------- | -------- | -------------------------------------- |
| `id` | path | `string` | Yes      | Deposit identifier. Opaque and stable. |

### Responses

| Status | Body                                                              | Description                                                                |
| ------ | ----------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `200`  | [BridgeDeposit](/exchange-api/exchange-api/schemas#bridgedeposit) | The deposit.                                                               |
| `401`  | `{"code": "unauthorized"}`                                        | Authentication failed.                                                     |
| `404`  | [BridgeError](/exchange-api/exchange-api/schemas#bridgeerror)     | The resource does not exist, or is not owned by the authenticated account. |
| `429`  | `{"code": "RateLimitExceeded", "tier": "…"}`                      | Rate limit exceeded.                                                       |

Note that a deposit belonging to another account is reported as `404`, not `403` — ownership is folded into existence so the endpoint does not confirm that an id exists.

### Example

```bash
DEPOSIT_ID="dep_01H..."
PATH_ONLY="/api/v1/bridge/deposits/$DEPOSIT_ID"
TS=$(python3 -c 'import time; print(int(time.time()*1000))')
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)
CANONICAL=$(printf '%s\nGET\n%s\n\n%s' "$TS" "$PATH_ONLY" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC \
  -macopt "hexkey:$SECRET_HEX" -hex | awk '{print $NF}')

curl -sS "$API_BASE$PATH_ONLY" \
  -H "X-API-Key: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG"
```

## Open questions against the contract

The contract declares the deposit interface but not the operational envelope around it. It does **not** state:

* which chains Phase A ships with — `BridgeChainAssets.chain` is an open string with `ethereum` and `base` given only as examples, and the concrete list comes from the deployment's asset catalog at runtime
* per-chain confirmation counts, minimum amounts, or fees — these are runtime values in the catalog response, not fixed in the spec
* an expected time-to-credit
* whether deposit addresses can be rotated or retired
* what a `failed` deposit means for the funds, or any recovery path
* rate limits specific to the bridge endpoints, beyond the shared tiered limits surfaced by [RateLimitStatus](/exchange-api/exchange-api/schemas#ratelimitstatus)

## See also

* [Authentication](/exchange-api/exchange-api/authentication) — API keys and the HMAC canonical string
* [Schema Reference](/exchange-api/exchange-api/schemas) — [BridgeAsset](/exchange-api/exchange-api/schemas#bridgeasset), [BridgeChainAssets](/exchange-api/exchange-api/schemas#bridgechainassets), [BridgeAssetsResponse](/exchange-api/exchange-api/schemas#bridgeassetsresponse), [BridgeDepositAddress](/exchange-api/exchange-api/schemas#bridgedepositaddress), [BridgeDeposit](/exchange-api/exchange-api/schemas#bridgedeposit), [BridgeError](/exchange-api/exchange-api/schemas#bridgeerror)


# Admin

Tier management. Three operations for assigning, listing, and clearing per-address rate-limit tier overrides.

{% hint style="warning" %}
**Operator-only.** These endpoints authenticate with the `ADMIN_SECRET` operator secret, not with an API key or a session token. They are not part of the integration surface: a trader, market maker, or agent building against the Exchange API will never call them, and cannot — the secret is held by whoever runs the deployment. They are documented here for completeness of the contract.

To request a higher rate-limit tier as an integrator, ask the operator. Read your own current tier and remaining budget with `GET /api/v1/account/rate-limit` (see [Account](/exchange-api/exchange-api/account)), which needs no admin credential.
{% endhint %}

## Authentication

All three operations declare the `adminAuth` security scheme:

| Scheme      | Type              | Transport                        | Credential                                                                           |
| ----------- | ----------------- | -------------------------------- | ------------------------------------------------------------------------------------ |
| `adminAuth` | `http` / `bearer` | `Authorization: Bearer <secret>` | The admin secret, supplied to the service as the `ADMIN_SECRET` environment variable |

Every operation returns `403 Admin secret required` when the header is missing or wrong. Unlike the HMAC-signed operations elsewhere in the contract, there is no per-request signature and no timestamp window — the secret is a bearer credential, so treat it accordingly: it grants tier control over any address on the deployment.

## Base URL

These three paths are **gateway-only**. The contract declares no `/api/v1` twin for any of them, so they resolve against the document-level server:

| Server            | URL                                       |
| ----------------- | ----------------------------------------- |
| Production        | `https://exchange.nexus.xyz/api/exchange` |
| Local development | `http://localhost:9090`                   |

## What a tier is

A rate-limit tier sets the request ceiling for an address. Tiers are distinct from **fee** tiers: `AccountFees.tier` (always `base` today) is a fee concept and is not affected by anything on this page.

The default tier is **Pro**. An override is a stored exception; removing the override reverts the address to the default. Overrides take effect immediately — setting a tier invalidates the address's existing rate-limit bucket rather than waiting for it to expire.

The contract does not publish an enumeration of tier names. The values that appear in it are `MarketMaker` and `Pro` on these operations, and `pro`, `marketmaker`, and `unlimited` in the [`RateLimitStatus`](/exchange-api/exchange-api/schemas#ratelimitstatus) description — see [Open questions](#open-questions) on the casing mismatch.

## Operations

| Operation           | Method and path                 | `operationId` |
| ------------------- | ------------------------------- | ------------- |
| Set account tier    | `PUT /admin/tiers`              | `setTier`     |
| List tier overrides | `GET /admin/tiers`              | `listTiers`   |
| Reset account tier  | `DELETE /admin/tiers/{address}` | `deleteTier`  |

## `PUT /admin/tiers`

Set account tier.

Assign a rate-limit tier to an address. Invalidates the existing rate-limit bucket so the new cap takes effect immediately.

**Authentication:** `adminAuth` — `Authorization: Bearer <ADMIN_SECRET>`.

### Request body

Required. The contract declares an example but **no schema** for this body:

| Field     | Type   | Required | Description                                                                                |
| --------- | ------ | -------- | ------------------------------------------------------------------------------------------ |
| `address` | string | Yes      | Ethereum address (0x-prefixed) to assign the tier to.                                      |
| `tier`    | string | Yes      | Tier name. The contract's example shows `MarketMaker`; the accepted set is not enumerated. |

```json
{
  "address": "0x1234...abcd",
  "tier": "MarketMaker"
}
```

### Responses

#### `200`

The updated account tier. The contract declares an example but no schema:

| Field     | Type   | Description                             |
| --------- | ------ | --------------------------------------- |
| `address` | string | The address the tier was assigned to.   |
| `tier`    | string | The tier now in force for that address. |

```json
{
  "address": "0x1234...abcd",
  "tier": "MarketMaker"
}
```

#### `403`

Admin secret required.

### Example

```bash
curl -X PUT 'https://exchange.nexus.xyz/api/exchange/admin/tiers' \
  -H 'Authorization: Bearer ADMIN_SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"address": "0x1234...abcd", "tier": "MarketMaker"}'
```

The `0x1234...abcd` value is the contract's own example, elided in the middle. Send a full 0x-prefixed address.

## `GET /admin/tiers`

List tier overrides.

Returns all addresses with non-default tier assignments. Addresses on the default Pro tier are not listed — an absent address means "default", not "unknown".

**Authentication:** `adminAuth`.

### Responses

#### `200`

The configured tier overrides. An array of objects; the contract declares an example but no schema:

| Field     | Type   | Description                    |
| --------- | ------ | ------------------------------ |
| `address` | string | Address carrying the override. |
| `tier`    | string | The overridden tier.           |

```json
[
  {
    "address": "0x1234...abcd",
    "tier": "MarketMaker"
  }
]
```

#### `403`

Admin secret required.

### Example

```bash
curl 'https://exchange.nexus.xyz/api/exchange/admin/tiers' \
  -H 'Authorization: Bearer ADMIN_SECRET'
```

## `DELETE /admin/tiers/{address}`

Reset account tier.

Remove a tier override, reverting the address to the default Pro tier.

**Authentication:** `adminAuth`.

### Parameters

| Name      | In   | Type   | Required | Description                     |
| --------- | ---- | ------ | -------- | ------------------------------- |
| `address` | path | string | Yes      | Ethereum address (0x-prefixed). |

### Responses

#### `200`

Tier removed. The contract declares no response body for this status.

#### `403`

Admin secret required.

#### `404`

Address not in allowlist.

### Example

```bash
curl -X DELETE 'https://exchange.nexus.xyz/api/exchange/admin/tiers/0x1234567890AbCdEf1234567890AbCdEf12345678' \
  -H 'Authorization: Bearer ADMIN_SECRET'
```

## Related

* [Account](/exchange-api/exchange-api/account) — `GET /api/v1/account/rate-limit`, the integrator-facing read of the caller's own tier and remaining budget.
* [Authentication](/exchange-api/exchange-api/authentication) — the HMAC and session-token schemes used by every non-admin operation.
* [Schemas](/exchange-api/exchange-api/schemas) — every component schema in the contract, including [`RateLimitStatus`](/exchange-api/exchange-api/schemas#ratelimitstatus).

## Open questions

Gaps in the contract, flagged rather than filled:

* **Tier names are not enumerated.** No schema, no `enum`, no list. Only `MarketMaker` appears as an example here, and `pro` / `marketmaker` / `unlimited` appear prose-only in the `RateLimitStatus` description. An operator cannot learn the valid set from the contract.
* **Casing is inconsistent across the contract.** These operations use `MarketMaker` and `Pro`; `RateLimitStatus.tier` is documented lowercase (`pro`, `marketmaker`, `unlimited`); the shared `429` body example returns `"tier": "Pro"`. The contract does not state which casing is canonical or whether the comparison is case-insensitive.
* **No request or response schemas.** All three operations carry examples only. Clients generated from the contract get untyped bodies.
* **`404 Address not in allowlist` on delete, but no allowlist elsewhere.** `DELETE /admin/tiers/{address}` returns `404` when the address is "not in allowlist", while `PUT /admin/tiers` documents no allowlist precondition and `GET /admin/tiers` describes its result as "overrides". The relationship between the override store and an allowlist is unstated.
* **No `401`, no rate limiting declared.** These operations declare only `200`, `403`, and (on delete) `404`. There is no documented behaviour for a malformed `Authorization` header distinct from a wrong secret, and no rate-limit response — a bearer secret with no declared throttle.
* **No audit trail on the surface.** Nothing in the contract exposes who set a tier or when. `GET /admin/tiers` returns the current state only.


# Schema Reference

Shared response and request types for the Nexus Exchange API (OpenAPI **v0.7.2**). Every schema in the contract's `components.schemas` is documented here once; the endpoint pages link into these anchors instead of repeating field tables.

The machine-readable contract is served at `/openapi.json` and is the authoritative source.

## Conventions

* **Decimals are strings.** Every monetary and quantity field is an arbitrary-precision decimal serialized as a JSON string (see [Decimal](#decimal)). Parse it with a decimal type, never a float — a float round-trip loses precision on prices and sizes.
* **Timestamps are Unix milliseconds.** Integer epoch milliseconds (see [TimestampMs](#timestampms)). Fields named `*_ms`, `*_at_ms`, or `timestamp` follow this convention. A few CCXT-shaped schemas also carry a `datetime` field, which is an ISO 8601 date-time string alongside the numeric timestamp. `ThroughputSample.timestamp` is the one exception: it is Unix **seconds**, as its field description states.
* **Nullability** is expressed two ways in the contract and both mean the same thing on the wire: a JSON Schema type union (`"type": ["string", "null"]`) for inline types, and `oneOf` / `anyOf` with a `null` branch where the non-null branch is a `$ref` (most often [Decimal](#decimal) or [TimestampMs](#timestampms)). A single-element `allOf` around a `$ref` is not nullability — it is the OpenAPI idiom for attaching a field-level description to a referenced type.
* **Null carries a reason, not a fabricated number.** Where a derived field can be unavailable, [Position](#position) pairs it with a companion `<field>_error` string holding a machine-readable reason (e.g. `mark_price_unavailable`). When the value is populated, the companion is `null`.
* **Two naming families.** Native Nexus schemas use `snake_case` keys and decimal strings. The CCXT-compatible schemas ([Ticker](#ticker), [OrderBook](#orderbook), [Trade](#trade)) use CCXT's `camelCase` keys and JSON numbers instead, for drop-in compatibility with existing CCXT tooling.
* **Requiredness.** The `Required` column reflects the schema's `required` array. Where a schema declares no `required` array at all the column shows `—`: the contract does not commit to per-field presence for that type, so treat every field as optional and check for absence.

## Index

* [AccountFees](#accountfees)
* [AccountFunding](#accountfunding)
* [AccountPortfolioSummary](#accountportfoliosummary)
* [AccountState](#accountstate)
* [AccountSummary](#accountsummary)
* [AdlClosureRecord](#adlclosurerecord)
* [AdlEventRecord](#adleventrecord)
* [AgentInfo](#agentinfo)
* [AgentRegistrationRequest](#agentregistrationrequest)
* [AmendOrderRequest](#amendorderrequest)
* [BridgeAsset](#bridgeasset)
* [BridgeAssetsResponse](#bridgeassetsresponse)
* [BridgeChainAssets](#bridgechainassets)
* [BridgeDeposit](#bridgedeposit)
* [BridgeDepositAddress](#bridgedepositaddress)
* [BridgeError](#bridgeerror)
* [CancelOnDisconnectStatus](#cancelondisconnectstatus)
* [ClosedPosition](#closedposition)
* [CreateBridgeDepositAddressRequest](#createbridgedepositaddressrequest)
* [CreditRequest](#creditrequest)
* [CreditResponse](#creditresponse)
* [Decimal](#decimal)
* [DepositRequest](#depositrequest)
* [DepositResponse](#depositresponse)
* [EquityPoint](#equitypoint)
* [FaucetResponse](#faucetresponse)
* [FeeDiscount](#feediscount)
* [Fill](#fill)
* [FundingSample](#fundingsample)
* [FundsEntry](#fundsentry)
* [LoginRequest](#loginrequest)
* [LoginResponse](#loginresponse)
* [Market](#market)
* [MarketRiskParams](#marketriskparams)
* [MarketStatus](#marketstatus)
* [MarketSummary](#marketsummary)
* [Order](#order)
* [OrderBook](#orderbook)
* [OrderHistoryEntry](#orderhistoryentry)
* [OrderRequest](#orderrequest)
* [OrderResponse](#orderresponse)
* [OrderResult](#orderresult)
* [OrderResultErr](#orderresulterr)
* [OrderResultOk](#orderresultok)
* [PortfolioHistory](#portfoliohistory)
* [PortfolioPoint](#portfoliopoint)
* [PortfolioWindow](#portfoliowindow)
* [Position](#position)
* [PreviewResponse](#previewresponse)
* [RateLimitStatus](#ratelimitstatus)
* [ServiceHealth](#servicehealth)
* [SetCancelOnDisconnectRequest](#setcancelondisconnectrequest)
* [StatsSnapshot](#statssnapshot)
* [ThroughputSample](#throughputsample)
* [Ticker](#ticker)
* [TimestampMs](#timestampms)
* [Trade](#trade)
* [Withdrawal](#withdrawal)

## AccountFees

The authenticated account's effective fee schedule, mirroring Hyperliquid `userFees`. Reports what the venue charges today: there are no per-account fee tiers or discounts yet (fee model still a draft), so `tier` is `base` and `discounts` is empty. The rate is the forward-looking schedule rate scoped by `schedule`, not a realized per-fill average.

| Field                  | Type                                   | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ---------------------- | -------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maker_fee_bps`        | `integer`                              | Yes      | Effective maker fee in basis points. Negative means the maker is *paid* a rebate — e.g. -2 is a 0.02% rebate.                                                                                                                                                                                                                                                                                                                                                                                            |
| `taker_fee_bps`        | `integer`                              | Yes      | Effective taker fee in basis points — e.g. 5 is a 0.05% fee.                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `tier`                 | `string`                               | Yes      | Fee tier for the account. Currently always `base`: there are no per-account fee tiers yet (distinct from rate-limit tiers). New values may appear when the fee model lands, so treat this as an open string.                                                                                                                                                                                                                                                                                             |
| `schedule`             | `string`                               | Yes      | Scope of the reported rate. Currently always `standard`. The venue charges a per-market schedule (standard crypto, mid-cap crypto, FX, commodities/indices all differ, and the split varies by deploy config), but this endpoint takes no market parameter, so it reports the standard crypto-group schedule and marks it here. Treat the rate as scoped by this value, not a venue-wide guarantee; per-market effective rates are a planned follow-up. Treat as an open string — new scopes may appear. |
| `volume_30d`           | [Decimal](#decimal)                    | Yes      | Rolling 30-day traded notional for the account, as a decimal string. Best-effort — see `volume_30d_estimated`.                                                                                                                                                                                                                                                                                                                                                                                           |
| `volume_30d_estimated` | `boolean`                              | Yes      | `true` when `volume_30d` may undercount: the source fill buffer was at capacity, so some older in-window fills may have been evicted. `false` when the full 30-day window is covered.                                                                                                                                                                                                                                                                                                                    |
| `discounts`            | `array` of [FeeDiscount](#feediscount) | Yes      | Active fee discounts applied to the account. Currently always empty — no discount program exists yet.                                                                                                                                                                                                                                                                                                                                                                                                    |

## AccountFunding

A funding payment for the account.

| Field           | Type                        | Required | Description                 |
| --------------- | --------------------------- | -------- | --------------------------- |
| `market_id`     | `string`                    | —        | —                           |
| `amount`        | [Decimal](#decimal)         | —        | Signed funding amount.      |
| `direction`     | `string`                    | —        | One of: `paid`, `received`. |
| `funding_rate`  | [Decimal](#decimal)         | —        | —                           |
| `position_size` | [Decimal](#decimal)         | —        | —                           |
| `timestamp`     | [TimestampMs](#timestampms) | —        | —                           |

## AccountPortfolioSummary

Portfolio summary for the authenticated account (aggregate equity, PnL, volume, open counts).

| Field                    | Type                | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------ | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collateral`             | [Decimal](#decimal) | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `total_equity`           | [Decimal](#decimal) | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `total_unrealized_pnl`   | [Decimal](#decimal) | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `total_realized_pnl_24h` | [Decimal](#decimal) | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `total_volume_24h`       | [Decimal](#decimal) | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `open_positions_count`   | `integer`           | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `open_orders_count`      | `integer`           | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `margin_used`            | [Decimal](#decimal) | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `available_margin`       | [Decimal](#decimal) | —        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `withdrawable`           | [Decimal](#decimal) | —        | Wallet-withdrawable balance: engine-authoritative free margin floored at zero (`max(0, available_margin)`). Free margin already nets each position's initial margin and pre-trade order reservations out of equity, so this is exactly what can leave the account. A negative free margin (an underwater account) is clamped to `"0"` and never surfaced negative. Derived from the authoritative margin view — the endpoint fails closed with `502` rather than reporting a local estimate when that view is unavailable. Example `8500.00`. |
| `early_access_allowed`   | `boolean`           | —        | Present only when the early-access gate is active.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |

## AccountState

Consolidated single-call account snapshot — the portfolio summary aggregates plus all open positions — matching Hyperliquid `clearinghouseState` ergonomics. Both parts are built from one coherent read, so `summary.open_positions_count` always equals the length of `positions`, and the embedded `summary` is identical to the standalone `/account/summary` response.

| Field       | Type                                                | Required | Description                         |
| ----------- | --------------------------------------------------- | -------- | ----------------------------------- |
| `summary`   | [AccountPortfolioSummary](#accountportfoliosummary) | Yes      | —                                   |
| `positions` | `array` of [Position](#position)                    | Yes      | All open positions for the account. |

## AccountSummary

| Field              | Type                             | Required | Description |
| ------------------ | -------------------------------- | -------- | ----------- |
| `balance`          | [Decimal](#decimal)              | —        | —           |
| `collateral`       | [Decimal](#decimal)              | —        | —           |
| `equity`           | [Decimal](#decimal)              | —        | —           |
| `available_margin` | [Decimal](#decimal)              | —        | —           |
| `positions`        | `array` of [Position](#position) | —        | —           |

## AdlClosureRecord

One counterparty's forced closure within an ADL settlement.

| Field               | Type                | Required | Description                                                       |
| ------------------- | ------------------- | -------- | ----------------------------------------------------------------- |
| `account_id`        | `string`            | —        | 0x-prefixed address of the counterparty whose position was closed |
| `position_closed`   | [Decimal](#decimal) | —        | Decimal quantity closed                                           |
| `settlement_amount` | [Decimal](#decimal) | —        | Decimal amount charged to the counterparty                        |

## AdlEventRecord

Single ADL settlement (insurance fund depleted → counterparty closures). v0.21.

| Field                       | Type                                             | Required | Description                  |
| --------------------------- | ------------------------------------------------ | -------- | ---------------------------- |
| `market_id`                 | `string`                                         | —        | —                            |
| `target_account`            | `string`                                         | —        | 0x-prefixed bankrupt account |
| `bankruptcy_price`          | [Decimal](#decimal)                              | —        | —                            |
| `bad_debt_absorbed_by_fund` | [Decimal](#decimal)                              | —        | —                            |
| `counterparty_closures`     | `array` of [AdlClosureRecord](#adlclosurerecord) | —        | —                            |
| `sequence`                  | `integer`                                        | —        | Engine event sequence number |
| `timestamp`                 | [TimestampMs](#timestampms)                      | —        | Unix ms                      |

## AgentInfo

| Field          | Type                        | Required | Description                 |
| -------------- | --------------------------- | -------- | --------------------------- |
| `address`      | `string`                    | —        | Agent address (0x-prefixed) |
| `expiresAt`    | [TimestampMs](#timestampms) | —        | Expiry Unix ms              |
| `registeredAt` | [TimestampMs](#timestampms) | —        | Registration time Unix ms   |
| `label`        | `string` \| `null`          | —        | Optional label              |

## AgentRegistrationRequest

| Field        | Type      | Required | Description                                                                                             |
| ------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `wallet`     | `string`  | Yes      | Owner wallet address (0x-prefixed, 20 bytes)                                                            |
| `agent`      | `string`  | Yes      | Agent Ethereum address (0x-prefixed, 20 bytes) derived from the agent keypair                           |
| `expires_at` | `integer` | No       | Expiry as Unix ms. Optional — defaults to now+30d. Must be in \[now+1d, now+90d].                       |
| `nonce`      | `integer` | Yes      | Monotonic nonce. Use the current Unix timestamp in ms as a safe starting value.                         |
| `signature`  | `string`  | Yes      | EIP-712 signature over RegisterAgent{agent, expiresAt, nonce} from the wallet private key (0x-prefixed) |
| `label`      | `string`  | No       | Optional human-readable label for the agent (e.g. 'my-bot')                                             |

## AmendOrderRequest

Atomic cancel-replace amend of a resting order. At least one of `price` (new limit price) or `size` (new quantity) must be present; an empty body is rejected with InvalidAmend.

| Field   | Type                | Required | Description |
| ------- | ------------------- | -------- | ----------- |
| `price` | [Decimal](#decimal) | —        | —           |
| `size`  | [Decimal](#decimal) | —        | —           |

## BridgeAsset

A bridgeable asset on a specific chain.

| Field              | Type                | Required | Description                                                                                                    |
| ------------------ | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `symbol`           | `string`            | Yes      | Asset symbol. Phase A supports USDC and USDX only (USDT is out of scope for this cut). One of: `USDC`, `USDX`. |
| `decimals`         | `integer`           | Yes      | On-chain token decimals for this asset on this chain.                                                          |
| `min_amount`       | [Decimal](#decimal) | Yes      | Minimum amount accepted for a single deposit.                                                                  |
| `confirmations`    | `integer`           | Yes      | Block confirmations required before a deposit is credited.                                                     |
| `fee`              | [Decimal](#decimal) | No       | Flat fee charged in units of the asset (may be "0").                                                           |
| `contract_address` | `string` \| `null`  | No       | 0x token contract address on the chain; null for a chain-native representation.                                |

## BridgeAssetsResponse

Supported bridge chains and their deposit/withdraw assets.

| Field    | Type                                               | Required | Description |
| -------- | -------------------------------------------------- | -------- | ----------- |
| `chains` | `array` of [BridgeChainAssets](#bridgechainassets) | Yes      | —           |

## BridgeChainAssets

Bridgeable assets for one chain.

| Field             | Type                                   | Required | Description                                                                                                                    |
| ----------------- | -------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `chain`           | `string`                               | Yes      | Chain identifier, e.g. `ethereum` or `base`.                                                                                   |
| `chain_id`        | `integer` \| `null`                    | No       | EVM chain ID, when applicable.                                                                                                 |
| `deposit_assets`  | `array` of [BridgeAsset](#bridgeasset) | Yes      | Assets that can be deposited from this chain (USDC, USDX).                                                                     |
| `withdraw_assets` | `array` of [BridgeAsset](#bridgeasset) | Yes      | Assets that can be withdrawn to this chain (USDX). Withdrawal endpoints are a later phase; this lists the eventual capability. |

## BridgeDeposit

A cross-chain deposit tracked by the watcher (read model).

| Field                    | Type                                  | Required | Description                                                                                                            |
| ------------------------ | ------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `id`                     | `string`                              | Yes      | Opaque, stable deposit identifier.                                                                                     |
| `account_id`             | `string`                              | Yes      | 0x-prefixed Nexus account being credited.                                                                              |
| `chain`                  | `string`                              | Yes      | Source chain.                                                                                                          |
| `asset`                  | `string`                              | Yes      | Deposited asset. One of: `USDC`, `USDX`.                                                                               |
| `amount`                 | [Decimal](#decimal)                   | Yes      | Deposit amount in units of `asset`.                                                                                    |
| `address`                | `string`                              | Yes      | Deposit address the funds arrived at.                                                                                  |
| `status`                 | `string`                              | Yes      | Lifecycle: `detected` → `confirming` → `credited` \| `failed`. One of: `detected`, `confirming`, `credited`, `failed`. |
| `confirmations`          | `integer` \| `null`                   | No       | Confirmations observed so far; null before the tx is seen on chain.                                                    |
| `required_confirmations` | `integer` \| `null`                   | No       | Confirmations required before crediting.                                                                               |
| `tx_hash`                | `string` \| `null`                    | No       | Source-chain transaction hash; null until detected.                                                                    |
| `created_at`             | [TimestampMs](#timestampms)           | Yes      | —                                                                                                                      |
| `updated_at`             | [TimestampMs](#timestampms)           | No       | —                                                                                                                      |
| `credited_at`            | [TimestampMs](#timestampms) \| `null` | No       | Unix ms when the deposit was credited; null until `status` is `credited`.                                              |

## BridgeDepositAddress

A per-account deposit address on a specific chain.

| Field        | Type                        | Required | Description                                                                                                   |
| ------------ | --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `address`    | `string`                    | Yes      | Deposit address on `chain` for the authenticated account. Sending a supported asset here credits the account. |
| `chain`      | `string`                    | Yes      | Chain this address belongs to.                                                                                |
| `accepts`    | `array` of `string`         | Yes      | Assets creditable via this address. One of: `USDC`, `USDX`.                                                   |
| `account_id` | `string`                    | Yes      | 0x-prefixed Nexus account the address credits.                                                                |
| `created_at` | [TimestampMs](#timestampms) | Yes      | —                                                                                                             |

## BridgeError

Error envelope returned by all non-2xx /v1/bridge responses.

| Field   | Type     | Required | Description |
| ------- | -------- | -------- | ----------- |
| `error` | `object` | Yes      | —           |

`error` object:

| Field     | Type     | Required | Description                                                                                                               |
| --------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `code`    | `string` | Yes      | Machine-readable, stable error code (snake\_case), e.g. `unsupported_chain`, `amount_below_minimum`, `deposit_not_found`. |
| `message` | `string` | Yes      | Human-readable description; not intended for programmatic matching.                                                       |
| `details` | `object` | No       | Optional structured context for the error.                                                                                |

## CancelOnDisconnectStatus

Cancel-on-disconnect status for the authenticated account.

| Field        | Type                | Required | Description                                                                                                                                                                                                                          |
| ------------ | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enabled`    | `boolean`           | Yes      | The account's own COD opt-in setting.                                                                                                                                                                                                |
| `active`     | `boolean`           | Yes      | Whether COD will actually fire for this account: the account opt-in AND the exchange-side feature switch. When `enabled` is true but `active` is false, the exchange has the feature switched off and no cancel fires on disconnect. |
| `grace_secs` | `integer` \| `null` | No       | Seconds the exchange waits after the last `/ws` disconnect before cancelling; a reconnect within the window disarms the cancel. Null when the feature is unavailable on this deployment.                                             |

## ClosedPosition

A closed position record.

| Field          | Type                        | Required | Description                                                          |
| -------------- | --------------------------- | -------- | -------------------------------------------------------------------- |
| `market_id`    | `string`                    | —        | —                                                                    |
| `side`         | `string`                    | —        | The side the position was before it closed. One of: `Long`, `Short`. |
| `size`         | [Decimal](#decimal)         | —        | Absolute size at close.                                              |
| `entry_price`  | [Decimal](#decimal)         | —        | —                                                                    |
| `exit_price`   | [Decimal](#decimal)         | —        | —                                                                    |
| `realized_pnl` | [Decimal](#decimal)         | —        | —                                                                    |
| `closed_at_ms` | [TimestampMs](#timestampms) | —        | —                                                                    |

## CreateBridgeDepositAddressRequest

| Field   | Type     | Required | Description                                                                                                                              |
| ------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `chain` | `string` | Yes      | Chain to get-or-create a deposit address on. The operation is idempotent per `(account, chain)`: repeated calls return the same address. |

## CreditRequest

| Field    | Type                | Required | Description                                                                                  |
| -------- | ------------------- | -------- | -------------------------------------------------------------------------------------------- |
| `amount` | [Decimal](#decimal) | —        | Synthetic USDX to credit (decimal string). Omit to claim the full remaining daily allowance. |

## CreditResponse

| Field            | Type                | Required | Description                                                        |
| ---------------- | ------------------- | -------- | ------------------------------------------------------------------ |
| `amount`         | [Decimal](#decimal) | Yes      | USDX credited by this request (decimal string).                    |
| `credited_today` | [Decimal](#decimal) | Yes      | Total USDX credited to this API key so far today (decimal string). |
| `daily_limit`    | [Decimal](#decimal) | Yes      | Per-API-key daily credit allowance in USDX (decimal string).       |

## Decimal

Arbitrary-precision decimal serialized as a string (lossless). Parse with a decimal type, never a float.

**Type:** `string`

## DepositRequest

| Field    | Type                | Required | Description                                     |
| -------- | ------------------- | -------- | ----------------------------------------------- |
| `amount` | [Decimal](#decimal) | Yes      | Deposit amount (positive decimal string).       |
| `asset`  | `string`            | No       | Asset symbol; defaults to USDX. Default `USDX`. |

## DepositResponse

Engine deposit acknowledgement (forwarded). Includes the updated authoritative balance.

| Field     | Type                | Required | Description                         |
| --------- | ------------------- | -------- | ----------------------------------- |
| `balance` | [Decimal](#decimal) | —        | Authoritative post-deposit balance. |

The contract permits additional properties beyond those listed.

## EquityPoint

One equity sample (balance + unrealized PnL) for the account, 5s cadence.

| Field          | Type                        | Required | Description                    |
| -------------- | --------------------------- | -------- | ------------------------------ |
| `timestamp_ms` | [TimestampMs](#timestampms) | —        | —                              |
| `equity`       | `number`                    | —        | Account equity at sample time. |

## FaucetResponse

Testnet faucet credit result.

| Field             | Type                        | Required | Description                                    |
| ----------------- | --------------------------- | -------- | ---------------------------------------------- |
| `amount`          | [Decimal](#decimal)         | —        | Amount credited.                               |
| `available_at_ms` | [TimestampMs](#timestampms) | —        | Earliest time the faucet may be claimed again. |

## FeeDiscount

An active fee discount applied to the account. The concrete shape is provisional and finalizes with the fee model (tiers and discounts are still a draft); `discounts` is currently always empty, so no properties are guaranteed yet. Additional properties may be added additively once the model lands.

**Type:** `object` with no declared properties. The contract permits additional properties.

## Fill

A single trade execution for the authenticated account

| Field            | Type                        | Required | Description                          |
| ---------------- | --------------------------- | -------- | ------------------------------------ |
| `id`             | `string`                    | —        | Fill ID. Format `uuid`.              |
| `order_id`       | `string`                    | —        | Parent order ID                      |
| `market_id`      | `string`                    | —        | Market (e.g. BTC-USDX-PERP)          |
| `side`           | `string`                    | —        | One of: `buy`, `sell`.               |
| `price`          | [Decimal](#decimal)         | —        | Executed price (decimal string)      |
| `size`           | [Decimal](#decimal)         | —        | Executed quantity (decimal string)   |
| `fee`            | [Decimal](#decimal)         | —        | Fee charged in USDX (decimal string) |
| `taker_or_maker` | `string`                    | —        | One of: `taker`, `maker`.            |
| `timestamp`      | [TimestampMs](#timestampms) | —        | Unix ms                              |
| `is_liquidation` | `boolean`                   | —        | —                                    |

## FundingSample

| Field           | Type                        | Required | Description |
| --------------- | --------------------------- | -------- | ----------- |
| `timestamp`     | [TimestampMs](#timestampms) | —        | —           |
| `funding_rate`  | [Decimal](#decimal)         | —        | —           |
| `premium_index` | [Decimal](#decimal)         | —        | —           |
| `mark_price`    | [Decimal](#decimal)         | —        | —           |
| `oracle_price`  | [Decimal](#decimal)         | —        | —           |

## FundsEntry

A deposit or withdrawal ledger entry.

| Field       | Type                        | Required | Description                                |
| ----------- | --------------------------- | -------- | ------------------------------------------ |
| `id`        | `integer`                   | —        | —                                          |
| `kind`      | `string`                    | —        | One of: `deposit`, `withdrawal`, `faucet`. |
| `account`   | `string`                    | —        | 0x-prefixed account address.               |
| `amount`    | [Decimal](#decimal)         | —        | —                                          |
| `asset`     | `string`                    | —        | —                                          |
| `timestamp` | [TimestampMs](#timestampms) | —        | —                                          |
| `status`    | `string`                    | —        | One of: `pending`, `confirmed`, `failed`.  |
| `tx_hash`   | `string` \| `null`          | —        | —                                          |

## LoginRequest

| Field       | Type     | Required | Description                                        |
| ----------- | -------- | -------- | -------------------------------------------------- |
| `message`   | `string` | Yes      | Must be exactly: "Sign in to Nexus Exchange"       |
| `signature` | `string` | Yes      | EIP-191 personal\_sign hex (0x-prefixed, 65 bytes) |

## LoginResponse

| Field     | Type     | Required | Description                                                           |
| --------- | -------- | -------- | --------------------------------------------------------------------- |
| `token`   | `string` | —        | Session token (64-char hex). Use as Bearer token for /keys endpoints. |
| `address` | `string` | —        | Recovered Ethereum address (0x-prefixed)                              |

## Market

| Field                     | Type                | Required | Description |
| ------------------------- | ------------------- | -------- | ----------- |
| `market_id`               | `string`            | —        | —           |
| `base_asset`              | `string`            | —        | —           |
| `quote_asset`             | `string`            | —        | —           |
| `tick_size`               | [Decimal](#decimal) | —        | —           |
| `lot_size`                | [Decimal](#decimal) | —        | —           |
| `min_order_size`          | [Decimal](#decimal) | —        | —           |
| `max_order_size`          | [Decimal](#decimal) | —        | —           |
| `initial_margin_rate`     | [Decimal](#decimal) | —        | —           |
| `maintenance_margin_rate` | [Decimal](#decimal) | —        | —           |
| `max_leverage`            | `integer`           | —        | —           |

## MarketRiskParams

Per-market risk parameters: margin rates and maximum leverage.

| Field                     | Type                | Required | Description                                                            |
| ------------------------- | ------------------- | -------- | ---------------------------------------------------------------------- |
| `market_id`               | `string`            | —        | —                                                                      |
| `max_leverage`            | `integer`           | —        | Maximum leverage allowed for this market                               |
| `initial_margin_rate`     | [Decimal](#decimal) | —        | Initial margin requirement as a decimal ratio (e.g., 0.05 = 5%)        |
| `maintenance_margin_rate` | [Decimal](#decimal) | —        | Maintenance margin requirement as a decimal ratio (e.g., 0.025 = 2.5%) |

## MarketStatus

Per-market halt status (v0.21).

| Field             | Type                | Required | Description                 |
| ----------------- | ------------------- | -------- | --------------------------- |
| `market_id`       | `string`            | —        | —                           |
| `status`          | `string`            | —        | One of: `active`, `halted`. |
| `halt_reason`     | `string` \| `null`  | —        | —                           |
| `halted_at`       | `integer` \| `null` | —        | —                           |
| `adl_event_count` | `integer`           | —        | —                           |

## MarketSummary

| Field              | Type                | Required | Description                                                                                                      |
| ------------------ | ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `market_id`        | `string`            | —        | —                                                                                                                |
| `last_trade_price` | `number` \| `null`  | —        | Last trade price ("what the market is trading at"). NOT the mark; the engine-derived mark is exposed separately. |
| `volume_24h`       | `number`            | —        | —                                                                                                                |
| `trade_count`      | `integer`           | —        | —                                                                                                                |
| `status`           | `string`            | —        | v0.21: halted when ADL pool exhausted. One of: `active`, `halted`.                                               |
| `halt_reason`      | `string` \| `null`  | —        | —                                                                                                                |
| `halted_at`        | `integer` \| `null` | —        | Unix ms timestamp when market was halted                                                                         |
| `adl_event_count`  | `integer`           | —        | Cumulative ADL settlement events for this market                                                                 |

## Order

| Field              | Type                        | Required | Description                                                                                                                                                      |
| ------------------ | --------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`               | `string`                    | —        | Format `uuid`.                                                                                                                                                   |
| `market_id`        | `string`                    | —        | —                                                                                                                                                                |
| `account_id`       | `string`                    | —        | —                                                                                                                                                                |
| `side`             | `string`                    | —        | One of: `Buy`, `Sell`.                                                                                                                                           |
| `order_type`       | `string`                    | —        | —                                                                                                                                                                |
| `limit_offset_bps` | `integer` \| `null`         | —        | Fire-time limit offset in basis points, echoed for `TrailingLimit` orders (see the `OrderRequest.limit_offset_bps` placement field); null for other order types. |
| `price`            | [Decimal](#decimal)         | —        | —                                                                                                                                                                |
| `quantity`         | [Decimal](#decimal)         | —        | —                                                                                                                                                                |
| `filled_qty`       | [Decimal](#decimal)         | —        | —                                                                                                                                                                |
| `status`           | `string`                    | —        | One of: `Open`, `PartiallyFilled`, `Filled`, `Cancelled`, `Expired`, `Rejected`.                                                                                 |
| `time_in_force`    | `string`                    | —        | —                                                                                                                                                                |
| `created_at`       | [TimestampMs](#timestampms) | —        | —                                                                                                                                                                |
| `updated_at`       | [TimestampMs](#timestampms) | —        | —                                                                                                                                                                |

## OrderBook

CCXT-compatible order book. Bids/asks are \[price, amount] arrays.

| Field       | Type                          | Required | Description         |
| ----------- | ----------------------------- | -------- | ------------------- |
| `symbol`    | `string`                      | —        | —                   |
| `bids`      | `array` of `[number, number]` | —        | —                   |
| `asks`      | `array` of `[number, number]` | —        | —                   |
| `timestamp` | [TimestampMs](#timestampms)   | —        | —                   |
| `datetime`  | `string`                      | —        | Format `date-time`. |
| `nonce`     | `integer`                     | —        | —                   |

## OrderHistoryEntry

A terminal-status order (filled / cancelled / rejected / expired).

| Field                 | Type                          | Required | Description                                                                          |
| --------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------ |
| `id`                  | `string`                      | —        | Format `uuid`.                                                                       |
| `market_id`           | `string`                      | —        | —                                                                                    |
| `side`                | `string`                      | —        | One of: `buy`, `sell`.                                                               |
| `order_type`          | `string`                      | —        | limit \| market \| stop\_\* \| take\_profit\_\* \| trailing\_stop \| trailing\_limit |
| `price`               | [Decimal](#decimal) \| `null` | —        | Limit price; null for market orders.                                                 |
| `size`                | [Decimal](#decimal)           | —        | Original quantity.                                                                   |
| `filled_qty`          | [Decimal](#decimal)           | —        | —                                                                                    |
| `status`              | `string`                      | —        | One of: `Filled`, `Cancelled`, `Rejected`, `Expired`.                                |
| `cancellation_reason` | `string` \| `null`            | —        | —                                                                                    |
| `created_at_ms`       | [TimestampMs](#timestampms)   | —        | —                                                                                    |
| `completed_at_ms`     | [TimestampMs](#timestampms)   | —        | —                                                                                    |

## OrderRequest

Order placement request. Supports plain `Limit` / `Market` orders and six conditional order types (`StopLimit`, `StopMarket`, `TakeProfitLimit`, `TakeProfitMarket`, `TrailingStop`, `TrailingLimit`). Field requirements depend on `order_type`:

* **Limit-family** (`Limit`, `StopLimit`, `TakeProfitLimit`) require a limit `price`.
* **Triggerable, non-trailing** orders (`StopLimit`, `StopMarket`, `TakeProfitLimit`, `TakeProfitMarket`) require a `trigger_price` (the legacy `stop_price` field is accepted as a fallback when `trigger_price` is absent).
* **`TrailingStop`** is market-only — it fires as a market order — and requires `trailing_offset_bps`. It does not take a limit `price` or a `trigger_price` (the trigger anchor is derived from the mark price and the offset).
* **`TrailingLimit`** trails like `TrailingStop` but fires a limit order instead of a market order. It requires both `trailing_offset_bps` (the trailing trigger) and `limit_offset_bps` (the fire-time limit offset). It does not take `price`, `trigger_price`, or `stop_price`; the limit price is computed at fire time from the mark that crossed the offset.

| Field                 | Type                          | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| --------------------- | ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `market_id`           | `string`                      | Yes      | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `side`                | `string`                      | Yes      | One of: `Buy`, `Sell`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `order_type`          | `string`                      | Yes      | Order type. `Limit` and `Market` are unconditional. The remaining six are conditional: `StopLimit` / `StopMarket` fire when the mark price crosses `trigger_price` in the adverse direction; `TakeProfitLimit` / `TakeProfitMarket` fire on the favorable direction; `TrailingStop` fires as a market order when the mark retraces from its best-seen extreme by `trailing_offset_bps`; `TrailingLimit` fires the same way but rests a limit order priced off the fire price by `limit_offset_bps`. See the schema description for per-type field requirements. One of: `Limit`, `Market`, `StopLimit`, `StopMarket`, `TakeProfitLimit`, `TakeProfitMarket`, `TrailingStop`, `TrailingLimit`. |
| `price`               | [Decimal](#decimal)           | No       | Limit price. Required for limit-family orders (`Limit`, `StopLimit`, `TakeProfitLimit`); omit for market-family and trailing orders.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `quantity`            | [Decimal](#decimal)           | Yes      | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `time_in_force`       | `string`                      | Yes      | Time-in-force policy. `PostOnly` rejects the order if it would take liquidity (cross the book) on entry, guaranteeing it rests as a maker. One of: `GTC`, `IOC`, `FOK`, `PostOnly`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `reduce_only`         | `boolean`                     | No       | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `stop_price`          | [Decimal](#decimal) \| `null` | No       | **Deprecated** — use `trigger_price` instead. Legacy trigger threshold for the stop / take-profit family. Accepted as a fallback only when `trigger_price` is absent; when both are supplied, `trigger_price` wins. Ignored for `Limit`, `Market`, `TrailingStop`, and `TrailingLimit` orders. **Deprecated.**                                                                                                                                                                                                                                                                                                                                                                                |
| `trigger_price`       | [Decimal](#decimal) \| `null` | No       | Canonical trigger threshold for triggerable, non-trailing orders (`StopLimit`, `StopMarket`, `TakeProfitLimit`, `TakeProfitMarket`), which require it (the legacy `stop_price` field is accepted as a fallback when this is omitted). Not used by `Limit`, `Market`, `TrailingStop`, or `TrailingLimit` orders.                                                                                                                                                                                                                                                                                                                                                                               |
| `trailing_offset_bps` | `integer` \| `null`           | No       | Trailing offset in basis points (1 bp = 0.01%). Required for `TrailingStop` and `TrailingLimit` orders; ignored for all other order types. The trailing trigger fires once the mark price retraces from its best-seen extreme by this many basis points: `TrailingStop` fires a market order, `TrailingLimit` fires a limit order priced by `limit_offset_bps`. A value of `0` is accepted and fires the trigger at the first mark-price evaluation after placement (no retracement required). Minimum `0`.                                                                                                                                                                                   |
| `limit_offset_bps`    | `integer` \| `null`           | No       | Offset in basis points for the fired limit price (`TrailingLimit` only; required together with `trailing_offset_bps`). When the trailing trigger fires at `fire_price`, the injected limit order rests at `fire_price` \* (1 + offset) for buys / \* (1 - offset) for sells, tick-rounded toward the tighter bound. A value of 0 rests the limit exactly at `fire_price`. Ignored for other order types. Minimum `0`. Maximum `9999`.                                                                                                                                                                                                                                                         |

## OrderResponse

| Field   | Type                     | Required | Description |
| ------- | ------------------------ | -------- | ----------- |
| `order` | [Order](#order)          | —        | —           |
| `fills` | `array` of [Fill](#fill) | —        | —           |

## OrderResult

One entry in the array returned by POST /orders/batch. The batch is sequential and non-atomic, so each entry independently reports either a placed order or a per-order rejection, in request order. Internally tagged by `outcome`: `ok` carries the same `{ order, fills }` shape as POST /orders, `err` carries the same `{ error, message }` shape as the global error envelope.

**One of:** [OrderResultOk](#orderresultok) or [OrderResultErr](#orderresulterr)

Discriminated by the `outcome` property: `ok` → [OrderResultOk](#orderresultok), `err` → [OrderResultErr](#orderresulterr).

## OrderResultErr

A rejected order in a batch result (outcome `err`). Mirrors the global error envelope.

| Field     | Type     | Required | Description                   |
| --------- | -------- | -------- | ----------------------------- |
| `outcome` | `string` | Yes      | One of: `err`.                |
| `error`   | `string` | Yes      | Machine-readable error code.  |
| `message` | `string` | Yes      | Human-readable error message. |

## OrderResultOk

A placed order in a batch result (outcome `ok`).

| Field     | Type                     | Required | Description   |
| --------- | ------------------------ | -------- | ------------- |
| `outcome` | `string`                 | Yes      | One of: `ok`. |
| `order`   | [Order](#order)          | Yes      | —             |
| `fills`   | `array` of [Fill](#fill) | No       | —             |

## PortfolioHistory

Portfolio time-series for the authenticated account over the requested window: equity, cumulative PnL, and cumulative volume, downsampled at a fixed per-window cadence and returned oldest first.

| Field        | Type                                         | Required | Description                                                                                                                                            |
| ------------ | -------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `window`     | [PortfolioWindow](#portfoliowindow)          | Yes      | The window that was served — echoes the `window` query parameter, or its `day` default.                                                                |
| `cadence_ms` | `integer`                                    | Yes      | Downsample interval between adjacent points, in milliseconds (e.g. 300000 for `day`, 86400000 for `all`).                                              |
| `points`     | `array` of [PortfolioPoint](#portfoliopoint) | Yes      | Samples for the window, oldest first. Length is bounded by the window's capacity (day 288, week 168, month 120, all 366) and by the `limit` parameter. |

## PortfolioPoint

One downsampled portfolio sample. Monetary fields are lossless decimal strings — parse with a decimal type, never a float.

| Field          | Type                        | Required | Description                                                                                                                                                                                                                                                                                              |
| -------------- | --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timestamp_ms` | [TimestampMs](#timestampms) | Yes      | —                                                                                                                                                                                                                                                                                                        |
| `equity`       | [Decimal](#decimal)         | Yes      | Account equity at sample time (collateral balance + Σ unrealized PnL). Derived from the same underlying value as `EquityPoint.equity`; note `EquityPoint` serializes equity as a JSON number, whereas this is a lossless decimal string, so compare by decimal value rather than by wire representation. |
| `pnl`          | [Decimal](#decimal)         | Yes      | Cumulative trading PnL up to this sample: Σ realized PnL on position close (including liquidation and ADL closes) + Σ funding (signed) + current unrealized PnL. Deposit-neutral — wallet deposits and withdrawals never move it — so the curve reflects trading performance only.                       |
| `volume`       | [Decimal](#decimal)         | Yes      | Cumulative traded notional (Σ price × size) up to this sample, across taker and maker fills; a self-trade is counted once. Monotonically non-decreasing.                                                                                                                                                 |

## PortfolioWindow

Portfolio time-series window selector: `day`, `week`, `month`, or `all`. Shared by the `window` query parameter and the `window` echoed in the response.

**Type:** `string`

**Values:**

* `day`
* `week`
* `month`
* `all`

## Position

An open position with per-position risk detail. Enriched risk fields are derived strictly from indexer-mirrored state (no engine round-trip, to stay on the low-latency read path): when an input is not mirrored, the field is `null` and its companion `<field>_error` carries a machine-readable reason rather than a fabricated number. Monetary fields are lossless decimal strings; leverage fields are JSON numbers.

| Field                  | Type                          | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                  |
| ---------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `market_id`            | `string`                      | —        | —                                                                                                                                                                                                                                                                                                                                                                                                            |
| `side`                 | `string`                      | —        | One of: `Long`, `Short`.                                                                                                                                                                                                                                                                                                                                                                                     |
| `size`                 | [Decimal](#decimal)           | —        | —                                                                                                                                                                                                                                                                                                                                                                                                            |
| `entry_price`          | [Decimal](#decimal)           | —        | —                                                                                                                                                                                                                                                                                                                                                                                                            |
| `unrealized_pnl`       | [Decimal](#decimal)           | —        | —                                                                                                                                                                                                                                                                                                                                                                                                            |
| `realized_pnl`         | [Decimal](#decimal)           | —        | —                                                                                                                                                                                                                                                                                                                                                                                                            |
| `liquidation_price`    | [Decimal](#decimal)           | —        | —                                                                                                                                                                                                                                                                                                                                                                                                            |
| `leverage`             | `number` \| `null`            | —        | Position leverage (the account's leverage multiplier for this position). Currently always `null`: deriving it needs the user's leverage setting or account equity/allocated margin, which the indexer does not mirror; when `null`, `leverage_error` carries the reason. Do not infer leverage from `margin_used` — that collapses to `1/initial_margin_rate`, a per-market constant, not the real leverage. |
| `leverage_error`       | `string` \| `null`            | —        | Machine-readable reason `leverage` is `null`, or `null` when `leverage` is populated. Currently always `margin_state_not_mirrored`.                                                                                                                                                                                                                                                                          |
| `notional_value`       | [Decimal](#decimal) \| `null` | —        | Position notional value (\|size\| × mark price), as a decimal string. `null` when the mark price is unavailable — see `notional_value_error`.                                                                                                                                                                                                                                                                |
| `notional_value_error` | `string` \| `null`            | —        | Machine-readable reason `notional_value` is `null` (e.g. `mark_price_unavailable`), or `null` when populated.                                                                                                                                                                                                                                                                                                |
| `roe`                  | [Decimal](#decimal) \| `null` | —        | Return on equity: `unrealized_pnl / margin_used` (return on initial margin), as a decimal string. `null` when a required input is unavailable or margin is zero — see `roe_error`.                                                                                                                                                                                                                           |
| `roe_error`            | `string` \| `null`            | —        | Machine-readable reason `roe` is `null` (e.g. `mark_price_unavailable`, `margin_rate_unavailable`, `margin_used_zero`), or `null` when populated.                                                                                                                                                                                                                                                            |
| `margin_used`          | [Decimal](#decimal) \| `null` | —        | Initial-margin requirement held against this position (`notional_value × initial_margin_rate`, under the engine's cross-margin model), as a decimal string. Isolated/custom margin allocations are not mirrored by the indexer. `null` when a required input is unavailable — see `margin_used_error`.                                                                                                       |
| `margin_used_error`    | `string` \| `null`            | —        | Machine-readable reason `margin_used` is `null` (e.g. `mark_price_unavailable`, `margin_rate_unavailable`), or `null` when populated.                                                                                                                                                                                                                                                                        |
| `max_leverage`         | `integer` \| `null`           | —        | Maximum leverage allowed for this market (from market risk params), as an integer matching `max_leverage` on `/markets/{market_id}/risk-params`. `null` when market params are unavailable — see `max_leverage_error`.                                                                                                                                                                                       |
| `max_leverage_error`   | `string` \| `null`            | —        | Machine-readable reason `max_leverage` is `null` (e.g. `market_params_unavailable`), or `null` when populated.                                                                                                                                                                                                                                                                                               |
| `funding_paid`         | [Decimal](#decimal)           | —        | Cumulative funding paid on this position, as a decimal string. Sign is **paid-positive**: a positive value means the position has paid funding, a negative value means it has received funding. Always present: `"0"` when no funding has accrued. Bounded by the funding history the indexer retains.                                                                                                       |

## PreviewResponse

Pre-trade preview: projects the margin/equity/fee impact of an order without submitting it.

| Field                                    | Type                          | Required | Description |
| ---------------------------------------- | ----------------------------- | -------- | ----------- |
| `accepted`                               | `boolean`                     | —        | —           |
| `reject_reason`                          | `string` \| `null`            | —        | —           |
| `required_initial_margin`                | [Decimal](#decimal)           | —        | —           |
| `projected_post_trade_equity`            | [Decimal](#decimal)           | —        | —           |
| `projected_post_trade_liquidation_price` | [Decimal](#decimal) \| `null` | —        | —           |
| `projected_post_trade_leverage`          | [Decimal](#decimal)           | —        | —           |
| `expected_fill_vwap`                     | [Decimal](#decimal) \| `null` | —        | —           |
| `projected_fees`                         | [Decimal](#decimal)           | —        | —           |

## RateLimitStatus

| Field         | Type                | Required | Description                                                                                                                                                                |
| ------------- | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tier`        | `string`            | Yes      | Rate limit tier name (e.g. `pro`, `marketmaker`, `unlimited`).                                                                                                             |
| `limit`       | `integer` \| `null` | Yes      | Maximum requests per second. Also the burst capacity — the token bucket holds one second's worth of tokens — so `remaining` never exceeds it. Null for the unlimited tier. |
| `remaining`   | `integer` \| `null` | Yes      | Requests that can be made right now before throttling (tokens currently in the bucket). Null for the unlimited tier.                                                       |
| `reset_at_ms` | `integer` \| `null` | Yes      | Unix timestamp in milliseconds when the bucket refills back to `limit`; `0` when it is already full. Null for the unlimited tier.                                          |

## ServiceHealth

Aggregate health for the indexer/engine/oracle/bots, consumed by status.nexus.xyz. The `services` object carries per-component detail; only the common fields are documented here.

| Field          | Type                        | Required | Description                                                                                                                                            |
| -------------- | --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status`       | `string`                    | —        | Worst-of across all components. One of: `ok`, `degraded`, `down`, `starting`.                                                                          |
| `timestamp_ms` | [TimestampMs](#timestampms) | —        | —                                                                                                                                                      |
| `services`     | `object`                    | —        | Per-component status (indexer, engine, oracle, bots). Component detail is informational and may evolve; clients should rely on the top-level `status`. |

## SetCancelOnDisconnectRequest

Cancel-on-disconnect opt-in change for the authenticated account.

| Field     | Type      | Required | Description                                           |
| --------- | --------- | -------- | ----------------------------------------------------- |
| `enabled` | `boolean` | Yes      | True to enable COD for the account, false to disable. |

## StatsSnapshot

Aggregate venue statistics. `/stats` augments the snapshot with rolling unique-trader counts.

| Field                   | Type                                  | Required | Description                                                  |
| ----------------------- | ------------------------------------- | -------- | ------------------------------------------------------------ |
| `events_received`       | `integer`                             | —        | —                                                            |
| `fills_total`           | `integer`                             | —        | —                                                            |
| `liquidations_total`    | `integer`                             | —        | —                                                            |
| `gap_count`             | `integer`                             | —        | —                                                            |
| `connected`             | `boolean`                             | —        | —                                                            |
| `last_event_ms`         | [TimestampMs](#timestampms) \| `null` | —        | —                                                            |
| `uptime_seconds`        | `integer`                             | —        | —                                                            |
| `events_per_sec`        | `number`                              | —        | —                                                            |
| `health`                | `string`                              | —        | Health classification (e.g. Healthy / Degraded / Unhealthy). |
| `highest_sequence_seen` | `integer`                             | —        | —                                                            |
| `unique_traders_24h`    | `integer`                             | —        | Rolling 24h unique traders (DAU). Present on `/stats`.       |
| `unique_traders_7d`     | `integer`                             | —        | Rolling 7d unique traders (WAU). Present on `/stats`.        |
| `unique_traders_30d`    | `integer`                             | —        | Rolling 30d unique traders (MAU). Present on `/stats`.       |

## ThroughputSample

One point in the venue throughput ring buffer (1s cadence, capped at 3600 points).

| Field       | Type      | Required | Description   |
| ----------- | --------- | -------- | ------------- |
| `timestamp` | `integer` | —        | Unix seconds. |
| `fills`     | `integer` | —        | —             |

## Ticker

CCXT-compatible ticker with 24h statistics

| Field         | Type                        | Required | Description                                                                                                                                                        |
| ------------- | --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `symbol`      | `string`                    | —        | —                                                                                                                                                                  |
| `timestamp`   | [TimestampMs](#timestampms) | —        | Unix ms                                                                                                                                                            |
| `datetime`    | `string`                    | —        | Format `date-time`.                                                                                                                                                |
| `high`        | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `low`         | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `bid`         | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `bidVolume`   | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `ask`         | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `askVolume`   | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `open`        | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `close`       | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `last`        | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `change`      | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `percentage`  | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `baseVolume`  | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `quoteVolume` | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `markPrice`   | `number` \| `null`          | —        | Engine-derived mark price (oracle + premium-index), falling back to the last trade until the first mark-price poll lands. The raw last trade is carried by `last`. |
| `indexPrice`  | `number` \| `null`          | —        | —                                                                                                                                                                  |
| `info`        | `object`                    | —        | —                                                                                                                                                                  |

## TimestampMs

Unix epoch timestamp in milliseconds.

**Type:** `integer` (format `int64`)

## Trade

CCXT-compatible trade record

| Field            | Type                        | Required | Description            |
| ---------------- | --------------------------- | -------- | ---------------------- |
| `id`             | `string`                    | —        | Format `uuid`.         |
| `symbol`         | `string`                    | —        | —                      |
| `price`          | `number`                    | —        | —                      |
| `amount`         | `number`                    | —        | —                      |
| `cost`           | `number`                    | —        | —                      |
| `side`           | `string`                    | —        | One of: `buy`, `sell`. |
| `timestamp`      | [TimestampMs](#timestampms) | —        | —                      |
| `datetime`       | `string`                    | —        | Format `date-time`.    |
| `takerOrMaker`   | `string` \| `null`          | —        | —                      |
| `is_liquidation` | `boolean`                   | —        | —                      |
| `info`           | `object`                    | —        | —                      |

## Withdrawal

A single withdrawal record for the authenticated account

| Field       | Type                        | Required | Description                                                          |
| ----------- | --------------------------- | -------- | -------------------------------------------------------------------- |
| `id`        | `string`                    | Yes      | Withdrawal ID                                                        |
| `amount`    | [Decimal](#decimal)         | Yes      | Withdrawn amount in USDX (decimal string)                            |
| `timestamp` | [TimestampMs](#timestampms) | Yes      | Unix ms                                                              |
| `status`    | `string`                    | Yes      | Withdrawal lifecycle status. One of: `pending`, `settled`, `failed`. |


# Overview

Programmatic access to the Nexus Exchange — OpenAPI spec, SDKs, CLI, and MCP.

The Nexus Exchange is API-first: everything a trader can do is available programmatically. The **Interfaces** are the officially supported clients for that API — a machine-readable OpenAPI specification, language SDKs, a command-line tool, and an MCP server for AI agents. All of them speak to the same REST and WebSocket gateway documented under [APIs & Rates](/exchange/apis-and-rates); pick whichever fits how you build.

### The OpenAPI specification

The API is contract-first. The machine-readable schema — every route, request/response body, and CCXT method mapping — is published at [`nexus-xyz/nexus-exchange-api`](https://github.com/nexus-xyz/nexus-exchange-api) and served live at `/openapi.json`. Releases are tracked on [GitHub Releases](https://github.com/nexus-xyz/nexus-exchange-api/releases). The SDKs, CLI, and MCP server below are all generated from — or pinned to — a released version of this spec, so they stay in lockstep with the gateway.

### SDKs

| Language   | Repository                                                            | Use it for                                       |
| ---------- | --------------------------------------------------------------------- | ------------------------------------------------ |
| Rust       | [`nexus-exchange-rs`](https://github.com/nexus-xyz/nexus-exchange-rs) | Latency-sensitive clients and market-making bots |
| TypeScript | [`nexus-exchange-ts`](https://github.com/nexus-xyz/nexus-exchange-ts) | Web, Node, and edge applications                 |
| Python     | [`nexus-exchange-py`](https://github.com/nexus-xyz/nexus-exchange-py) | Research, backtesting, and scripting             |

Each SDK handles request signing (HMAC), pagination, and the WebSocket subscription lifecycle so you don't reimplement them. Version support and breaking-change policy are documented in each repository's README.

For the account and portfolio surface — consolidated account state, the withdrawable balance, the fee schedule, enriched position risk fields, and the equity/PnL/volume time-series — see [Portfolio & Account State](/interfaces/portfolio), which covers all four interfaces together.

Whatever you build with, the same budget applies: requests are charged by **weight per second** rather than counted, and reads, order writes, and the WebSocket control plane draw on three independent pools. See [Rate Limits](/interfaces/rate-limits) before writing a client-side limiter — a client that paces by request count will be refused while its own counter still looks healthy.

### Command line

The [`nexus-exchange-cli`](https://github.com/nexus-xyz/nexus-exchange-cli) wraps the same API for interactive use and shell scripting — manage keys, place and cancel orders, and query account state without writing code. `nexus --version` reports the API spec and SDK versions it is built against.

### MCP server

The [`nexus-exchange-mcp`](https://github.com/nexus-xyz/nexus-exchange-mcp) server exposes the Exchange as [Model Context Protocol](https://modelcontextprotocol.io) tools, so an AI assistant or agent can trade and read account state through the same authenticated API a human client uses. It is published as [`@nexus-xyz/exchange-mcp`](https://www.npmjs.com/package/@nexus-xyz/exchange-mcp):

```bash
claude mcp add nexus-exchange -- npx -y @nexus-xyz/exchange-mcp
```

It runs over stdio, so credentials stay on the machine that adds it, and its public market-data tools work with no key at all.

### Choosing a network

The Exchange runs on **Nexus Testnet** today as a development preview, with a public **mainnet** to follow. Every interface targets one network at a time — `testnet`, `mainnet`, or `local` — selected when you construct the client, which bundles that network's REST and WebSocket targets, faucet availability, and signing domain together. Testnet is the default everywhere; mainnet is not reachable yet. Credentials are scoped to the network they were created on.

See [Networks](/interfaces/networks) for how each interface selects one, how to override the target, and what binds an API key to a network; [APIs & Rates](/exchange/apis-and-rates) for the current base URL; and the [Quickstart](/exchange/trading/quickstart) for the end-to-end connection flow.

### Authentication

Authentication is identical across every interface. You sign a fixed message with your wallet (EIP-191) to obtain a short-lived session token, use that token once to mint an HMAC API key, then sign each trading request with that key. The SDKs and CLI perform the request signing for you. The full walkthrough, with runnable examples, is in the [Quickstart](/exchange/trading/quickstart).

> **Status:** development preview on testnet. The interfaces track the OpenAPI spec release-by-release; pin to a spec version in production and consult each repository's release notes before upgrading. Testnet credentials and balances have no real-world value.


# Networks

How every Nexus Exchange interface selects a network — testnet, mainnet, local, or a custom target you describe yourself.

Every Nexus Exchange interface talks to exactly one **network**, chosen when you construct the client. A network is not a release channel: it is whose money is at stake. **Testnet** carries synthetic play funds, **mainnet** will carry real funds, and **local** is a developer convenience. Selecting one bundles everything that differs between them — the REST and WebSocket targets, whether a faucet exists, and the signing domain your requests are scoped to — so you pick a network rather than assembling a URL. For the current base URLs, see [APIs & Rates](/exchange/apis-and-rates).

Those three are the published networks. A deployment you run yourself is a [`Custom` network](#the-custom-network) — the same bundle, described by you rather than resolved for you.

### The three published networks

| Network   | Funds                                           | Faucet | Availability                                                            |
| --------- | ----------------------------------------------- | ------ | ----------------------------------------------------------------------- |
| `testnet` | Synthetic USDX with no real-world value         | Yes    | Live today. The default in every interface.                             |
| `mainnet` | Real funds — USDX bridged from Ethereum Mainnet | No     | Will launch with the real-funds Exchange. Not reachable yet; see below. |
| `local`   | Synthetic, against an indexer you run yourself  | Yes    | For local development. Not a public network.                            |

Testnet is the default everywhere, deliberately: defaulting to real funds would be the unsafe direction.

**Mainnet is not reachable yet.** Its public host is not live, so no interface will resolve a mainnet target on your behalf — rather than send your requests somewhere plausible-but-wrong, each one fails closed in the way that fits it: the Python and TypeScript clients raise at construction, the MCP server refuses to start, and the Rust client (and therefore the CLI) builds fine but rejects every request locally, before any bytes leave the process. Naming the target yourself is the one way through: Python, TypeScript, and the MCP server accept `mainnet` when it comes with an explicit base URL (see [Pointing at a host without describing it](#pointing-at-a-host-without-describing-it)), which is how a real-funds deployment is reached before the durable host is live. In Rust the override is a separate constructor that carries no network at all, so it targets a URL rather than mainnet. Nothing is guessed for you, and that is the point: guessing a real-funds target is the one failure that cannot be rehearsed. When mainnet launches, this page and each interface's release notes will say so.

### Selecting a network

| Interface  | Select a network                                                 | Default   |
| ---------- | ---------------------------------------------------------------- | --------- |
| Python     | `Client(network=Network.TESTNET)`                                | `testnet` |
| TypeScript | `new Client({ network: Network.Testnet })`                       | `testnet` |
| Rust       | `Config::new(Network::Testnet)`                                  | `testnet` |
| CLI        | `--network <mainnet\|testnet\|local\|LABEL>`, or `NEXUS_NETWORK` | `testnet` |
| MCP server | `NEXUS_EXCHANGE_NETWORK`                                         | `testnet` |

The CLI's `--network` also accepts the label of a custom target declared in its config file, and the MCP server accepts `NEXUS_EXCHANGE_NETWORK=custom` alongside a described bundle — see [Describing a custom target](#describing-a-custom-target).

An unrecognized network name is always an error. No interface falls back to a default, and none falls back to `local` — an unknown identifier is treated as real funds until proven otherwise, so it must be corrected rather than guessed at. Retired release-channel names are rejected with a pointer to their replacement: `stable` named a host that serves testnet, so `testnet` is its direct equivalent.

### Example

```python
from nexus_exchange import Client, Network

client = Client(network=Network.TESTNET, api_key=..., api_secret=...)
```

```typescript
import { Client, Network } from "@nexus-xyz/exchange-ts";

const client = new Client({ network: Network.Testnet, apiKey: "…", apiSecret: "…" });
```

```rust
use nexus_exchange::{Config, Network};

let config = Config::new(Network::Testnet);
```

```bash
nexus --network local markets
```

### The `Custom` network

The three networks above are the published ones. A deployment you run yourself — a private stage, a preview environment, an indexer on your own infrastructure — is a **`Custom` network**: a fourth kind of target that you describe, rather than a URL bolted onto one of the three.

It is a network rather than an address because everything a named network bundles still has to be answered for a private deployment, and a URL answers none of it. `Custom` carries the same bundle, supplied by you:

| Part of the bundle        | Who supplies it                                                                                                                                                                                                                                                            |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **REST base**             | You. Required.                                                                                                                                                                                                                                                             |
| **Direct `/api/v1` base** | You, where the deployment splits it from the REST base. Defaults to the REST base, which is where a single-host deployment serves it.                                                                                                                                      |
| **WebSocket origin**      | You, in the interfaces that let a deployment split it out — the Rust SDK and CLI never derive one, because pairing a stream token with an origin that did not mint it is the failure that would follow. See [the field-set note](#the-two-field-sets-differ-deliberately). |
| **Funds**                 | You. Required, no default — see below.                                                                                                                                                                                                                                     |
| **Faucet**                | You. Assumed **absent** until declared, so a funding call cannot route at a faucet that is not there.                                                                                                                                                                      |
| **Signing domain**        | Read from this deployment's `GET /metadata`, or supplied by you. Never guessed.                                                                                                                                                                                            |
| **Label**                 | You. Required — it is the credential namespace.                                                                                                                                                                                                                            |

#### Funds is a required tri-state

`funds` is `real`, `play`, or `unknown`, and it has **no default**. Neither boolean answer is safe to assume: a staging deployment of the real-funds Exchange is real-funds-shaped, and a preview environment with production data behind it is not play money just because it is not the published host. Only the operator knows.

`unknown` **fails closed**. It is a legitimate answer — you may genuinely not know, and saying so is better than guessing — but the interfaces then refuse the operations guarded on real funds rather than letting them through. If a command or tool that moves value declines on a custom target, an undeclared `funds` is the first thing to check.

#### The label is required, and it is a credential namespace

Every `Custom` target carries a caller-chosen label, constrained to `[A-Za-z0-9._-]` with a maximum of 64 characters. `.` and `..` are rejected outright, and so are the names the built-in networks already answer to — `mainnet`, `testnet`, `local`, and `custom` itself — which are legal under that character set but would address another target's credentials by naming it.

The constraint is not cosmetic. The label is the key your stored credentials are namespaced under, so it reaches the filesystem in the clients that persist configuration. An unvalidated label containing `/` or `..` is a path traversal, and the credential it would address belongs to a different target. The character set and the length cap hold across every interface, each enforcing them itself rather than inheriting them — the MCP server is not built on the Rust SDK and carries its own copy of the rule. The reserved names are the one part that is not yet uniform: the Rust SDK and the CLI refuse a built-in network's name, the MCP server does not. Choose a label that collides with none of them rather than relying on the refusal to catch it.

Labels in your own configuration are yours to choose; `dev`, `preview` and `example` are all fine.

#### The signing domain is never guessed

A `Custom` target does not inherit a signing domain from anywhere. The client reads the EIP-712 chain id from the deployment's own `GET /metadata`, or takes one you supply — and if it has neither, it **refuses to sign** rather than reusing a value from another network.

This is the one rule worth understanding before you hit it, because the failure is silent in the other direction: a signature made under the wrong domain can be *valid on a different network*. Refusing is the only safe answer.

### Pointing at a host without describing it

Every interface still accepts a bare base URL:

| Interface  | Bare base URL                                               |
| ---------- | ----------------------------------------------------------- |
| Python     | `base_url=`, plus `direct_base_url=` for the direct surface |
| TypeScript | `baseUrl`                                                   |
| Rust       | `Config::with_base_url(…)`, plus `.with_direct_base_url(…)` |
| CLI        | `--base-url <URL>`, or `NEXUS_BASE_URL`                     |
| MCP server | `NEXUS_EXCHANGE_API_URL`                                    |

This is the shortcut, not the documented path — in the MCP server it is formally deprecated, still working unchanged but printing a notice that points at the bundle. A bare URL builds a `Custom` target with **undeclared funds** and **no signing domain** — so the real-funds-guarded operations refuse, and the client will not sign. That is usable for a read-only look at a host, and it is deliberately not enough to trade against one.

To do more than read, describe the target instead of naming an address.

### Describing a custom target

Two interfaces take a full bundle from configuration rather than from constructor arguments.

**MCP server** — set `NEXUS_EXCHANGE_NETWORK=custom`, then:

| Variable                       | Meaning                                                                                              |
| ------------------------------ | ---------------------------------------------------------------------------------------------------- |
| `NEXUS_EXCHANGE_API_URL`       | REST base. Required.                                                                                 |
| `NEXUS_EXCHANGE_NETWORK_LABEL` | The label. Required.                                                                                 |
| `NEXUS_EXCHANGE_FUNDS`         | `real`, `play` or `unknown`. Required.                                                               |
| `NEXUS_EXCHANGE_FAUCET`        | Whether a faucet exists here. Absent means no.                                                       |
| `NEXUS_EXCHANGE_GATEWAY_PATH`  | Where the gateway surface hangs off the origin: `/api/exchange` (default) or `/` for a bare indexer. |

Setting any of these **without** `NEXUS_EXCHANGE_NETWORK=custom` is an error rather than a silent no-op — half a bundle applied quietly would leave you believing you had configured a safety property that is not in effect.

**CLI** — declare the target under `custom_networks` in its config file (`$XDG_CONFIG_HOME/nexus/config.json`, falling back to `~/.config/nexus/config.json`) and select it by label:

```json
{
  "custom_networks": {
    "dev": {
      "base_url": "https://exchange.example.com/api/exchange",
      "direct_base_url": "https://api.example.com",
      "funds": "play",
      "ws_url": "wss://stream.example.com",
      "faucet": true
    }
  }
}
```

```bash
nexus --network dev markets
```

A `chain_id` belongs in that entry too, once you have read it from this deployment's own `GET /metadata` — it is deliberately omitted above rather than shown with a placeholder value. There is no sentinel that means *unknown*: any number you write is taken as the EIP-712 domain, so a made-up one buys a signature under the wrong domain instead of the refusal that leaving it out gives you.

An undeclared label is an error, and a declared one is validated when it is selected rather than when the file is read — a mistake in a stage you are not using does not break every other command.

#### The two field sets differ, deliberately

Neither list is a subset of the other. Three differences, each with a reason:

* **`chain_id` is CLI-only.** The MCP server never produces an EIP-712 signature — it authenticates with HMAC — so it has no use for a signing domain, and carrying one would be a second place for it to go stale.
* **`gateway_path` is MCP-only**, and is the same knob as the CLI's `direct_base_url` spelled the other way round: the MCP server takes the path and derives the direct base, the CLI takes the direct base and needs no path.
* **`ws_url` is CLI-only.** The MCP server derives its WebSocket origin from the gateway base, which the API contract supports because the stream paths carry no separate host. The CLI accepts an explicit one because a deployment may put its stream on a different host — so a custom target whose WebSocket origin is genuinely separate can be described to the CLI, and cannot yet be described to the MCP server.

### What a network carries

Selecting a network resolves all of the following together, which is why it is one choice rather than several:

* **REST base** for the trading and account surface.
* **WebSocket bases** for public market data and for the authenticated stream, where the interface streams at all — the Python SDK ships no WebSocket client, and the Rust SDK refuses to connect on testnet until the published stream host is live rather than pair a stream token with an origin that did not mint it, so supply an explicit WebSocket URL there.
* **Whether a faucet exists.** Among the published networks, synthetic funding is testnet- and local-only; mainnet has none. A custom target declares its own, and is assumed to have none until it does.
* **Whether balances are real money** — a single flag to branch on before anything irreversible, rather than pattern-matching a hostname.
* **The EIP-712 signing domain**, which scopes agent registration to this network. Its chain id is server-authoritative: read it from `GET /metadata` for the network you are connected to. A client that cannot obtain one refuses to sign rather than reusing a value from elsewhere.

### Credentials and network isolation

Session tokens, HMAC API keys, and agent registrations are minted **per network** and are invalid on every other. A key configured for one network will not authenticate against another, so a credential exposed on testnet cannot sign for real funds.

A client is bound to its network for its whole lifetime — there is no setter. Switching networks means constructing a new client with that network's own credentials, and never carrying a signature, nonce, or agent registration across.

The CLI stores credentials **per network**, keyed by network name — and for a custom target, by its label rather than its URL, so two stages on the same host keep separate credentials. `--network` therefore selects the target *and* the credential set together. What it cannot do is mint one: switching to a network you have not set up yet leaves you with no stored key for it, so run `nexus setup` for that network, or pass its credentials alongside `--network` on the command line.

#### What binds a key to a network

**The host you mint it against.** There is no network parameter on key creation: `POST /keys` records the network of the instance that served the request, and the key is valid only there from then on. So the base URL you point at when you create a key *is* the binding decision, made implicitly — there is nothing to set and nothing to confirm.

Two consequences worth planning around:

* Creating a key while pointed at one network and then trading against another cannot work, however the client is configured afterwards.
* You need a separate key per network you intend to use, minted separately against each.

#### What a wrong-network key looks like

It looks exactly like a key that does not exist — **HTTP 401 Unauthorized** with this body:

```json
{
  "code": "UNAUTHORIZED",
  "message": "Authentication required"
}
```

That response is **deliberately indistinguishable** from a bad signature, an unknown key id, or a missing header. A rejected key must not be identifiable as "real, but registered elsewhere", because that would let someone confirm a guessed key id is live on another network. The API therefore tells you nothing beyond "this request is not authenticated."

The practical implication: **an authentication failure after changing which network you target is far more likely to be the network than a revoked key.** Check which host minted the key before assuming it was deleted or the secret was lost — nothing in the response will point you there.

#### Keys minted before per-network binding

Keys created before network binding existed carry no network of their own. They keep working on testnet and local, which adopt such keys as their own and record the network on them. They will **not** be accepted on mainnet when it launches: an unmarked key proves nothing about where it came from, and real funds will not extend trust on an assumption. Mint a fresh key against mainnet rather than expecting an existing one to carry over.

For the full authentication walkthrough — wallet sign-in through to a signed request — see the [Quickstart](/exchange/trading/quickstart).

### Related

* [APIs & Rates](/exchange/apis-and-rates) — current base URLs and rate limits
* [Interfaces overview](/interfaces/interfaces)
* [Portfolio & Account State](/interfaces/portfolio)
* [Quickstart](/exchange/trading/quickstart)

> **Status:** development preview on testnet. Mainnet has not launched and is not reachable yet. The network axis replaced the earlier `{stable, beta, local}` release-channel selector; pin to a released version in production and check each interface's release notes before upgrading. Testnet credentials and balances have no real-world value.


# Rate Limits

What a request costs, what the rate-limit headers mean, which budgets are independent of which, and what happens when you exceed each.

The Exchange does not budget your traffic in requests per second. It budgets **weight per second**: every request is charged a cost, most requests cost one unit, and a few cost considerably more. A client that paces itself by counting requests will be refused while its own counter still looks healthy, which is the single most common integration surprise on this surface.

This page explains the model — what things cost, which budgets are separate, and how to read the headers. The **normative** definition lives in the OpenAPI contract, published at [`nexus-xyz/nexus-exchange-api`](https://github.com/nexus-xyz/nexus-exchange-api) and served at `/openapi.json`: the "Rate limits" section of the API description owns header semantics, and each operation carries its own machine-readable cost. Where this page and the contract disagree, the contract is right.

### What a request costs

Your budget is a **token bucket that refills continuously at your tier's per-second rate**, with a capacity of exactly one second of tokens. Two consequences follow from that capacity: the sustained rate and the burst allowance are the same number — there is no multi-second reserve to bank by idling — and `remaining` can never exceed `limit`.

Most requests cost **1**. The exceptions:

| Cost                              | Applies to                                                                                    | Why                                                                                                 |
| --------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **5**                             | `GET /account/summary`, `GET /fills`, `GET /orders/history`, `GET /account/portfolio-history` | Each folds or scans a large per-account buffer — fills, order history, or the portfolio time-series |
| **`1 + floor(order_count / 40)`** | `POST /orders/batch`                                                                          | Up to 40 orders cost the same as one; every further 40 adds a unit                                  |
| **1**                             | every other operation the contract documents                                                  | The default                                                                                         |

So a Pro caller at `limit: 20` gets 20 ticker reads per second — **or 4 `/fills` reads**. Same budget, same tier, different arithmetic. Pace on the weight, not on the request count.

Two safety properties are worth knowing, because they bound the worst case. A single request's charge is capped at one second of tokens, so an oversized batch can never be permanently unsatisfiable — it drains the whole second once the bucket has refilled and goes through, rather than looping forever on a retry it could never afford. And a batch body the server cannot parse is charged the base unit rather than refused over weighting.

Rather than hardcoding the table above, read the cost from the contract: operations costing more than one unit carry **`x-nexus-rate-limit-weight`**, and those whose cost depends on the body also carry **`x-nexus-rate-limit-weight-formula`**. **For an operation the contract documents, absence of the marker means weight 1.** That is the form to build a client-side limiter against.

The qualifier is deliberate. The contract enumerates the supported surface, and that rule holds across it — but it is not a statement about any path that happens to answer. A route the contract does not list carries no marker, is not covered by the rule, and may be charged differently from what its absence suggests. Build against the operations the contract documents rather than against paths found by probing; that is the surface the weights, and this page, describe.

### Three budgets, not one

There are three independent resource classes. Spending one does not spend the others, and each refuses in its own way:

| Class                       | Covers                                                                                    | Charged against                                                          |
| --------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| **Requests**                | Every REST operation that is not an order write                                           | Your per-key and per-owner request buckets                               |
| **Trading actions**         | `POST`, `PATCH` and `DELETE` under `/orders` — marked `x-nexus-rate-limit-class: trading` | A dedicated order bucket, the same per-second size as the request bucket |
| **WebSocket control plane** | Connections, subscriptions, and inbound client frames                                     | Per-tier ceilings — see [WebSocket ceilings](#websocket-ceilings)        |

An order write is charged to the trading bucket **instead of** the request bucket, not in addition to it. That is the point of the split: a burst of polling cannot starve your order placement, and order flow cannot starve your reads. The corollary is the part to internalize — **a healthy `x-ratelimit-remaining` on your last read tells you nothing about your order-placement headroom.** They are different pools, and `GET /account/rate-limit` reports the request class only.

`POST /orders/preview` is a trading action too, which catches people out: it is a write on the `/orders` surface and costs a trading-class unit exactly as placing an order does. Previewing before every order therefore halves your effective placement rate. Budget two trading-class charges per order placed that way, or skip the preview once you already know the sizing.

A caller presenting an HMAC key passes a **per-key** bucket and then the **per-owner** bucket for its tier; the effective ceiling is whichever binds first. `GET /account/rate-limit` reports that minimum, and polling it is free — it is the one operation that consumes no tokens, precisely so that pacing yourself cannot throttle you.

One wrinkle for anyone who has just been promoted: a key's own ceiling is recorded when the key is created (20/s by default) and a tier change does not rewrite it. A Market Maker account still using a key minted at the default can therefore be held at the key's number rather than the tier's. Read `/account/rate-limit` after a promotion instead of assuming the tier figure; mint a fresh key if the minimum reported is not the one you expect.

### Reading the headers

* On **every** authenticated response: `x-ratelimit-limit` and `x-ratelimit-remaining`.
* On a **`429` only**, additionally: `x-ratelimit-reset` (unix seconds) and `retry-after` (seconds, never below 1).

Do not expect the latter two on a success — a client that reads `x-ratelimit-reset` off a 2xx reads nothing.

**`remaining` and `retry-after` are deliberately in different units.** `remaining` is expressed in unit-cost requests: `x-ratelimit-remaining: 10` means ten weight-1 requests *or* two heavy ones. `retry-after`, by contrast, is derived from the weighted cost of the request that was actually refused. A limiter that reads `remaining` as "requests of the kind I am about to send" will over-send on heavy endpoints and 429 itself.

A refusal is `HTTP 429` with this body:

```json
{
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Order placement rate limit exceeded",
  "tier": "Pro"
}
```

Branch on `code`. The `message` names which pool bottlenecked — `Rate limit exceeded`, `API key rate limit exceeded`, `Order placement rate limit exceeded`, or `IP rate limit exceeded` — and is a **diagnostic**: its wording is not stable and must not be matched programmatically.

Unlike the `403` jurisdiction refusals, a `429` **is** retryable: honour `retry-after` and back off. Pacing off `x-ratelimit-remaining` beats discovering the ceiling by hitting it.

The free `GET /account/rate-limit` reports the same state without spending a token, for the request class:

```json
{
  "tier": "pro",
  "limit": 20,
  "remaining": 17,
  "reset_at_ms": 1750000000123
}
```

Two details to handle when you use both this endpoint and the headers. `reset_at_ms` is **milliseconds**, while the `x-ratelimit-reset` header is unix **seconds**. And the tier name is lower-cased here (`pro`) but not in the `429` body (`Pro`), so compare it case-insensitively rather than against a literal.

All three numeric fields are `null` for an `Unlimited` caller, which is bucketed per IP rather than per account.

### Tiers and current ceilings

Tiers are multipliers on one model, not different models. **Pro** is the default for every account. **MarketMaker** is admin-assigned — request it through your Nexus contact, see the [Market Maker Guide](/exchange/apis-and-rates/market-maker-guide). **Unlimited** exists for gateway keys that multiplex many users and is never assigned to a trading account.

| Tier          | Requests     | Trading actions                         | WS connections | WS subscriptions | WS inbound frames |
| ------------- | ------------ | --------------------------------------- | -------------- | ---------------- | ----------------- |
| `Pro`         | 20/s         | 20/s                                    | 5              | 50               | 10/s              |
| `MarketMaker` | 2,000/s      | 2,000/s                                 | 100            | 1,000            | 50/s              |
| `Unlimited`   | per-IP, 50/s | per-IP, 50/s — the same bucket as reads | exempt         | exempt           | exempt            |

Two limits apply regardless of tier. Unauthenticated market-data reads are bucketed **per client IP at 50/s**. And traffic whose client IP cannot be resolved at all is not admitted unthrottled — it shares one strict bucket of 5/s, so an unresolvable origin degrades to a low ceiling rather than to no ceiling.

`Unlimited` deserves one caveat, because its exemptions are narrower than the name suggests. Its order writes are **not** exempt from rate limiting: they skip the dedicated trading bucket and are charged to the same per-IP bucket as its reads, so the class independence above does not hold there — a gateway's order flow can be crowded out by its own polling, on the tier whose traffic mix is least predictable. And the WS ceilings it is exempt from are the *per-account* ones; the per-IP connection cap below still binds.

**These numbers are current defaults, not a contract.** They are deployment configuration — some of them still code constants — and they will change as the tier system is finalized. Read `/account/rate-limit` rather than hardcoding them.

### WebSocket ceilings

The WebSocket ceilings are a resource class of their own: independent of the REST request budget and of the trading-action budget, and exhausting one does not affect the others.

**Connections** are capped per account by tier (Pro 5, MarketMaker 100). A separate **per-IP cap — 5 by default** — is applied first, at upgrade time, and binds on every tier including `Unlimited` — so one origin address cannot reach the per-account figure on its own. A connection refused there gets an `HTTP 429` (`ws_conn_limit_exceeded`) on the upgrade request rather than a close frame, and the single-use stream token is spent either way: mint a fresh one before reconnecting.

**Subscriptions** are capped at the same number twice — per connection *and* across all of an account's connections. Opening more sockets therefore does not buy more subscriptions. Re-subscribing a `(channel, market)` key you already hold replaces it in place and is free. Exceeding the ceiling returns an `error` frame (`subscription_limit_exceeded`) rather than a status code; there are no HTTP responses once the socket is open.

**Inbound frames** — your subscribes, unsubscribes and pings — are limited to the sustained per-tier rate with a **2× burst** tolerated above it, so a reconnect-and-resubscribe storm is not penalized. Beyond that, over-limit frames are **dropped**, and you get one `error` notice per accounting window rather than one per frame (an inbound flood is not amplified into an outbound one). Sustained flooding — enough dropped frames inside one window — closes the connection with code **`1008`** (policy violation). A dropped frame is silently not applied: if you do not see a `subscribed` ack, re-send the subscribe after backing off rather than assuming it took effect.

### Budgets are per network

Each network is its own deployment, so **each network has its own buckets**. Spending on testnet does not reduce mainnet headroom when mainnet launches, and neither does the reverse. Credentials do not cross networks either — a key is bound to the network that minted it. See [Networks](/interfaces/networks).

### Where enforcement lives today

One honest limitation, because it is visible from outside and it moves in your favour rather than against you.

Limiter state is held **in memory at the gateway process**, not in a shared store. Two things follow. First, it is not durable: a redeploy resets your buckets, and a tier promotion may briefly fall back to the base tier until it is re-applied. Second, when a network's gateway runs more than one replica, each holds its own buckets, so the *aggregate* ceiling a client observes can be higher than the published per-second figure, depending on how its connections land.

Do not design against that headroom. Treat the published number as the ceiling you are entitled to and pace to it: the extra is an artifact of where enforcement currently lives, it is not distributed evenly, and it goes away when counters move to a shared store. A client built to the published figure keeps working when that lands; one tuned to the observed aggregate will start seeing 429s.

### Designing for the limits

* **Batch instead of looping.** `POST /orders/batch` charges `1 + floor(n / 40)`, so 40 orders in one request cost a fortieth of 40 single submits.
* **Stream instead of polling.** Order book and trade data over WebSocket costs nothing against your request budget, and arrives sooner: the subscribe frame is one inbound frame, and the stream that follows is free.
* **Budget heavy reads at their real cost.** `/fills`, `/orders/history`, `/account/summary` and `/account/portfolio-history` cost 5 each. Polling all four every second costs 20/s — a Pro caller's entire budget.
* **Prefer one coherent read.** `GET /account/state` returns the summary and every open position together: cheaper than two calls, and free of the race between them (see [Portfolio & Account State](/interfaces/portfolio)).
* **Cache what does not change.** Market metadata from `GET /markets` does not need re-fetching every cycle.
* **Pace from the headers, and from `/account/rate-limit`.** Polling that endpoint is free. Retrying blindly after a `429` is not.

### Related

* [OpenAPI specification](https://github.com/nexus-xyz/nexus-exchange-api) — normative: per-operation weights, classes, and header semantics
* [Networks](/interfaces/networks) — how a network is selected, and what binds a key to one
* [Portfolio & Account State](/interfaces/portfolio) — the heavy-read surface, and how to read it in one call
* [Rate & Connection Limits](/exchange/apis-and-rates/rate-limits) — HMAC window, token lifetimes, faucet allowance
* [Market Maker Guide](/exchange/apis-and-rates/market-maker-guide) — requesting the MarketMaker tier
* [Interfaces overview](/interfaces/interfaces)

> **Status:** development preview on testnet. Mainnet has not launched. Every ceiling on this page is current configuration rather than a frozen contract — read `/account/rate-limit` in production instead of hardcoding a number, and pin to a released spec version so the per-operation weights you build against stay fixed. Limiter state is not yet durable across gateway restarts. Testnet credentials and balances have no real-world value.


# Portfolio & Account State

Consolidated account state, withdrawable balance, fee schedule, enriched positions, and the portfolio time-series — across the SDKs and CLI.

The portfolio surface answers four questions about an account in one place: what is it worth right now, what can actually be withdrawn, what does it get charged, and how has it performed over time. It is exposed as a small set of authenticated REST endpoints plus enriched per-position risk fields, and every interface — the Rust, TypeScript and Python SDKs and the CLI — reaches the same routes on the gateway documented under [APIs & Rates](/exchange/apis-and-rates).

If you are building a portfolio view, read [Reading these values safely](#reading-these-values-safely) before you render anything. Several fields are deliberately nullable, one sign convention is easy to invert, and there is one race condition that a naive two-call implementation will hit in production.

### Getting it

```bash
npm install @nexus-xyz/exchange-ts    # TypeScript
cargo add nexus-exchange              # Rust
pip install nexus-exchange            # Python
```

The CLI is distributed through [`nexus-exchange-cli`](https://github.com/nexus-xyz/nexus-exchange-cli) releases. See the [Interfaces overview](/interfaces/interfaces) for all four clients.

### Authentication

Every route on this page is account-scoped and requires an HMAC API key — there is no public or unauthenticated variant. Requests carry `X-API-Key`, `X-Timestamp` and `X-Signature`, and the timestamp must be within 30 seconds of server time. The SDKs and CLI sign for you. The full flow, with runnable cURL, is in the [Quickstart](/exchange/trading/quickstart).

Keep the API secret out of source and out of shell history — it is shown once at creation and cannot be retrieved later. The examples below read it from the environment.

### Choosing a network

The Exchange runs on Nexus Testnet today; mainnet will follow. Select the network when you construct the client — see [Networks](/interfaces/networks) for how each interface does it and how API keys bind to a network, and [APIs & Rates](/exchange/apis-and-rates) for the current base URL. The HMAC key these routes require is scoped to the network it was created on.

### Reference

| Method | Path                                        | Auth | Description                                                            |
| ------ | ------------------------------------------- | ---- | ---------------------------------------------------------------------- |
| GET    | `/account/state`                            | HMAC | Portfolio summary **and** every open position, from one coherent read  |
| GET    | `/account/summary`                          | HMAC | Portfolio summary alone — the same object embedded in `/account/state` |
| GET    | `/account/fees`                             | HMAC | Effective fee schedule for the account                                 |
| GET    | `/account/portfolio-history?window=&limit=` | HMAC | Equity, cumulative PnL and cumulative volume time-series               |

Per-SDK entry points:

| Interface  | Consolidated state      | Fee schedule           | Time-series                                        |
| ---------- | ----------------------- | ---------------------- | -------------------------------------------------- |
| Rust       | `fetch_account_state()` | `fetch_account_fees()` | `fetch_portfolio_history(window, limit)`           |
| Python     | `fetch_account_state()` | `fetch_account_fees()` | `fetch_portfolio_history(window, limit)`           |
| TypeScript | `getAccountState()`     | `getAccountFees()`     | `getPortfolioHistory({ window, limit })`           |
| CLI        | `nexus account state`   | `nexus account fees`   | `nexus account portfolio-history --window --limit` |

### Consolidated account state

`GET /account/state` returns the portfolio aggregates and all open positions built from **one** server-side read. Because both halves come from the same snapshot, `summary.open_positions_count` always equals the length of `positions`.

The summary carries `collateral`, `total_equity`, `total_unrealized_pnl`, `total_realized_pnl_24h`, `total_volume_24h`, `open_positions_count`, `open_orders_count`, `margin_used`, `available_margin` and `withdrawable`.

**`withdrawable`** is the balance that can actually leave the account: engine-authoritative free margin floored at zero, `max(0, available_margin)`. Free margin already nets each position's initial margin and every pre-trade order reservation out of equity, so this is the amount a withdrawal can draw on — not `total_equity`, and not `collateral`. An underwater account clamps to `"0"` and is never reported negative. It is derived from the authoritative margin view, so when that view is unavailable the endpoint **fails closed with `502`** rather than returning a locally estimated number.

### Fee schedule

`GET /account/fees` reports what the venue charges the account today — the forward-looking schedule rate, not a realized average over past fills.

| Field                  | Notes                                                                                |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `maker_fee_bps`        | Basis points. **May be negative**, which means the maker is paid a rebate            |
| `taker_fee_bps`        | Basis points — `5` is 0.05%                                                          |
| `tier`                 | Currently always `base`; there are no per-account tiers yet                          |
| `schedule`             | Scope of the reported rate. Currently always `standard`                              |
| `volume_30d`           | Rolling 30-day traded notional, decimal string. Best-effort — see the flag below     |
| `volume_30d_estimated` | `true` when `volume_30d` may **undercount** (the source fill buffer was at capacity) |
| `discounts`            | Active discounts. Currently always empty                                             |

`tier`, `schedule` and `discounts` are provisional: the fee model is still being finalized, and `discounts` entries have no guaranteed properties yet. Treat the two `_bps` integers as the stable part of this response and do not branch on a discount shape that does not exist.

### Portfolio time-series

`GET /account/portfolio-history` returns equity, cumulative trading PnL and cumulative traded notional over a window, downsampled server-side. Points are **oldest first**.

| `window` | Cadence | Max points | Span  |
| -------- | ------- | ---------- | ----- |
| `day`    | 5 min   | 288        | 24 h  |
| `week`   | 1 h     | 168        | 7 d   |
| `month`  | 6 h     | 120        | 30 d  |
| `all`    | 1 d     | 366        | \~1 y |

Omitting `window` gives `day`. A value outside that set is rejected with `400` (`invalid_window`); if the parameter is repeated, the first value is used. `limit` must be between `1` and `366` — inside that range it narrows the result and is **clamped** to the window's capacity rather than rejected, so asking for 366 points of `day` returns 288 rather than an error; outside it, the request is rejected with `400`.

The response echoes the `window` and the `cadence_ms` that were actually served. Read them back rather than assuming the values you sent: it is the difference between labelling an axis correctly and labelling it plausibly.

Each point carries `timestamp_ms`, `equity`, `pnl` and `volume`. `pnl` and `volume` are **cumulative up to that sample**, not per-interval — to chart per-interval activity, difference adjacent points yourself.

### Enriched position fields

Positions returned by `/account/state` (and `/positions`) carry per-position risk detail alongside `market_id`, `side`, `size`, `entry_price`, `unrealized_pnl` and `realized_pnl`:

| Field            | Meaning                                                                |
| ---------------- | ---------------------------------------------------------------------- |
| `notional_value` | `abs(size) × mark price`                                               |
| `margin_used`    | Initial margin held against the position, under the cross-margin model |
| `roe`            | Return on initial margin: `unrealized_pnl / margin_used`               |
| `max_leverage`   | Maximum leverage the market allows, from its risk params               |
| `leverage`       | The account's leverage multiplier for this position                    |
| `funding_paid`   | Cumulative funding on the position — see the sign convention below     |

These are computed on the low-latency read path rather than by round-tripping to the matching engine, which is what keeps the endpoint fast. The trade-off is that when one of their inputs is not available on that path, the field is `null` and a companion `<field>_error` carries a machine-readable reason instead of a fabricated number.

**`leverage` is currently always `null`,** with `leverage_error` set to `margin_state_not_mirrored`: deriving it needs the account's leverage setting or its allocated margin, and neither is available on the read path. Do not reconstruct it from `margin_used` — that expression collapses to `1 / initial_margin_rate`, which is a per-market constant, not the position's real leverage. Showing that as leverage would be confidently wrong on every position in the market.

**`funding_paid` is paid-positive.** A positive value means the position has *paid* funding; a negative value means it has *received* funding. It is always present, `"0"` before any funding accrues, and bounded by the funding history the venue retains. Inverting this sign turns a cost into income on a P\&L screen, so it is worth a test.

### Reading these values safely

**Monetary values are decimal strings, not numbers.** `equity`, `pnl`, `volume`, `withdrawable`, `notional_value` and the rest are arbitrary-precision decimals serialized as strings so they are lossless. Parse them with a decimal type. Passing them through a float — `parseFloat`, `float()`, `as f64` — reintroduces exactly the rounding error the string encoding exists to prevent. Leverage fields (`leverage`, `max_leverage`) are genuine JSON numbers.

**Derived fields have three states, not two.** Each of the five computed position fields — `notional_value`, `margin_used`, `roe`, `leverage` and `max_leverage` — can be:

1. **a value** — computed and authoritative;
2. **`null`** — reported, but not computable; the paired `<field>_error` says why;
3. **absent** — the server predates the field.

Collapsing any of these into `0` invents data. "Not reported", "not computable" and "zero" are three different answers to a user asking what their position is worth, and only one of them is a number. Render the missing cases as an explicit gap — the CLI prints `-` — and surface the `<field>_error` when you have it. The same applies to `withdrawable`: it is optional in the schema, so an older deployment can omit it, and defaulting that to `"0"` would tell someone they have nothing available when the truth is that nobody asked.

Those five are exactly the fields carrying a companion `<field>_error`. `funding_paid` is not one of them — it is always present, so there is no `funding_paid_error` to branch on.

**Prefer `/account/state` over two calls.** Fetching `/account/summary` and `/positions` separately is two independent requests against a live account. A fill landing between them returns an aggregate that disagrees with the position list — `open_positions_count` says three, the array has four — and the window is real under any load. The consolidated endpoint exists so that one coherent read backs both halves.

**Handle the failure codes distinctly.** `401` is a credential or clock problem — check that `X-Timestamp` is within 30 seconds of server time before assuming the key is wrong. `429` means you exceeded the rate budget; see [Rate Limits](/interfaces/rate-limits) and back off rather than retrying immediately. Note that three routes on this page — `/account/summary`, `/fills` and `/account/portfolio-history` — each cost **five** units of that budget rather than one, so polling them on a tight loop exhausts a Pro caller's 20/s allowance four times faster than a request count suggests. `/account/state` costs one and returns the summary and positions together, which is the cheaper way to read both. `502` on `/account/state` and `/account/summary` means the authoritative margin view was unreachable and the server declined to guess — retry, and do not fall back to a locally computed `withdrawable`.

### Example

```bash
export NEXUS_API_KEY=nx_7f3a1b...          # the key ID is not secret

# Prompt for the secret instead of typing it inline — an `export
# NEXUS_API_SECRET=...` would leave it in your shell history.
read -rs NEXUS_API_SECRET && export NEXUS_API_SECRET

nexus account state
nexus account fees
nexus account portfolio-history --window week
```

```typescript
import { Client, Network } from "@nexus-xyz/exchange-ts";

const client = new Client({
  network: Network.Stable,
  apiKey: process.env.NEXUS_API_KEY!,
  apiSecret: process.env.NEXUS_API_SECRET!,
});

// One coherent read: open_positions_count cannot disagree with positions.length.
const { summary, positions } = await client.getAccountState();

// Absent is not zero — say so rather than defaulting.
console.log(`withdrawable: ${summary.withdrawable ?? "<not reported>"}`);

for (const p of positions) {
  // null carries a reason in the companion field; absent carries nothing.
  const roe = p.roe ?? (p.roe_error ? `<${p.roe_error}>` : "<not reported>");
  // Paid-positive: a positive funding_paid means this position paid.
  console.log(`${p.market_id} ${p.side} ${p.size}  roe=${roe}  funding_paid=${p.funding_paid}`);
}

// The response echoes what was served — read it back, don't assume.
const history = await client.getPortfolioHistory({ window: "week" });
console.log(`${history.window} @ ${history.cadence_ms}ms, ${history.points.length} points`);
```

```json
{
  "summary": {
    "collateral": "25000.00",
    "total_equity": "25500.00",
    "total_unrealized_pnl": "500.00",
    "margin_used": "1075.00",
    "available_margin": "24425.00",
    "withdrawable": "24425.00",
    "open_positions_count": 1,
    "open_orders_count": 0
  },
  "positions": [
    {
      "market_id": "BTC-USDX-PERP",
      "side": "Long",
      "size": "0.25",
      "entry_price": "84000.00",
      "unrealized_pnl": "500.00",
      "notional_value": "21500.00",
      "margin_used": "1075.00",
      "roe": "0.4651",
      "max_leverage": 20,
      "leverage": null,
      "leverage_error": "margin_state_not_mirrored",
      "funding_paid": "3.21"
    }
  ]
}
```

The numbers above are self-consistent, which is worth tracing once: at a mark price of `86000`, `notional_value` is `0.25 × 86000`, `unrealized_pnl` is `0.25 × (86000 − 84000)`, `margin_used` is `notional_value × 1/max_leverage`, `roe` is `unrealized_pnl / margin_used`, and `withdrawable` equals `total_equity − margin_used` because there are no open orders reserving margin.

Runnable end-to-end examples live in the SDK repositories: [`examples/portfolio.ts`](https://github.com/nexus-xyz/nexus-exchange-ts/blob/main/examples/portfolio.ts) and [`examples/portfolio.rs`](https://github.com/nexus-xyz/nexus-exchange-rs/blob/main/examples/portfolio.rs).

### Related

* [APIs & Rates](/exchange/apis-and-rates)
* [Exchange REST](/exchange/apis-and-rates/exchange-rest)
* [Rate Limits](/interfaces/rate-limits)
* [Interfaces overview](/interfaces/interfaces)
* [Quickstart](/exchange/trading/quickstart)

> **Status:** development preview on testnet. These routes ship in OpenAPI spec v0.7.2; pin to a spec version in production and check each SDK's release notes before upgrading. `tier`, `schedule` and `discounts` on `/account/fees` are provisional and finalize with the fee model, and `leverage` reports `null` until the margin state it needs is mirrored. Testnet credentials and balances have no real-world value.


# Overview

The Math Engine derives the Exchange's mathematics directly from its own implementation, rather than describing it by hand: every expression on these pages is extracted from the Exchange's source code, adversarially checked against it and its own test suite, and versioned with a changelog whenever the underlying mathematics genuinely changes.

## Pages

* [`engine.md`](/math-engine/engine) — the engine itself, formalized: how expressions are derived and verified, what each stage guarantees, and what is not yet verified.
* [`global.md`](/math-engine/global) — the global state-space model composed across all systems.
* [`system.md`](/math-engine/system) — the cross-model relationship graph: how expressions in one system feed, bound, or trigger expressions in another.
* [`closed-loop.md`](/math-engine/closed-loop) — the oracle-endogenous closed loop.
* [`funding-rate.md`](/math-engine/funding-rate), [`margin-math.md`](/math-engine/margin-math), [`liquidation-engine.md`](/math-engine/liquidation-engine), [`oracle.md`](/math-engine/oracle), [`order-book.md`](/math-engine/order-book), [`position-tracker.md`](/math-engine/position-tracker), [`insurance-fund.md`](/math-engine/insurance-fund), [`settlement.md`](/math-engine/settlement) — per-system derived mathematics: expressions, invariants, and citations into the source.
* [`performance.md`](/math-engine/performance) — analytical performance/scaling model fitted to benchmark data.

## How this is produced

Each expression is derived from the Exchange's implementation, then checked by an independent adversarial pass whose only goal is to try to disprove it against the code and the code's own tests. See [`engine.md`](/math-engine/engine) for the full trust model and its known limits.


# The Engine, Formally

The corpus you are reading is produced by a machine. This page documents and formalizes that machine: its AI actors, its pipeline, what each stage guarantees, and — just as important — what is *not* yet verified.

## The engine as a composition

Let $$\Sigma$$ be the Exchange's source code (the ground truth) and $$\mathcal{M}$$ the space of structured mathematical models. The corpus is the image of the source under a composition of maps:

$$
\mathcal{C} ;=; R \circ P \circ J \circ G,(\Sigma)
$$

* $$G$$ **— the Generator** (LLM): per system, $$G\_k : \Sigma\_k \to M\_k$$ reads the Rust source and derives a structured model — expressions with LaTeX and an *executable* Python form, typed domains and units, test vectors extracted from the code's own tests, chart and figure specifications. $$G$$ runs under two locks that stabilize re-derivation: the corpus-wide **notation lock** and the **prior-model baseline**.
* $$J$$ **— the Judge** (LLM, adversarial): $$J\_k : (M\_k, \Sigma\_k) \to$$ verdicts. Its only goal is to *refute* the Generator — formula vs. cited code, rounding semantics, edge regimes — and to extract further test vectors. Vectors merge only if they pass numeric evaluation; a refutation trumps passing vectors. The Judge has caught two real derivation bugs to date (the partial-liquidation full-close branch; the settlement merge summation).
* $$P$$ **— the composition passes** (LLM): the relationship graph $$P\_{\text{sys}} : {M\_k} \to \mathcal{G}$$, the global state-space model $$P\_{\text{glob}} : ({M\_k}, \mathcal{G}) \to M\_\ast$$ (rendered as the Exchange Engine and the closed loop), the **state-space classifier** $$P\_{\text{ss}} : {M\_k} \to \mathcal{S}$$ (v0.0.9: every corpus quantity classified state / input / parameter / derived; the full hierarchical state vector; the completed event alphabet — rendered as the formal appendix, its totality enforced mechanically), and the specification passes for surfaces, structural figures, and the analytical performance model. These run strictly *after* $$G$$ and $$J$$ — the composition reads verified inputs.
* $$R$$ **— the renderers** (mechanical, no LLM): deterministic Markdown with numbered equations and resolved citations, 1-D response curves, sensitivity elasticities, 2-D surfaces and regime maps, lane diagrams, performance fits. Models are the single source of truth; $$R$$ never adds content.

Gates sit between the stages: source-hash **staleness**, numeric **test vectors** (every expression must carry passing vectors), **dimensional analysis** (units balance, ast-checked), the **notation** and **coherence** gates (one symbol → one quantity → one dimension; duplicated quantities agree numerically), and since v0.0.9 the **closure gate** — the completeness of the formal appendix as a checked property: no orphan expressions, every state coordinate written by an event and read somewhere, every event map citing only defined coordinates. A corpus release is an immutable snapshot of $$\mathcal{C}$$ with a semantic changelog. The residue that survives every adversarial pass without being attributable to a modeling error exits the pipeline as a **design finding** — the engine's second product, alongside the corpus itself.

## What each stage guarantees — and what it does not

| Layer                          | Verified by                                                                 | Status                  |
| ------------------------------ | --------------------------------------------------------------------------- | ----------------------- |
| Expression ↔ code              | Judge (adversarial) + vectors + dims                                        | strong                  |
| Re-derivation stability        | notation lock + baseline + semantic differ                                  | strong                  |
| Cross-model coherence          | coherence gate (numeric agreement of duplicated quantities) + notation gate | strong                  |
| Graph / global / closed loop   | composition Judge (adversarial) + reconciliation audit                      | strong                  |
| System-wide invariants         | property-tested (simulator, 512 trials each) or Judge-ruled if structural   | strong\*                |
| Completeness of the definition | closure gate over the state-space classification (mechanical)               | strong — v0.0.9         |
| Design quality (vs. peers)     | not evaluated                                                               | v0.0.10 (Market Critic) |

\*with an honest caveat: the composition Judge *refuted* both closed-loop invariants as originally stated, and the corpus now states them **conditionally** — the equity-shock bound covers the anchor leg on non-re-anchor prints, and staleness fail-closed holds only with the trade-reference leg frozen and settlement suspended, assumptions no component expression yet witnesses. The unresolved residue was not hidden inside a weaker claim: it became the first entries of the findings log, the engine's channel for design-level observations that belong to engineering rather than to the corpus. Since v0.0.8 the composition layer is no longer trusting: components still derive in parallel, but a coherence gate checks their duplicated quantities numerically, a composition Judge attacks the graph and global model, an invariant simulator property-tests every numeric claim, and a final reconciliation audit closes each cycle — its directives (and any new design findings) feed the next one.

## Actor configuration

All actors run headless against a single pinned large language model and a fixed, versioned prompt per actor, and every run is recorded with its model, duration, token usage, and cost.

* **Model**: every run to date has executed on the same pinned model. Effort has been pinned at the highest tier since v0.0.8.
* **Prompts**: each actor's full prompt is versioned with the engine, so every corpus release is attributable to an exact prompt + model pair via the engine's own history.

## The trust loop, end to end

$$
\Sigma \xrightarrow{;G;} M \xrightarrow{;J;} M^{\checkmark}
\xrightarrow{;P;} (\mathcal{G}, M\_\ast)
\xrightarrow{;R;} \text{docs, charts, figures}
\xrightarrow{;\text{gates};} \text{release } v\_n
$$

A source change breaks the staleness gate → re-derivation under the locks → the Judge re-verifies → the semantic differ writes the changelog: only real mathematical change is flagged; renames and refactors are recognized as equivalent. The reader-facing promise is that every equation on this site is (1) pinned to the exact source it describes, (2) numerically tested against that source's own test suite plus adversarially extracted vectors, and (3) dimensionally consistent — with the composition layers explicitly labeled as the current frontier of verification.

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.


# Global Model

Fix a family of markets $$k$$, accounts $$a$$, and per-account-per-market positions. The engine is the transition system $$S' = f\_e(S, u)$$ where $$e$$ ranges over a finite alphabet of engine events — deposit and withdrawal, order submission with matching, cancel/expiry, funding accrual, funding settlement, and the liquidation cascade — and $$u = (P\_{oracle,k}, t\_{last,k})*k$$ is an exogenous input: a trusted price and its timestamp per market, handed to the engine as given. From $$u$$ and its own trade window $$W\_k$$ the engine derives the mark $$m\_k = w,P*{oracle,k} + (1-w),P\_{trade}(W\_k)$$ ([(O.8)](/math-engine/oracle), [(O.7)](/math-engine/oracle)); every event map below is a function of $$(S, u)$$ through $$m\_k$$ alone. The orbit of the Exchange is the trajectory of $$S$$ under an interleaving of these maps: fills build entry prices ([(T.1)](/math-engine/position-tracker)), marks revalue them ([(T.2)](/math-engine/position-tracker)), funding transfers value between the sides ([(F.4)](/math-engine/funding-rate)), and the cascade repossesses positions whose equity has fallen to the maintenance floor ([(L.4)](/math-engine/liquidation-engine)).

The coupling quantity that organizes the whole engine is **equity**, $$E = C + \Pi$$ ([(M.5)](/math-engine/margin-math)): affine in the mark, additive over positions, and read by every guard that matters. Admission compares equity headroom against initial margin ([(M.13)](/math-engine/margin-math), [(M.14)](/math-engine/margin-math)); the liquidation trigger compares it against maintenance margin ([(L.4)](/math-engine/liquidation-engine)); withdrawal deliberately ignores it and moves only realized collateral ([(M.16)](/math-engine/margin-math)). Because maintenance is strictly inside initial ($$r\_m < r\_i$$, [(M.4)](/math-engine/margin-math) vs [(M.2)](/math-engine/margin-math)), the state space is stratified into a healthy region, a buffer, and the liquidation region — and the engine's dynamics are precisely the story of how event maps move accounts between these strata while conserving value at every step.

## The state space

**The state set.** A state is the tuple

$$S = \big( (C\_a, M\_{resv,a}, n\_{resv,a})*a,\ (s*{a,k}, P\_{e,a,k})*{a,k},\ (B\_k, A\_k, T\_k, W\_k, \Phi\_k, \Sigma*{\mathrm{abs},k}, \Sigma\_{\mathrm{rec},k})*k,\ C*{pool},\ C\_{fee} \big)$$

with components, per account, realized collateral, reserved margin, and the open-reservation count; per position, a signed size $$s$$ (direction $$\sigma = \operatorname{sign}(s)$$, magnitude $$q = |s|$$) and a volume-weighted entry price $$P\_e$$; per market, the resting-order multiset $$B\_k$$ (each order carrying side, limit price, and remaining quantity $$q - q\_f$$), the funding accumulator pair $$(A\_k, T\_k)$$, the five-trade window $$W\_k$$, and the insurance fund balance $$\Phi\_k$$ with its lifetime ledgers; and two system accounts, the funding pool $$C\_{pool}$$ and the Exchange fee account $$C\_{fee}$$. The input $$u\_k = (P\_{oracle,k}, t\_{last,k})$$ and its guard bookkeeping $$\Omega\_k$$ (ten-print history, pending re-anchor block) are input-process state, exogenous to the engine but declared here because Part II's map writes them.

**The per-position funding accumulator is not a coordinate.** The corpus fixes the convention $$\varphi\_{a,k} \equiv 0$$: funding accrual touches only the market pair $$(A\_k, T\_k)$$ ([(F.2)](/math-engine/funding-rate)), and settlement computes each payment fresh from $$(A, T)$$ and resets atomically — no event map ever writes a nonzero value into a per-position accumulator, so carrying one would be writer-less state, not well-defined dynamics. Consequently the funding-integral term of [(M.8)](/math-engine/margin-math) reads identically zero at every observable state, and live equity is $$E\_{pf} = C + \sum\_i s\_i (m\_i - P\_{e,i})$$: the funding channel into equity is the collateral debit at settlement, nothing else.

**The admissible region** $$\mathcal{A}$$ is cut out by: strictly positive stored position sizes $$q\_{a,k} > 0$$ with $$|s|$$ on the lot lattice $$\ell\mathbb{Z}$$ ([(B.1)](/math-engine/order-book)); positive entry prices; resting limit prices strictly positive on the tick lattice $$\delta\mathbb{Z}$$ ([(B.2)](/math-engine/order-book)) with the book uncrossed, $$P\_b < P\_a$$; strictly positive remaining quantity on every resting order; $$M\_{resv} \ge 0$$ and $$n\_{resv} \ge 0$$; market parameters with $$0 \le r\_m < r\_i$$ (validated at parse, the ordering behind [(M.4)](/math-engine/margin-math) $$\le$$ [(M.2)](/math-engine/margin-math)); $$\Phi\_k \ge 0$$ with $$\Phi = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}}$$; $$T\_k \ge 0$$; and $$C\_a$$ *unrestricted in sign* — collateral can be driven negative by unguarded engine-internal debits, and the model keeps that honesty rather than assuming $$C\_a \ge 0$$.

**The event alphabet and totality.** The engine and input alphabets are

$$\begin{gathered} \Sigma\_E = {\texttt{deposit\_withdrawal},\ \texttt{order\_submission\_fill},\ \texttt{order\_cancel\_expiry}, \ \texttt{funding\_accrual},\ \texttt{funding\_settlement},\ \texttt{liquidation\_cascade}}, \qquad \Sigma\_O = {\texttt{oracle\_print}}. \end{gathered}$$

Every map is well-defined on $$\mathcal{A}$$ because each carries explicit domain guards: withdrawal is gated by the flat-and-unreserved predicate ([(M.16)](/math-engine/margin-math)); admission by alignment, collar, and headroom guards evaluated before any mutation; the funding rate is total via its explicit $$T = 0$$ branch ([(F.3)](/math-engine/funding-rate)); accrual accepts only strictly advancing time and positive anchor ([(F.2)](/math-engine/funding-rate)); every $$\min$$/$$\max$$ in the cascade and the fund ([(I.1)](/math-engine/insurance-fund), [(I.9)](/math-engine/insurance-fund)) is total and sign-safe; tick alignment clamps to one tick rather than producing non-positive prices ([(L.9)](/math-engine/liquidation-engine)); and the reduce-only guard ([(L.13)](/math-engine/liquidation-engine)) rejects the one known oversized-close regime before the book is touched.

## State coordinates

| Symbol            | Name                     | Scope    | Description                                                                                                                                                                                                                                                                                                                                                                                                         | Units                    |
| ----------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| $$C\_a$$          | account\_collateral      | account  | Realized USDX collateral of account $$a$$: the only coordinate deposits and withdrawals touch, the cash leg of realized PnL, fees, funding, and liquidation settlement. Sign-unrestricted — engine-internal debits (funding settlement, penalties) are unguarded.                                                                                                                                                   | USDX                     |
| $$M\_{resv}$$     | reserved\_margin         | account  | Initial margin reserved by resting orders of account $$a$$, carried with the open-reservation count $$n\_{resv}$$; written at admission, released on fill or cancel, and read by the cross admission gate [(M.14)](/math-engine/margin-math) and the withdrawal guard [(M.16)](/math-engine/margin-math).                                                                                                           | USDX                     |
| $$s\_{a,k}$$      | signed\_position\_size   | position | Signed position size of account $$a$$ in market $$k$$: $$\sigma = \operatorname{sign}(s)$$, $$q =                                                                                                                                                                                                                                                                                                                   | s                        |
| $$P\_{e,a,k}$$    | entry\_price             | position | Volume-weighted entry price maintained exclusively by [(T.1)](/math-engine/position-tracker): same-side fills blend, reductions leave it untouched, flips re-seed it at the fill price. The anchor of every PnL and bankruptcy computation.                                                                                                                                                                         | USDX per base unit       |
| $$B\_k$$          | order\_book              | market   | Resting-order multiset of market $$k$$ — per order: side, limit price on the tick lattice, remaining quantity $$q - q\_f > 0$$ — with its id index in exact agreement. Uncrossed ($$P\_b < P\_a$$) in every reachable state; mutated by matching, resting, cancel/expiry, and self-trade prevention [(B.8)](/math-engine/order-book).                                                                               | multiset of orders       |
| $$A\_k$$          | accumulated\_premium     | market   | Time-weighted premium accumulator of the current funding interval, written only by [(F.2)](/math-engine/funding-rate) and reset to zero atomically at settlement.                                                                                                                                                                                                                                                   | dimensionless-seconds    |
| $$T\_k$$          | interval\_clock          | market   | Elapsed accumulated time of the current funding interval; $$T \ge 0$$ always (saturating subtraction), reset with $$A\_k$$ at settlement; the $$T = 0$$ branch of [(F.3)](/math-engine/funding-rate) keeps the rate total.                                                                                                                                                                                          | seconds                  |
| $$W\_k$$          | trade\_window            | market   | The last five executed fills $$(P^{\star}, q^{\star})$$ of market $$k$$, written by the fill map (including liquidation close fills) and read by the volume-weighted median [(O.7)](/math-engine/oracle) — the engine-owned leg of the mark blend and the channel by which the engine feeds back into its own input.                                                                                                | five (price, size) pairs |
| $$\Phi\_k$$       | insurance\_fund\_balance | system   | Per-market insurance fund balance, carried with its lifetime ledgers $$\Sigma\_{\mathrm{abs}}, \Sigma\_{\mathrm{rec}}$$ satisfying $$\Phi = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}}$$; credited by spread profit [(I.7)](/math-engine/insurance-fund) and penalties [(I.10)](/math-engine/insurance-fund), debited by absorption [(I.2)](/math-engine/insurance-fund); $$\Phi \ge 0$$ with exact depletion. | USDX                     |
| $$C\_{pool}$$     | funding\_pool            | system   | The funding pool book through which every funding transfer routes ([(S.6)](/math-engine/settlement)): positive payments flow account-to-pool, negative pool-to-account. Across the matched open interest of one settlement it nets to zero exactly.                                                                                                                                                                 | USDX                     |
| $$C\_{fee}$$      | exchange\_fee\_account   | system   | The Exchange fee account: credited taker fees, debited maker rebates ([(S.2)](/math-engine/settlement), [(S.3)](/math-engine/settlement)), with closure $$V = F - R$$ ([(S.5)](/math-engine/settlement)). Liquidation penalties never enter it — they route to the fund.                                                                                                                                            | USDX                     |
| $$P\_{oracle,k}$$ | oracle\_anchor           | market   | The trusted anchor price of market $$k$$ — the engine's input $$u\_k$$. Input-process state: moved only by the guarded accept and re-anchor promotion branches of the oracle map, never by any engine event.                                                                                                                                                                                                        | USDX per unit of asset   |
| $$t\_{last,k}$$   | anchor\_timestamp        | market   | Timestamp of the last trusted anchor update, read by the staleness predicate [(O.4)](/math-engine/oracle); frozen together with the anchor while a re-anchor is pending.                                                                                                                                                                                                                                            | milliseconds             |
| $$\Omega\_k$$     | oracle\_guard\_state     | market   | The input process's defense bookkeeping: the ten-print history $$H$$ behind [(O.2)](/math-engine/oracle) and the pending re-anchor block $$(P\_{cand}, n\_p, t\_0)$$ behind [(O.5)](/math-engine/oracle) and [(O.6)](/math-engine/oracle). Declared per the state-completeness directive; touched only by the oracle map.                                                                                           | prints and milliseconds  |

## Event dynamics

The engine is event-driven: the state sits still between events, and each event is a deterministic map $$S' = f(S, u)$$ with the oracle price $$u$$ given.

### Deposit / Withdrawal

Fires when an account moves external USDX in or out. A deposit is unguarded: any positive amount credits $$C\_a$$ and touches nothing else. A withdrawal is guarded by the flat-and-unreserved predicate [(M.16)](/math-engine/margin-math): it debits at most $$C\_a$$, and only when $$n\_{pos} = 0$$ and $$n\_{resv} = 0$$ — equity and available margin never enter the withdrawal path, so unrealized PnL can never leave the venue before a close realizes it into collateral. The guard's $$n\_{resv} = 0$$ arm is what the cancel/expiry map (below) makes reachable. Touches: $$C\_a$$ only.

$$
C\_a' = C\_a + x \quad (x > 0,\ \text{deposit}); \qquad C\_a' = C\_a - x \quad \big(0 < x \le W\_{max},\ \ W\_{max} = C\_a,\mathbb{1}\[,n\_{pos} = 0 \wedge n\_{resv} = 0,]\big) \tag{G.1}
$$

### Order submission, matching, and fill

Fires on order submission. Admission guards run first, mutating nothing on rejection: lot and tick/positivity alignment ([(B.1)](/math-engine/order-book), [(B.2)](/math-engine/order-book)), the mark collar ([(B.3)](/math-engine/order-book)), FOK availability ([(B.5)](/math-engine/order-book)), and margin headroom — the isolated path [(M.13)](/math-engine/margin-math) at the effective rate [(M.1)](/math-engine/margin-math), the cross path [(M.14)](/math-engine/margin-math) charging only added exposure ([(M.11)](/math-engine/margin-math), [(M.12)](/math-engine/margin-math)) against equity net of all outstanding reservations. Admission writes $$M\_{resv}$$ atomically with the check. The matching loop then walks price-time priority: each fill exchanges $$q^{\star} = \min(q\_t, q\_m)$$ at the maker's price ([(B.6)](/math-engine/order-book), [(B.7)](/math-engine/order-book)), bounded for market orders by the running-VWAP slippage band ([(B.9)](/math-engine/order-book), [(B.10)](/math-engine/order-book)). Self-trade prevention fires inside this loop: [(B.8)](/math-engine/order-book) decrements both same-account orders' quantities with no virtual fill, handing the fully-reduced side to the cancel map. Each fill updates positions — same-side blend [(T.1)](/math-engine/position-tracker), reduce [(T.3)](/math-engine/position-tracker) realizing [(T.4)](/math-engine/position-tracker) into $$C\_a$$ at the fill price, flip [(T.5)](/math-engine/position-tracker) — debits the taker fee and credits the maker rebate between $$C\_a$$ and $$C\_{fee}$$ ([(S.2)](/math-engine/settlement), [(S.3)](/math-engine/settlement), closure [(S.5)](/math-engine/settlement)), and appends $$(P^{\star}, q^{\star})$$ to the trade window $$W\_k$$ — the coordinate Part III's mark blend reads. Touches: $$B, M\_{resv}, s, P\_e, C\_a, C\_{fee}, W$$.

$$
\begin{aligned} q^{\star} &= \min(q\_t, q\_m), \qquad P^{\star} = P\_{mk}, \qquad W' = W \oplus (P^{\star}, q^{\star}), \qquad B' = B \ominus q^{\star}\ \text{at}\ P\_{mk} \ P\_e' &= \frac{q,P\_e + q\_f,P^{\star}}{q + q\_f}\ \ (\text{same side}), \qquad q\_c = \min(q, q\_f), \qquad q\_{new} = q\_f - q\ \ (\text{flip}) \ C\_a' &= C\_a + \sigma,(P^{\star} - P\_e),q\_c - F\_t + F\_m, \qquad C\_{fee}' = C\_{fee} + F\_t - F\_m \end{aligned} \tag{G.2}
$$

### Order cancel / expiry

Fires on user cancellation, IOC/FOK/market-remainder expiry, post-only rejection of a crossing order, slippage-cap cancellation ([(B.10)](/math-engine/order-book)), the reduce-only rejection of an oversized liquidation close ([(L.13)](/math-engine/liquidation-engine)), and self-trade prevention's cancel-of-the-fully-reduced-side ([(B.8)](/math-engine/order-book)) — this map is where the STP path lands at the composition level. It removes the order from $$B\_k$$ and its id index together (the book/index agreement is preserved by the shared removal helpers), decrements $$n\_{resv}$$, and releases the order's reserved margin from $$M\_{resv}$$. This release is load-bearing: [(M.16)](/math-engine/margin-math) guards on $$n\_{resv} = 0$$ and [(M.14)](/math-engine/margin-math) reads $$M\_{resv}$$, and both are consistent only because cancellation returns the reservation. The book-side removal is fully witnessed; the corpus contains no expression for the per-order release amount $$\mu(o)$$, so the collateral-side arithmetic of this map is a named gap (cancel\_release\_arithmetic\_unwitnessed) — nothing is cited for it. Touches: $$B, M\_{resv}, n\_{resv}$$.

$$
B' = B \setminus {o}, \qquad n\_{resv}' = n\_{resv} - 1, \qquad M\_{resv}' = M\_{resv} - \mu(o) \quad (\mu(o)\ \text{unwitnessed — named gap}) \tag{G.3}
$$

### Funding accrual

Fires on each premium sample with strictly advancing time and a strictly positive anchor; otherwise the state passes through unchanged. It computes the premium of the perp reference $$P\_{trade}$$ against the given input $$u$$ ([(F.1)](/math-engine/funding-rate)) — not the mark — and adds its time-weighted contribution to the interval accumulator ([(F.2)](/math-engine/funding-rate)). This map touches exactly two coordinates — $$(A\_k, T\_k)$$ — and no account, position, book, or fund coordinate. In particular it writes nothing into any per-position accumulator: this is the $$\varphi \equiv 0$$ convention made operational, and it is why the funding accumulator is not a state coordinate of this model.

$$
A' = A + \frac{P\_{trade} - u\_k}{u\_k},\Delta t, \qquad T' = T + \Delta t \qquad (\Delta t > 0,\ u\_k > 0); \qquad \text{all other coordinates unchanged} \tag{G.4}
$$

### Funding settlement

Fires at the end of each funding interval. The rate is the clamped, pro-rated time-weighted average premium plus the fixed interest term, with the total $$T = 0$$ branch returning zero ([(F.3)](/math-engine/funding-rate)); each open position's payment is $$\Pi^f = \sigma, q, P\_{oracle,k}, f$$ ([(F.4)](/math-engine/funding-rate)), struck at the oracle price rather than the mark, computed fresh from $$(A, T)$$ — settle-and-reset, never read from accrued per-position state. Each payment routes through the funding pool as a transfer of magnitude $$|\Pi^f|$$ with sign-selected direction ([(S.6)](/math-engine/settlement)): the account leg debits or credits $$C\_a$$, the pool leg mirrors it in $$C\_{pool}$$. The collateral debit is **unguarded** — no margin check precedes it, so settlement can push an account through its maintenance floor with no input motion (one of the three engine-internal channels scoped into the margin-monotonicity invariant). The interval pair resets atomically: $$(A', T') = (0, 0)$$. Touches: $$C\_a$$ for every account with an open position, $$C\_{pool}$$, $$(A\_k, T\_k)$$.

$$
f = \begin{cases} 0 & T = 0 \ \operatorname{clamp}!\big((A/T + i)\cdot W/28800,, -c,, +c\big) & T > 0 \end{cases}; \qquad C\_a' = C\_a - \Pi^f\_a,\ \ \Pi^f\_a = \sigma\_a, q\_a, P\_{oracle,k}, f; \qquad C\_{pool}' = C\_{pool} + \textstyle\sum\_a \Pi^f\_a; \qquad (A', T') = (0, 0) \tag{G.5}
$$

### Liquidation cascade

Fires when an account's fresh-mark equity falls to its maintenance floor: the inclusive trigger $$E \le \sum\_i M\_i$$ ([(L.4)](/math-engine/liquidation-engine)) over [(L.1)](/math-engine/liquidation-engine), [(L.2)](/math-engine/liquidation-engine), [(L.3)](/math-engine/liquidation-engine). Collateral is apportioned — cross accounts split the shared pool loss-proportionally with the remainder fold conserving it exactly ([(L.6)](/math-engine/liquidation-engine), [(L.7)](/math-engine/liquidation-engine)); isolated positions use their stamped cushion ([(L.5)](/math-engine/liquidation-engine)). Close orders are sized ([(L.11)](/math-engine/liquidation-engine), [(L.12)](/math-engine/liquidation-engine)), guarded reduce-only ([(L.13)](/math-engine/liquidation-engine) — a negative share yields an oversized order that is rejected each scan, a stall not a flip), priced from the bankruptcy price ([(L.8)](/math-engine/liquidation-engine)) tick-aligned toward executability ([(L.9)](/math-engine/liquidation-engine), [(L.10)](/math-engine/liquidation-engine)), and executed through the same matching map as any order — their fills enter $$W\_k$$. Settlement per market: $$X\_i = s\_i + \Pi\_{\mathrm{fill},i} + \Pi\_{\mathrm{res},i} - g\_i$$ with realized fill PnL [(L.15)](/math-engine/liquidation-engine), mark-valued residual [(L.16)](/math-engine/liquidation-engine), and spread profit [(L.14)](/math-engine/liquidation-engine); bad debt is $$D\_i = \max(0, -X\_i)$$ ([(L.17)](/math-engine/liquidation-engine)). The liquidated account's collateral is defined explicitly: $$C\_a' = \sum\_i (\max(X\_i, 0) - \Lambda\_i)$$, where the penalty $$\Lambda\_i$$ is the owed amount [(S.4)](/math-engine/settlement) capped at the retained collateral ([(I.9)](/math-engine/insurance-fund)) — a debit whose fund-side destination is witnessed: [(I.10)](/math-engine/insurance-fund) and [(I.11)](/math-engine/insurance-fund) credit the same $$\Lambda$$ to $$\Phi$$ in one atomic no-mint pair (v0.0.9 expressions closing the formerly-open liquidation\_penalty\_sink finding). Fund sequencing is credit-before-absorb for BOTH credits (settled, code-verified 2026-07-12: apply\_liquidation\_penalty precedes fill accounting in execute\_liquidation, per\_market.rs:552-574, and the spread credit precedes absorb\_bad\_debt): $$\Phi\_1 = \Phi + \Lambda + g$$ ([(I.10)](/math-engine/insurance-fund), [(I.7)](/math-engine/insurance-fund)), then absorption $$\min(D, \Phi\_1)$$ ([(I.1)](/math-engine/insurance-fund), [(I.2)](/math-engine/insurance-fund)) with lockstep ledgers ([(I.3)](/math-engine/insurance-fund), [(I.8)](/math-engine/insurance-fund)). ADL arms through two **nested** predicates: the settle amount $$D\_{\mathrm{adl}} = \max(D - \Phi\_1, 0) > 0$$ ([(I.4)](/math-engine/insurance-fund)) and, after absorption, the threshold $$\Phi' \le \kappa$$ ([(I.5)](/math-engine/insurance-fund)). Settle-amount firing implies threshold firing ($$D\_{\mathrm{adl}} > 0$$ forces $$\Phi' = 0 \le \kappa$$); the divergence is one-sided — e.g. $$\Phi\_1 = 12, D = 5, \kappa = 10$$ gives $$D\_{\mathrm{adl}} = 0$$ yet $$\Phi' = 7 \le \kappa$$: armed with nothing to settle. Whether ADL executes in that regime is the open finding adl\_arming\_condition\_ambiguity. Counterparties rank by $$\rho = \pi L$$ ([(L.18)](/math-engine/liquidation-engine), [(I.6)](/math-engine/insurance-fund)); the counterparty settlement map itself is unmodeled — a named gap (adl\_settlement\_unmodeled). One further seam is named rather than hidden: on a partial fill the settle map counts $$\Pi\_{\mathrm{res}}$$ as cash in $$X$$ while the surviving remainder retains entry $$P\_e$$ ([(T.1)](/math-engine/position-tracker) is untouched by reductions) — no expression re-marks the residual's entry, so the partial-fill case double-carries that PnL across layers (residual\_entry\_remark\_unwitnessed). Touches: $$s, P\_e, B, W, C\_a, \Phi, \Sigma\_{\mathrm{abs}}, \Sigma\_{\mathrm{rec}}, C\_{fee}$$. The fee treatment of liquidation close fills (whether the liquidatee is charged taker fees, and resting counterparties earn rebates, on cascade fills) is unwitnessed by any component expression; $$C\_{fee}$$ is therefore excluded from this map's write set and the question is a named gap (liquidation\_fill\_fee\_treatment).

$$
\begin{aligned} &\text{fire: } E \le \textstyle\sum\_i M\_i; \qquad s\_i = C,\tfrac{\ell\_i}{\mathcal{L}}\ (+\ \text{remainder fold}), \qquad X\_i = s\_i + \Pi\_{\mathrm{fill},i} + \Pi\_{\mathrm{res},i} - g\_i, \qquad D = \textstyle\sum\_i \max(0, -X\_i) \ \&C\_a' = \textstyle\sum\_i \big( \max(X\_i, 0) - \Lambda\_i \big), \qquad \Lambda\_i = \min!\big(\Lambda\_{\mathrm{owed},i},\ \max(X\_i, 0)\big) \ &\Phi\_1 = \Phi + \Lambda + g, \qquad \Phi' = \max(\Phi\_1 - D,\ 0), \qquad D\_{\mathrm{adl}} = \max(D - \Phi\_1,\ 0) \end{aligned} \tag{G.6}
$$

## Invariants of the engine

### Aggregate funding zero-sum across matched open interest

$$
\sum\_{a} \Pi^{f}*{a} ;=; f, P*{oracle,k} \sum\_{a} s\_{a,k} ;=; 0 \qquad\Longrightarrow\qquad C\_{pool}' = C\_{pool} \tag{G.7}
$$

Within each market, a funding settlement transfers value between the sides but creates none: the payments [(F.4)](/math-engine/funding-rate) sum to zero over all accounts because open interest is matched ($$\sum\_a s\_{a,k} = 0$$), so the pool's net position across the settlement's transfers ([(S.6)](/math-engine/settlement)) is exactly zero. Honest scope: stated for a solvent payer set. When a payer's debit exceeds its own collateral, the insurance fund covers the shortfall so the receiving side is still paid in full — the fund is a source in that case, not a pure transfer between matched sides, so the account legs no longer cancel exactly and $$C\_{pool}' = C\_{pool}$$ does not hold for that window.

*Why it holds:* Position coordinates are written only by fill maps, and every fill adjusts taker and maker by the same $$q^{\star}$$ with opposite signs ([(B.6)](/math-engine/order-book), with reduces and flips per [(T.3)](/math-engine/position-tracker) and [(T.5)](/math-engine/position-tracker)) — so $$\sum\_a s\_{a,k} = 0$$ inductively from the empty market; deposits, withdrawals, cancels, and accrual never touch $$s$$, and liquidation closes execute through the same fill map. The payment is linear in signed size with the common factor $$f, P\_{oracle,k}$$ ([(F.4)](/math-engine/funding-rate)), so the account legs cancel exactly in decimal arithmetic, and each pool transfer carries exactly the account leg's magnitude with mirrored direction ([(S.6)](/math-engine/settlement)).

*The one exception, named:* the derivation above assumes every payer's debit lands in full, and one engine-internal channel breaks that — the **capped-payer funding shortfall**. A payer's debit is capped at its own collateral, and the insurance fund contributes the difference so the receiving side is paid in full. What becomes of the payer's uncovered remainder is a **ratified decision that has not shipped**: ADR-0003 accepts carrying it into `pending_funding` rather than forgiving it, and as of `main` that carry does not exist — `RiskMutation::FundingSettled` has no carried-remainder field, `accrue_pending_funding` is reached only from the FS-03 close-path flush in `fills.rs`, and the settlement path resets every touched position's `funding_integral` along with the rest of the window. So today a covered shortfall leaves no liability recorded anywhere, and one the fund cannot cover books an `UncoveredSystemLoss` and halts the market fail-closed (ENG-7114). Whenever that path fires, the fund is a *source* for the settlement rather than a matched counterparty: $$\sum\_a \Pi^f\_a$$ equals the covered shortfall instead of zero, and $$C\_{pool}' = C\_{pool}$$ fails by exactly that amount for the window. The equality above is therefore an invariant of the solvent-payer regime, not of every settlement — the same shape as the margin-monotonicity invariant's three engine-internal channels below.

No component expression in this corpus witnesses that path. The insurance-fund expressions [(I.1)](/math-engine/insurance-fund)–[(I.8)](/math-engine/insurance-fund) model the liquidation bad-debt waterfall only, and neither the payer-side cap, the fund's funding-side contribution, nor the shortfall's disposition appears in [(F.4)](/math-engine/funding-rate) or [(S.6)](/math-engine/settlement). So the exception is named here rather than composed, and the citation this clause should eventually carry is a gap rather than an omission (funding\_capped\_payer\_unwitnessed).

*Composes:* [*(F.4)*](/math-engine/funding-rate) [*(F.3)*](/math-engine/funding-rate) [*(S.6)*](/math-engine/settlement) [*(B.6)*](/math-engine/order-book) [*(T.3)*](/math-engine/position-tracker) [*(T.5)*](/math-engine/position-tracker)

### Collateral conservation through the liquidation cascade

$$
\sum\_i s\_i = C, \qquad D = \min(D, \Phi\_1) + \max(D - \Phi\_1,\ 0), \qquad \Delta C\_a\big|*{\Lambda} = -\Lambda = -\Delta\Phi\big|*{\Lambda}, \qquad \Phi = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}} \tag{G.8}
$$

Through a fully-filled cascade, equity is apportioned and transferred, never created: the cross pool splits into shares that sum back to it exactly ([(L.6)](/math-engine/liquidation-engine), [(L.7)](/math-engine/liquidation-engine)); every unit of bad debt [(L.17)](/math-engine/liquidation-engine) is covered exactly once, split between fund absorption [(I.1)](/math-engine/insurance-fund) and the ADL settle amount [(I.4)](/math-engine/insurance-fund) at the post-spread-credit balance $$\Phi\_1$$; and the penalty is a no-mint transfer — the account debit and the fund credit are the same capped $$\Lambda$$ ([(I.9)](/math-engine/insurance-fund), [(I.10)](/math-engine/insurance-fund)). Honest scope: stated for fully-filled liquidations ($$Q\_f = q$$, $$\Pi\_{\mathrm{res}} = 0$$); the partial-fill case is unreconciled by the residual entry-remark gap named in the composition, and the ADL leg conserves only up to the unmodeled counterparty settlement.

*Why it holds:* The remainder fold adds $$C - \sum\_j s\_j$$ to exactly one share, restoring $$\sum\_i s\_i = C$$ identically regardless of rounding. The $$\min/\max$$ pair is a complementary split of $$D$$ at $$\Phi\_1$$, and credit-before-absorb makes $$\Phi\_1 = \Phi + g$$ the balance absorption actually reads ([(I.7)](/math-engine/insurance-fund) commits before [(I.2)](/math-engine/insurance-fund)). The penalty legs are one atomic mutation carrying a single $$\Lambda$$ capped at available collateral, so neither side can exceed the other. Fund closure $$\Phi = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}}$$ holds because every fund update moves the balance and exactly one ledger by the same amount.

*Composes:* [*(L.6)*](/math-engine/liquidation-engine) [*(L.7)*](/math-engine/liquidation-engine) [*(L.17)*](/math-engine/liquidation-engine) [*(I.1)*](/math-engine/insurance-fund) [*(I.4)*](/math-engine/insurance-fund) [*(I.7)*](/math-engine/insurance-fund) [*(I.2)*](/math-engine/insurance-fund) [*(I.9)*](/math-engine/insurance-fund) [*(I.10)*](/math-engine/insurance-fund)

### Margin monotonicity (maintenance strictly inside initial)

$$
r\_m < r\_i ;\Longrightarrow; \big{, S : E \le M\_{maint}^{pf} ,\big} \subsetneq \big{, S : E \le M\_{init}^{pf} ,\big} \tag{G.9}
$$

For every admissible parameter set, maintenance margin is strictly below initial margin position-wise and in the portfolio sums ([(M.4)](/math-engine/margin-math) vs [(M.2)](/math-engine/margin-math), [(M.10)](/math-engine/margin-math) vs [(M.9)](/math-engine/margin-math)), so the liquidation region [(L.4)](/math-engine/liquidation-engine) is strictly contained in the admission-blocked region and a just-admitted order sits strictly above its maintenance floor at the admission mark. Honest behavioral scope: this is a *geometric* buffer, not a temporal guarantee — three engine-internal channels can close the gap with no input motion: the unguarded funding-settlement collateral debit ([(F.4)](/math-engine/funding-rate)), the taker-fee debit that admission headroom does not charge ([(S.2)](/math-engine/settlement) vs [(M.14)](/math-engine/margin-math)), and trade-leg mark motion from the engine's own fills entering the blend (findings: unguarded funding debit, admission fee gap, trade-leg self-influence).

*Why it holds:* Both requirements are the product $$q \cdot P \cdot r$$ differing only in the rate, and $$r\_m < r\_i$$ is validated at parameter parse, so the per-position inequality is strict and survives summation over the identical position set. The effective rate only widens the gap ($$r\_{eff} \ge r\_i$$, [(M.1)](/math-engine/margin-math)). No engine map weakens the inclusion — the three gap-closing channels move $$E$$, not the region ordering — so the invariant is preserved by every event while the behavioral contract is scoped to the cited channels.

*Composes:* [*(M.4)*](/math-engine/margin-math) [*(M.2)*](/math-engine/margin-math) [*(M.10)*](/math-engine/margin-math) [*(M.9)*](/math-engine/margin-math) [*(M.1)*](/math-engine/margin-math) [*(M.14)*](/math-engine/margin-math) [*(L.4)*](/math-engine/liquidation-engine) [*(S.2)*](/math-engine/settlement) [*(F.4)*](/math-engine/funding-rate)

### Bounded funding transfer per interval

$$
\lvert \Pi^{f}*{a} \rvert ;\le; c, q\_a, P*{oracle,k} \qquad \text{per position, per funding interval} \tag{G.10}
$$

No funding settlement can move more than the cap fraction of a position's oracle notional in one interval: the rate is clamped to $$\[-c, +c]$$ on both of its branches ([(F.3)](/math-engine/funding-rate)), and the payment is linear in the rate ([(F.4)](/math-engine/funding-rate)), so each account's per-position debit or credit is bounded by $$c, q, P\_{oracle,k}$$ and the pool's gross throughput by $$c, P\_{oracle,k} \sum\_a q\_a$$.

*Why it holds:* The rate map has exactly two return paths: $$T = 0$$ returns zero, and $$T > 0$$ applies the clamp directly, so $$|f| \le c$$ in every reachable state. The payment map multiplies $$f$$ by $$\sigma, q, P\_{oracle,k}$$ with no other rate dependence, and the settlement transfer carries exactly $$|\Pi^f|$$ ([(S.6)](/math-engine/settlement)) — no map between the clamp and the ledger can amplify the amount.

*Composes:* [*(F.3)*](/math-engine/funding-rate) [*(F.4)*](/math-engine/funding-rate) [*(S.6)*](/math-engine/settlement)

### Fee closure across the fill path

$$
\Delta C\_{fee} ;=; F - R ;=; V, \qquad \Delta C\_{fee} + \textstyle\sum\_a \Delta C\_a \big|\_{\text{fees}} = 0 \tag{G.11}
$$

Across any batch of fills, the fee flows between accounts and the Exchange fee account close exactly: every taker fee debited from an account is either paid out as a maker rebate or retained as revenue ([(S.2)](/math-engine/settlement), [(S.3)](/math-engine/settlement), [(S.5)](/math-engine/settlement)), the closure is preserved linearly under batch merge ([(S.7)](/math-engine/settlement)), and liquidation penalties never contaminate it — they route to the fund, never to $$C\_{fee}$$ ([(S.4)](/math-engine/settlement)).

*Why it holds:* Both accumulators are built fill-by-fill from the identical truncated amounts placed into the transfer ledger, and revenue is computed once as their difference — there is no independent revenue path to drift. The account-side legs are the same $$F\_t$$ and $$F\_m$$ the fill map applies to $$C\_a$$, so the system-wide fee sum telescopes to zero. Merge only adds accumulators and concatenates transfers, so closure survives batching; the penalty push site targets the insurance fund and never enters $$F$$, $$R$$, or $$V$$.

*Composes:* [*(S.2)*](/math-engine/settlement) [*(S.3)*](/math-engine/settlement) [*(S.5)*](/math-engine/settlement) [*(S.7)*](/math-engine/settlement) [*(S.4)*](/math-engine/settlement) [*(S.1)*](/math-engine/settlement)

## Structure of the flow

**Geometry of the healthy region.** For a fixed position set, equity is affine in each mark with slope the net signed size, $$\partial E / \partial m\_k = \sum\_i s\_{i,k}$$ ([(M.8)](/math-engine/margin-math)), while total maintenance is piecewise-linear with slope $$\sum\_i q\_{i,k}, r\_{m,k}$$ ([(M.10)](/math-engine/margin-math)) — so the healthy region $${S : E > \sum\_i M\_i}$$ is, in the mark coordinate of a single-market account, a half-line whose boundary is exactly the displayed liquidation price ([(T.8)](/math-engine/position-tracker)), with the bankruptcy price ([(T.9)](/math-engine/position-tracker)) strictly beyond it for any under-collateralized position. Margin requirements scale linearly in size ([(M.2)](/math-engine/margin-math)), the effective rate never undercuts the market rate ([(M.1)](/math-engine/margin-math)), added exposure is non-negative so admission never pre-credits a reduce ([(M.11)](/math-engine/margin-math)), and the slippage cap is monotone in its band ([(B.10)](/math-engine/order-book)).

**Fixed points under constant input.** Hold $$u$$ constant with an empty or agreeing trade window (so $$m\_k = P\_{oracle,k}$$) and suppress user events. Then the premium vanishes ([(F.1)](/math-engine/funding-rate)), accrual adds $$0 \cdot \Delta t$$ to $$A$$ while only the clock $$T$$ advances — that half of the claim still holds — but settlement no longer computes $$f = 0$$: the fixed interest term makes the resting rate $$f = i \cdot W/28800$$, so settlement still moves a small amount through the pool even at an agreeing mark ([(F.3)](/math-engine/funding-rate), [(S.6)](/math-engine/settlement)). The trigger stays silent on every healthy account and the position, book, and fund coordinates $$(s, P\_e, B, \Phi, C\_{fee})$$ are fixed, but $$C\_a$$, $$C\_{pool}$$, and the funding pair $$(A, T)$$ still cycle through nonzero settlements. Healthy states at an agreeing mark are thus not full equilibria of the autonomous engine — only the non-funding coordinates are fixed. The interesting non-equilibria are one-sided: once $$E \le \sum M\_i$$, the cascade fires and the account's position coordinates contract monotonically toward flat; and because the trigger is inclusive ([(L.4)](/math-engine/liquidation-engine)), the boundary itself belongs to the liquidation region, not the healthy one.

## Appendix — the complete formal system

This appendix is rendered mechanically from the state-space classification (`models/state-space.json`) — derived, not written. Its completeness claim is *checked*: the closure gate (`ci/closure.py`) verifies on every run that every corpus expression is classified, every state coordinate is written by an event and read somewhere, and every event map cites only defined coordinates. The state space factors into 16 coordinates (global fund and cash books; per-market book, trade window, premium accumulator, oracle anchor and re-anchor pending block; per-account collateral and reserved margin; per-position size/entry/direction and funding accumulator), 9 inputs, 20 parameters, and 54 derived observables, with all 133 corpus variables and 84 expressions classified totally across 10 events. Alphabet completion added adl\_execution, oracle\_reanchor\_step, oracle\_reanchor\_commit — including adl\_execution emitted with empty writes so the closure gate keeps flagging the unmodeled ADL counterparty settlement map. Deliberately uncited update maps (deposit/withdrawal transfer, cancel removal and reservation release, anchor assignments, trade-window push, funding resets) remain named gaps rather than inventions.

### A.1 The state vector

**global**

| Coordinate             | Symbol        | Units | Owner      | Description                                                                                                                                                                                                                                                                  |
| ---------------------- | ------------- | ----- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `funding_pool`         | $$C\_{pool}$$ | USDX  | settlement | The funding pool book through which every funding transfer routes; nets to zero exactly across the matched open interest of one settlement. No component variable persists it — declared by the global composition; its only witnessed update is the transfer-magnitude map. |
| `exchange_fee_account` | $$C\_{fee}$$  | USDX  | settlement | The Exchange fee account, carried with its settlement-record accumulators (total fees F, total rebates R, net revenue V = F - R, and their batch-merge operands): credited taker fees, debited maker rebates; liquidation penalties never enter it.                          |

**per-market**

| Coordinate                      | Symbol                     | Units                                                                     | Owner          | Description                                                                                                                                                                                                                                                              |
| ------------------------------- | -------------------------- | ------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `order_book`                    | $$B\_k$$                   | multiset of orders (side, limit price, remaining quantity, fill progress) | order-book     | Resting-order multiset of the market with its id index in exact agreement; per-order fill progress (filled\_qty) lives inside this composite; uncrossed (P\_b < P\_a) in every reachable state.                                                                          |
| `accumulated_premium`           | $$A\_k$$                   | dimensionless-seconds                                                     | funding-rate   | Time-weighted premium accumulator of the current funding interval, written only by the sample-contribution map and reset to zero atomically at settlement.                                                                                                               |
| `interval_clock`                | $$T\_k$$                   | seconds                                                                   | funding-rate   | Elapsed accumulated time of the current funding interval, advanced with each sample and reset with A\_k at settlement; the T = 0 branch of the rate keeps the map total. (The component text calls it derived; the composed system persists it as the interval's clock.) |
| `trade_window`                  | $$W\_k$$                   | five (price, size) pairs                                                  | oracle         | The last five executed fills of the market, written by the fill map (including liquidation close fills) and read by the volume-weighted median trade reference — the engine-owned leg of the mark blend.                                                                 |
| `insurance_fund_balance`        | $$\Phi\_k$$                | USDX                                                                      | insurance-fund | Per-market insurance fund balance: credited by spread profit and penalties, debited by bad-debt absorption; Phi >= 0 with exact depletion, and Phi = Sigma\_rec - Sigma\_abs against its lifetime ledgers.                                                               |
| `insurance_fund_total_absorbed` | $$\Sigma\_{\mathrm{abs}}$$ | USDX                                                                      | insurance-fund | Lifetime bad-debt absorption ledger of the fund; monotonically non-decreasing, incremented in lockstep with each absorption.                                                                                                                                             |
| `insurance_fund_total_received` | $$\Sigma\_{\mathrm{rec}}$$ | USDX                                                                      | insurance-fund | Lifetime receipts ledger of the fund (initial balance plus spread profits and penalty credits); monotonically non-decreasing, moving in lockstep with balance credits. The initial-balance credit has no event in the alphabet (initialization, not dynamics).           |
| `oracle_anchor`                 | $$P\_{oracle,k}$$          | USDX per unit of asset                                                    | oracle         | The trusted anchor price of the market — the engine's exogenous input process state; moved only by the guarded fresh-accept and re-anchor-commit branches of the oracle map, never by any engine event.                                                                  |
| `anchor_timestamp`              | $$t\_{last,k}$$            | milliseconds                                                              | oracle         | Timestamp of the last trusted anchor update, read by the staleness predicate; frozen together with the anchor while a re-anchor is pending.                                                                                                                              |
| `oracle_guard_state`            | $$\Omega\_k$$              | prints and milliseconds                                                   | oracle         | The input process's defense bookkeeping: the ten-print history (whose oldest element the path check reads) and the pending re-anchor block (candidate price, pending print count, confirmation counter, opening timestamp); touched only by the oracle map.              |

**per-account**

| Coordinate           | Symbol        | Units | Owner       | Description                                                                                                                                                                                                          |
| -------------------- | ------------- | ----- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account_collateral` | $$C\_a$$      | USDX  | margin-math | Realized USDX collateral of an account: the cash leg of deposits/withdrawals, realized PnL, fees, funding, and liquidation settlement; sign-unrestricted because engine-internal debits are unguarded.               |
| `reserved_margin`    | $$M\_{resv}$$ | USDX  | margin-math | Initial margin reserved by resting/in-flight orders, carried with the open-reservation count n\_{resv}; written at admission, released on fill or cancel, read by the cross admission gate and the withdrawal guard. |

**per-position**

| Coordinate             | Symbol         | Units              | Owner            | Description                                                                                                                                                                                   |
| ---------------------- | -------------- | ------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signed_position_size` | $$s\_{a,k}$$   | base units, signed | position-tracker | Signed position size (sigma = sign(s), q = \|s\| > 0 for a stored position, \|s\| a lot multiple); written only by the fill map (open, increase, reduce, flip) and the cascade's close fills. |
| `entry_price`          | $$P\_{e,a,k}$$ | USDX per base unit | position-tracker | Volume-weighted entry price maintained exclusively by the VWAP blend: same-side fills blend, reductions leave it untouched, flips re-seed it at the fill price.                               |

### A.2 Inputs and parameters

Inputs are exogenous — they arrive from outside the state; parameters are constants of market or system configuration.

**Inputs**

| Input                      | Symbol       | Units                  | Description                                                                                                                     |
| -------------------------- | ------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `oracle_print_price`       | $$P\_{new}$$ | USDX per unit of asset | An incoming external oracle print being validated against the anchor or the pending re-anchor candidate.                        |
| `wall_clock_time`          | $$t\_{now}$$ | milliseconds           | Feed-supplied timestamp of the incoming print or staleness evaluation, in Unix milliseconds.                                    |
| `time_delta`               | $$\Delta t$$ | seconds                | Exogenous elapsed time since the previous funding premium sample; non-advancing samples are ignored.                            |
| `order_quantity`           | $$q$$        | base units             | Quantity of an arriving order; must be a lot multiple to pass admission.                                                        |
| `order_limit_price`        | $$P\_{lim}$$ | USDX per base unit     | Limit price of an arriving limit order; must be strictly positive, tick-aligned, and inside the mark collar.                    |
| `order_signed_size`        | $$o$$        | base units, signed     | Signed size of an arriving order (buy positive, sell negative), read by the added-exposure computation.                         |
| `max_slippage_bps`         | $$\beta$$    | basis points           | Taker-supplied per-order slippage cap on a market order; absent means no cap.                                                   |
| `preview_requested_qty`    | $$q\_{req}$$ | base units             | Quantity requested by a hypothetical market order in the read-only VWAP preview; undefined for non-positive requests.           |
| `external_transfer_amount` | $$x$$        | USDX                   | External USDX amount of a deposit or withdrawal; not a corpus variable — carried by the composition's deposit/withdrawal event. |

**Parameters**

| Parameter                    | Symbol        | Units                         | Scope       | Description                                                                                                                                                                                                                                                                                                                                  |
| ---------------------------- | ------------- | ----------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `funding_rate_cap`           | $$c$$         | fraction per funding interval | per-market  | Symmetric cap on the funding rate. The crypto perps use 0.001 (0.1%); the FX, commodity, and index perps (seven markets, including NDQ) are configured at 0.0005. Which of the seven are actually deployed changes with the venue's rollout state — see Market Specifications for the live per-market set rather than a fixed snapshot here. |
| `adl_threshold`              | $$\kappa$$    | USDX                          | per-market  | ADL trigger threshold on the fund balance; default zero arms ADL only at full depletion.                                                                                                                                                                                                                                                     |
| `maintenance_margin_rate`    | $$r\_m$$      | dimensionless ratio           | per-market  | Market maintenance margin rate; strictly less than the initial margin rate.                                                                                                                                                                                                                                                                  |
| `initial_margin_rate`        | $$r\_i$$      | dimensionless ratio           | per-market  | Market initial margin rate, equal to one over the market's maximum leverage.                                                                                                                                                                                                                                                                 |
| `account_leverage`           | $$L$$         | multiplier                    | per-account | Account-selected leverage per market (integer >= 1, validated before storage); user configuration with no state-mutating expression in the corpus, hence a parameter, not a coordinate.                                                                                                                                                      |
| `tick_size`                  | $$\delta$$    | USDX per base unit            | per-market  | Minimum price increment; non-positive tick disables alignment.                                                                                                                                                                                                                                                                               |
| `lot_size`                   | $$\ell$$      | base units                    | per-market  | Market lot size; order and position sizes are integer multiples of it; zero disables the alignment check.                                                                                                                                                                                                                                    |
| `taker_fee_bps`              | $$b\_t$$      | basis points                  | per-market  | Taker fee rate charged on fill notional.                                                                                                                                                                                                                                                                                                     |
| `maker_rebate_bps`           | $$b\_m$$      | basis points                  | per-market  | Maker rebate rate, stored negative by convention; applied by absolute value.                                                                                                                                                                                                                                                                 |
| `liquidation_penalty_bps`    | $$b\_{liq}$$  | basis points                  | per-market  | Penalty rate applied to the notional of liquidation fills and routed to the insurance fund.                                                                                                                                                                                                                                                  |
| `price_band_bps`             | $$b\_{band}$$ | basis points                  | per-market  | Maximum admissible relative deviation of a limit price from the mark (the admission collar).                                                                                                                                                                                                                                                 |
| `oracle_deviation_threshold` | $$\theta$$    | dimensionless fraction        | per-market  | Single-step deviation threshold for accepting an oracle print against the anchor.                                                                                                                                                                                                                                                            |
| `oracle_history_size`        | $$N\_h$$      | prints                        | global      | Fixed size of the rolling price-update window used by the path-manipulation check (HISTORY\_SIZE = 10).                                                                                                                                                                                                                                      |
| `oracle_staleness_seconds`   | $$\tau\_s$$   | seconds                       | per-market  | Staleness threshold: the anchor is stale strictly beyond this many seconds since the last trusted update.                                                                                                                                                                                                                                    |
| `reanchor_max_deviation`     | $$\theta\_r$$ | dimensionless fraction        | per-market  | Per-step consistency bound for re-anchor confirmations against the running candidate.                                                                                                                                                                                                                                                        |
| `escalation_max_deviation`   | $$\theta\_e$$ | dimensionless fraction        | per-market  | Widened per-step bound applied once the escalation trigger has fired.                                                                                                                                                                                                                                                                        |
| `required_confirmations`     | $$k$$         | prints                        | per-market  | Consecutive mutually-consistent prints required to promote a re-anchor; floored at 2 effectively.                                                                                                                                                                                                                                            |
| `escalation_prints`          | $$N\_e$$      | prints                        | per-market  | Print-count arm of the re-anchor escalation trigger.                                                                                                                                                                                                                                                                                         |
| `escalation_seconds`         | $$\tau\_e$$   | seconds                       | per-market  | Wall-clock arm of the re-anchor escalation trigger, measured from the opening of the pending sequence.                                                                                                                                                                                                                                       |
| `oracle_mark_weight`         | $$w$$         | dimensionless fraction        | per-market  | Oracle weight in the mark blend; unit-interval, default 0.95 (oracle-dominant).                                                                                                                                                                                                                                                              |

### A.3 Derived observables

Pure functions of state, inputs, and parameters — recomputed, never persisted.

| Quantity                   | Symbol              | Units                 | Defined by                                | Description                                                                                                                                                                                                                |
| -------------------------- | ------------------- | --------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mark_price`               | $$m\_k$$            | USDX per base unit    | [(O.8)](/math-engine/oracle)              | Convex blend of the trusted anchor and the volume-weighted trade reference; equals the anchor when no trade reference exists. Recomputed on demand, never persisted.                                                       |
| `trade_ref`                | $$P\_{trade}$$      | USDX per base unit    | [(O.7)](/math-engine/oracle)              | Volume-weighted median price of the five-trade window; falls back to the last trade price when the window is short.                                                                                                        |
| `premium_index`            | $$p$$               | dimensionless         | [(F.1)](/math-engine/funding-rate)        | Fractional deviation of the perp reference (NOT the mark) from the anchor at a sample; skipped when the anchor is non-positive.                                                                                            |
| `funding_rate`             | $$f$$               | fraction per interval | [(F.3)](/math-engine/funding-rate)        | Clamped, pro-rated time-weighted average premium plus the fixed interest term — $$\text{clamp}((A/T + i) \cdot W/28800, -c, +c)$$ — recomputed fresh from (A, T) at settlement with the T = 0 branch returning zero.       |
| `funding_payment`          | $$\Pi^f$$           | USDX                  | [(F.4)](/math-engine/funding-rate)        | Signed per-position funding payment sigma q P\_oracle f, struck at the oracle price rather than the mark; simultaneously the settlement event's collateral update delta.                                                   |
| `unrealized_pnl`           | $$\mathrm{uPnL}$$   | USDX                  | [(T.2)](/math-engine/position-tracker)    | Per-position mark-to-market PnL; the account total is its sum over open positions (coherence-merged with liquidation-engine.fresh\_unrealized\_pnl).                                                                       |
| `account_equity`           | $$E$$               | USDX                  | [(M.8)](/math-engine/margin-math)         | Collateral plus mark-to-market PnL (net of funding integrals in the portfolio form); coherence-merged across margin-math.equity, margin-math.portfolio\_equity, and liquidation-engine.account\_equity.                    |
| `maintenance_margin`       | $$M\_m$$            | USDX                  | [(M.4)](/math-engine/margin-math)         | Maintenance margin at the mark; coherence-merged with the portfolio and liquidation-engine instances.                                                                                                                      |
| `initial_margin`           | $$M\_i$$            | USDX                  | [(M.2)](/math-engine/margin-math)         | Initial margin at the mark (stamped allocated margin where set); coherence-merged with the portfolio instance.                                                                                                             |
| `available_margin`         | $$M\_{avail}$$      | USDX                  | [(M.7)](/math-engine/margin-math)         | Equity minus total initial margin held; can be negative; gates order admission, not withdrawal.                                                                                                                            |
| `added_exposure`           | $$\Delta q$$        | base units            | [(M.11)](/math-engine/margin-math)        | Magnitude of newly-opened exposure an order adds: growth on increase, zero on reduce/close, the whole new side on a flip.                                                                                                  |
| `admission_added_margin`   | $$M\_{add}$$        | USDX                  | [(M.12)](/math-engine/margin-math)        | Initial margin charged on added exposure at the mark and effective rate; also the amount written into the reservation at admission.                                                                                        |
| `isolated_margin_cushion`  | $$C\_{iso}$$        | USDX                  | [(L.5)](/math-engine/liquidation-engine)  | Collateral backing an isolated position: the margin allocated at fill time if recorded, otherwise the open-time initial margin at market rate; the trigger ((L.4)) and the liquidation pricing both use this single value. |
| `bankruptcy_price`         | $$p\_b$$            | USDX per base unit    | [(L.8)](/math-engine/liquidation-engine)  | Price at which the position's backing collateral is exactly exhausted; coherence-merged with position-tracker.bankruptcy\_price; may be zero or negative before alignment.                                                 |
| `aligned_bankruptcy_price` | $$\tilde{p}\_b$$    | USDX per base unit    | [(L.9)](/math-engine/liquidation-engine)  | Tick-aligned close-order limit: floor for sells (liquidation-engine.aligned\_price\_sell), ceil for buys (liquidation-engine.aligned\_price\_buy), clamped to one tick.                                                    |
| `liquidation_price`        | $$p\_{liq}$$        | USDX per base unit    | [(T.8)](/math-engine/position-tracker)    | Analytically-solved mark at which equity meets the maintenance requirement; display/analysis, neither moves state nor gates events.                                                                                        |
| `cross_collateral_share`   | $$s\_i$$            | USDX                  | [(L.6)](/math-engine/liquidation-engine)  | Loss-proportional share of the shared cross pool per market, with the remainder folded into the largest-loss position's share.                                                                                             |
| `collateral_share_sum`     | $$S$$               | USDX                  | \`\`                                      | Sum of the proportional shares before the remainder fold; prose-defined only — no corpus expression id (read by the remainder fold).                                                                                       |
| `position_loss`            | $$\ell\_i$$         | USDX                  | \`\`                                      | max(0, -uPnL\_i) per market with entry-price fallback; defined only in variable prose, no corpus expression id.                                                                                                            |
| `total_loss`               | $$\mathcal{L}$$     | USDX                  | \`\`                                      | Sum of position losses across the positions liquidated together; prose-defined only.                                                                                                                                       |
| `safe_size`                | $$q\_{safe}$$       | base units            | [(L.11)](/math-engine/liquidation-engine) | Largest lot-multiple size whose 1.5x-padded initial margin the collateral share covers.                                                                                                                                    |
| `liquidation_qty`          | $$q\_{liq}$$        | base units            | [(L.12)](/math-engine/liquidation-engine) | Close-order quantity: full size in Full mode or degenerate cases, else reduction to safe size.                                                                                                                             |
| `fill_quantity`            | $$q^{\star}$$       | base units            | [(B.6)](/math-engine/order-book)          | Quantity of a single fill: min of taker and front-maker remainders; coherence-merged with settlement.size and position-tracker.fill\_quantity.                                                                             |
| `fill_price`               | $$P^{\star}$$       | USDX per base unit    | [(B.7)](/math-engine/order-book)          | Price of a single fill — always the maker's limit price; coherence-merged with settlement.price and position-tracker/liquidation-engine fill prices.                                                                       |
| `closed_quantity`          | $$q\_c$$            | base units            | [(T.3)](/math-engine/position-tracker)    | Portion of an opposing fill that closes existing size: min(size, fill quantity).                                                                                                                                           |
| `total_filled`             | $$Q\_f$$            | base units            | \`\`                                      | Sum of the liquidation close order's fill quantities; within-event accumulation, prose-defined only.                                                                                                                       |
| `spread_profit`            | $$g$$               | USDX                  | [(L.14)](/math-engine/liquidation-engine) | Positive part of fills' price improvement over the aligned bankruptcy price, summed over fills; credited to the fund.                                                                                                      |
| `realized_fill_pnl`        | $$\Pi\_{fill}$$     | USDX                  | [(L.15)](/math-engine/liquidation-engine) | Signed realized PnL of liquidation fills, each at its own fill price; a component of the cascade's per-market settlement X\_i.                                                                                             |
| `residual_unfilled_pnl`    | $$\Pi\_{res}$$      | USDX                  | [(L.16)](/math-engine/liquidation-engine) | Mark-valued PnL of the unfilled remainder of the close order; zero on complete fill.                                                                                                                                       |
| `bad_debt`                 | $$D$$               | USDX                  | [(L.17)](/math-engine/liquidation-engine) | Non-negative shortfall after fills-aware settlement against the collateral share; drawn from the fund, then ADL.                                                                                                           |
| `absorbed_amount`          | $$D\_{abs}$$        | USDX                  | [(I.1)](/math-engine/insurance-fund)      | min(bad debt, fund balance): the delta by which the fund and its absorption ledger move.                                                                                                                                   |
| `adl_settle_amount`        | $$D\_{adl}$$        | USDX                  | [(I.4)](/math-engine/insurance-fund)      | Shortfall handed to ADL after the fund is drained; an instruction is emitted only when strictly positive.                                                                                                                  |
| `adl_priority_score`       | $$\rho$$            | dimensionless         | [(L.18)](/math-engine/liquidation-engine) | ADL ranking score pi \* L, descending with deterministic account-id tie-break; coherence-merged with the insurance-fund instance.                                                                                          |
| `adl_pnl_percent`          | $$\pi$$             | fraction              | \`\`                                      | ADL candidate's unrealized PnL as a fraction of position value; no corpus expression defines it — a gap for the closure gate.                                                                                              |
| `liquidation_penalty_owed` | $$\Lambda\_{owed}$$ | USDX                  | [(S.4)](/math-engine/settlement)          | Penalty owed on filled liquidation notional at the market's penalty rate.                                                                                                                                                  |
| `penalty_charged`          | $$\Lambda$$         | USDX                  | [(I.9)](/math-engine/insurance-fund)      | Penalty actually debited/credited: the owed amount capped at available collateral, so the pair cannot mint USDX.                                                                                                           |
| `fill_notional`            | $$V^{\star}$$       | USDX                  | [(S.1)](/math-engine/settlement)          | Notional of a single fill, size times price, exact decimal.                                                                                                                                                                |
| `net_exchange_revenue`     | $$V$$               | USDX                  | [(S.5)](/math-engine/settlement)          | Settlement-record closure: total taker fees minus total maker rebates.                                                                                                                                                     |
| `margin_ratio`             | $$\rho\_M$$         | dimensionless         | [(M.6)](/math-engine/margin-math)         | Equity over total notional; undefined at zero notional; diagnostic — gates nothing in the corpus.                                                                                                                          |
| `max_position_size`        | $$q\_{max}$$        | base units            | [(M.15)](/math-engine/margin-math)        | Largest lot-aligned position openable with given collateral at the market rate; sizing/display analysis.                                                                                                                   |
| `withdrawable_collateral`  | $$W\_{max}$$        | USDX                  | [(M.16)](/math-engine/margin-math)        | Full realized collateral for a flat, unreserved account, zero otherwise; the withdrawal guard's cap.                                                                                                                       |
| `best_bid`                 | $$P\_b$$            | USDX per base unit    | \`\`                                      | Highest resting bid — a structural readout of the order\_book coordinate; no corpus expression id.                                                                                                                         |
| `best_ask`                 | $$P\_a$$            | USDX per base unit    | \`\`                                      | Lowest resting ask — a structural readout of the order\_book coordinate; no corpus expression id.                                                                                                                          |
| `mid_price`                | $$P\_{mid}$$        | USDX per base unit    | [(B.4)](/math-engine/order-book)          | Midpoint of best bid and ask, snapshotted once at market-order submission for the slippage cap; undefined when either side is empty.                                                                                       |
| `slippage_span`            | $$\Delta\_{slip}$$  | USDX per base unit    | [(B.9)](/math-engine/order-book)          | Half-width of the admissible VWAP band anchored at the mid-price snapshot.                                                                                                                                                 |
| `available_qty`            | $$Q\_{avail}$$      | base units            | \`\`                                      | Pre-match opposing liquidity at prices satisfying the taker's limit; within-event readout of the book, prose-defined only.                                                                                                 |
| `taker_remaining`          | $$q\_t$$            | base units            | \`\`                                      | Taker's unfilled remainder during the matching walk; within-event intermediate, prose-defined only.                                                                                                                        |
| `running_notional`         | $$V\_k$$            | USDX                  | \`\`                                      | Cumulative notional of fills accepted so far in a market-order walk; within-event accumulator, prose-defined only.                                                                                                         |
| `running_filled`           | $$Q\_k$$            | base units            | \`\`                                      | Cumulative quantity of fills accepted so far in a market-order walk; within-event accumulator, prose-defined only.                                                                                                         |
| `walk_notional`            | $$V$$               | USDX                  | \`\`                                      | Total notional of a hypothetical price-time-priority walk for the VWAP preview; prose-defined only.                                                                                                                        |
| `vwap_estimate`            | $$\overline{P}$$    | USDX per base unit    | [(B.11)](/math-engine/order-book)         | Read-only market-order VWAP preview; undefined when liquidity cannot cover the request.                                                                                                                                    |
| `funding_integral`         | $$\varphi$$         | USDX                  | \`\`                                      | Per-position accumulated funding read by portfolio equity; identically zero under the composed settle-and-reset convention (phi = 0) — no accrual map exists in the corpus, by design.                                     |
| `total_notional`           | $$N$$               | USDX                  | \`\`                                      | Sum of size times mark over open positions; prose-defined only, the margin ratio's denominator.                                                                                                                            |
| `open_positions`           | $$n\_{pos}$$        | count                 | \`\`                                      | Count of the account's open positions — a structural readout of the position coordinates; no corpus expression id.                                                                                                         |

### A.4 The transition matrix

**Guards**

| Event                    | Guard                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Reads                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deposit_withdrawal`     | deposit: x > 0 unguarded; withdrawal: 0 < x \le W\_{max} = C\_a , \mathbb{1}\[n\_{pos} = 0 \wedge n\_{resv} = 0] (margin-math.withdrawable\_collateral)                                                                                                                                                                                                                                                                                                                                                                  | `account_collateral`, `withdrawable_collateral`, `open_positions`, `reserved_margin`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `order_submission_fill`  | admission conjunction, nothing mutated on rejection: lot residue = 0 (order-book.lot\_alignment) \wedge tick residue = 0 \wedge P\_{lim} > 0 (order-book.tick\_alignment) \wedge band deviation \le b\_{band} (order-book.price\_band) \wedge FOK availability (order-book.fok\_availability) \wedge margin headroom \ge 0 (isolated: margin-math.order\_margin\_headroom; cross: margin-math.cross\_admission\_headroom); each market-order fill additionally gated by the running-VWAP band (order-book.running\_vwap) | `account_collateral`, `reserved_margin`, `signed_position_size`, `entry_price`, `order_book`, `trade_window`, `exchange_fee_account`, `mark_price`, `account_equity`, `available_margin`, `initial_margin`, `added_exposure`, `admission_added_margin`, `best_bid`, `best_ask`, `mid_price`, `slippage_span`, `available_qty`, `taker_remaining`, `running_notional`, `running_filled`, `fill_quantity`, `fill_price`, `closed_quantity`                                                                                                                                                                                                                                |
| `order_cancel_expiry`    | user cancel \vee IOC/FOK/market-remainder expiry \vee post-only cross rejection \vee slippage-cap breach (order-book.running\_vwap strict breach) \vee reduce-only rejection q\_{liq} > q (liquidation-engine.reduce\_only\_guard fails) \vee STP full reduction (order-book.stp\_decrement)                                                                                                                                                                                                                             | `order_book`, `reserved_margin`, `mid_price`, `slippage_span`, `liquidation_qty`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `funding_accrual`        | \Delta t > 0 \wedge u\_k > 0 (non-advancing or non-positive-anchor samples pass state through unchanged)                                                                                                                                                                                                                                                                                                                                                                                                                 | `accumulated_premium`, `interval_clock`, `oracle_anchor`, `mark_price`, `premium_index`, `trade_window`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `funding_settlement`     | funding interval boundary reached (schedule is configuration; the corpus contributes the T = 0 totality branch of funding-rate.funding\_rate)                                                                                                                                                                                                                                                                                                                                                                            | `accumulated_premium`, `interval_clock`, `funding_rate`, `funding_payment`, `mark_price`, `signed_position_size`, `account_collateral`, `funding_pool`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `liquidation_cascade`    | E \le \sum\_i M\_i at the fresh mark, inclusive (liquidation-engine.liquidation\_trigger)                                                                                                                                                                                                                                                                                                                                                                                                                                | `account_collateral`, `signed_position_size`, `entry_price`, `order_book`, `trade_window`, `insurance_fund_balance`, `insurance_fund_total_absorbed`, `insurance_fund_total_received`, `exchange_fee_account`, `mark_price`, `unrealized_pnl`, `account_equity`, `maintenance_margin`, `position_loss`, `total_loss`, `cross_collateral_share`, `collateral_share_sum`, `bankruptcy_price`, `aligned_bankruptcy_price`, `safe_size`, `liquidation_qty`, `fill_price`, `fill_quantity`, `total_filled`, `spread_profit`, `realized_fill_pnl`, `residual_unfilled_pnl`, `bad_debt`, `absorbed_amount`, `liquidation_penalty_owed`, `penalty_charged`, `adl_settle_amount` |
| `adl_execution`          | D\_{adl} = \max(D - \Phi\_1, 0) > 0 (insurance-fund.adl\_settle\_amount) nested with \Phi' \le \kappa (insurance-fund.adl\_trigger); the one-sided divergence (armed with nothing to settle) is the open finding adl\_arming\_condition\_ambiguity                                                                                                                                                                                                                                                                       | `adl_settle_amount`, `insurance_fund_balance`, `adl_priority_score`, `adl_pnl_percent`, `signed_position_size`, `entry_price`, `account_collateral`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `oracle_print_accept`    | \neg stale (oracle.is\_stale = 0) \wedge \Delta\_{step} \le \theta (oracle.single\_step\_deviation) \wedge (\|H\| = N\_h \Rightarrow \Delta\_{path} \le \theta\sqrt{N\_h}) (oracle.path\_deviation vs oracle.path\_threshold)                                                                                                                                                                                                                                                                                            | `oracle_anchor`, `anchor_timestamp`, `oracle_guard_state`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `oracle_reanchor_step`   | stale (oracle.is\_stale = 1) \wedge print arrives \wedge confirmations after this step < \max(k, 2); active per-step bound is \theta\_r, widened to \theta\_e once the escalation trigger fires (oracle.reanchor\_step\_deviation, oracle.escalation\_trigger)                                                                                                                                                                                                                                                           | `oracle_guard_state`, `oracle_anchor`, `anchor_timestamp`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `oracle_reanchor_commit` | stale \wedge consecutive mutually-consistent confirmations \ge \max(k, 2) (oracle.reanchor\_step\_deviation within the active bound on the promoting print)                                                                                                                                                                                                                                                                                                                                                              | `oracle_guard_state`, `anchor_timestamp`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

**Writes** — rows are state coordinates, columns are events; a cell cites the component equation defining that update; `·` means provably untouched.

| Coordinate                                   | `deposit_withdrawal` | `order_submission_fill`                                                                                  | `order_cancel_expiry` | `funding_accrual`                  | `funding_settlement`               | `liquidation_cascade`                                                                                                    | `adl_execution` | `oracle_print_accept` | `oracle_reanchor_step` | `oracle_reanchor_commit` |
| -------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- | --------------------- | ---------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------- | --------------------- | ---------------------- | ------------------------ |
| `account_collateral` (per-account)           | ✓                    | [(T.4)](/math-engine/position-tracker) [(S.2)](/math-engine/settlement) [(S.3)](/math-engine/settlement) | ·                     | ·                                  | [(F.4)](/math-engine/funding-rate) | [(L.15)](/math-engine/liquidation-engine) [(L.16)](/math-engine/liquidation-engine) [(I.9)](/math-engine/insurance-fund) | ·               | ·                     | ·                      | ·                        |
| `reserved_margin` (per-account)              | ·                    | [(M.12)](/math-engine/margin-math)                                                                       | ✓                     | ·                                  | ·                                  | ·                                                                                                                        | ·               | ·                     | ·                      | ·                        |
| `signed_position_size` (per-position)        | ·                    | [(T.3)](/math-engine/position-tracker) [(T.5)](/math-engine/position-tracker) ✓                          | ·                     | ·                                  | ·                                  | [(T.3)](/math-engine/position-tracker)                                                                                   | ·               | ·                     | ·                      | ·                        |
| `entry_price` (per-position)                 | ·                    | [(T.1)](/math-engine/position-tracker)                                                                   | ·                     | ·                                  | ·                                  | [(T.1)](/math-engine/position-tracker)                                                                                   | ·               | ·                     | ·                      | ·                        |
| `order_book` (per-market)                    | ·                    | [(B.6)](/math-engine/order-book) [(B.8)](/math-engine/order-book)                                        | ✓                     | ·                                  | ·                                  | [(B.6)](/math-engine/order-book)                                                                                         | ·               | ·                     | ·                      | ·                        |
| `accumulated_premium` (per-market)           | ·                    | ·                                                                                                        | ·                     | [(F.2)](/math-engine/funding-rate) | ✓                                  | ·                                                                                                                        | ·               | ·                     | ·                      | ·                        |
| `interval_clock` (per-market)                | ·                    | ·                                                                                                        | ·                     | ✓                                  | ✓                                  | ·                                                                                                                        | ·               | ·                     | ·                      | ·                        |
| `trade_window` (per-market)                  | ·                    | [(B.7)](/math-engine/order-book)                                                                         | ·                     | ·                                  | ·                                  | [(B.7)](/math-engine/order-book)                                                                                         | ·               | ·                     | ·                      | ·                        |
| `insurance_fund_balance` (per-market)        | ·                    | ·                                                                                                        | ·                     | ·                                  | ·                                  | [(I.7)](/math-engine/insurance-fund) [(I.2)](/math-engine/insurance-fund) [(I.10)](/math-engine/insurance-fund)          | ·               | ·                     | ·                      | ·                        |
| `insurance_fund_total_absorbed` (per-market) | ·                    | ·                                                                                                        | ·                     | ·                                  | ·                                  | [(I.3)](/math-engine/insurance-fund)                                                                                     | ·               | ·                     | ·                      | ·                        |
| `insurance_fund_total_received` (per-market) | ·                    | ·                                                                                                        | ·                     | ·                                  | ·                                  | [(I.8)](/math-engine/insurance-fund) [(I.11)](/math-engine/insurance-fund)                                               | ·               | ·                     | ·                      | ·                        |
| `funding_pool` (global)                      | ·                    | ·                                                                                                        | ·                     | ·                                  | [(S.6)](/math-engine/settlement)   | ·                                                                                                                        | ·               | ·                     | ·                      | ·                        |
| `exchange_fee_account` (global)              | ·                    | [(S.2)](/math-engine/settlement) [(S.3)](/math-engine/settlement) [(S.7)](/math-engine/settlement)       | ·                     | ·                                  | ·                                  | [(S.2)](/math-engine/settlement)                                                                                         | ·               | ·                     | ·                      | ·                        |
| `oracle_anchor` (per-market)                 | ·                    | ·                                                                                                        | ·                     | ·                                  | ·                                  | ·                                                                                                                        | ·               | ✓                     | ·                      | ✓                        |
| `anchor_timestamp` (per-market)              | ·                    | ·                                                                                                        | ·                     | ·                                  | ·                                  | ·                                                                                                                        | ·               | ✓                     | ·                      | ✓                        |
| `oracle_guard_state` (per-market)            | ·                    | ·                                                                                                        | ·                     | ·                                  | ·                                  | ·                                                                                                                        | ·               | ✓                     | ✓                      | ✓                        |

### A.5 The invariant registry

Every invariant the corpus states — component and global — with its scope and how it is verified.

> **Stale relative to the model above.** Rows 46 and 49 below are machine-rendered from an earlier revision of the state-space model (`models/state-space.json`) and still state the pre-ENG-7077 forms of G.7 and G.10 — `f · m_k` and `c · q_a · m_k` — rather than the `P_{oracle,k}` forms already fixed in the Event dynamics and Invariants sections above. Regenerating this appendix requires the Modeling & Security pod's pipeline to re-derive it from the current Rust source, which is out of scope for this doc-alignment pass; flagging here rather than hand-editing generated output or silently leaving it uncaveated.

| #  | Invariant                                                                                                                                                                                                                                                                                                                                                                                 | Scope       | Where                                              | Verification          |
| -- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------- | --------------------- |
| 1  | The funding rate always lies in the cap band: $$f \in \[-c, +c]$$ for every reachable state ((F.3)).                                                                                                                                                                                                                                                                                      | component   | [Funding](/math-engine/funding-rate)               | component Judge       |
| 2  | (F.3) is total: it is defined for every state, including the empty interval, where $$T = 0$$ yields $$f = 0$$.                                                                                                                                                                                                                                                                            | component   | [Funding](/math-engine/funding-rate)               | component Judge       |
| 3  | Funding is zero-sum between matched sides: for equal size $$q$$, the long and short payments cancel exactly, $$\Pi^{f}*{long} + \Pi^{f}*{short} = 0$$ ((F.4)).                                                                                                                                                                                                                            | component   | [Funding](/math-engine/funding-rate)               | component Judge       |
| 4  | When the clamp is inactive, the sign of the rate matches the sign of the average premium: a perpetual trading persistently rich yields $$f > 0$$ and a perpetual trading cheap yields $$f < 0$$ ((F.3)).                                                                                                                                                                                  | component   | [Funding](/math-engine/funding-rate)               | component Judge       |
| 5  | The accumulator $$A$$ is a well-formed time-weighted sum: only samples with strictly advancing time and a strictly positive oracle price contribute ((F.2)).                                                                                                                                                                                                                              | component   | [Funding](/math-engine/funding-rate)               | component Judge       |
| 6  | Non-negativity with exact depletion: $$\Phi \ge 0$$ after every operation, and the balance is exactly zero — no dust, no overdraw — whenever an ADL instruction is emitted ((I.2)).                                                                                                                                                                                                       | component   | [The Insurance Fund](/math-engine/insurance-fund)  | component Judge       |
| 7  | Accounting closure: $$\Phi = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}}$$ at all times — including through the penalty receipt path, which required no correction on the fund side.                                                                                                                                                                                                  | component   | [The Insurance Fund](/math-engine/insurance-fund)  | component Judge       |
| 8  | Bad-debt conservation: $$D = \min(D, \Phi) + \max(D - \Phi, 0)$$ — every liquidation's debt is covered exactly once, split between fund absorption ((I.1)) and ADL settlement ((I.4)).                                                                                                                                                                                                    | component   | [The Insurance Fund](/math-engine/insurance-fund)  | component Judge       |
| 9  | Monotone ledgers: $$\Sigma\_{\mathrm{abs}}$$ and $$\Sigma\_{\mathrm{rec}}$$ never decrease, and liquidation processing leaves $$\Sigma\_{\mathrm{rec}}$$ untouched — ADL counterparty settlements live entirely outside the fund's books.                                                                                                                                                 | component   | [The Insurance Fund](/math-engine/insurance-fund)  | component Judge       |
| 10 | Credit-before-absorb sequencing: for a single liquidation carrying both spread profit $$g > 0$$ and bad debt $$D > 0$$, the spread credit lands first, so absorption evaluates against the post-credit balance — the fund absorbs $$\min(D, \Phi + g)$$ and ADL settles $$\max(D - \Phi - g, 0)$$ ((I.7), (I.1), (I.4)).                                                                  | component   | [The Insurance Fund](/math-engine/insurance-fund)  | component Judge       |
| 11 | No-mint penalty pair: the liquidatee debit and the fund credit are the same capped amount $$\Lambda = \min(\Lambda\_{\mathrm{owed}}, \max(C, 0))$$ ((I.9)) — the penalty transfers USDX, it never creates it.                                                                                                                                                                             | component   | [The Insurance Fund](/math-engine/insurance-fund)  | component Judge       |
| 12 | For a long position with positive backing collateral, the bankruptcy price (L.8) lies strictly below the entry price and at or below the liquidation-trigger price; for a short, strictly above and at or above, respectively.                                                                                                                                                            | component   | [Liquidation](/math-engine/liquidation-engine)     | component Judge       |
| 13 | The trigger threshold is inclusive: an account with $$E = \sum\_i M\_i$$ is liquidated ((L.4)).                                                                                                                                                                                                                                                                                           | component   | [Liquidation](/math-engine/liquidation-engine)     | component Judge       |
| 14 | Cross-collateral shares conserve the pool exactly: after (L.7), $$\sum\_i s\_i = C$$, so aggregate bad debt over a fully-unfilled liquidation equals $$\mathcal{L} - C$$, the true portfolio shortfall.                                                                                                                                                                                   | component   | [Liquidation](/math-engine/liquidation-engine)     | component Judge       |
| 15 | Tick alignment concedes at most one tick and never produces a non-positive limit: for $$\delta > 0$$, $$\|P - P^{\downarrow}\| < \delta$$ and $$P^{\downarrow} \ge \delta$$ (mirrored for (L.10)); for $$\delta \le 0$$ the price passes through unchanged.                                                                                                                               | component   | [Liquidation](/math-engine/liquidation-engine)     | component Judge       |
| 16 | In Partial mode, $$q\_{\text{liq}} \in (0, q]$$ holds if and only if the collateral share backing the position is non-negative. When $$C < 0$$, $$q\_{\text{safe}} < 0$$ and (L.12) yields $$q\_{\text{liq}} = q - q\_{\text{safe}} > q$$ — an oversized close that would flip the position if executed (finding liquidation\_close\_order\_exceeds\_position\_size, v0.0.9 audit, high). | component   | [Liquidation](/math-engine/liquidation-engine)     | component Judge       |
| 17 | A non-positive or missing mark never fabricates losses or prices an order: the trigger and the pool split evaluate such markets at entry (zero PnL contribution, (L.1)), and no liquidation order is generated for them.                                                                                                                                                                  | component   | [Liquidation](/math-engine/liquidation-engine)     | component Judge       |
| 18 | A fully-filled liquidation whose every fill prints at or above the raw bankruptcy price produces at most the sub-tick alignment concession $$q,\delta$$ of bad debt in (L.17) — exactly zero when the bankruptcy price is already tick-aligned.                                                                                                                                           | component   | [Liquidation](/math-engine/liquidation-engine)     | component Judge       |
| 19 | Maintenance never exceeds initial margin: for the same $$(q, P)$$, (M.4) $$\leq$$ (M.2), per position and in the portfolio sums.                                                                                                                                                                                                                                                          | component   | [Margining](/math-engine/margin-math)              | component Judge       |
| 20 | The effective rate never undercuts the market: $$r\_{eff} \geq r\_i$$ for every admissible leverage, so a selected leverage only ever reserves more margin ((M.1)).                                                                                                                                                                                                                       | component   | [Margining](/math-engine/margin-math)              | component Judge       |
| 21 | Added exposure is non-negative: (M.11) satisfies $$\Delta q \geq 0$$ for all $$(s\_0, o)$$, so the admission charge (M.12) can only hold the portfolio requirement flat or raise it — a reduce is never pre-credited margin.                                                                                                                                                              | component   | [Margining](/math-engine/margin-math)              | component Judge       |
| 22 | Required margin never rounds down: the computed (M.2) and (M.4) always satisfy $$M \geq q \cdot P \cdot r$$ exactly.                                                                                                                                                                                                                                                                      | component   | [Margining](/math-engine/margin-math)              | component Judge       |
| 23 | For $$C \geq 0$$, (M.15) returns a non-negative integer multiple of $$\ell$$ with $$q\_{max} \cdot P \cdot r\_i \leq C$$; the invariant is scoped to non-negative collateral.                                                                                                                                                                                                             | component   | [Margining](/math-engine/margin-math)              | component Judge       |
| 24 | Withdrawals move only realized collateral from flat accounts: an account with any open position or outstanding reservation withdraws nothing, and a flat account withdraws at most $$C$$ ((M.16)); unrealized PnL is never withdrawable.                                                                                                                                                  | component   | [Margining](/math-engine/margin-math)              | component Judge       |
| 25 | Concurrent orders cannot double-spend headroom: the admission comparison in (M.14) includes all outstanding reservations $$M\_{resv}$$, and the check and the reservation write are atomic.                                                                                                                                                                                               | component   | [Margining](/math-engine/margin-math)              | component Judge       |
| 26 | The mark price always lies between the oracle anchor and the trade reference: $$\min(P\_{oracle}, P\_{trade}) \le P\_{mark} \le \max(P\_{oracle}, P\_{trade})$$, and $$P\_{mark} = P\_{oracle}$$ exactly when no trade reference exists.                                                                                                                                                  | component   | [The Oracle](/math-engine/oracle)                  | component Judge       |
| 27 | While a re-anchor is pending, the trusted anchor and its timestamp never move: $$P\_{oracle}$$ and $$t\_{last}$$ are unchanged by every pending print, so an unconfirmed candidate can never reach the mark.                                                                                                                                                                              | component   | [The Oracle](/math-engine/oracle)                  | component Judge       |
| 28 | On a fresh anchor, every trusted print satisfies $$\Delta\_{step} \le \theta$$ ((O.1)), and once the history window is full, $$\Delta\_{path} \le \theta\sqrt{N\_h}$$ ((O.3)) as well — the anchor moves by at most a factor $$1+\theta$$ per print and its ten-print path is square-root bounded.                                                                                        | component   | [The Oracle](/math-engine/oracle)                  | component Judge       |
| 29 | A large move off a stale anchor is never trusted from a single print: promotion requires at least $$\max(k, 2) \ge 2$$ consecutive prints each within the active step bound of the running candidate, even when escalated.                                                                                                                                                                | component   | [The Oracle](/math-engine/oracle)                  | component Judge       |
| 30 | A single trade — including a wash or self-trade fired immediately before a liquidation check — cannot move the mark by more than the volume-weighted median allows: shifting $$P\_{trade}$$ requires manipulated prices to carry strictly more than half the window's total traded size.                                                                                                  | component   | [The Oracle](/math-engine/oracle)                  | component Judge       |
| 31 | The book is never crossed: whenever both sides are non-empty, $$P\_b < P\_a$$.                                                                                                                                                                                                                                                                                                            | component   | [The Order Book](/math-engine/order-book)          | component Judge       |
| 32 | No order overfills: $$q\_f \le q$$ throughout an order's life, so remaining quantity $$q - q\_f$$ never goes negative.                                                                                                                                                                                                                                                                    | component   | [The Order Book](/math-engine/order-book)          | component Judge       |
| 33 | Every fill respects the taker's limit: a buy fills at $$P^{\star} \le P\_{lim}$$ and a sell at $$P^{\star} \ge P\_{lim}$$.                                                                                                                                                                                                                                                                | component   | [The Order Book](/math-engine/order-book)          | component Judge       |
| 34 | Every resting order has strictly positive remaining quantity, $$q - q\_f > 0$$.                                                                                                                                                                                                                                                                                                           | component   | [The Order Book](/math-engine/order-book)          | component Judge       |
| 35 | The order index and the book agree exactly: the index holds precisely the ids of resting orders, each mapped to its true side and price.                                                                                                                                                                                                                                                  | component   | [The Order Book](/math-engine/order-book)          | component Judge       |
| 36 | The slippage cap is monotone: for a fixed book and taker, a larger cap $$\beta$$ fills at least as much quantity as a smaller one.                                                                                                                                                                                                                                                        | component   | [The Order Book](/math-engine/order-book)          | component Judge       |
| 37 | Every stored position has strictly positive size: $$q > 0$$.                                                                                                                                                                                                                                                                                                                              | component   | [Position Tracking](/math-engine/position-tracker) | component Judge       |
| 38 | The entry price is a convex combination of the fill prices that built the current side, so $$P\_e > 0$$ and $$q,P\_e$$ equals the total notional paid; the aggregate entry is independent of the order of same-side fills.                                                                                                                                                                | component   | [Position Tracking](/math-engine/position-tracker) | component Judge       |
| 39 | A partial close changes size but never entry price, and the settled amount is exactly (T.4) at the closing fill's price, not the mark.                                                                                                                                                                                                                                                    | component   | [Position Tracking](/math-engine/position-tracker) | component Judge       |
| 40 | Bankruptcy is at least as adverse as liquidation: $$\sigma,(P\_{liq} - P\_{bkr}) \ge 0$$ for any position that is not over-collateralized ($$C \le P\_e,q$$, $$0 \le r\_m < 1$$).                                                                                                                                                                                                         | component   | [Position Tracking](/math-engine/position-tracker) | component Judge       |
| 41 | P\&L channels are disjoint: a funding payment changes only the funding accumulator, fees change only the fee channel, and neither moves the entry price, size, or realized P\&L.                                                                                                                                                                                                          | component   | [Position Tracking](/math-engine/position-tracker) | component Judge       |
| 42 | Fee accounting closure: within any settlement record, $$V = F - R$$ ((S.5)) holds exactly — every unit collected as a taker fee is either paid out as a maker rebate or retained as revenue, and money is neither created nor destroyed.                                                                                                                                                  | component   | [Settlement](/math-engine/settlement)              | component Judge       |
| 43 | Non-negativity of all transfer amounts: $$\text{fee}\_t \ge 0$$ ((S.2)), $$\text{rebate}\_m \ge 0$$ ((S.3)), and $$\text{penalty} \ge 0$$ ((S.4)), and every emitted funding transfer carries a strictly positive amount $$\|x\| > 0$$ ((S.6)).                                                                                                                                           | component   | [Settlement](/math-engine/settlement)              | component Judge       |
| 44 | Liquidation-penalty destination: every liquidation penalty transfer originates at the taker (the liquidated account) and terminates at the insurance fund — it is never routed to the Exchange fee account and never enters $$F$$, $$R$$, or $$V$$.                                                                                                                                       | component   | [Settlement](/math-engine/settlement)              | component Judge       |
| 45 | Merge conservation: for any batch, each merged accumulator equals the sum of that accumulator over the constituents ((S.7)), the merged transfer count equals the sum of constituent transfer counts, and the merged period is $$\[\min\_j \text{start}\_j, \max\_j \text{end}\_j]$$.                                                                                                     | component   | [Settlement](/math-engine/settlement)              | component Judge       |
| 46 | $$\sum\_{a} \Pi^{f}*{a} ;=; f, m\_k \sum*{a} s\_{a,k} ;=; 0 \qquad\Longrightarrow\qquad C\_{pool}' = C\_{pool}$$ — Aggregate funding zero-sum across matched open interest                                                                                                                                                                                                                | open-loop   | global model                                       | composition Judge     |
| 47 | $$\sum\_i s\_i = C, \qquad D = \min(D, \Phi\_1) + \max(D - \Phi\_1,\ 0), \qquad \Delta C\_a\big\|*{\Lambda} = -\Lambda = -\Delta\Phi\big\|*{\Lambda}, \qquad \Phi = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}}$$ — Collateral conservation through the liquidation cascade                                                                                                           | open-loop   | global model                                       | composition Judge     |
| 48 | $$r\_m < r\_i ;\Longrightarrow; \big{, S : E \le M\_{maint}^{pf} ,\big} \subsetneq \big{, S : E \le M\_{init}^{pf} ,\big}$$ — Margin monotonicity (maintenance strictly inside initial)                                                                                                                                                                                                   | open-loop   | global model                                       | simulator, 512 trials |
| 49 | $$\lvert \Pi^{f}\_{a} \rvert ;\le; c, q\_a, m\_k \qquad \text{per position, per funding interval}$$ — Bounded funding transfer per interval                                                                                                                                                                                                                                               | open-loop   | global model                                       | simulator, 512 trials |
| 50 | $$\big\lvert E(S, u') - E(S, u) \big\rvert ;\le; w, \theta, P\_{oracle} \sum\_i q\_i \qquad \text{(fresh-anchor accept branch only)}$$ — Bounded equity shock per fresh-anchor tick                                                                                                                                                                                                       | closed-loop | global model                                       | composition Judge     |
| 51 | $$\mathrm{stale} ;\Longrightarrow; (P\_{oracle}',, t\_{last}') = (P\_{oracle},, t\_{last}) \quad\text{and}\quad \lvert \Delta m\_k \rvert \le (1 - w), \lvert \Delta P\_{trade} \rvert$$ — Staleness freezes the anchor leg only                                                                                                                                                          | closed-loop | global model                                       | composition Judge     |
| 52 | $$\Delta C\_{fee} ;=; F - R ;=; V, \qquad \Delta C\_{fee} + \textstyle\sum\_a \Delta C\_a \big\|\_{\text{fees}} = 0$$ — Fee closure across the fill path                                                                                                                                                                                                                                  | open-loop   | global model                                       | composition Judge     |

## References

* The oracle-endogenous closed loop: [closed-loop](/math-engine/closed-loop)
* Component model: [funding-rate](/math-engine/funding-rate)
* Component model: [insurance-fund](/math-engine/insurance-fund)
* Component model: [liquidation-engine](/math-engine/liquidation-engine)
* Component model: [margin-math](/math-engine/margin-math)
* Component model: [oracle](/math-engine/oracle)
* Component model: [order-book](/math-engine/order-book)
* Component model: [position-tracker](/math-engine/position-tracker)
* Component model: [settlement](/math-engine/settlement)


# System

The oracle's guarded mark price is the system's central clock: it simultaneously revalues every position's unrealized PnL, sets maintenance requirements, and collars order prices, while executed fills feed back into the trade window that shapes the mark itself. The funding premium is the deliberate exception — it is measured against the perp reference price (the book's own volume-weighted median trade price), not the mark; see the funding-rate cluster below. Account health composes as fills → entry price → unrealized PnL → equity → margin ratios and admission headroom, and when equity crosses total maintenance margin the liquidation cascade fires: collateral is apportioned, bankruptcy-priced close orders execute, and the settlement residue splits into spread profit and penalties credited to the insurance fund versus bad debt drawn from it, with ADL as the terminal backstop. Funding closes the loop as a periodic value transfer that debits or credits equity through the settlement layer, coupling the price-formation and account-health subsystems continuously rather than only at liquidation.

84 expressions, 86 relationships (41 cross-primitive), across 6 clusters.

![The full relationship graph: every tagged expression grouped by primitive (order-book, position-tracker, margin-math, oracle, funding-rate, liquidation-engine, insurance-fund), with edges showing which expressions feed, bound, or trigger which others.](/files/o7dVQK3F6HENJ1587opO)

*The interactive version of this graph — pan, zoom, and click through to each expression — lives on the engine's own site; this is a static capture of it.*

## Clusters

### Price formation & oracle defense

Deviation guards, staleness, and re-anchor escalation defend the trusted anchor that blends with the trade median into the mark price every downstream primitive consumes.

`oracle.single_step_deviation`, `oracle.path_deviation`, `oracle.path_threshold`, `oracle.is_stale`, `oracle.reanchor_step_deviation`, `oracle.escalation_trigger`, `oracle.trade_ref`, `oracle.oracle_price`, `oracle.mark_price`

### Order admission & matching

Margin headroom gates, alignment and band checks, and the price-time matching loop that decides which orders enter the book and at what prices and quantities they fill.

`margin-math.effective_initial_margin_rate`, `margin-math.initial_margin_required`, `margin-math.initial_margin_required_effective`, `margin-math.added_exposure`, `margin-math.admission_added_margin`, `margin-math.order_margin_headroom`, `margin-math.cross_admission_headroom`, `margin-math.max_position_size`, `order-book.lot_alignment`, `order-book.tick_alignment`, `order-book.price_band`, `order-book.mid_price`, `order-book.fok_availability`, `order-book.fill_quantity`, `order-book.fill_price`, `order-book.stp_decrement`, `order-book.slippage_span`, `order-book.running_vwap`, `order-book.vwap_estimate`

### Account health

The position lifecycle (entry, PnL realization, flips) rolled up through equity, margin ratios, and the displayed liquidation/bankruptcy prices that describe how close an account is to the cascade.

`margin-math.equity`, `margin-math.margin_ratio`, `margin-math.available_margin`, `margin-math.portfolio_equity`, `margin-math.portfolio_initial_margin`, `margin-math.portfolio_maintenance_margin`, `margin-math.maintenance_margin_required`, `margin-math.withdrawable_collateral`, `position-tracker.vwap_entry`, `position-tracker.unrealized_pnl`, `position-tracker.realized_pnl`, `position-tracker.closed_quantity`, `position-tracker.flip_size`, `position-tracker.liquidation_price`, `position-tracker.bankruptcy_price`

### Liquidation cascade

From the equity-versus-maintenance trigger through collateral apportionment, bankruptcy-priced close orders, and fills-aware settlement into spread profit, bad debt, and ADL ranking.

`liquidation-engine.fresh_unrealized_pnl`, `liquidation-engine.account_equity`, `liquidation-engine.maintenance_margin`, `liquidation-engine.liquidation_trigger`, `liquidation-engine.isolated_margin_fallback`, `liquidation-engine.cross_collateral_share`, `liquidation-engine.cross_remainder_fold`, `liquidation-engine.bankruptcy_price`, `liquidation-engine.aligned_price_sell`, `liquidation-engine.aligned_price_buy`, `liquidation-engine.safe_size`, `liquidation-engine.partial_liquidation_qty`, `liquidation-engine.reduce_only_guard`, `liquidation-engine.spread_profit`, `liquidation-engine.realized_fill_pnl`, `liquidation-engine.residual_unfilled_pnl`, `liquidation-engine.bad_debt`, `liquidation-engine.adl_priority_score`

### Loss absorption backstop

The insurance fund's balance-capped debt absorption, its spread-profit and penalty income with lockstep lifetime ledgers, and the ADL threshold and settlement path when the fund runs dry.

`insurance-fund.absorbed_amount`, `insurance-fund.post_liquidation_balance`, `insurance-fund.total_absorbed_update`, `insurance-fund.adl_settle_amount`, `insurance-fund.adl_trigger`, `insurance-fund.adl_priority_score`, `insurance-fund.spread_profit_balance`, `insurance-fund.total_received_update`, `insurance-fund.penalty_charged`, `insurance-fund.penalty_credit_balance`, `insurance-fund.penalty_received_update`

### Funding & settlement flows

Periodic value transfers — the time-weighted funding rate settling as signed payments, and per-fill fees, rebates, and penalties accumulating into exchange revenue and fund income.

`funding-rate.premium_index`, `funding-rate.sample_contribution`, `funding-rate.funding_rate`, `funding-rate.funding_payment`, `settlement.funding_transfer`, `settlement.fill_notional`, `settlement.taker_fee`, `settlement.maker_rebate`, `settlement.liquidation_penalty`, `settlement.net_exchange_revenue`, `settlement.merge_totals`, `position-tracker.taker_fee`, `position-tracker.maker_rebate`

## Relationships

| From                                          | Kind     | To                                           | How                                                                                                                                                                                                                                                          |
| --------------------------------------------- | -------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `order-book.fill_price`                       | feeds    | `oracle.trade_ref`                           | Executed fill prices populate the five-trade window whose volume-weighted median becomes the trade reference.                                                                                                                                                |
| `oracle.trade_ref`                            | feeds    | `oracle.mark_price`                          | The volume-weighted trade median is the trade side of the convex mark-price blend.                                                                                                                                                                           |
| `oracle.trade_ref`                            | feeds    | `funding-rate.premium_index`                 | The perp reference — not the mark — is the numerator deviation term measured against the oracle anchor in every premium sample. Routing the mark here would make the premium self-referential, since the mark already contains the anchor.                   |
| `oracle.mark_price`                           | feeds    | `position-tracker.unrealized_pnl`            | Every mark update revalues each open position's unrealized PnL against its entry price.                                                                                                                                                                      |
| `oracle.mark_price`                           | feeds    | `liquidation-engine.fresh_unrealized_pnl`    | The liquidation engine recomputes per-position PnL from the fresh mark rather than trusting cached values.                                                                                                                                                   |
| `oracle.mark_price`                           | feeds    | `liquidation-engine.maintenance_margin`      | Maintenance margin is notional at the fresh mark scaled by the maintenance rate.                                                                                                                                                                             |
| `oracle.mark_price`                           | feeds    | `order-book.price_band`                      | The mark anchors the basis-point collar that admissible limit prices are measured against.                                                                                                                                                                   |
| `funding-rate.premium_index`                  | feeds    | `funding-rate.sample_contribution`           | Each sample's premium is time-weighted and added to the interval accumulator.                                                                                                                                                                                |
| `funding-rate.sample_contribution`            | feeds    | `funding-rate.funding_rate`                  | The accumulated time-weighted premium is the numerator of the clamped average funding rate.                                                                                                                                                                  |
| `funding-rate.funding_rate`                   | feeds    | `funding-rate.funding_payment`               | The settled rate scales each position's oracle-price notional (not the mark — see (F.4) and the `oracle.oracle_price` edge below) into a signed cash payment.                                                                                                |
| `funding-rate.funding_payment`                | feeds    | `settlement.funding_transfer`                | The signed funding payment is the amount routed through the funding pool, its sign selecting the transfer direction.                                                                                                                                         |
| `order-book.fill_price`                       | feeds    | `position-tracker.vwap_entry`                | Same-side fills fold their price into the size-weighted average entry price.                                                                                                                                                                                 |
| `position-tracker.unrealized_pnl`             | feeds    | `margin-math.equity`                         | Total unrealized PnL over open positions is the mark-to-market component of account equity.                                                                                                                                                                  |
| `margin-math.equity`                          | feeds    | `margin-math.margin_ratio`                   | Equity is the numerator of the margin ratio over total notional.                                                                                                                                                                                             |
| `margin-math.equity`                          | feeds    | `margin-math.available_margin`               | Available margin is equity less the initial margin already held against open positions.                                                                                                                                                                      |
| `margin-math.available_margin`                | feeds    | `margin-math.order_margin_headroom`          | Isolated-path admission headroom is available margin minus the order's effective-rate requirement.                                                                                                                                                           |
| `margin-math.order_margin_headroom`           | bounds   | `order-book.fill_quantity`                   | An order reaches the matching loop only if its margin headroom is non-negative, constraining which quantities can ever fill.                                                                                                                                 |
| `liquidation-engine.fresh_unrealized_pnl`     | feeds    | `liquidation-engine.account_equity`          | Fresh per-position PnL sums with collateral into the equity the trigger evaluates.                                                                                                                                                                           |
| `liquidation-engine.account_equity`           | feeds    | `liquidation-engine.liquidation_trigger`     | Equity is the left side of the inclusive trigger comparison.                                                                                                                                                                                                 |
| `liquidation-engine.maintenance_margin`       | bounds   | `liquidation-engine.liquidation_trigger`     | Total maintenance margin is the inclusive floor below which equity fires the liquidation.                                                                                                                                                                    |
| `liquidation-engine.liquidation_trigger`      | triggers | `liquidation-engine.cross_collateral_share`  | Only a fired trigger causes the shared cross pool to be split loss-proportionally across liquidating markets.                                                                                                                                                |
| `liquidation-engine.cross_collateral_share`   | feeds    | `liquidation-engine.bankruptcy_price`        | Each market's final collateral share (after the remainder fold) is the C displacing entry price into that position's bankruptcy price.                                                                                                                       |
| `position-tracker.vwap_entry`                 | feeds    | `liquidation-engine.bankruptcy_price`        | The volume-weighted entry price is the anchor from which the collateral share per unit displaces the bankruptcy price.                                                                                                                                       |
| `liquidation-engine.bankruptcy_price`         | feeds    | `liquidation-engine.spread_profit`           | The tick-aligned bankruptcy price is the reference each fill's price improvement is measured against.                                                                                                                                                        |
| `order-book.fill_price`                       | feeds    | `liquidation-engine.realized_fill_pnl`       | Liquidation close orders settle each fill's PnL at the maker price the book actually printed.                                                                                                                                                                |
| `liquidation-engine.realized_fill_pnl`        | feeds    | `liquidation-engine.bad_debt`                | Fills-aware realized PnL enters the post-liquidation equity whose negative part is bad debt.                                                                                                                                                                 |
| `liquidation-engine.spread_profit`            | feeds    | `liquidation-engine.bad_debt`                | Spread profit is subtracted from retained collateral in the shortfall computation.                                                                                                                                                                           |
| `liquidation-engine.spread_profit`            | feeds    | `insurance-fund.spread_profit_balance`       | Positive liquidation spread profit is credited to the insurance fund balance before absorption is evaluated.                                                                                                                                                 |
| `liquidation-engine.bad_debt`                 | feeds    | `insurance-fund.absorbed_amount`             | The liquidation's non-negative shortfall is the debt the fund attempts to absorb, capped by its balance.                                                                                                                                                     |
| `insurance-fund.absorbed_amount`              | feeds    | `insurance-fund.post_liquidation_balance`    | The new fund balance is the old balance less the absorbed amount, floored at zero.                                                                                                                                                                           |
| `insurance-fund.post_liquidation_balance`     | feeds    | `insurance-fund.adl_trigger`                 | The post-absorption balance is what the inclusive per-market ADL threshold predicate evaluates.                                                                                                                                                              |
| `liquidation-engine.bad_debt`                 | feeds    | `insurance-fund.adl_settle_amount`           | Bad debt exceeding the fund balance emits an ADL instruction for exactly the excess.                                                                                                                                                                         |
| `order-book.fill_price`                       | feeds    | `settlement.fill_notional`                   | Every fill's maker price times quantity is the notional on which all settlement fees and penalties are computed.                                                                                                                                             |
| `settlement.fill_notional`                    | feeds    | `settlement.liquidation_penalty`             | The liquidation penalty is the fill notional scaled by the penalty rate on liquidation fills only.                                                                                                                                                           |
| `settlement.liquidation_penalty`              | feeds    | `insurance-fund.penalty_charged`             | The settlement-computed penalty is the owed amount the fund charges, capped at the liquidatee's available collateral.                                                                                                                                        |
| `funding-rate.funding_payment`                | feeds    | `margin-math.equity`                         | Funding settles atomically into the collateral balance C (settle-and-reset), the first term of equity; the accrual integral is identically zero at every observable state, so the live channel is C, not the phi term.                                       |
| `order-book.fill_quantity`                    | feeds    | `position-tracker.closed_quantity`           | The matched fill quantity is the amount tested against existing position size to determine closed quantity.                                                                                                                                                  |
| `margin-math.initial_margin_required`         | feeds    | `margin-math.available_margin`               | Per-position initial margin (or stamped allocated margin) is the hold subtracted from equity.                                                                                                                                                                |
| `liquidation-engine.cross_collateral_share`   | feeds    | `liquidation-engine.cross_remainder_fold`    | The pure proportional shares are corrected by folding the rounding remainder into the largest-loss share.                                                                                                                                                    |
| `liquidation-engine.cross_remainder_fold`     | feeds    | `liquidation-engine.bankruptcy_price`        | Each market's folded collateral share is the C that sets its bankruptcy price.                                                                                                                                                                               |
| `liquidation-engine.liquidation_trigger`      | triggers | `liquidation-engine.partial_liquidation_qty` | Close-order sizing runs only for accounts the trigger selected.                                                                                                                                                                                              |
| `insurance-fund.adl_settle_amount`            | triggers | `insurance-fund.adl_priority_score`          | A strictly positive unabsorbed amount emits an ADL instruction whose counterparties are ranked by descending score.                                                                                                                                          |
| `liquidation-engine.partial_liquidation_qty`  | feeds    | `order-book.fill_quantity`                   | The cascade's close orders execute through the book: liquidation sizing becomes matched fill quantity, closing the loop by which liquidation fills re-enter the trade window and hence the mark.                                                             |
| `order-book.fill_price`                       | feeds    | `liquidation-engine.spread_profit`           | P\*\_j in the spread-profit sum is the book's executed fill price; the bankruptcy price only bounds it.                                                                                                                                                      |
| `position-tracker.realized_pnl`               | feeds    | `margin-math.equity`                         | Realized PnL settles into collateral C on the fill (C' = C + sigma(P\* - P\_e)q\_c), the first term of equity — the realized channel alongside the unrealized one.                                                                                           |
| `settlement.taker_fee`                        | feeds    | `margin-math.equity`                         | Taker fees debit the collateral balance C at fill time — the same collateral channel into equity as realized PnL.                                                                                                                                            |
| `settlement.maker_rebate`                     | feeds    | `margin-math.equity`                         | Maker rebates credit the collateral balance C at fill time — the same collateral channel into equity as realized PnL.                                                                                                                                        |
| `oracle.mark_price`                           | feeds    | `margin-math.portfolio_maintenance_margin`   | Portfolio maintenance margin is valued at the mark (sum q\_i \* m\_i \* r\_mm); wires the margin-math cluster to price formation.                                                                                                                            |
| `oracle.mark_price`                           | feeds    | `margin-math.portfolio_initial_margin`       | Portfolio initial margin is valued at the mark (sum q\_i \* m\_i \* r\_im).                                                                                                                                                                                  |
| `oracle.mark_price`                           | feeds    | `liquidation-engine.safe_size`               | Close-order sizing consumes the mark directly (C / (1.5 \* m \* r\_i)) — the mark's third entry point into the cascade.                                                                                                                                      |
| `oracle.mark_price`                           | feeds    | `liquidation-engine.residual_unfilled_pnl`   | The unfilled remainder is valued at the mark on its way into bad\_debt — the shortfall computation's mark dependency.                                                                                                                                        |
| `settlement.liquidation_penalty`              | feeds    | `margin-math.equity`                         | The cascade debits the penalty from the account's residual collateral (C\_a' = ... - sum Lambda\_i) — the account-side leg of the penalty transfer. The fund-side leg is penalty\_charged -> penalty\_credit\_balance.                                       |
| `insurance-fund.adl_trigger`                  | triggers | `insurance-fund.adl_priority_score`          | The threshold arm-path 1\[Phi' <= kappa]. Settle-amount firing implies threshold firing (D\_adl > 0 forces Phi' = 0 <= kappa); the divergence is one-sided — the threshold can fire with D\_adl = 0 (open finding: adl\_arming\_is\_nested\_not\_ambiguous). |
| `oracle.oracle_price`                         | feeds    | `funding-rate.funding_payment`               | The payment sigma*S*P\_oracle\*f is struck at the oracle price, not the mark, so both sides of a matched pair are valued on the anchor neither side's own trading can move.                                                                                  |
| `liquidation-engine.safe_size`                | feeds    | `liquidation-engine.partial_liquidation_qty` | partial\_liquidation\_qty embeds q\_safe verbatim (size - q\_safe; full mode when q\_safe >= size) — the sizing sub-chain.                                                                                                                                   |
| `liquidation-engine.isolated_margin_fallback` | feeds    | `liquidation-engine.bankruptcy_price`        | For isolated positions the stamped cushion (or q*P\_e*r\_i fallback) is the C in p\_b = P\_e - d\*C/q — the isolated collateral path into bankruptcy pricing.                                                                                                |
| `liquidation-engine.residual_unfilled_pnl`    | feeds    | `liquidation-engine.bad_debt`                | bad\_debt consumes the residual unfilled PnL leg directly — the second PnL leg of the shortfall computation.                                                                                                                                                 |
| `position-tracker.unrealized_pnl`             | feeds    | `insurance-fund.adl_priority_score`          | pnl\_percent in the ADL ranking derives from position unrealized PnL — the ranking's inbound dependency on the position layer.                                                                                                                               |
| `oracle.is_stale`                             | triggers | `oracle.reanchor_step_deviation`             | The re-anchor protocol runs only off a stale anchor; is\_stale routes prints into candidate confirmation — the control edge between the oracle's two regimes.                                                                                                |
| `order-book.mid_price`                        | feeds    | `order-book.slippage_span`                   | slippage\_span = mid\_price \* beta / 10^4 — the slippage band is priced off the mid.                                                                                                                                                                        |
| `order-book.slippage_span`                    | bounds   | `order-book.running_vwap`                    | The running VWAP of a market-order walk is bounded by the slippage band around the mid.                                                                                                                                                                      |
| `order-book.fill_quantity`                    | feeds    | `position-tracker.vwap_entry`                | fill\_quantity is the blend weight of the VWAP entry update (q*Pe + q\_f*P\*)/(q + q\_f) — the quantity leg alongside the price leg.                                                                                                                         |
| `order-book.fill_price`                       | feeds    | `position-tracker.realized_pnl`              | Realized PnL is valued at the executed fill price P\*.                                                                                                                                                                                                       |
| `position-tracker.closed_quantity`            | feeds    | `position-tracker.realized_pnl`              | closed\_qty = min(q, q\_f) is a declared input of realized PnL — the reduce-path quantity.                                                                                                                                                                   |
| `oracle.mark_price`                           | feeds    | `margin-math.margin_ratio`                   | margin\_ratio's denominator total\_notional = sum q\_i \* m\_i is valued at the mark.                                                                                                                                                                        |
| `liquidation-engine.bankruptcy_price`         | feeds    | `liquidation-engine.aligned_price_sell`      | Close orders are tick-aligned toward executability from the bankruptcy-derived close price (sell side).                                                                                                                                                      |
| `liquidation-engine.bankruptcy_price`         | feeds    | `liquidation-engine.aligned_price_buy`       | Close orders are tick-aligned toward executability from the bankruptcy-derived close price (buy side).                                                                                                                                                       |
| `order-book.fill_quantity`                    | feeds    | `liquidation-engine.spread_profit`           | The per-fill spread-profit sum consumes fill quantities from the book's matching output.                                                                                                                                                                     |
| `order-book.fill_quantity`                    | feeds    | `liquidation-engine.realized_fill_pnl`       | Realized fill PnL consumes fill quantities from the matching output.                                                                                                                                                                                         |
| `order-book.fill_quantity`                    | feeds    | `liquidation-engine.residual_unfilled_pnl`   | The residual leg consumes total filled quantity to size the unfilled remainder.                                                                                                                                                                              |
| `position-tracker.vwap_entry`                 | feeds    | `position-tracker.unrealized_pnl`            | entry\_price is maintained by the VWAP entry update and read by unrealized PnL — the fills -> entry -> PnL chain.                                                                                                                                            |
| `position-tracker.vwap_entry`                 | feeds    | `liquidation-engine.fresh_unrealized_pnl`    | The liquidation engine recomputes PnL from the entry price that only vwap\_entry maintains.                                                                                                                                                                  |
| `insurance-fund.spread_profit_balance`        | feeds    | `insurance-fund.absorbed_amount`             | Absorption evaluates against the post-credit balance Phi\_1 = Phi + g (credit-before-absorb, code-verified 2026-07-12).                                                                                                                                      |
| `insurance-fund.penalty_charged`              | feeds    | `insurance-fund.penalty_credit_balance`      | Lambda is the penalty input of the fund-side credit — the no-mint pair's fund leg.                                                                                                                                                                           |
| `margin-math.equity`                          | feeds    | `margin-math.cross_admission_headroom`       | Equity is the first input of the cross admission check E - (M\_used + M\_resv + M\_add).                                                                                                                                                                     |
| `margin-math.admission_added_margin`          | feeds    | `margin-math.cross_admission_headroom`       | Exposure netting enters admission here: added initial margin is a declared input of the cross headroom check.                                                                                                                                                |
| `margin-math.added_exposure`                  | feeds    | `margin-math.admission_added_margin`         | Netted added exposure feeds the added-margin computation on the cross path (the isolated path charges gross size — open finding isolated\_headroom\_charges\_gross\_size).                                                                                   |
| `order-book.fill_quantity`                    | feeds    | `settlement.fill_notional`                   | fill\_notional = q \* P — the quantity leg alongside the price leg.                                                                                                                                                                                          |
| `settlement.fill_notional`                    | feeds    | `settlement.taker_fee`                       | Fees are notional \* bps; wires the exchange-revenue chain to the fill.                                                                                                                                                                                      |
| `liquidation-engine.partial_liquidation_qty`  | feeds    | `liquidation-engine.reduce_only_guard`       | The computed close quantity is the guard's tested input — the negative-share stall path (finding liquidation\_close\_order\_exceeds\_position\_size).                                                                                                        |
| `insurance-fund.penalty_credit_balance`       | feeds    | `insurance-fund.absorbed_amount`             | Absorption evaluates against the post-credit balance Phi\_1 = Phi + Lambda + g — the penalty leg of credit-before-absorb (code-verified 2026-07-12, per\_market.rs:552-574).                                                                                 |
| `position-tracker.vwap_entry`                 | feeds    | `position-tracker.liquidation_price`         | The displayed liquidation price consumes the entry price that only vwap\_entry maintains.                                                                                                                                                                    |
| `position-tracker.vwap_entry`                 | feeds    | `position-tracker.bankruptcy_price`          | The display twin of the engine's bankruptcy price likewise consumes the VWAP-maintained entry price.                                                                                                                                                         |
| `oracle.single_step_deviation`                | bounds   | `oracle.mark_price`                          | The accept guard bounds anchor-leg motion of the mark to theta per tick — the channel the bounded-equity-shock invariant composes.                                                                                                                           |
| `liquidation-engine.aligned_price_sell`       | bounds   | `order-book.fill_price`                      | The tick-aligned close price is the cascade order's limit — it bounds which maker prices liquidation fills can print at (sell side).                                                                                                                         |
| `liquidation-engine.aligned_price_buy`        | bounds   | `order-book.fill_price`                      | Buy-side twin: the aligned close price bounds liquidation fill prices, closing the cascade's pricing loop through the book.                                                                                                                                  |

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.


# Closed Loop

The Nexus Exchange is one dynamical system. This document formalizes it as a state-space model in three staged parts, from a single corpus of verified component mathematics. Part I is the heart: the Exchange **engine** alone, a self-contained transition system $$S' = f(S, u)$$ in which the oracle price $$u$$ is simply *given* — every economic fact of the venue (matching, position lifecycle, funding, margining, liquidation, loss absorption) is an event map acting on one state vector. Part II summarizes the **input process**: $$u$$ is not raw but the output of a guarded process $$u' = g(u, \text{prints})$$, detailed in the oracle component document. Part III is the **union**: the closed loop obtained by composing the two, whose invariants — bounded equity shock per accepted tick, the conditional freeze under staleness — exist only because the guarded input and the engine response interlock.

Notation throughout matches the component corpus: $$C\_a$$ collateral, $$s$$ signed position size with $$\sigma = \operatorname{sign}(s)$$ and $$q = |s|$$, $$P\_e$$ entry price, $$m\_k$$ mark, $$\Phi$$ insurance fund, $$f$$ funding rate, $$E$$ equity, $$M$$ margin requirements. Claims are grounded by inline equation references into the component models; where the corpus lacks an expression, the gap is named rather than papered over.

## The input process: $$u' = g(u, \text{sources})$$

The input is itself a guarded process $$u' = g(u, \text{prints})$$: an incoming print moves the trusted anchor only if it passes the single-step and path deviation guards ([(O.1)](/math-engine/oracle), [(O.2)](/math-engine/oracle), [(O.3)](/math-engine/oracle)); a silent feed trips the staleness predicate ([(O.4)](/math-engine/oracle)) and routes prints into a multi-print re-anchor confirmation with escalation ([(O.5)](/math-engine/oracle), [(O.6)](/math-engine/oracle)). One event map, given below, summarizes the whole process; the oracle component document carries the detail.

### Oracle print (input process)

The single event of the input process $$u' = g(u, \text{prints})$$. On a fresh anchor, an incoming print is accepted only if it passes the single-step guard ([(O.1)](/math-engine/oracle)) and, once the ten-print history is full, the path guard ([(O.2)](/math-engine/oracle) against [(O.3)](/math-engine/oracle)); acceptance moves $$(P\_{oracle}, t\_{last})$$ and appends to the history, rejection changes nothing. When the staleness predicate trips ([(O.4)](/math-engine/oracle)), prints route into the re-anchor protocol instead: each print within the active bound of the running candidate advances the confirmation counter, an inconsistent print restarts it ([(O.5)](/math-engine/oracle)), escalation widens the bound by print count or elapsed time ([(O.6)](/math-engine/oracle)), and promotion requires $$\max(k, 2) \ge 2$$ consecutive confirmations — while pending, the anchor and its timestamp never move. The engine sees only the result: $$u$$ steps by at most a factor $$1 + \theta$$ per fresh accept, or jumps on a confirmed re-anchor commit. Touches: $$P\_{oracle}, t\_{last}, \Omega$$ only — no engine coordinate.

$$
u' = \begin{cases} (P\_{new},\ t\_{now}) & \neg\mathrm{stale} \wedge \Delta\_{step} \le \theta \wedge \Delta\_{path} \le \theta\sqrt{N\_h} \quad (\text{fresh accept}) \ (P\_{cand},\ t\_{now}) & \mathrm{stale} \wedge n\_p' \ge \max(k, 2) \quad (\text{re-anchor promotion}) \ (P\_{oracle},\ t\_{last}) & \text{otherwise (reject / pending)} \end{cases} \tag{U.1}
$$

## The union: the closed loop

The union is the closed loop: the guarded input process feeds the engine, and the engine feeds back into its own input. Downward, every accepted print re-prices the entire state at once — equity, maintenance floors, and the order collar all move with $$m\_k$$ (the funding premium is the exception, per the leg-specific damping below) — and the guards compose into the closed-loop bounds above: a fresh tick's motion is capped by $$\theta$$ before the engine ever sees it, so the equity shock per tick is bounded ([(O.1)](/math-engine/oracle) composed with [(O.8)](/math-engine/oracle) and [(M.5)](/math-engine/margin-math)), while a stale feed freezes exactly the anchor leg ([(O.4)](/math-engine/oracle)) and leaves liquidation live on the trade leg. Upward, every fill — including the cascade's own close fills — enters the five-trade window that shapes the trade reference ([(O.7)](/math-engine/oracle)): the engine influences the mark that will next judge its accounts, damped by $$(1-w)$$ and by the volume-weighted median's majority-volume requirement.

The damping is **leg-specific**, and the two legs differ. On the MARGIN leg a fill's influence is still damped by $$(1-w)$$, because the mark blends the trade reference at that weight. On the FUNDING leg it is not: the premium index (F.1) takes the trade reference at full weight, so a fill moves the next funding sample undamped. The remaining guards on that leg are the volume-weighted median's majority-volume requirement and the per-window cap $$c$$; there is no time-based recency gate — the reference reverts to the oracle only when the five-trade window is empty (the market has never traded, or a large oracle re-anchor just cleared its history), not when its prints have simply aged. This two-way coupling is what makes the composed system a genuine feedback loop rather than a filter followed by a plant: a liquidation prints fills, the fills move the trade leg, the moved mark re-evaluates the next account.

The composed system's long-run geometry: healthy states at an agreeing mark ($$m\_k = P\_{oracle,k}$$, zero premium) are not full equilibria — accrual still adds zero, but settlement no longer transfers nothing: the fixed interest term leaves the rate at $$f = i \cdot W/28800$$ even at zero premium, so [(S.6)](/math-engine/settlement) still moves a small amount through the pool every interval. The trigger stays silent, and the position, book, and fund coordinates hold fixed while $$C\_a$$ and $$C\_{pool}$$ keep cycling through that small transfer. Two absorbing regimes matter. Fund depletion: $$\Phi = 0$$ after an exhausting absorption ([(I.2)](/math-engine/insurance-fund) hits its floor exactly) arms ADL through both nested predicates and stays armed until a spread or penalty credit refills the balance ([(I.7)](/math-engine/insurance-fund), [(I.10)](/math-engine/insurance-fund)); the credit-before-absorb ordering (settled, code-verified) means a single liquidation carrying both profit and debt absorbs against $$\Phi + g$$, never the pre-credit balance. Liquidation stall: a negative cross share produces an oversized close that [(L.13)](/math-engine/liquidation-engine) rejects on every scan — a live-lock fixed point of the cascade in which the account remains triggered but untouched, exiting only when the mark or the share changes.

What the composition does *not* yet witness, named plainly. (1) adl\_settlement\_unmodeled: the ADL counterparty settlement map — closing price, per-counterparty size reduction, both sides' $$C\_a$$ updates, open-interest preservation — has no component expressions; the cascade's conservation invariant is scoped up to this leg, and the next cycle derives it from code (v0.0.9 audit, medium). (2) residual\_entry\_remark\_unwitnessed: the settle map values a partial liquidation's unfilled remainder at the mark and folds it into cash ([(L.16)](/math-engine/liquidation-engine) inside [(L.17)](/math-engine/liquidation-engine)), while the position layer retains the remainder's entry price ([(T.1)](/math-engine/position-tracker) is untouched by reductions) — consistency requires either excluding $$\Pi\_{\mathrm{res}}$$ from the cash settle or re-anchoring the residual's entry at the mark, and no expression witnesses either; conservation is therefore stated for fully-filled cascades. (3) cancel\_release\_arithmetic\_unwitnessed: the cancel/expiry map's per-order margin release has no corpus expression, though the guards that depend on it ([(M.16)](/math-engine/margin-math), [(M.14)](/math-engine/margin-math)) fix its required semantics. (4) adl\_arming\_condition\_ambiguity: the two arming predicates are nested, not symmetric — settle-amount firing implies threshold firing, and the surviving question is whether ADL executes when the threshold arms with $$D\_{\mathrm{adl}} = 0$$. Conversely, one previously-open gap is now closed: the liquidation penalty's fund-side destination is witnessed by the v0.0.9 expressions [(I.9)](/math-engine/insurance-fund), [(I.10)](/math-engine/insurance-fund), and [(I.11)](/math-engine/insurance-fund), retiring liquidation\_penalty\_sink as an open finding of this model.

## Invariants of the closed loop

### Bounded equity shock per fresh-anchor tick

$$
\big\lvert E(S, u') - E(S, u) \big\rvert ;\le; w, \theta, P\_{oracle} \sum\_i q\_i \qquad \text{(fresh-anchor accept branch only)} \tag{U.2}
$$

On the fresh-anchor accept branch of the input process, one oracle tick can shock an account's equity by at most $$w,\theta,P\_{oracle}\sum\_i q\_i$$ per market: the accepted anchor moves by at most $$\theta,P\_{oracle}$$ ([(O.1)](/math-engine/oracle)), the blend damps the anchor leg by $$w$$ ([(O.8)](/math-engine/oracle)), and equity is affine in the mark with slope bounded by total size ([(T.2)](/math-engine/position-tracker), [(M.5)](/math-engine/margin-math)). Explicitly excluded: the re-anchor commit branch — commits are bounded by $$\theta\_r/\theta\_e$$ relative to the *candidate*, not by $$\theta$$ relative to the prior anchor, and can exceed this bound (simulator result reanchor\_commit\_exceeds\_tick\_bound).

*Why it holds:* This invariant exists only at the composition: the engine alone accepts any $$u$$, and the oracle alone bounds prices, not equity. The accept guard rejects before any state write, so a committed fresh tick satisfies $$|\Delta P\_{oracle}| \le \theta P\_{oracle}$$; the mark is a convex combination, so $$|\Delta m\_k| = w,|\Delta P\_{oracle}|$$ with $$W\_k$$ unchanged by an oracle event; and $$E$$ is affine in $$m\_k$$ with slope $$\sum\_i s\_{i,k}$$, giving the bound by the triangle inequality. The exclusion is forced: re-anchor promotion moves the anchor to the candidate in one step, which the corpus bounds only relative to the candidate chain.

*Composes:* [*(O.1)*](/math-engine/oracle) [*(O.8)*](/math-engine/oracle) [*(T.2)*](/math-engine/position-tracker) [*(M.5)*](/math-engine/margin-math) [*(M.8)*](/math-engine/margin-math)

### Staleness freezes the anchor leg only

$$
\mathrm{stale} ;\Longrightarrow; (P\_{oracle}',, t\_{last}') = (P\_{oracle},, t\_{last}) \quad\text{and}\quad \lvert \Delta m\_k \rvert \le (1 - w), \lvert \Delta P\_{trade} \rvert \tag{U.3}
$$

While the anchor is stale ([(O.4)](/math-engine/oracle)), no pending print can move $$P\_{oracle}$$ or $$t\_{last}$$ — the re-anchor protocol persists only candidate bookkeeping until $$\max(k,2)$$ confirmations ([(O.5)](/math-engine/oracle)) — so the anchor leg of the mark is frozen and mark motion is confined to the trade leg, damped by $$(1-w)$$ and defended by the volume-weighted median ([(O.8)](/math-engine/oracle), [(O.7)](/math-engine/oracle)). Stated honestly: staleness does **not** pause liquidations — the trigger [(L.4)](/math-engine/liquidation-engine) keeps evaluating at the partially-frozen mark, whose trade leg the engine's own fills keep moving. The fail-closed property is confined to the anchor.

*Why it holds:* Only the trusted-accept path writes the anchor pair, and the pending branch returns without calling it, with the Trusted/Pending discriminant preventing a silent commit — so the anchor leg's contribution to $$\Delta m\_k$$ is zero under staleness. The mark is a convex combination, so the residual motion is exactly $$(1-w),\Delta P\_{trade}$$, and shifting $$P\_{trade}$$ requires manipulated prices to carry strictly more than half the window's volume. The liquidation half is a non-claim: no component expression conditions the trigger on freshness, so the composed system inherits liquidation-at-the-frozen-leg rather than a pause.

*Composes:* [*(O.4)*](/math-engine/oracle) [*(O.5)*](/math-engine/oracle) [*(O.8)*](/math-engine/oracle) [*(O.7)*](/math-engine/oracle) [*(L.4)*](/math-engine/liquidation-engine)

## Appendix — the complete formal system

This appendix is rendered mechanically from the state-space classification (`models/state-space.json`) — derived, not written. Its completeness claim is *checked*: the closure gate (`ci/closure.py`) verifies on every run that every corpus expression is classified, every state coordinate is written by an event and read somewhere, and every event map cites only defined coordinates. The state space factors into 16 coordinates (global fund and cash books; per-market book, trade window, premium accumulator, oracle anchor and re-anchor pending block; per-account collateral and reserved margin; per-position size/entry/direction and funding accumulator), 9 inputs, 20 parameters, and 54 derived observables, with all 133 corpus variables and 84 expressions classified totally across 10 events. Alphabet completion added adl\_execution, oracle\_reanchor\_step, oracle\_reanchor\_commit — including adl\_execution emitted with empty writes so the closure gate keeps flagging the unmodeled ADL counterparty settlement map. Deliberately uncited update maps (deposit/withdrawal transfer, cancel removal and reservation release, anchor assignments, trade-window push, funding resets) remain named gaps rather than inventions.

### B.1 The state vector

**per-market**

| Coordinate           | Symbol            | Units                    | Owner  | Description                                                                                                                                                                                                                                                 |
| -------------------- | ----------------- | ------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trade_window`       | $$W\_k$$          | five (price, size) pairs | oracle | The last five executed fills of the market, written by the fill map (including liquidation close fills) and read by the volume-weighted median trade reference — the engine-owned leg of the mark blend.                                                    |
| `oracle_anchor`      | $$P\_{oracle,k}$$ | USDX per unit of asset   | oracle | The trusted anchor price of the market — the engine's exogenous input process state; moved only by the guarded fresh-accept and re-anchor-commit branches of the oracle map, never by any engine event.                                                     |
| `anchor_timestamp`   | $$t\_{last,k}$$   | milliseconds             | oracle | Timestamp of the last trusted anchor update, read by the staleness predicate; frozen together with the anchor while a re-anchor is pending.                                                                                                                 |
| `oracle_guard_state` | $$\Omega\_k$$     | prints and milliseconds  | oracle | The input process's defense bookkeeping: the ten-print history (whose oldest element the path check reads) and the pending re-anchor block (candidate price, pending print count, confirmation counter, opening timestamp); touched only by the oracle map. |

### B.2 Inputs and parameters

Inputs are exogenous — they arrive from outside the state; parameters are constants of market or system configuration.

**Inputs**

| Input                      | Symbol       | Units                  | Description                                                                                                                     |
| -------------------------- | ------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `oracle_print_price`       | $$P\_{new}$$ | USDX per unit of asset | An incoming external oracle print being validated against the anchor or the pending re-anchor candidate.                        |
| `wall_clock_time`          | $$t\_{now}$$ | milliseconds           | Feed-supplied timestamp of the incoming print or staleness evaluation, in Unix milliseconds.                                    |
| `time_delta`               | $$\Delta t$$ | seconds                | Exogenous elapsed time since the previous funding premium sample; non-advancing samples are ignored.                            |
| `order_quantity`           | $$q$$        | base units             | Quantity of an arriving order; must be a lot multiple to pass admission.                                                        |
| `order_limit_price`        | $$P\_{lim}$$ | USDX per base unit     | Limit price of an arriving limit order; must be strictly positive, tick-aligned, and inside the mark collar.                    |
| `order_signed_size`        | $$o$$        | base units, signed     | Signed size of an arriving order (buy positive, sell negative), read by the added-exposure computation.                         |
| `max_slippage_bps`         | $$\beta$$    | basis points           | Taker-supplied per-order slippage cap on a market order; absent means no cap.                                                   |
| `preview_requested_qty`    | $$q\_{req}$$ | base units             | Quantity requested by a hypothetical market order in the read-only VWAP preview; undefined for non-positive requests.           |
| `external_transfer_amount` | $$x$$        | USDX                   | External USDX amount of a deposit or withdrawal; not a corpus variable — carried by the composition's deposit/withdrawal event. |

**Parameters**

| Parameter                    | Symbol        | Units                         | Scope       | Description                                                                                                                                                                                                                                                                                                                                  |
| ---------------------------- | ------------- | ----------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `funding_rate_cap`           | $$c$$         | fraction per funding interval | per-market  | Symmetric cap on the funding rate. The crypto perps use 0.001 (0.1%); the FX, commodity, and index perps (seven markets, including NDQ) are configured at 0.0005. Which of the seven are actually deployed changes with the venue's rollout state — see Market Specifications for the live per-market set rather than a fixed snapshot here. |
| `adl_threshold`              | $$\kappa$$    | USDX                          | per-market  | ADL trigger threshold on the fund balance; default zero arms ADL only at full depletion.                                                                                                                                                                                                                                                     |
| `maintenance_margin_rate`    | $$r\_m$$      | dimensionless ratio           | per-market  | Market maintenance margin rate; strictly less than the initial margin rate.                                                                                                                                                                                                                                                                  |
| `initial_margin_rate`        | $$r\_i$$      | dimensionless ratio           | per-market  | Market initial margin rate, equal to one over the market's maximum leverage.                                                                                                                                                                                                                                                                 |
| `account_leverage`           | $$L$$         | multiplier                    | per-account | Account-selected leverage per market (integer >= 1, validated before storage); user configuration with no state-mutating expression in the corpus, hence a parameter, not a coordinate.                                                                                                                                                      |
| `tick_size`                  | $$\delta$$    | USDX per base unit            | per-market  | Minimum price increment; non-positive tick disables alignment.                                                                                                                                                                                                                                                                               |
| `lot_size`                   | $$\ell$$      | base units                    | per-market  | Market lot size; order and position sizes are integer multiples of it; zero disables the alignment check.                                                                                                                                                                                                                                    |
| `taker_fee_bps`              | $$b\_t$$      | basis points                  | per-market  | Taker fee rate charged on fill notional.                                                                                                                                                                                                                                                                                                     |
| `maker_rebate_bps`           | $$b\_m$$      | basis points                  | per-market  | Maker rebate rate, stored negative by convention; applied by absolute value.                                                                                                                                                                                                                                                                 |
| `liquidation_penalty_bps`    | $$b\_{liq}$$  | basis points                  | per-market  | Penalty rate applied to the notional of liquidation fills and routed to the insurance fund.                                                                                                                                                                                                                                                  |
| `price_band_bps`             | $$b\_{band}$$ | basis points                  | per-market  | Maximum admissible relative deviation of a limit price from the mark (the admission collar).                                                                                                                                                                                                                                                 |
| `oracle_deviation_threshold` | $$\theta$$    | dimensionless fraction        | per-market  | Single-step deviation threshold for accepting an oracle print against the anchor.                                                                                                                                                                                                                                                            |
| `oracle_history_size`        | $$N\_h$$      | prints                        | global      | Fixed size of the rolling price-update window used by the path-manipulation check (HISTORY\_SIZE = 10).                                                                                                                                                                                                                                      |
| `oracle_staleness_seconds`   | $$\tau\_s$$   | seconds                       | per-market  | Staleness threshold: the anchor is stale strictly beyond this many seconds since the last trusted update.                                                                                                                                                                                                                                    |
| `reanchor_max_deviation`     | $$\theta\_r$$ | dimensionless fraction        | per-market  | Per-step consistency bound for re-anchor confirmations against the running candidate.                                                                                                                                                                                                                                                        |
| `escalation_max_deviation`   | $$\theta\_e$$ | dimensionless fraction        | per-market  | Widened per-step bound applied once the escalation trigger has fired.                                                                                                                                                                                                                                                                        |
| `required_confirmations`     | $$k$$         | prints                        | per-market  | Consecutive mutually-consistent prints required to promote a re-anchor; floored at 2 effectively.                                                                                                                                                                                                                                            |
| `escalation_prints`          | $$N\_e$$      | prints                        | per-market  | Print-count arm of the re-anchor escalation trigger.                                                                                                                                                                                                                                                                                         |
| `escalation_seconds`         | $$\tau\_e$$   | seconds                       | per-market  | Wall-clock arm of the re-anchor escalation trigger, measured from the opening of the pending sequence.                                                                                                                                                                                                                                       |
| `oracle_mark_weight`         | $$w$$         | dimensionless fraction        | per-market  | Oracle weight in the mark blend; unit-interval, default 0.95 (oracle-dominant).                                                                                                                                                                                                                                                              |

### B.3 Derived observables

Pure functions of state, inputs, and parameters — recomputed, never persisted.

| Quantity                   | Symbol              | Units                 | Defined by                                | Description                                                                                                                                                                                                                |
| -------------------------- | ------------------- | --------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mark_price`               | $$m\_k$$            | USDX per base unit    | [(O.8)](/math-engine/oracle)              | Convex blend of the trusted anchor and the volume-weighted trade reference; equals the anchor when no trade reference exists. Recomputed on demand, never persisted.                                                       |
| `trade_ref`                | $$P\_{trade}$$      | USDX per base unit    | [(O.7)](/math-engine/oracle)              | Volume-weighted median price of the five-trade window; falls back to the last trade price when the window is short.                                                                                                        |
| `premium_index`            | $$p$$               | dimensionless         | [(F.1)](/math-engine/funding-rate)        | Fractional deviation of the perp reference (NOT the mark) from the anchor at a sample; skipped when the anchor is non-positive.                                                                                            |
| `funding_rate`             | $$f$$               | fraction per interval | [(F.3)](/math-engine/funding-rate)        | Clamped, pro-rated time-weighted average premium plus the fixed interest term — $$\text{clamp}((A/T + i) \cdot W/28800, -c, +c)$$ — recomputed fresh from (A, T) at settlement with the T = 0 branch returning zero.       |
| `funding_payment`          | $$\Pi^f$$           | USDX                  | [(F.4)](/math-engine/funding-rate)        | Signed per-position funding payment sigma q P\_oracle f, struck at the oracle price rather than the mark; simultaneously the settlement event's collateral update delta.                                                   |
| `unrealized_pnl`           | $$\mathrm{uPnL}$$   | USDX                  | [(T.2)](/math-engine/position-tracker)    | Per-position mark-to-market PnL; the account total is its sum over open positions (coherence-merged with liquidation-engine.fresh\_unrealized\_pnl).                                                                       |
| `account_equity`           | $$E$$               | USDX                  | [(M.8)](/math-engine/margin-math)         | Collateral plus mark-to-market PnL (net of funding integrals in the portfolio form); coherence-merged across margin-math.equity, margin-math.portfolio\_equity, and liquidation-engine.account\_equity.                    |
| `maintenance_margin`       | $$M\_m$$            | USDX                  | [(M.4)](/math-engine/margin-math)         | Maintenance margin at the mark; coherence-merged with the portfolio and liquidation-engine instances.                                                                                                                      |
| `initial_margin`           | $$M\_i$$            | USDX                  | [(M.2)](/math-engine/margin-math)         | Initial margin at the mark (stamped allocated margin where set); coherence-merged with the portfolio instance.                                                                                                             |
| `available_margin`         | $$M\_{avail}$$      | USDX                  | [(M.7)](/math-engine/margin-math)         | Equity minus total initial margin held; can be negative; gates order admission, not withdrawal.                                                                                                                            |
| `added_exposure`           | $$\Delta q$$        | base units            | [(M.11)](/math-engine/margin-math)        | Magnitude of newly-opened exposure an order adds: growth on increase, zero on reduce/close, the whole new side on a flip.                                                                                                  |
| `admission_added_margin`   | $$M\_{add}$$        | USDX                  | [(M.12)](/math-engine/margin-math)        | Initial margin charged on added exposure at the mark and effective rate; also the amount written into the reservation at admission.                                                                                        |
| `isolated_margin_cushion`  | $$C\_{iso}$$        | USDX                  | [(L.5)](/math-engine/liquidation-engine)  | Collateral backing an isolated position: the margin allocated at fill time if recorded, otherwise the open-time initial margin at market rate; the trigger ((L.4)) and the liquidation pricing both use this single value. |
| `bankruptcy_price`         | $$p\_b$$            | USDX per base unit    | [(L.8)](/math-engine/liquidation-engine)  | Price at which the position's backing collateral is exactly exhausted; coherence-merged with position-tracker.bankruptcy\_price; may be zero or negative before alignment.                                                 |
| `aligned_bankruptcy_price` | $$\tilde{p}\_b$$    | USDX per base unit    | [(L.9)](/math-engine/liquidation-engine)  | Tick-aligned close-order limit: floor for sells (liquidation-engine.aligned\_price\_sell), ceil for buys (liquidation-engine.aligned\_price\_buy), clamped to one tick.                                                    |
| `liquidation_price`        | $$p\_{liq}$$        | USDX per base unit    | [(T.8)](/math-engine/position-tracker)    | Analytically-solved mark at which equity meets the maintenance requirement; display/analysis, neither moves state nor gates events.                                                                                        |
| `cross_collateral_share`   | $$s\_i$$            | USDX                  | [(L.6)](/math-engine/liquidation-engine)  | Loss-proportional share of the shared cross pool per market, with the remainder folded into the largest-loss position's share.                                                                                             |
| `collateral_share_sum`     | $$S$$               | USDX                  | \`\`                                      | Sum of the proportional shares before the remainder fold; prose-defined only — no corpus expression id (read by the remainder fold).                                                                                       |
| `position_loss`            | $$\ell\_i$$         | USDX                  | \`\`                                      | max(0, -uPnL\_i) per market with entry-price fallback; defined only in variable prose, no corpus expression id.                                                                                                            |
| `total_loss`               | $$\mathcal{L}$$     | USDX                  | \`\`                                      | Sum of position losses across the positions liquidated together; prose-defined only.                                                                                                                                       |
| `safe_size`                | $$q\_{safe}$$       | base units            | [(L.11)](/math-engine/liquidation-engine) | Largest lot-multiple size whose 1.5x-padded initial margin the collateral share covers.                                                                                                                                    |
| `liquidation_qty`          | $$q\_{liq}$$        | base units            | [(L.12)](/math-engine/liquidation-engine) | Close-order quantity: full size in Full mode or degenerate cases, else reduction to safe size.                                                                                                                             |
| `fill_quantity`            | $$q^{\star}$$       | base units            | [(B.6)](/math-engine/order-book)          | Quantity of a single fill: min of taker and front-maker remainders; coherence-merged with settlement.size and position-tracker.fill\_quantity.                                                                             |
| `fill_price`               | $$P^{\star}$$       | USDX per base unit    | [(B.7)](/math-engine/order-book)          | Price of a single fill — always the maker's limit price; coherence-merged with settlement.price and position-tracker/liquidation-engine fill prices.                                                                       |
| `closed_quantity`          | $$q\_c$$            | base units            | [(T.3)](/math-engine/position-tracker)    | Portion of an opposing fill that closes existing size: min(size, fill quantity).                                                                                                                                           |
| `total_filled`             | $$Q\_f$$            | base units            | \`\`                                      | Sum of the liquidation close order's fill quantities; within-event accumulation, prose-defined only.                                                                                                                       |
| `spread_profit`            | $$g$$               | USDX                  | [(L.14)](/math-engine/liquidation-engine) | Positive part of fills' price improvement over the aligned bankruptcy price, summed over fills; credited to the fund.                                                                                                      |
| `realized_fill_pnl`        | $$\Pi\_{fill}$$     | USDX                  | [(L.15)](/math-engine/liquidation-engine) | Signed realized PnL of liquidation fills, each at its own fill price; a component of the cascade's per-market settlement X\_i.                                                                                             |
| `residual_unfilled_pnl`    | $$\Pi\_{res}$$      | USDX                  | [(L.16)](/math-engine/liquidation-engine) | Mark-valued PnL of the unfilled remainder of the close order; zero on complete fill.                                                                                                                                       |
| `bad_debt`                 | $$D$$               | USDX                  | [(L.17)](/math-engine/liquidation-engine) | Non-negative shortfall after fills-aware settlement against the collateral share; drawn from the fund, then ADL.                                                                                                           |
| `absorbed_amount`          | $$D\_{abs}$$        | USDX                  | [(I.1)](/math-engine/insurance-fund)      | min(bad debt, fund balance): the delta by which the fund and its absorption ledger move.                                                                                                                                   |
| `adl_settle_amount`        | $$D\_{adl}$$        | USDX                  | [(I.4)](/math-engine/insurance-fund)      | Shortfall handed to ADL after the fund is drained; an instruction is emitted only when strictly positive.                                                                                                                  |
| `adl_priority_score`       | $$\rho$$            | dimensionless         | [(L.18)](/math-engine/liquidation-engine) | ADL ranking score pi \* L, descending with deterministic account-id tie-break; coherence-merged with the insurance-fund instance.                                                                                          |
| `adl_pnl_percent`          | $$\pi$$             | fraction              | \`\`                                      | ADL candidate's unrealized PnL as a fraction of position value; no corpus expression defines it — a gap for the closure gate.                                                                                              |
| `liquidation_penalty_owed` | $$\Lambda\_{owed}$$ | USDX                  | [(S.4)](/math-engine/settlement)          | Penalty owed on filled liquidation notional at the market's penalty rate.                                                                                                                                                  |
| `penalty_charged`          | $$\Lambda$$         | USDX                  | [(I.9)](/math-engine/insurance-fund)      | Penalty actually debited/credited: the owed amount capped at available collateral, so the pair cannot mint USDX.                                                                                                           |
| `fill_notional`            | $$V^{\star}$$       | USDX                  | [(S.1)](/math-engine/settlement)          | Notional of a single fill, size times price, exact decimal.                                                                                                                                                                |
| `net_exchange_revenue`     | $$V$$               | USDX                  | [(S.5)](/math-engine/settlement)          | Settlement-record closure: total taker fees minus total maker rebates.                                                                                                                                                     |
| `margin_ratio`             | $$\rho\_M$$         | dimensionless         | [(M.6)](/math-engine/margin-math)         | Equity over total notional; undefined at zero notional; diagnostic — gates nothing in the corpus.                                                                                                                          |
| `max_position_size`        | $$q\_{max}$$        | base units            | [(M.15)](/math-engine/margin-math)        | Largest lot-aligned position openable with given collateral at the market rate; sizing/display analysis.                                                                                                                   |
| `withdrawable_collateral`  | $$W\_{max}$$        | USDX                  | [(M.16)](/math-engine/margin-math)        | Full realized collateral for a flat, unreserved account, zero otherwise; the withdrawal guard's cap.                                                                                                                       |
| `best_bid`                 | $$P\_b$$            | USDX per base unit    | \`\`                                      | Highest resting bid — a structural readout of the order\_book coordinate; no corpus expression id.                                                                                                                         |
| `best_ask`                 | $$P\_a$$            | USDX per base unit    | \`\`                                      | Lowest resting ask — a structural readout of the order\_book coordinate; no corpus expression id.                                                                                                                          |
| `mid_price`                | $$P\_{mid}$$        | USDX per base unit    | [(B.4)](/math-engine/order-book)          | Midpoint of best bid and ask, snapshotted once at market-order submission for the slippage cap; undefined when either side is empty.                                                                                       |
| `slippage_span`            | $$\Delta\_{slip}$$  | USDX per base unit    | [(B.9)](/math-engine/order-book)          | Half-width of the admissible VWAP band anchored at the mid-price snapshot.                                                                                                                                                 |
| `available_qty`            | $$Q\_{avail}$$      | base units            | \`\`                                      | Pre-match opposing liquidity at prices satisfying the taker's limit; within-event readout of the book, prose-defined only.                                                                                                 |
| `taker_remaining`          | $$q\_t$$            | base units            | \`\`                                      | Taker's unfilled remainder during the matching walk; within-event intermediate, prose-defined only.                                                                                                                        |
| `running_notional`         | $$V\_k$$            | USDX                  | \`\`                                      | Cumulative notional of fills accepted so far in a market-order walk; within-event accumulator, prose-defined only.                                                                                                         |
| `running_filled`           | $$Q\_k$$            | base units            | \`\`                                      | Cumulative quantity of fills accepted so far in a market-order walk; within-event accumulator, prose-defined only.                                                                                                         |
| `walk_notional`            | $$V$$               | USDX                  | \`\`                                      | Total notional of a hypothetical price-time-priority walk for the VWAP preview; prose-defined only.                                                                                                                        |
| `vwap_estimate`            | $$\overline{P}$$    | USDX per base unit    | [(B.11)](/math-engine/order-book)         | Read-only market-order VWAP preview; undefined when liquidity cannot cover the request.                                                                                                                                    |
| `funding_integral`         | $$\varphi$$         | USDX                  | \`\`                                      | Per-position accumulated funding read by portfolio equity; identically zero under the composed settle-and-reset convention (phi = 0) — no accrual map exists in the corpus, by design.                                     |
| `total_notional`           | $$N$$               | USDX                  | \`\`                                      | Sum of size times mark over open positions; prose-defined only, the margin ratio's denominator.                                                                                                                            |
| `open_positions`           | $$n\_{pos}$$        | count                 | \`\`                                      | Count of the account's open positions — a structural readout of the position coordinates; no corpus expression id.                                                                                                         |

### B.4 The transition matrix

**Guards**

| Event                    | Guard                                                                                                                                                                                                                                                          | Reads                                                     |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `oracle_print_accept`    | \neg stale (oracle.is\_stale = 0) \wedge \Delta\_{step} \le \theta (oracle.single\_step\_deviation) \wedge (\|H\| = N\_h \Rightarrow \Delta\_{path} \le \theta\sqrt{N\_h}) (oracle.path\_deviation vs oracle.path\_threshold)                                  | `oracle_anchor`, `anchor_timestamp`, `oracle_guard_state` |
| `oracle_reanchor_step`   | stale (oracle.is\_stale = 1) \wedge print arrives \wedge confirmations after this step < \max(k, 2); active per-step bound is \theta\_r, widened to \theta\_e once the escalation trigger fires (oracle.reanchor\_step\_deviation, oracle.escalation\_trigger) | `oracle_guard_state`, `oracle_anchor`, `anchor_timestamp` |
| `oracle_reanchor_commit` | stale \wedge consecutive mutually-consistent confirmations \ge \max(k, 2) (oracle.reanchor\_step\_deviation within the active bound on the promoting print)                                                                                                    | `oracle_guard_state`, `anchor_timestamp`                  |

**Writes** — rows are state coordinates, columns are events; a cell cites the component equation defining that update; `·` means provably untouched.

| Coordinate                        | `oracle_print_accept` | `oracle_reanchor_step` | `oracle_reanchor_commit` |
| --------------------------------- | --------------------- | ---------------------- | ------------------------ |
| `oracle_anchor` (per-market)      | ✓                     | ·                      | ✓                        |
| `anchor_timestamp` (per-market)   | ✓                     | ·                      | ✓                        |
| `oracle_guard_state` (per-market) | ✓                     | ✓                      | ✓                        |

## References

* The engine alone: [global](/math-engine/global)
* The oracle component model: [oracle](/math-engine/oracle)


# Funding Rate

Perpetual futures never expire, so a periodic cash transfer between longs and shorts — funding — anchors the contract's price to the oracle (spot) price. During each funding interval the Exchange samples the instantaneous premium index $$(P\_{ref} - P\_{oracle})/P\_{oracle}$$ — the perpetual's own traded price against the oracle, not the mark (F.1) — and accumulates it weighted by the time elapsed since the previous sample. At any point in the interval the rate is the time-weighted average premium plus a fixed interest component, pro-rated from an 8-hour quote convention to the actual settlement window and clamped to a symmetric cap $$\pm c$$ (F.3). At settlement, each position pays or receives an amount proportional to its notional value at the oracle price (F.4): with a positive rate, longs pay shorts; with a negative rate, shorts pay longs. The construction is exactly zero-sum between matched long and short open interest.

![The funding rate is a fan-in of the premium accumulator and the interval clock, and settlement feeds back both into a fresh accumulator and into the positioning pressure that moves the next premium.](/files/fhOhTBi5KwZFG60k6O6S)

*The funding rate is a fan-in of the premium accumulator and the interval clock, and settlement feeds back both into a fresh accumulator and into the positioning pressure that moves the next premium.*

## Setting

| Symbol          | Name                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | Units                                         | Domain    |
| --------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | --------- |
| $$P\_{oracle}$$ | oracle\_price          | External index (oracle) price of the underlying asset. Samples with a non-positive oracle price are skipped by the implementation, so the effective domain is strictly positive.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | USDX per unit of asset                        | (0, ∞)    |
| $$P\_{ref}$$    | perp\_reference\_price | The perpetual's own traded price on the Exchange's book — the volume-weighted median of the recent-trade window (O.7). This is the perp side of the premium index. `OracleValidator::trade_reference_price` returns the last trade price when the window is empty, and `None` when that is also unset — a market that has never traded, or one whose trade history and last trade price were both just cleared by a large oracle re-anchor (`OracleValidator::accept_trusted`) — at which point the oracle commit path substitutes $$P\_{oracle}$$ (`commit_oracle_and_sample_funding`); there is no time-based staleness check on the window otherwise, so on a market that traded a few times and then went quiet without a re-anchor, $$P\_{ref}$$ keeps reflecting those trades — however old — until enough new trades displace them. | USDX per unit of asset                        | (0, ∞)    |
| $$P\_{mark}$$   | mark\_price            | The Exchange's mark price, $$w \cdot P\_{oracle} + (1-w) \cdot P\_{ref}$$. Drives margin and liquidation — but NOT the premium index (see F.1) or the settlement notional, which is struck at $$P\_{oracle}$$ (see F.4).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | USDX per unit of asset                        | (0, ∞)    |
| $$i$$           | interest\_8h           | Fixed interest component of the rate quote, added to the average premium before clamping. $$0.0001$$ (0.01% per 8h).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | dimensionless (per 8h)                        | fixed     |
| $$W$$           | window\_secs           | Length of one funding settlement window. Hourly in production (3600).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | seconds                                       | (0, ∞)    |
| $$\Delta t$$    | time\_delta\_secs      | Seconds elapsed since the previous sample in the interval. Samples at or before the previous sample time are ignored, so the effective domain is strictly positive. Derived in code from millisecond timestamps divided by 1000.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | seconds                                       | (0, ∞)    |
| $$A$$           | accumulated\_premium   | Running sum of premium-index samples weighted by their time deltas, $$\sum\_i p\_i , \Delta t\_i$$, over the current funding interval. Reset to zero at each interval boundary.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | dimensionless-seconds (premium × seconds)     | unbounded |
| $$T$$           | total\_time\_secs      | Total elapsed time in the current interval, from the interval start to the most recent sample. Derived, not stored: when zero (no samples yet) the implementation short-circuits and returns a zero rate rather than dividing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | seconds                                       | (0, ∞)    |
| $$c$$           | funding\_rate\_cap     | Per-market symmetric cap on the funding rate, stored in market parameters. Two configured values: $$0.001$$ (0.1% per settlement window) on the crypto perps, $$0.0005$$ on the FX, commodity, and index perps. Which markets in each class are actually deployed changes with the venue's rollout state — see Market Specifications for the live per-market set rather than a fixed snapshot here.                                                                                                                                                                                                                                                                                                                                                                                                                                        | dimensionless (fraction per funding interval) | (0, ∞)    |
| $$f$$           | funding\_rate          | The clamped funding rate applied to positions at settlement; always lies in $$\[-c, +c]$$.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | dimensionless (fraction per funding interval) | \[-c, +c] |
| $$S$$           | size                   | Absolute position size in units of the underlying asset (always non-negative; direction is carried separately by the position side).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | units of asset                                | \[0, ∞)   |
| $$\sigma$$      | side\_sign             | Direction indicator for the position: $$+1$$ for a long position, $$-1$$ for a short position. Encodes the match on position side in the implementation.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | dimensionless                                 | \[-1, 1]  |

## The mechanism

### Premium Index Sampling

Each oracle tick produces one sample of the instantaneous premium index: the relative deviation of the **perpetual's own traded price** from the oracle price.

The numerator is $$P\_{ref}$$, not $$P\_{mark}$$, and the distinction is load-bearing. Since $$P\_{mark} = w \cdot P\_{oracle} + (1-w) \cdot P\_{ref}$$, substituting the mark would give $$(P\_{mark} - P\_{oracle})/P\_{oracle} = (1-w) \cdot (P\_{ref} - P\_{oracle})/P\_{oracle}$$ — the true premium scaled by $$(1-w)$$, i.e. 5% of it at the production $$w = 0.95$$, with a damping factor that moves silently whenever a margin parameter is retuned. The premium is defined against the traded price so that it measures the deviation it is named for. A positive premium means the perpetual trades rich to the index (longs are crowded); a negative premium means it trades cheap. The implementation skips any sample whose oracle price is not strictly positive, leaving the interval state unchanged, so the division is always well defined.

$$
p = \frac{P\_{ref} - P\_{oracle}}{P\_{oracle}} \tag{F.1}
$$

Samples arrive at an irregular cadence, so each premium index (F.1) is weighted by the time elapsed since the previous sample before being added to the interval accumulator $$A$$. This makes the accumulator the exact time integral of the (piecewise-constant) premium index over the interval, independent of sampling frequency: 1-minute and 5-second cadences produce identical averages for the same premium path. Samples with a timestamp at or before the previous sample time are ignored, so $$\Delta t$$ is strictly positive.

$$
A \leftarrow A + p \cdot \Delta t \tag{F.2}
$$

### Funding Rate

The funding rate is built in three steps: the time-weighted average premium (the accumulator (F.2) divided by total elapsed time $$T$$), plus the fixed interest component $$i$$, pro-rated from the 8-hour quote convention to the actual settlement window $$W$$, and finally clamped to the symmetric per-market cap $$\pm c$$.

The quote convention matters: $$A/T + i$$ is a **per-8-hour** rate, the industry standard. The amount charged at an hourly boundary is that rate scaled by $$W / 28800$$. The clamp is applied last, to the pro-rated value, so $$c$$ bounds the wealth transfer per settlement window regardless of how dislocated the perpetual becomes. When no time has elapsed in the interval ($$T = 0$$, i.e. no samples yet), the implementation returns zero rather than dividing by zero. The predicted funding rate published mid-interval is defined to be this same value: the current average is the best estimate of the final rate.

$$
f = \operatorname{clamp}!\left(\left(\frac{A}{T} + i\right) \cdot \frac{W}{28800},; -c,; +c\right) \tag{F.3}
$$

Because the clamp binds on the composed quantity, the basis at which it engages is $$8c - i$$: at the production $$c = 0.001$$ that is a **0.79% basis**, not 0.1%.

### Funding Payments

At settlement each open position exchanges cash proportional to its notional value **at the oracle price**. The signed amount is the position's notional $$S \cdot P\_{oracle}$$ times the funding rate (F.3), with the sign flipped for shorts via $$\sigma$$. The convention is: a positive amount means the account pays; a negative amount means the account receives. So with a positive rate ($$f > 0$$, perpetual rich to index) longs pay and shorts receive, and with a negative rate shorts pay and longs receive. For equal long and short size at the same rate, the two amounts cancel exactly.

The notional is struck at $$P\_{oracle}$$ rather than $$P\_{mark}$$ so that both sides of a matched pair are valued on the same external anchor — the two counterparties must exchange identical magnitudes, and the oracle is the price neither side's own trading can move.

$$
\Pi = \sigma \cdot S \cdot P\_{oracle} \cdot f \tag{F.4}
$$

## Invariants

* The funding rate is always bounded: $$-c \le f \le +c$$ for every interval state ((F.3)).
* Zero premium implies a rate of exactly the pro-rated interest: if $$P\_{ref} = P\_{oracle}$$ for the whole interval, then $$A = 0$$ and $$f = i \cdot W/28800$$. (Earlier revisions of this model omitted the interest term and read this as a zero rate; the interest term makes the resting rate non-zero.)
* When not clamped, the sign of the rate matches the sign of $$(A/T + i)$$, not of the average premium alone: a perpetual trading rich enough to the index yields $$f > 0$$; $$f < 0$$ needs $$A/T < -i$$, i.e. the perpetual must trade cheap by more than the interest component (0.01% per 8h) before the rate flips negative.
* Funding is exactly zero-sum for matched open interest: for equal long and short size at the same oracle price and rate, $$\Pi\_{long} + \Pi\_{short} = 0$$ ((F.4)); verified by property-based testing across the full rate range.
* Payments are linear in position size: tripling $$S$$ triples $$\Pi$$ exactly.
* The rate is well defined at all times: with no samples in the interval ($$T = 0$$) the rate is exactly $$0$$, never a division error.
* Samples with non-positive oracle price or non-increasing timestamps leave the interval state unchanged.
* The TWAP is sampling-cadence invariant for a constant premium: any positive sampling frequency yields the same $$f$$ for a given premium $$p$$, equal to $$(p + i) \cdot W/28800$$ (when below the cap) — not to $$p$$ itself, since the interest term and the window pro-rating both apply.

## Worked example

Consider the BTC perpetual with the oracle steady at $$P\_{oracle} = 50{,}000$$, observed over 8 hours of premium accumulation. For the first 4 hours the perp reference is $$50{,}020$$, a premium index of $$p\_1 = 20/50{,}000 = 0.0004$$ per (F.1); for the last 4 hours it is $$50{,}035$$, so $$p\_2 = 35/50{,}000 = 0.0007$$. Sampling once per minute, each phase contributes its premium times its duration to the accumulator per (F.2): $$A = 0.0004 \times 14{,}400 + 0.0007 \times 14{,}400 = 5.76 + 10.08 = 15.84$$ premium-seconds.

Over $$T = 28{,}800$$ seconds the raw time-weighted average premium is $$A/T = 15.84 / 28{,}800 = 0.00055$$ — exactly the duration-weighted mean of the two phases, $$(0.0004 + 0.0007)/2$$. Adding the interest component gives an 8-hour quote of $$0.00055 + 0.0001 = 0.00065$$.

Production settles **hourly**, so per (F.3) the rate actually charged at a boundary is that quote pro-rated: $$f = 0.00065 \times 3600/28800 = 0.00008125$$. With a cap of $$c = 0.001$$ this is far inside the band, so the clamp does not bind.

Now settle a 2 BTC long. Its notional is $$2 \times 50{,}000 = 100{,}000$$ USDX, so per (F.4) the payment is $$\Pi = +1 \times 100{,}000 \times 0.00008125 = 8.125$$ USDX — positive, so the long pays. A 2 BTC short of the same size has $$\Pi = -8.125$$ USDX and receives the same amount: the transfer is exactly zero-sum.

Had the perpetual instead sustained a 10% premium, the 8-hour quote of $$0.1001$$ would pro-rate to $$0.01251$$ and be clamped to $$c = 0.001$$, so every position would settle at the cap. Note where that boundary sits: the clamp engages at a basis of $$8c - i = 0.0079$$, i.e. **0.79%**, not at 0.1%.

## Analysis

> **Stale relative to the model above.** The Sensitivity, Response curves, and Parameter space sections below are machine-generated by the math-engine pipeline (`eng/ops/intelligence/math-engine`, owned by the Modeling & Security pod) from an earlier revision of this model — they still elasticize/plot against `mark_price` rather than `perp_reference_price`/`oracle_price`, and the surfaces still hold the cap at `±0.0075` (0.75%) rather than the production `0.001` (0.1%) discussed above. Regenerating them requires that pipeline to re-derive the model from the current Rust source, which is out of scope for this doc-alignment pass; flagging here rather than hand-editing generated output or silently leaving it uncaveated.

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression            | Input                | Elasticity ε |
| --------------------- | -------------------- | ------------ |
| `premium_index`       | mark\_price          | 1.43e+03     |
| `premium_index`       | oracle\_price        | -1.43e+03    |
| `sample_contribution` | mark\_price          | 1.43e+03     |
| `sample_contribution` | oracle\_price        | -1.43e+03    |
| `sample_contribution` | time\_delta\_secs    | 1            |
| `funding_rate`        | accumulated\_premium | 1            |
| `funding_rate`        | total\_time\_secs    | -1           |
| `funding_rate`        | funding\_rate\_cap   | 0            |
| `funding_payment`     | side\_sign           | 1            |
| `funding_payment`     | size                 | 1            |
| `funding_payment`     | mark\_price          | 1            |
| `funding_payment`     | funding\_rate        | 1            |

![Sensitivity tornado — Premium index](/files/0rmlq9xOdNUQCEyMqtoG)

![Sensitivity tornado — Funding rate (clamped TWAP)](/files/D5A0l56shLjrGV6vccNq)

![Sensitivity tornado — Funding payment](/files/4NjHeksTEZDaxcAiyABd)

### Response curves

![The instantaneous premium index is linear in the mark price and crosses zero exactly where mark equals the fixed $50,000 oracle price.](/files/1SUb1mnSA8fBVdGHRDph)

*The instantaneous premium index is linear in the mark price and crosses zero exactly where mark equals the fixed $50,000 oracle price.*

![Over a full 8-hour interval (T = 28,800 s) the funding rate rises linearly with the accumulated premium until the average hits the ±0.0075 cap, where it saturates.](/files/tqWwYRMIzFSX3NdUVsJg)

*Over a full 8-hour interval (T = 28,800 s) the funding rate rises linearly with the accumulated premium until the average hits the ±0.0075 cap, where it saturates.*

![At a positive 0.05% rate and $50,000 mark price, payments scale linearly with size and are exactly mirrored between long and short, so matched open interest nets to zero.](/files/D9Gh9DRfsPPKs892Ov1G)

*At a positive 0.05% rate and $50,000 mark price, payments scale linearly with size and are exactly mirrored between long and short, so matched open interest nets to zero.*

### Parameter space

Joint parameter effects evaluated from the verified expressions over 2-D grids.

![The clamp carves wedge-shaped saturation zones where |accumulated premium / time| exceeds the ±0.75% cap, so short accumulation windows saturate at far smaller premiums; cap held at 0.0075.](/files/ji9aNK1hpC7XtKG2JwFF)

*The clamp carves wedge-shaped saturation zones where |accumulated premium / time| exceeds the ±0.75% cap, so short accumulation windows saturate at far smaller premiums; cap held at 0.0075.*

![The bilinear size × rate interaction produces hyperbolic level sets of equal payment, with the sign flip at rate = 0 splitting payers from receivers; mark price held at 50,000 for a long position.](/files/NAwQ05XWubuRagsGi4Av)

*The bilinear size × rate interaction produces hyperbolic level sets of equal payment, with the sign flip at rate = 0 splitting payers from receivers; mark price held at 50,000 for a long position.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [insurance-fund](/math-engine/insurance-fund)
* Sibling model: [liquidation-engine](/math-engine/liquidation-engine)
* Sibling model: [margin-math](/math-engine/margin-math)
* Sibling model: [oracle](/math-engine/oracle)
* Sibling model: [order-book](/math-engine/order-book)
* Sibling model: [position-tracker](/math-engine/position-tracker)
* Sibling model: [settlement](/math-engine/settlement)


# Margin Math

Every position on the Exchange is backed by collateral. This document derives the complete margin model from the implementation: the initial margin reserved when exposure is opened, the maintenance margin that must be preserved to keep it open, and the account-level aggregates — equity, margin ratio, and available margin — that gate every new order. The model is linear in size and price, parameterized per market by an initial margin rate $$r\_{im}$$ and a maintenance margin rate $$r\_{mm}$$ with $$r\_{mm} < r\_{im}$$, and extended per position by an optional leverage selection that can only make requirements stricter, never looser.

![All per-position quantities fan in to a single account-level equity, which fans back out into the two derived quantities (available margin, margin ratio) that respectively gate admission and trigger liquidation — and every admitted order feeds new position state back into that fan-in.](/files/03EWErZK8wafwLLRbAS1)

*All per-position quantities fan in to a single account-level equity, which fans back out into the two derived quantities (available margin, margin ratio) that respectively gate admission and trigger liquidation — and every admitted order feeds new position state back into that fan-in.*

## Setting

| Symbol         | Name                      | Description                                                                                                                                               | Units                 | Domain    |
| -------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | --------- |
| $$q$$          | size                      | Position or order size (unsigned magnitude).                                                                                                              | base units (e.g. BTC) | \[0, ∞)   |
| $$P$$          | price                     | Mark price used for margin computation; falls back to the position's entry price when no fresh mark is available.                                         | USDX per base unit    | (0, ∞)    |
| $$r\_{im}$$    | initial\_margin\_rate     | Market initial margin rate, equal to one over the market's maximum leverage.                                                                              | dimensionless ratio   | (0, 1]    |
| $$r\_{mm}$$    | maintenance\_margin\_rate | Market maintenance margin rate; strictly less than the initial margin rate.                                                                               | dimensionless ratio   | (0, 1]    |
| $$L$$          | leverage                  | Account-selected leverage for a market. Unset or zero falls back to the market rate; values of 1 or more map to a rate of 1/L floored at the market rate. | multiplier            | \[1, ∞)   |
| $$s\_0$$       | existing\_signed          | Signed size of the resting position before an order: positive long, negative short, zero flat.                                                            | base units, signed    | unbounded |
| $$o$$          | order\_signed             | Signed size of the order: a buy is positive, a sell is negative.                                                                                          | base units, signed    | unbounded |
| $$C$$          | collateral                | Account collateral on deposit.                                                                                                                            | USDX                  | \[0, ∞)   |
| $$\Pi$$        | total\_unrealized\_pnl    | Sum of unrealized profit and loss across the account's open positions.                                                                                    | USDX                  | unbounded |
| $$N$$          | total\_notional           | Sum of position size times mark price across open positions.                                                                                              | USDX                  | (0, ∞)    |
| $$M\_{used}$$  | total\_initial\_margin    | Sum of initial margin held for existing positions — the stamped allocated margin where set, otherwise the market rate against the current mark.           | USDX                  | \[0, ∞)   |
| $$M\_{avail}$$ | available\_margin         | Equity minus total initial margin held; can be negative when profitable positions offset requirements elsewhere.                                          | USDX                  | unbounded |
| $$\ell$$       | lot\_size                 | Market lot size; position sizes are integer multiples of it.                                                                                              | base units            | (0, ∞)    |
| $$m$$          | mark                      | Fresh mark price for a position's market.                                                                                                                 | USDX per base unit    | (0, ∞)    |
| $$e$$          | entry\_price              | Volume-weighted entry price of a position.                                                                                                                | USDX per base unit    | (0, ∞)    |
| $$s$$          | signed\_size              | Signed position size: +size for a long, −size for a short.                                                                                                | base units, signed    | unbounded |
| $$\varphi$$    | funding\_integral         | Accumulated signed funding on a position; positive means the account has paid funding (a debit against equity), negative means it has received funding.   | USDX                  | unbounded |

## The mechanism

### Margin rates

Each market carries a default initial margin rate $$r\_{im} = 1/L\_{max}$$, where $$L\_{max}$$ is the market's maximum leverage. An account may select a lower leverage $$L$$ for a market, which tightens the rate to $$1/L$$. The two are combined with a maximum so that a leverage selection can never reserve less margin than the market minimum: a selection above $$L\_{max}$$ would imply a rate below the floor and is clamped back to $$r\_{im}$$ (validation rejects such values before storage; the clamp is defense in depth). An unset or zero leverage falls back to the market rate.

$$
r\_{eff} = \max!\left(\frac{1}{L},; r\_{im}\right) \tag{M.1}
$$

### Position margin requirements

The margin reserved to open exposure is linear in size, price, and rate. The rate is the market default $$r\_{im}$$ or, under per-position leverage, the effective rate from (M.1). The implementation computes the product exactly in decimal arithmetic and rounds the result away from zero at 28 decimal places, so the Exchange always requires at least the exact amount, never less.

$$
M\_{init} = q \cdot P \cdot r\_{im} \tag{M.2}
$$

Once open, a position must maintain a smaller cushion computed with the market's maintenance rate $$r\_{mm}$$. Because the implementation enforces $$r\_{mm} < r\_{im}$$ at parameter parse time, maintenance margin is strictly below initial margin for any positive size and price ((M.2)), giving every position a buffer between opening and liquidation thresholds. The same round-up (away from zero, 28 decimal places) applies.

$$
M\_{maint} = q \cdot P \cdot r\_{mm} \tag{M.3}
$$

### Pre-trade exposure

When an order of signed size $$\delta$$ arrives against a resting signed position $$s\_0$$ (positive long, negative short), the Exchange charges margin only for exposure the order *adds*. If the order flips the position through zero, the entire new side is fresh exposure; otherwise only the growth in position magnitude counts, clamped at zero so that pure reductions and closes add nothing. The result is always non-negative and is scaled by the initial margin rate via (M.2) to obtain the pre-trade margin charge; risk-reducing orders therefore charge zero, with the margin they free recognized after the fill rather than pre-credited.

$$
\Delta q = \begin{cases} |s\_0 + \delta| & \text{if } s\_0(s\_0+\delta) < 0 \quad \text{(flip)} \ \max!\big(|s\_0 + \delta| - |s\_0|,; 0\big) & \text{otherwise} \end{cases} \tag{M.4}
$$

### Account-level aggregates

Account equity is collateral plus the sum of unrealized profit and loss across all open positions. It is the account's liquidation-relevant net worth: gains on one position directly offset losses or margin requirements on another under cross margin.

$$
E = C + \Pi \tag{M.5}
$$

The margin ratio normalizes equity ((M.5)) by total position notional, where each position's notional is its size times the current mark price (falling back to entry price when no mark is available). It is undefined — the implementation returns no value — when the account has no open positions or when total notional is zero.

$$
\rho = \frac{E}{N} = \frac{C + \Pi}{\sum\_i q\_i , m\_i} \tag{M.6}
$$

Available margin is equity ((M.5)) minus the initial margin held for every existing position. A position stamped with an allocated margin at fill time (from the account's selected leverage) is held at exactly that amount; otherwise the market rate applies against the current mark per (M.2). The result can be negative, and can also exceed collateral when unrealized gains outweigh margin held — cross-margin offsetting is intentional.

$$
M\_{avail} = E - M\_{used} = (C + \Pi) - \sum\_i M\_{init,i} \tag{M.7}
$$

### Order admission

A new or increasing order is admitted when available margin ((M.7)) is at least the initial margin its quantity requires at the effective rate ((M.2)); equality passes — the comparison is greater-than-or-equal. Reduce-only orders skip the check entirely, since reducing a position releases margin rather than consuming it. On rejection the Exchange surfaces both the required and available amounts.

$$
H = M\_{avail} - q \cdot P \cdot r\_{im} ;; \geq ; 0 \iff \text{order admitted} \tag{M.8}
$$

Inverting (M.2) gives the largest position collateral can support at the mark price and market rate. The exact quotient is floored to an integer number of lots (truncation toward zero), so an account never receives a rounded-up fractional lot: sub-lot collateral yields a hard zero.

$$
q\_{max} = \left\lfloor \frac{C}{P \cdot r\_{im} \cdot \ell} \right\rfloor \cdot \ell \tag{M.9}
$$

### Portfolio aggregates

For cross-margined accounts, portfolio equity recomputes each position's unrealized PnL from fresh mark prices rather than trusting a cached value: signed size times mark-minus-entry, less the accumulated funding integral (positive $$\varphi$$ means funding paid, reducing equity). Isolated-mode positions are excluded — their margin is tracked per position. The formula below shows one position's contribution; the implementation sums over all cross positions.

$$
E\_{pf} = C + \sum\_i \Big( s\_i ,(m\_i - e\_i) - \varphi\_i \Big) \tag{M.10}
$$

The portfolio's initial margin sums (M.2) at the current mark across cross-mode positions. Positions stamped with an allocated margin contribute that amount instead of the market-rate computation, positions in markets with missing parameters are defensively skipped, and a missing mark price falls back to the entry price. The evaluable form shows one unstamped position's term.

$$
M\_{init}^{pf} = \sum\_i q\_i , m\_i , r\_{im,i} \tag{M.11}
$$

The maintenance analogue of (M.11): (M.3) summed across cross-mode positions at fresh marks, with the same entry-price fallback and missing-parameter skip. Because $$r\_{mm} < r\_{im}$$ per market, the portfolio maintenance total never exceeds the portfolio initial total on the same positions.

$$
M\_{maint}^{pf} = \sum\_i q\_i , m\_i , r\_{mm,i} \tag{M.12}
$$

## Invariants

* $$r\_{mm} < r\_{im}$$ for every market, enforced at parameter parse time; hence $$M\_{maint} < M\_{init}$$ ((M.3), (M.2)) for any positive size and price, and the same ordering holds for the portfolio sums.
* Required margin never rounds down: (M.2) and (M.3) round away from zero at 28 decimal places, so the stored requirement is always $$\geq$$ the exact real value.
* The effective initial margin rate is floored at the market rate: $$r\_{eff} \geq r\_{im}$$ ((M.1)), so per-position leverage can only tighten requirements.
* $$\Delta q \geq 0$$ always ((M.4)); pure reductions and exact closes yield exactly $$0$$, and a flip charges only the new side, never the full order size.
* Margin is monotone: $$M\_{init}$$ strictly increases in both size and price for fixed positive parameters.
* (M.9) always returns an integer multiple of the lot size, and collateral below one lot's requirement yields exactly zero — never a rounded-up position.
* Reduce-only orders unconditionally pass the margin check; the admission boundary in (M.8) is inclusive (available equal to required is admitted).
* Isolated-mode positions are excluded from every portfolio aggregate ((M.10), (M.11), (M.12)); their margin is checked per position.

## Worked example

Consider a BTC market with $$r\_{im} = 0.05$$ (20x maximum leverage), $$r\_{mm} = 0.025$$, and lot size $$\ell = 0.001$$, with the mark at $$P = 50{,}000$$. An account deposits $$C = 10{,}000$$ of collateral and submits a buy for $$q = 1$$ BTC with no leverage override. By (M.2) the order requires $$1 \times 50{,}000 \times 0.05 = 2{,}500$$ of initial margin. With no open positions, equity equals collateral ((M.5)), available margin is $$10{,}000$$ ((M.7)), and the headroom in (M.8) is $$10{,}000 - 2{,}500 = 7{,}500 \geq 0$$, so the order is admitted. Had the account instead selected 10x leverage, (M.1) gives $$r\_{eff} = \max(1/10, 0.05) = 0.10$$ and the requirement doubles to $$5{,}000$$.

After the fill the account is long $$s\_0 = +1$$ BTC at entry $$50{,}000$$. Its margin ratio ((M.6)) is $$\rho = 10{,}000 / 50{,}000 = 0.20$$, comfortably above the maintenance rate. The largest position this collateral could have supported is given by (M.9): the exact quotient $$10{,}000 / (50{,}000 \times 0.05) = 4$$ BTC is already a clean lot multiple, so $$q\_{max} = 4$$. With only $$2{,}750.50$$ of collateral the quotient would be $$1.1002$$ BTC, floored to $$1.100$$ — the fractional $$0.0002$$ above the lot grid is discarded, never rounded up.

Now suppose the account sells $$3$$ BTC against its $$+1$$ BTC position. By (M.4) the trade flips through zero: $$s\_0 + \delta = 1 - 3 = -2$$, so the added exposure is $$|{-2}| = 2$$ BTC — the new short side only, not the full 3 BTC order. The pre-trade charge is therefore $$2 \times 50{,}000 \times 0.05 = 5{,}000$$ via (M.2). A sell of at most $$1$$ BTC would have added zero exposure and, if flagged reduce-only, would bypass the margin check entirely.

## Analysis

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression                      | Input                     | Elasticity ε |
| ------------------------------- | ------------------------- | ------------ |
| `effective_initial_margin_rate` | leverage                  | -1           |
| `effective_initial_margin_rate` | initial\_margin\_rate     | 0            |
| `initial_margin_required`       | size                      | 1            |
| `initial_margin_required`       | price                     | 1            |
| `initial_margin_required`       | initial\_margin\_rate     | 1            |
| `maintenance_margin_required`   | size                      | 1            |
| `maintenance_margin_required`   | price                     | 1            |
| `maintenance_margin_required`   | maintenance\_margin\_rate | 1            |
| `added_exposure`                | order\_signed             | 1.5          |
| `added_exposure`                | existing\_signed          | -0.5         |
| `equity`                        | total\_unrealized\_pnl    | 0.8          |
| `equity`                        | collateral                | 0.2          |
| `margin_ratio`                  | collateral                | 1            |
| `margin_ratio`                  | total\_notional           | -1           |
| `margin_ratio`                  | total\_unrealized\_pnl    | 0            |
| `available_margin`              | total\_unrealized\_pnl    | 1.053        |
| `available_margin`              | total\_initial\_margin    | -0.3158      |
| `available_margin`              | collateral                | 0.2632       |
| `order_margin_headroom`         | available\_margin         | 1.333        |
| `order_margin_headroom`         | size                      | -0.3333      |
| `order_margin_headroom`         | price                     | -0.3333      |
| `order_margin_headroom`         | initial\_margin\_rate     | -0.3333      |
| `max_position_size`             | collateral                | 125          |
| `max_position_size`             | price                     | -125         |
| `max_position_size`             | initial\_margin\_rate     | -6.25        |
| `max_position_size`             | lot\_size                 | 0            |
| `portfolio_equity`              | mark                      | 2.4          |
| `portfolio_equity`              | entry\_price              | -1.6         |
| `portfolio_equity`              | signed\_size              | 0.8          |
| `portfolio_equity`              | collateral                | 0.2          |
| `portfolio_equity`              | funding\_integral         | 0            |
| `portfolio_initial_margin`      | size                      | 1            |
| `portfolio_initial_margin`      | mark                      | 1            |
| `portfolio_initial_margin`      | initial\_margin\_rate     | 1            |
| `portfolio_maintenance_margin`  | size                      | 1            |
| `portfolio_maintenance_margin`  | mark                      | 1            |
| `portfolio_maintenance_margin`  | maintenance\_margin\_rate | 1            |

![Sensitivity tornado — Initial margin required](/files/JGGTcbOONtqaBNanIrmp)

![Sensitivity tornado — Added exposure](/files/O3GFF452mWU2tNjEXskQ)

![Sensitivity tornado — Maximum position size](/files/EYQi35NWa5QnEVfoYAVW)

### Response curves

![Required initial margin grows linearly in price, with lower leverage (a higher effective rate) reserving proportionally more; position size is held at 1 unit.](/files/g9PmW80VidySGXTa3q9l)

*Required initial margin grows linearly in price, with lower leverage (a higher effective rate) reserving proportionally more; position size is held at 1 unit.*

![Against a +1 long, sells up to the position size add zero exposure, larger sells charge only the new short side, and buys charge the full increment; the existing position is held at +1.](/files/bHAFtr24Rfu4TtX4rWnG)

*Against a +1 long, sells up to the position size add zero exposure, larger sells charge only the new short side, and buys charge the full increment; the existing position is held at +1.*

![Maximum openable size scales linearly with collateral but steps down to the lot grid, and collateral below one lot's requirement yields exactly zero; price and margin rate are held constant.](/files/779CEUviCPNA4iQqKL6R)

*Maximum openable size scales linearly with collateral but steps down to the lot grid, and collateral below one lot's requirement yields exactly zero; price and margin rate are held constant.*

### Parameter space

Joint parameter effects evaluated from the verified expressions over 2-D grids.

![The zero-headroom line available\_margin = size × 2,500 USDX separates accepted from rejected orders at 50,000 USDX price and 5% initial margin, with rejection depth growing linearly with size.](/files/nUSOT7J7KAT3ZK20ywyE)

*The zero-headroom line available\_margin = size × 2,500 USDX separates accepted from rejected orders at 50,000 USDX price and 5% initial margin, with rejection depth growing linearly with size.*

![Along the curve leverage = 1/initial\_margin\_rate the binding constraint switches from the trader's chosen leverage to the market floor, so higher leverage requests stop reducing margin once they cross it.](/files/apVJg8H4WdTyr72k48Q7)

*Along the curve leverage = 1/initial\_margin\_rate the binding constraint switches from the trader's chosen leverage to the market floor, so higher leverage requests stop reducing margin once they cross it.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [funding-rate](/math-engine/funding-rate)
* Sibling model: [insurance-fund](/math-engine/insurance-fund)
* Sibling model: [liquidation-engine](/math-engine/liquidation-engine)
* Sibling model: [oracle](/math-engine/oracle)
* Sibling model: [order-book](/math-engine/order-book)
* Sibling model: [position-tracker](/math-engine/position-tracker)
* Sibling model: [settlement](/math-engine/settlement)


# Liquidation Engine

When a leveraged account's equity falls to or below the maintenance margin required by its open positions, the Exchange liquidates: it generates reduce-only IOC orders that close positions at prices anchored to the account's bankruptcy point. This document derives the exact mathematics implemented by the liquidation engine. Equity is always recomputed from fresh mark prices $$m$$ rather than cached values, with a non-positive mark treated as missing and replaced by the entry price so an un-oracled market can never fabricate phantom losses. The trigger threshold is inclusive: an account exactly at maintenance liquidates. For cross-margin accounts underwater in several markets at once, the single collateral pool $$C$$ is allocated across markets in proportion to each market's loss, so the pool offsets the portfolio shortfall exactly once. All arithmetic is exact decimal arithmetic; rounding enters only where quantities are floored to the lot size and prices are aligned to the tick.

![One trigger inequality fans out into two independent per-position computations — a price (bankruptcy, tick-aligned) and a quantity (safe-size, partial-or-full) — that reconverge at the forced close, whose realized outcome feeds equity back into the trigger it came from.](/files/0yObb5920KoDR5ksHraJ)

*One trigger inequality fans out into two independent per-position computations — a price (bankruptcy, tick-aligned) and a quantity (safe-size, partial-or-full) — that reconverge at the forced close, whose realized outcome feeds equity back into the trigger it came from.*

## Setting

| Symbol          | Name                      | Description                                                                                                                                                  | Units                | Domain    |
| --------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | --------- |
| $$C$$           | account\_collateral       | Collateral backing the positions being evaluated: the account's shared pool for cross margin, or the position's allocated cushion for isolated margin.       | USDX                 | \[0, ∞)   |
| $$p\_e$$        | entry\_price              | Volume-weighted entry price of the position.                                                                                                                 | USDX per contract    | (0, ∞)    |
| $$m$$           | mark\_price               | Current oracle mark price. A non-positive stored mark means 'no oracle update yet' and is replaced by the entry price before any equity or loss computation. | USDX per contract    | (0, ∞)    |
| $$q$$           | size                      | Position size (always positive; direction is carried separately).                                                                                            | contracts            | (0, ∞)    |
| $$d$$           | direction                 | Position direction: $$+1$$ for long, $$-1$$ for short.                                                                                                       | dimensionless        | \[-1, 1]  |
| $$r\_m$$        | maintenance\_margin\_rate | Maintenance margin rate of the market (2.5% in the reference test parameters).                                                                               | fraction of notional | (0, 1]    |
| $$r\_i$$        | initial\_margin\_rate     | Initial margin rate of the market (5% in the reference test parameters).                                                                                     | fraction of notional | (0, 1]    |
| $$\delta$$      | tick\_size                | Minimum price increment of the market. A non-positive tick disables alignment entirely (the raw price passes through).                                       | USDX                 | (0, ∞)    |
| $$\lambda$$     | lot\_size                 | Minimum quantity increment of the market.                                                                                                                    | contracts            | (0, ∞)    |
| $$P$$           | price                     | A raw (unaligned) candidate limit price, e.g. an unrounded bankruptcy price, prior to tick alignment.                                                        | USDX                 | unbounded |
| $$\ell\_i$$     | loss\_i                   | This market's loss at mark: $$\max(0, -\text{uPnL}\_i)$$, using the entry-price fallback when the mark is unset.                                             | USDX                 | \[0, ∞)   |
| $$\mathcal{L}$$ | total\_loss               | Sum of losses across all positions being liquidated together.                                                                                                | USDX                 | (0, ∞)    |
| $$\pi$$         | pnl\_percent              | ADL candidate's unrealized PnL as a fraction of position value.                                                                                              | fraction             | unbounded |
| $$\Lambda$$     | leverage                  | ADL candidate's effective leverage.                                                                                                                          | dimensionless        | (0, ∞)    |

## The mechanism

### The Liquidation Trigger

Equity is never computed from a cached PnL field, which may be stale between mark updates. Instead the engine recomputes unrealized PnL directly from the current mark price. For a long ($$d=+1$$) the PnL is the mark's excess over entry scaled by size; for a short ($$d=-1$$) the sign flips. If the stored mark for a market is zero or negative — the stateless-boot sentinel for 'no oracle push yet' — the entry price is substituted, making the position's fresh PnL exactly zero rather than a catastrophic phantom loss.

$$
\text{uPnL} = d,(m - p\_e),q \tag{L.1}
$$

Equity is collateral plus the sum of fresh unrealized PnL ((L.1)) over all open positions. For a cross-margin account $$C$$ is the shared account collateral; for an isolated position $$C$$ is that position's own allocated margin ((L.5)) and the sum runs over that single position only.

$$
E = C + \sum\_{i} d\_i,(m\_i - p\_{e,i}),q\_i \tag{L.2}
$$

Each position requires maintenance margin proportional to its notional at the current mark, $$q,m,r\_m$$, computed by the margin primitive (see [(M.3)](/math-engine/margin-math) where published). The account-level requirement is the sum over positions, with the same entry-price fallback for unset marks.

$$
M = q,m,r\_m \tag{L.3}
$$

An account liquidates exactly when its equity ((L.2)) is at or below its total maintenance requirement ((L.3)). The threshold is inclusive: equity exactly equal to maintenance triggers liquidation. An account with no open positions never liquidates. The isolated variant is the single-position specialization with $$C$$ replaced by the position's allocated margin ((L.5)), evaluated against that position alone — sibling positions and account-level collateral never enter the isolated decision.

$$
\text{liquidate} \iff E \le \sum\_i M\_i \tag{L.4}
$$

### Isolated Positions

An isolated position is backed by the margin allocated to it at fill time. When no allocation was recorded, the engine falls back to the open-time initial margin at market rate — the same cushion the position margins against downstream. The trigger ((L.4)) and the liquidation pricing both use this single value, computed once per trigger, as the position's collateral.

$$
C\_{\text{iso}} = \begin{cases} A & \text{if allocated margin } A \text{ is recorded} \ q,p\_e,r\_i & \text{otherwise} \end{cases} \tag{L.5}
$$

### Cross-Margin Collateral Allocation

A cross-margin account holds one collateral pool backing positions in many markets. When several markets liquidate together, cushioning each market with the full pool would offset the portfolio's loss $$N$$ times, setting bankruptcy prices too far from entry and understating bad debt by $$(N-1),C$$. Instead each market receives a share proportional to its loss $$\ell\_i = \max(0, -\text{uPnL}\_i)$$; flat and winning positions receive nothing. If no position is at a loss (a degenerate case, since such a portfolio would not be liquidating) the pool splits evenly, $$C/N$$. The exact-decimal rounding remainder $$C - \sum\_i s\_i$$ is folded into the largest-loss position (ties break to the highest market identifier after sorting by market, making the placement replay-deterministic), so the shares always sum to exactly $$C$$. A set of zero or one positions receives the whole pool unchanged, so isolated and single-market paths are byte-identical to the pre-allocation behavior.

$$
s\_i = C,\frac{\ell\_i}{\mathcal{L}}, \qquad \mathcal{L} = \sum\_j \ell\_j \tag{L.6}
$$

### Liquidation Order Pricing

The bankruptcy price is the mark at which the position's loss exactly exhausts its collateral share: equity from this position alone reaches zero. For a long it sits below entry by $$C/q$$; for a short, above. This is the accounting anchor for the liquidation order's limit price, and the book enforces it as a matching bound: liquidation orders are limit-type IOC, so the primary order fills at bankruptcy-or-better only, and a fill better than the bound produces insurance-fund spread profit. Fills worse than the bound come only from the bounded backstop sweep, and surface as bad debt.

$$
p\_b = p\_e - d,\frac{C}{q} \tag{L.7}
$$

Bankruptcy prices ((L.7)) are unrounded ratios, and the book rejects limits not aligned to the market tick. The engine rounds toward executability — a sell (closing a long) rounds down — conceding at most one tick beyond the bankruptcy bound. The result is clamped to at least one tick so the limit stays strictly positive; a non-positive tick size disables alignment and passes the raw price through.

$$
P^{\downarrow} = \max!\left(\left\lfloor \frac{P}{\delta} \right\rfloor \delta,; \delta\right) \tag{L.8}
$$

Symmetrically, a buy (closing a short) rounds up to the tick grid, so the worst-acceptable price stays acceptable. The same one-tick positivity clamp applies, though for a buy it can only bind when the raw price is non-positive.

$$
P^{\uparrow} = \max!\left(\left\lceil \frac{P}{\delta} \right\rceil \delta,; \delta\right) \tag{L.9}
$$

### Liquidation Sizing

The sizing rule is selected by the market's liquidation mode. In Full mode (the current default) the entire position closes. In Partial mode the engine computes the largest size the collateral can support at $$1.5\times$$ the initial margin rate — a buffer above initial margin so the surviving position is comfortably healthy — and floors it to the lot grid. Rounding is downward by design: rounding up would leave the position too large.

$$
q\_{\text{safe}} = \left\lfloor \frac{C}{1.5, m, r\_i,\lambda} \right\rfloor \lambda \tag{L.10}
$$

In Partial mode the order closes just the excess above the safe size ((L.10)); if the safe size already meets or exceeds the position, or the denominator is non-positive, the full position closes. In Full mode the quantity is simply $$q$$. A non-positive resulting quantity suppresses the order entirely.

$$
q\_{\text{liq}} = \begin{cases} q & q\_{\text{safe}} \ge q ;\text{or}; P,r\_i,1.5 \le 0 \ q - q\_{\text{safe}} & \text{otherwise} \end{cases} \tag{L.11}
$$

### Auto-Deleveraging

When liquidation cannot fully absorb a bankrupt position, profitable counterparties are auto-deleveraged in priority order. The score multiplies profitability by leverage, so the most profitable, most leveraged accounts are selected first. Candidates are ranked descending by score; exact ties break ascending on account identifier bytes, which makes the ordering fully deterministic under replay. The amount a counterparty can absorb in settlement is capped at its unrealized PnL — the profit it gives up to cover bad debt.

$$
S = \pi \cdot \Lambda \tag{L.12}
$$

### Position Takeover

On markets where the takeover model is enabled (rolled out per risk class), a liquidation does not close the position against the book inside the liquidation call. Instead, the position transfers **atomically, in whole, to a per-market system liquidation account at the bankruptcy price**, and the venue's liquidation engine unwinds it through the normal order book over the following ticks — one bankruptcy-bounded IOC attempt per tick, with a bounded depth sweep for thin books.

Three things are fixed at the moment of transfer, and nothing that happens afterwards changes them:

1. **Your settlement is final at transfer.** Your post-liquidation equity is set by the bankruptcy contract at the moment the position transfers, irrespective of how the unwind goes afterwards.
2. **Improvement goes to the insurance fund.** If the engine unwinds the position at better than the bankruptcy price, the improvement is added to the insurance fund — it is not returned to the liquidated account.
3. **Deterioration never comes back to you.** If the unwind does worse than the bankruptcy price, you owe nothing more. The shortfall is absorbed by the insurance fund up to a per-takeover budget fixed when the takeover opened; past that budget the remaining inventory is closed by auto-deleveraging against opposing positions. If no opposing positions remain to close against, that inventory stays on the system account and the market halts rather than the position being written off. Either way, any residual cash deficit is recorded as an explicit, auditable system loss with the market halted — never socialized onto other accounts' balances and never clawed back from winners.

The unwind pays no fee, earns no rebate, and gets no queue priority — the system account's orders route through the same book as everyone else's.

## Invariants

* The trigger threshold is inclusive: $$E \le \sum\_i M\_i$$ liquidates, so an account exactly at maintenance is liquidated ((L.4)).
* An account with no open positions is never liquidated, and no liquidation order is ever generated against a market whose mark is unset (non-positive) — such marks fall back to the entry price for equity purposes and suppress order generation entirely.
* For a long, $$p\_b < p\_{\text{liq}} < m$$ at a healthy mark; for a short the inequalities reverse — the bankruptcy price always sits on the far side of the liquidation price ((L.7)).
* Cross-collateral shares conserve the pool exactly: $$\sum\_i s\_i = C$$, with the rounding remainder folded deterministically into the largest-loss market ((L.6)).
* Isolated liquidation depends only on the position's own allocated margin — account collateral and sibling positions never affect the isolated decision ((L.5)).
* Liquidation orders are always reduce-only, limit-type, IOC, flagged as liquidations, sized at most the position size ($$q\_{\text{liq}} \le q$$), and sided opposite the position (long → sell, short → buy).
* The position size is the only bound on a liquidation order's quantity. In Full mode the whole position closes as a single order, which may exceed the market's per-order cap `max_order_size` — that cap bounds order entry, and liquidations are exempt from it, so a position larger than the cap is still closed in one attempt rather than being unliquidatable. Slippage is bounded by price instead: the bankruptcy limit on the primary order, and the sweep depth on the backstop.
* Aligned limit prices are strictly positive and within one tick of the raw bankruptcy price, on the executable side: $$P - \delta < P^{\downarrow} \le P$$ for sells and $$P \le P^{\uparrow} < P + \delta$$ for buys, subject to the one-tick floor ((L.8), (L.9)).
* ADL ranking is a permutation of its input (no drops, no duplicates) and is deterministic across replays, via the descending-score, ascending-account-id ordering ((L.12)).

## Worked example

Consider a 1-contract long opened at $$p\_e = 50{,}000$$ with collateral $$C = 2{,}500$$ (5% initial margin), in a market with $$r\_m = 0.025$$, $$r\_i = 0.05$$, tick $$\delta = 0.5$$. At entry the position is healthy: equity is $$2{,}500$$ against maintenance of $$50{,}000 \times 0.025 = 1{,}250$$ ((L.3)). Now the mark drops to $$m = 48{,}000$$. Fresh PnL is $$(48{,}000 - 50{,}000) \times 1 = -2{,}000$$ ((L.1)), so equity is $$2{,}500 - 2{,}000 = 500$$ ((L.2)), while maintenance rises against the new notional to $$48{,}000 \times 0.025 = 1{,}200$$. Since $$500 \le 1{,}200$$, the trigger fires ((L.4)).

With a single position the collateral share is the whole pool ((L.6)), so the bankruptcy price is $$50{,}000 - 2{,}500 / 1 = 47{,}500$$ ((L.7)). That value is already a multiple of the $$0.5$$ tick, so sell-side alignment leaves it unchanged ((L.8)), and the engine emits a reduce-only IOC sell for the full 1 contract (Full mode) with limit price $$47{,}500$$.

In Partial mode with a larger book — a 5-contract long at the same entry, collateral $$12{,}500$$, mark at $$50{,}000$$ — the safe size is $$12{,}500 / (50{,}000 \times 0.05 \times 1.5) = 3.3\overline{3}$$, floored to the $$0.001$$ lot grid to $$3.333$$ contracts ((L.10)). The liquidation order therefore closes $$5 - 3.333 = 1.667$$ contracts ((L.11)), leaving the survivor margined at one and a half times the initial requirement.

## Analysis

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression                 | Input                     | Elasticity ε |
| -------------------------- | ------------------------- | ------------ |
| `fresh_unrealized_pnl`     | entry\_price              | 25           |
| `fresh_unrealized_pnl`     | mark\_price               | -24          |
| `fresh_unrealized_pnl`     | direction                 | 1            |
| `fresh_unrealized_pnl`     | size                      | 1            |
| `account_equity`           | entry\_price              | -100         |
| `account_equity`           | mark\_price               | 96           |
| `account_equity`           | account\_collateral       | 5            |
| `account_equity`           | direction                 | -4           |
| `account_equity`           | size                      | -4           |
| `maintenance_margin`       | size                      | 1            |
| `maintenance_margin`       | mark\_price               | 1            |
| `maintenance_margin`       | maintenance\_margin\_rate | 1            |
| `liquidation_trigger`      | account\_collateral       | -5e+05       |
| `liquidation_trigger`      | direction                 | 5e+05        |
| `liquidation_trigger`      | mark\_price               | -5e+05       |
| `liquidation_trigger`      | entry\_price              | 5e+05        |
| `liquidation_trigger`      | size                      | 5e+05        |
| `liquidation_trigger`      | maintenance\_margin\_rate | 1.25e+04     |
| `isolated_margin_fallback` | size                      | 1            |
| `isolated_margin_fallback` | entry\_price              | 1            |
| `isolated_margin_fallback` | initial\_margin\_rate     | 1            |
| `cross_collateral_share`   | account\_collateral       | 1            |
| `cross_collateral_share`   | loss\_i                   | 1            |
| `cross_collateral_share`   | total\_loss               | -1           |
| `bankruptcy_price`         | entry\_price              | 1.053        |
| `bankruptcy_price`         | direction                 | -0.05263     |
| `bankruptcy_price`         | account\_collateral       | -0.05263     |
| `bankruptcy_price`         | size                      | 0.05263      |
| `aligned_price_sell`       | tick\_size                | 1            |
| `aligned_price_sell`       | price                     | 0            |
| `aligned_price_buy`        | tick\_size                | 1            |
| `aligned_price_buy`        | price                     | 0            |
| `safe_size`                | lot\_size                 | 0.09991      |
| `safe_size`                | account\_collateral       | 0            |
| `safe_size`                | mark\_price               | 0            |
| `safe_size`                | initial\_margin\_rate     | 0            |
| `partial_liquidation_qty`  | size                      | 2.999        |
| `partial_liquidation_qty`  | lot\_size                 | -0.1998      |
| `partial_liquidation_qty`  | account\_collateral       | 0            |
| `partial_liquidation_qty`  | mark\_price               | 0            |
| `partial_liquidation_qty`  | initial\_margin\_rate     | 0            |
| `adl_priority_score`       | pnl\_percent              | 1            |
| `adl_priority_score`       | leverage                  | 1            |

![Sensitivity tornado — Account equity](/files/txrGy0ONz2nNYFWHXBn3)

![Sensitivity tornado — Bankruptcy price](/files/JXS2fd5anwc3f3EAIHgY)

![Sensitivity tornado — Liquidation quantity](/files/859LtPb3MZ71oBO80hGh)

### Response curves

![Equity of a 1-contract long at a $50,000 entry as the mark falls, for three collateral levels; the shaded region marks where the thinnest account's equity is at or below maintenance margin (2.5% of notional at mark).](/files/axK2vp5JblOoOIgn1h8C)

*Equity of a 1-contract long at a $50,000 entry as the mark falls, for three collateral levels; the shaded region marks where the thinnest account's equity is at or below maintenance margin (2.5% of notional at mark).*

![The liquidation order's price anchor falls linearly away from the $50,000 entry as the collateral backing the position grows, with slope inversely proportional to position size.](/files/E6YwkPuJnhGXgDwIborR)

*The liquidation order's price anchor falls linearly away from the $50,000 entry as the collateral backing the position grows, with slope inversely proportional to position size.*

![How much of a 5-contract position Partial mode closes as collateral varies, holding the mark at 50,000; beyond 18,750 the safe size covers the whole position and the close quantity reaches zero.](/files/vscQHMO78IcPhOfa1fnb)

*How much of a 5-contract position Partial mode closes as collateral varies, holding the mark at* $$50,000; beyond$$*18,750 the safe size covers the whole position and the close quantity reaches zero.*

### Parameter space

Joint parameter effects evaluated from the verified expressions over 2-D grids.

![The liquidation boundary is a straight line in mark-collateral space whose slope reflects the size-weighted price sensitivity, holding a 1-contract long from 50,000 entry at a 2.5% maintenance rate.](/files/ZTqGJZSLq6RktmsYT1t6)

*The liquidation boundary is a straight line in mark-collateral space whose slope reflects the size-weighted price sensitivity, holding a 1-contract long from 50,000 entry at a 2.5% maintenance rate.*

![Higher maintenance rates pull the liquidation price toward entry, showing how risk-parameter tightening shrinks the safe zone for a 1-contract long with 2,500 USDX collateral.](/files/bDJS9BdxGWvRcu8dYqiE)

*Higher maintenance rates pull the liquidation price toward entry, showing how risk-parameter tightening shrinks the safe zone for a 1-contract long with 2,500 USDX collateral.*

![The liquidated quantity falls in lot-quantized steps as collateral grows, and the full-liquidation cliff shifts outward with mark price since the safe size scales inversely with notional, for a 5-contract position at 5% initial margin.](/files/YyiQCNPBcktuR9z2nwNN)

*The liquidated quantity falls in lot-quantized steps as collateral grows, and the full-liquidation cliff shifts outward with mark price since the safe size scales inversely with notional, for a 5-contract position at 5% initial margin.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [funding-rate](/math-engine/funding-rate)
* Sibling model: [insurance-fund](/math-engine/insurance-fund)
* Sibling model: [margin-math](/math-engine/margin-math)
* Sibling model: [oracle](/math-engine/oracle)
* Sibling model: [order-book](/math-engine/order-book)
* Sibling model: [position-tracker](/math-engine/position-tracker)
* Sibling model: [settlement](/math-engine/settlement)


# Oracle

Every perpetual market on the Exchange marks positions against a price that must be resistant to both fat-finger errors and deliberate manipulation. The oracle validator accepts a new print only if it stays within a per-step deviation bound of the current anchor $$P\_{oracle}$$ and, once ten prints have accumulated, within a wider path bound of the oldest print in the window. When the anchor is stale, a large move is never trusted on a single print: it must be confirmed by $$k$$ consecutive mutually-consistent prints, with a per-step bound that widens only after an escalation trigger fires.

The mark price $$P\_{mark}$$ that drives margin and liquidation is not the raw oracle price: it blends the trusted oracle anchor with a robust trade reference — the volume-weighted median of the last five trades — so that neither a single anomalous oracle tick nor a lone wash trade can move the mark across a liquidation threshold.

![Every print must clear both deviation bounds (a conjunctive fan-in on the anchor), while staleness opens a widening re-anchor loop whose confirmation threshold relaxes under escalation.](/files/6MYF6gqEszokSeBqgtBj)

*Every print must clear both deviation bounds (a conjunctive fan-in on the anchor), while staleness opens a widening re-anchor loop whose confirmation threshold relaxes under escalation.*

## Setting

| Symbol          | Name                       | Description                                                                                                                                                                                                                                                                                     | Units                  | Domain    |
| --------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | --------- |
| $$P\_{oracle}$$ | oracle\_price              | The current trusted oracle anchor price for the market. Strictly positive whenever the market has bootstrapped; a zero value denotes a brand-new market with no prior print.                                                                                                                    | USDX per unit of asset | (0, ∞)    |
| $$P\_{new}$$    | new\_price                 | The incoming oracle print being validated.                                                                                                                                                                                                                                                      | USDX per unit of asset | (0, ∞)    |
| $$P\_{old}$$    | oldest\_price              | The oldest accepted print in the rolling ten-entry update history, used by the path-manipulation check.                                                                                                                                                                                         | USDX per unit of asset | (0, ∞)    |
| $$\theta$$      | deviation\_threshold       | Per-market single-step deviation threshold (oracle\_deviation\_threshold); a print deviating from the anchor by strictly more than this is rejected.                                                                                                                                            | dimensionless fraction | (0, 1]    |
| $$N\_h$$        | history\_size              | Fixed size of the rolling price-update window used for path-manipulation detection (HISTORY\_SIZE = 10). The path check is armed only when the window is full.                                                                                                                                  | prints                 | \[10, 10] |
| $$P\_{trade}$$  | trade\_ref                 | Robust trade reference for the mark blend: the volume-weighted median price of the last five recorded trades, falling back to the last trade price when the window is empty. Weighting by traded size means moving the reference requires washing a majority of traded volume, not trade count. | USDX per unit of asset | (0, ∞)    |
| $$w$$           | oracle\_weight             | Per-market oracle weight in the mark blend (MarketParams.oracle\_mark\_weight), a unit-interval value defaulting to 0.95 (oracle-dominant).                                                                                                                                                     | dimensionless fraction | \[0, 1]   |
| $$P\_{mark}$$   | mark\_price                | The Exchange's mark price for the market: the oracle-weighted blend of the trusted anchor and the trade reference. Equals the oracle price when no trade has ever been recorded.                                                                                                                | USDX per unit of asset | (0, ∞)    |
| $$t\_{now}$$    | current\_time              | Timestamp of the incoming print or the staleness evaluation, in Unix milliseconds.                                                                                                                                                                                                              | milliseconds           | \[0, ∞)   |
| $$t\_{last}$$   | last\_update\_time         | Timestamp of the last trusted oracle update, in Unix milliseconds. Not advanced by pending (un-trusted) re-anchor prints.                                                                                                                                                                       | milliseconds           | \[0, ∞)   |
| $$\tau\_s$$     | staleness\_seconds         | Per-market staleness threshold (oracle\_staleness\_seconds); the anchor is stale when strictly more than this many seconds have elapsed since the last trusted update.                                                                                                                          | seconds                | (0, ∞)    |
| $$P\_{cand}$$   | candidate\_price           | The provisional re-anchor candidate: the running price level being confirmed while the anchor is stale. Not trusted and never used as the mark while pending.                                                                                                                                   | USDX per unit of asset | (0, ∞)    |
| $$\theta\_r$$   | reanchor\_max\_deviation   | Per-step consistency bound for re-anchor confirmations (oracle\_reanchor\_max\_deviation): a confirming print must land within this fraction of the running candidate or the confirmation counter restarts.                                                                                     | dimensionless fraction | (0, 1]    |
| $$\theta\_e$$   | escalation\_max\_deviation | Widened per-step bound (oracle\_reanchor\_escalation\_max\_deviation) applied once the escalation trigger has fired, letting a sustained-but-volatile legitimate correction accumulate confirmations. Finite: a step beyond it still restarts the counter.                                      | dimensionless fraction | (0, 1]    |
| $$k$$           | required\_confirmations    | Number of consecutive mutually-consistent prints required to trust a large move off a stale anchor (oracle\_reanchor\_confirmations). Floored at 2 in code so a misconfigured value of 1 cannot re-open the single-print bypass.                                                                | prints                 | \[2, ∞)   |
| $$n\_p$$        | pending\_prints            | Total prints observed during the current pending re-anchor sequence. Unlike the confirmation counter it never resets on an inconsistent step; it measures how long the market has been wedged.                                                                                                  | prints                 | \[0, ∞)   |
| $$N\_e$$        | escalation\_prints         | Print-count arm of the escalation trigger (oracle\_reanchor\_escalation\_prints).                                                                                                                                                                                                               | prints                 | \[1, ∞)   |
| $$t\_0$$        | pending\_since             | Timestamp (Unix milliseconds) of the print that opened the current pending re-anchor sequence; zero when not pending.                                                                                                                                                                           | milliseconds           | \[0, ∞)   |
| $$\tau\_e$$     | escalation\_seconds        | Wall-clock arm of the escalation trigger (oracle\_reanchor\_escalation\_seconds), measured against the feed-supplied update time.                                                                                                                                                               | seconds                | (0, ∞)    |

## The mechanism

### Deviation Guards

When the stored anchor is usable — positive and not stale — every incoming print is first measured against it as a relative deviation. The print is rejected when this deviation strictly exceeds the per-market threshold $$\theta$$ (the comparison is strict, so a move of exactly $$\theta$$ is accepted). This is the workhorse guard: a fat-finger print or a single-tick spike from the price producer is rejected here and never moves the anchor.

$$
\Delta\_{step} = \frac{\lvert P\_{new} - P\_{oracle} \rvert}{P\_{oracle}}, \qquad \text{accept only if } \Delta\_{step} \le \theta \tag{O.1}
$$

A manipulator who keeps each step under $$\theta$$ could still walk the price far over many prints. Once the rolling update history holds $$N\_h = 10$$ accepted prints, the validator also measures the incoming print against the oldest print in the window. This cumulative move is compared against the path threshold (O.3): a random walk over $$N$$ steps has expected deviation proportional to $$\sqrt{N}$$, while a directed walk grows proportionally to $$N$$, so scaling the threshold by $$\sqrt{N\_h}$$ separates the two regimes. The check is inactive while the window is refilling — after a re-anchor or on a brand-new market — a documented, accepted residual exposure.

$$
\Delta\_{path} = \frac{\lvert P\_{new} - P\_{old} \rvert}{P\_{old}} \tag{O.2}
$$

The path bound applied to (O.2) is the single-step threshold scaled by $$\sqrt{N\_h}$$. With the default $$\theta = 0.10$$ and $$N\_h = 10$$, the bound is $$0.10 \times \sqrt{10} \approx 0.3162$$: a cumulative move of more than about $$31.6%$$ across the window is rejected even though every individual step passed the single-step guard. The implementation uses the fixed decimal constant $$\sqrt{10} = 3.16227766016838$$.

$$
\theta\_{path} = \theta \cdot \sqrt{N\_h} \tag{O.3}
$$

### Staleness and Re-anchoring

The anchor is stale when strictly more than $$\tau\_s$$ seconds have elapsed since the last trusted update. Timestamps are Unix milliseconds, so the threshold is converted by multiplying by 1000 (with saturating integer arithmetic in the implementation). A stale anchor may hold an arbitrarily wrong value, so the deviation guards (O.1) and (O.2) cannot be trusted against it — staleness routes the print into the re-anchor path instead.

$$
\mathrm{stale} = \mathbb{1}\left\[, t\_{now} - t\_{last} > 1000,\tau\_s ,\right] \tag{O.4}
$$

A large move off a stale anchor — one whose deviation from the last-known-good price exceeds $$\theta$$ — is never trusted on a single print. It only seeds a candidate $$P\_{cand}$$; the mark does not move and the market stays fail-closed. Each subsequent print is measured against the running candidate: if the step stays within the active per-step bound (base $$\theta\_r$$, or the widened $$\theta\_e$$ once (O.6) fires) the confirmation counter advances; a larger step makes the print the fresh candidate with the counter restarted at 1. Only after $$k$$ consecutive consistent prints (with $$k$$ floored at 2) is the level trusted, the anchor moved, and the bookkeeping cleared. A benign recovery — a post-staleness print within $$\theta$$ of the old anchor — skips confirmation entirely and is trusted immediately.

$$
\Delta\_{cand} = \frac{\lvert P\_{new} - P\_{cand} \rvert}{P\_{cand}}, \qquad \text{confirm if } \Delta\_{cand} \le \theta\_{r}\ (\text{or } \theta\_{e} \text{ when escalated}) \tag{O.5}
$$

A legitimate correction whose true price moves more than $$\theta\_r$$ per print would reset the confirmation counter on every step and wedge the market fail-closed forever. The escalation trigger fires once the pending sequence has accumulated $$N\_e$$ prints or once $$\tau\_e$$ seconds have elapsed since it opened, whichever crosses first — the print counter $$n\_p$$ never resets on an inconsistent step, so it measures how long the market has been wedged. When active, the per-step bound in (O.5) widens from $$\theta\_r$$ to $$\theta\_e$$; no single print is ever trusted, and the widened bound remains finite.

$$
\mathrm{escalated} = \mathbb{1}\left\[, n\_p \ge N\_e ;\lor; t\_{now} - t\_0 \ge 1000,\tau\_e ,\right] \tag{O.6}
$$

### Mark Price

The mark price used for margin and liquidation blends the trusted oracle anchor with a robust trade reference. Because $$w \in \[0,1]$$, the mark always lies between the oracle price and the trade reference — an anomalous-but-accepted oracle tick is damped by trades, and vice versa. The trade reference $$P\_{trade}$$ is the volume-weighted median of the last five recorded trades: sorting the window by price and walking cumulative traded size, the reference is the first price at which cumulative size strictly exceeds half the total (when a sample's cumulative size lands exactly on half, the straddling price and the next positive-size price are averaged). Moving this reference therefore requires washing a majority of traded volume, not merely a majority of trade count. When the window is empty the reference falls back to the last trade price, and when no trade has ever been recorded the mark is simply the oracle price. A confirmed re-anchor over a large jump clears the trade window so pre-gap trades cannot pull the fresh mark back toward the stale level.

$$
P\_{mark} = w \cdot P\_{oracle} + (1 - w) \cdot P\_{trade} \tag{O.7}
$$

## Invariants

* The trusted anchor is strictly positive after bootstrap: $$P\_{oracle} > 0$$, and validation divides only by positive prices.
* On the fresh-anchor path, every accepted print satisfies $$\Delta\_{step} \le \theta$$ ((O.1)); the comparison is strict, so a move of exactly $$\theta$$ is accepted.
* When the ten-print history is full, every accepted print also satisfies $$\Delta\_{path} \le \theta \sqrt{10}$$ ((O.2), (O.3)).
* Because $$w \in \[0,1]$$, the mark is always bounded: $$\min(P\_{oracle}, P\_{trade}) \le P\_{mark} \le \max(P\_{oracle}, P\_{trade})$$ ((O.7)).
* A pending re-anchor never moves the anchor or advances the trusted timestamp: $$P\_{oracle}$$ and $$t\_{last}$$ are unchanged until $$k$$ consecutive consistent confirmations accumulate ((O.5)).
* The required confirmation count is floored at 2 in code, so a single print can never promote a large move off a stale anchor.
* A single trade with a minority of the window's traded volume cannot move the volume-weighted median trade reference, and hence cannot move the mark by more than the blend permits.
* Escalation widens the per-step bound to $$\theta\_e$$ but never removes it: a step beyond $$\theta\_e$$ still restarts the confirmation counter ((O.6)).

## Worked example

Consider a market with anchor $$P\_{oracle} = 100{,}000$$ USDX, deviation threshold $$\theta = 0.10$$, staleness window $$\tau\_s = 30$$ s, and oracle weight $$w = 0.95$$. A print of $$P\_{new} = 105{,}000$$ arrives one second after the last update. The anchor is fresh, so (O.1) gives $$\Delta\_{step} = |105{,}000 - 100{,}000| / 100{,}000 = 0.05 \le 0.10$$: the print is trusted and becomes the new anchor. Had the print been $$115{,}000$$, the deviation of $$0.15$$ would strictly exceed $$\theta$$ and the print would be rejected outright. Even a sequence of near-threshold steps is bounded: once ten prints fill the history, (O.2) is checked against (O.3) $$= 0.10 \times \sqrt{10} \approx 0.3162$$, so a compounding $$9%$$-per-print walk is cut off at the eleventh print.

Now suppose four trades of size 1 execute at $$99{,}000$$, $$100{,}000$$, $$101{,}000$$, and $$102{,}000$$. The equal-size volume-weighted median lands exactly on half the total volume between the two central prices, so the trade reference averages them: $$P\_{trade} = 100{,}500$$. By (O.7), $$P\_{mark} = 0.95 \times 100{,}000 + 0.05 \times 100{,}500 = 100{,}025$$ USDX. A lone wash trade at $$50{,}000$$ appended to a window of genuine $$100{,}000$$ trades leaves the median — and therefore the mark — unmoved, whereas the old single-last-trade blend would have dropped the mark to $$97{,}500$$.

Finally, suppose the feed goes silent for two minutes, so (O.4) fires ($$120{,}000 - 0 > 30 \times 1000$$), and the next print is $$42{,}000$$ against a stale anchor of $$160.165$$. The move is far beyond $$\theta$$, so it only seeds a candidate: the anchor and mark stay at the old level and the market remains fail-closed. Each subsequent print within $$\theta\_r = 0.10$$ of the running candidate ((O.5)) advances the confirmation counter; after $$k = 3$$ consecutive consistent prints the level is trusted, the anchor moves, and the stale pre-gap trade window is cleared so the fresh mark is the pure re-anchored oracle price.

## Analysis

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression                | Input                | Elasticity ε |
| ------------------------- | -------------------- | ------------ |
| `single_step_deviation`   | new\_price           | 21           |
| `single_step_deviation`   | oracle\_price        | -21          |
| `path_deviation`          | new\_price           | 4.165        |
| `path_deviation`          | oldest\_price        | -4.165       |
| `path_threshold`          | deviation\_threshold | 1            |
| `is_stale`                | current\_time        | 0            |
| `is_stale`                | last\_update\_time   | 0            |
| `is_stale`                | staleness\_seconds   | 0            |
| `reanchor_step_deviation` | new\_price           | 1.111        |
| `reanchor_step_deviation` | candidate\_price     | -1.111       |
| `escalation_trigger`      | pending\_prints      | 5e+05        |
| `escalation_trigger`      | escalation\_prints   | -5e+05       |
| `escalation_trigger`      | current\_time        | 0            |
| `escalation_trigger`      | pending\_since       | 0            |
| `escalation_trigger`      | escalation\_seconds  | 0            |
| `mark_price`              | oracle\_price        | 0.9505       |
| `mark_price`              | trade\_ref           | 0.04952      |
| `mark_price`              | oracle\_weight       | 0.009505     |

![Sensitivity tornado — Single-step deviation](/files/xmZ0ew7yxv3TNmlSLImB)

![Sensitivity tornado — Mark price blend](/files/mTcGvoIiy4gM7IwADEtx)

### Response curves

![Relative deviation of an incoming print from a fixed 100,000 anchor; prints outside the ±10% band are rejected by the single-step guard.](/files/kZdFuKQS8BUOZCoT6Azf)

*Relative deviation of an incoming print from a fixed 100,000 anchor; prints outside the ±10% band are rejected by the single-step guard.*

![The mark blend as the trade reference varies, with the oracle anchor held at 100,000; higher oracle weights flatten the mark's sensitivity to trades.](/files/cjWCbRbEIsZ9dvzfP0JV)

*The mark blend as the trade reference varies, with the oracle anchor held at 100,000; higher oracle weights flatten the mark's sensitivity to trades.*

![The cumulative path bound is the single-step threshold scaled by √10 ≈ 3.162, holding the ten-print window size fixed.](/files/0097XQhRU1Wrhz0kHqWy)

*The cumulative path bound is the single-step threshold scaled by √10 ≈ 3.162, holding the ten-print window size fixed.*

### Parameter space

Joint parameter effects evaluated from the verified expressions over 2-D grids.

![The fresh/stale boundary is the line elapsed\_ms = 1000 × threshold, showing directly how tightening the staleness parameter shrinks the fresh region; last update time held at 0.](/files/0gWFhHLr6RHSJEk4KtQC)

*The fresh/stale boundary is the line elapsed\_ms = 1000 × threshold, showing directly how tightening the staleness parameter shrinks the fresh region; last update time held at 0.*

![Level sets fan out from the anchor point where trade\_ref equals the oracle price, showing that manipulation of the trade reference moves the mark by only (1 − weight) of the displacement; oracle price held at 100,000.](/files/YFHgNSsgri7x9p9hL8Rg)

*Level sets fan out from the anchor point where trade\_ref equals the oracle price, showing that manipulation of the trade reference moves the mark by only (1 − weight) of the displacement; oracle price held at 100,000.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [funding-rate](/math-engine/funding-rate)
* Sibling model: [insurance-fund](/math-engine/insurance-fund)
* Sibling model: [liquidation-engine](/math-engine/liquidation-engine)
* Sibling model: [margin-math](/math-engine/margin-math)
* Sibling model: [order-book](/math-engine/order-book)
* Sibling model: [position-tracker](/math-engine/position-tracker)
* Sibling model: [settlement](/math-engine/settlement)


# Order Book

Every market on the Exchange clears through one mechanism: a central limit order book. Traders who are willing to wait post limit orders that rest in the book, forming visible liquidity at discrete price levels; traders who want immediacy send orders that cross the spread and consume that liquidity. The book's contract is price-time priority — a better price always trades first, and at equal prices the earlier order trades first — which rewards the makers who quote tightest and earliest, and gives every taker the best available execution the book can offer.

Because makers commit first, every trade prints at the maker's quoted price: a taker willing to pay more than the best ask still pays only the ask. Around this core, the book layers execution guarantees that protect both sides — fill-or-kill orders that refuse partial execution, post-only orders that refuse to take liquidity, an opt-in self-trade prevention regime, and a server-enforced slippage cap that halts a market order the moment its running average price would drift too far from the mid-price it saw at submission.

![The fill event is a fan-in of resting depth, availability, and self-trade decrements, and each fill feeds back into both the depth it consumes and the running VWAP that bounds the next fill.](/files/naEFGv7c48sTUWiyqgCL)

*The fill event is a fan-in of resting depth, availability, and self-trade decrements, and each fill feeds back into both the depth it consumes and the running VWAP that bounds the next fill.*

## Setting

A market is described by parameters: the tick size $$\delta$$ (minimum price increment), the lot size $$\ell$$ (minimum quantity increment), and minimum and maximum order sizes. The state is a pair of price-level maps — bids and asks — each holding a first-in-first-out queue of resting orders per price, together with the derived best bid $$P\_b$$ and best ask $$P\_a$$. The admissible region for an incoming order requires a strictly positive, tick-aligned limit price and a lot-aligned quantity within the market's size bounds; orders outside it are rejected before touching the book.

| Symbol         | Name               | Description                                                                                                                                   | Units              | Domain  |
| -------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------- |
| $$q$$          | quantity           | Order quantity in base units of the asset.                                                                                                    | base units         | (0, ∞)  |
| $$q\_f$$       | filled\_qty        | Cumulative quantity of the order already filled.                                                                                              | base units         | \[0, ∞) |
| $$\ell$$       | lot\_size          | Market lot size; order quantities must be integer multiples of it (a zero lot size disables the check).                                       | base units         | \[0, ∞) |
| $$\delta$$     | tick\_size         | Minimum price increment of the market. A non-positive tick disables alignment entirely (the raw price passes the check).                      | USDX per base unit | \[0, ∞) |
| $$P\_{lim}$$   | limit\_price       | Limit price of a resting or incoming limit order; must be strictly positive and tick-aligned.                                                 | USDX per base unit | (0, ∞)  |
| $$P\_b$$       | best\_bid          | Highest resting bid price; undefined when the bid side is empty.                                                                              | USDX per base unit | (0, ∞)  |
| $$P\_a$$       | best\_ask          | Lowest resting ask price; undefined when the ask side is empty.                                                                               | USDX per base unit | (0, ∞)  |
| $$P\_{mid}$$   | mid\_price         | Arithmetic midpoint of the best bid and best ask; the reference price captured once at market-order submission for the slippage cap.          | USDX per base unit | (0, ∞)  |
| $$\beta$$      | max\_slippage\_bps | Taker-supplied slippage cap on a market order, in basis points of the mid-price; absent means no cap.                                         | basis points       | \[0, ∞) |
| $$q\_t$$       | taker\_remaining   | Unfilled quantity remaining on the incoming (taker) order at the moment it meets a maker.                                                     | base units         | (0, ∞)  |
| $$q\_m$$       | maker\_remaining   | Unfilled quantity remaining on the resting (maker) order at the front of the queue.                                                           | base units         | (0, ∞)  |
| $$P\_{mk}$$    | maker\_price       | Limit price of the resting maker order; every fill prints at this price.                                                                      | USDX per base unit | (0, ∞)  |
| $$q^{\star}$$  | fill\_qty          | Quantity exchanged in a single fill between the taker and the front maker.                                                                    | base units         | (0, ∞)  |
| $$P^{\star}$$  | fill\_price        | Price of a single fill — always the maker's limit price.                                                                                      | USDX per base unit | (0, ∞)  |
| $$Q\_{avail}$$ | available\_qty     | Total resting quantity on the opposing side at prices satisfying the taker's limit — the pre-match liquidity visible to a fill-or-kill check. | base units         | \[0, ∞) |
| $$V\_k$$       | running\_notional  | Cumulative notional of the fills accepted so far in the current market-order walk.                                                            | USDX               | \[0, ∞) |
| $$Q\_k$$       | running\_filled    | Cumulative quantity of the fills accepted so far in the current market-order walk.                                                            | base units         | \[0, ∞) |
| $$V$$          | notional           | Total notional accumulated by a hypothetical walk of the opposing side in price-time priority.                                                | USDX               | (0, ∞)  |
| $$q\_{req}$$   | requested\_qty     | Quantity requested by a hypothetical market order in a VWAP preview.                                                                          | base units         | (0, ∞)  |

## The mechanism

### Admissibility

Before an order can touch the book, its quantity must be an exact multiple of the market's lot size $$\ell$$, and must lie within the market's minimum and maximum order sizes. Quantization keeps every resting queue and every fill expressible in whole lots, so partial fills never strand dust below the tradable increment. A zero lot size disables the multiplicity check.

$$
q \bmod \ell = 0 \tag{B.1}
$$

A limit price must be strictly positive and an exact multiple of the tick size $$\delta$$. Positivity is checked separately because zero is tick-aligned for every tick size — without the strict guard, a market booted before its first oracle print could accumulate zero-priced resting orders and print zero-priced trades. A non-positive tick size disables the alignment check but never the positivity one.

$$
P\_{lim} > 0 \quad\text{and}\quad P\_{lim} \bmod \delta = 0 \tag{B.2}
$$

### Book state

When both sides of the book are populated, the mid-price is the arithmetic midpoint of the best bid and best ask. It is the book's instantaneous consensus price and the reference captured once at submission for the market-order slippage cap (B.8). When either side is empty the mid-price is undefined, and a market order carrying a slippage cap is rejected outright rather than executed against an unpriced book.

$$
P\_{mid} = \frac{P\_b + P\_a}{2} \tag{B.3}
$$

### Matching

A fill-or-kill order executes only if the book can satisfy it entirely; otherwise it cancels without touching the book. Before matching, the engine sums the resting quantity $$Q\_{avail}$$ on the opposing side across every price level satisfying the taker's limit, and admits the order to the matching loop only when that sum covers the full request. A rejected fill-or-kill order records an expiry cancellation and generates no fills.

$$
Q\_{avail} \ge q \quad\text{where}\quad Q\_{avail} = \sum\_{\substack{\text{levels } P \text{ satisfying} \ \text{the taker limit}}} ; \sum\_{\text{orders at } P} (q - q\_f) \tag{B.4}
$$

The matching loop walks the opposing side of the book from the best price inward, and within each price level from the oldest order forward — price-time priority. Each encounter between the taker and the front maker exchanges exactly the smaller of the two remaining quantities: the maker cannot give more than it has resting, and the taker cannot take more than it still needs. A fully consumed maker leaves the book; a fully satisfied taker ends the walk.

$$
q^{\star} = \min\left(q\_t,; q\_m\right) \tag{B.5}
$$

Every fill prints at the maker's limit price, not the taker's. The maker committed capital first at a stated price, and the taker who crosses receives that price even when willing to trade at a worse one — the price improvement accrues entirely to the taker. Together with (B.5) this fully determines each fill: a buy taker therefore never pays above its limit, and a sell taker never receives below it.

$$
P^{\star} = P\_{mk} \tag{B.6}
$$

### Self-trade prevention

Self-trade prevention is opt-in and fires per encountered same-account maker, not once at order entry. In cancel-newest mode the taker cancels immediately; in cancel-oldest mode the maker cancels and the taker keeps walking. In decrement-and-cancel mode both orders shrink by the quantity they would have exchanged — the same minimum as (B.5) — but as a reduction of order size rather than a fill, so no trade is recorded. The smaller side is cancelled and the larger continues with reduced quantity; when both sides are equal, both cancel.

$$
\Delta\_{stp} = \min\left(q\_t,; q\_m\right) \tag{B.7}
$$

### Market-order slippage cap

A market order may carry a slippage cap $$\beta$$ in basis points. At submission the engine captures the mid-price (B.3) once and converts the cap into an absolute half-band around it; the running average execution price must remain within $$\[P\_{mid} - h,; P\_{mid} + h]$$ for the walk to continue. If either side of the book is empty at submission the mid-price does not exist and the capped order is rejected with insufficient liquidity rather than executed unbounded.

$$
h = P\_{mid} \cdot \frac{\beta}{10^4} \tag{B.8}
$$

As a capped market order walks the book, the engine evaluates, before accepting each candidate fill, the volume-weighted average price the order would have after that fill. If the prospective average would leave the band defined by (B.8), the fill is refused, the walk halts, and the remainder cancels with a slippage-cap reason — fills already accepted stand. The check is per-fill on the cumulative average, not per-level on the marginal price, so a deep sweep is halted exactly when its blended cost breaches the cap. The cap applies only to market orders; on limit orders it is silently ignored, since limit-or-better execution makes it meaningless.

$$
\bar{P}*{k+1} = \frac{V\_k + q^{\star} P^{\star}}{Q\_k + q^{\star}}, \qquad P*{mid} - h ;\le; \bar{P}*{k+1} ;\le; P*{mid} + h \tag{B.9}
$$

### Execution preview

The book also answers a read-only question: at what average price would a hypothetical market order of size $$q\_{req}$$ execute right now? The preview walks the opposing side in the same price-time order as live matching, accumulating notional $$V$$ until the request is covered, and returns the volume-weighted average. If resting liquidity cannot cover the request — or the requested quantity is non-positive — the preview returns no price rather than a partial estimate. Self-trade prevention and slippage caps are deliberately not applied here; they are concerns of the live submission path.

$$
\bar{P} = \frac{V}{q\_{req}}, \qquad V = \sum\_{\text{walked fills}} q^{\star} P^{\star} \tag{B.10}
$$

## Invariants

* The book is never crossed: whenever both sides are non-empty, $$P\_b < P\_a$$. *Why it holds:* An incoming limit order matches against every opposing level its price crosses before any remainder rests, so a resting bid at or above a resting ask cannot coexist — one would have consumed the other at submission. Post-only orders that would cross are rejected outright, and cancellations only remove orders, which cannot create a crossing.
* No overfill: for every order, cumulative filled quantity never exceeds order quantity, i.e. $$q\_f \le q$$; equivalently the sum of fill quantities equals at most the taker's quantity ((B.5)). *Why it holds:* Every fill quantity is $$\min(q\_t, q\_m)$$ of the two remaining quantities, so each fill reduces both remainders by a non-negative amount that cannot exceed either. The loop terminates the moment the taker's remainder reaches zero, so filled quantity is a monotone sum bounded by the original quantity.
* Limit-or-better execution: every fill of a buy taker satisfies $$P^{\star} \le P\_{lim}$$ and of a sell taker $$P^{\star} \ge P\_{lim}$$ ((B.6)). *Why it holds:* The matching loop selects a maker level only if it satisfies the taker's limit — asks at or below a buy limit, bids at or above a sell limit — and every fill prints at that maker price. Levels beyond the limit terminate the walk before generating fills.
* Index-book consistency: the order index contains exactly the identifiers of orders resting in the book, and every resting order has strictly positive remaining quantity. *Why it holds:* Every mutation path — insertion, cancellation, fill consumption, self-trade removal, and restore — updates the index and the price-level queues together in the same operation. Makers are removed from both the moment their remaining quantity reaches zero, and emptied price levels are deleted, so no zero-quantity order or dangling index entry survives any operation.
* Slippage-cap monotonicity: for the same book and order, a larger cap $$\beta$$ never fills less quantity than a smaller one ((B.8), (B.9)). *Why it holds:* The fills accepted before the first band breach are identical for both caps, since the walk order and the running VWAP sequence do not depend on the cap. A wider band can only move the first breach later in that fixed sequence, so the accepted prefix — and hence the filled quantity — is weakly larger. This is pinned by the property test p021\_slippage\_cap\_monotonic.

## Worked example

Consider a market with tick size $$\delta = 0.5$$ and lot size $$\ell = 0.001$$, and three resting asks from distinct accounts: 1 unit at $$100{,}000$$, 1 unit at $$101{,}000$$, and 1 unit at $$102{,}000$$. A market buy for $$2.5$$ units walks the levels in price priority. At the first level (B.5) gives $$q^{\star} = \min(2.5, 1) = 1$$ at fill price $$P^{\star} = 100{,}000$$ per (B.6); the second level fills another unit at $$101{,}000$$; at the third the taker's remainder is $$0.5$$, so $$q^{\star} = \min(0.5, 1) = 0.5$$ at $$102{,}000$$. The taker fully fills with notional $$V = 1 \cdot 100{,}000 + 1 \cdot 101{,}000 + 0.5 \cdot 102{,}000 = 252{,}000$$, an average price of $$252{,}000 / 2.5 = 100{,}800$$ per (B.10), and the third maker rests with $$0.5$$ units remaining.

Now add a slippage cap. With a bid resting at $$99{,}000$$ and the best ask at $$100{,}000$$, the submission-time mid-price is $$P\_{mid} = (99{,}000 + 100{,}000)/2 = 99{,}500$$ per (B.3). A market buy carrying $$\beta = 50$$ basis points gets a half-band $$h = 99{,}500 \cdot 50 / 10^4 = 497.5$$ per (B.8), so the running VWAP must stay at or below $$99{,}997.5$$. The very first candidate fill at $$100{,}000$$ would set the cumulative VWAP to $$100{,}000 > 99{,}997.5$$ per (B.9), so the fill is refused and the order cancels with a slippage-cap reason and zero fills — exactly the behavior pinned by the Rust test suite.

Finally, self-trade prevention in decrement-and-cancel mode: an account with $$1$$ unit resting sends a $$0.4$$-unit crossing order against itself. Per (B.7) both orders shrink by $$\Delta\_{stp} = \min(0.4, 1) = 0.4$$ with no fill recorded; the taker (the smaller side) cancels, and the maker continues resting with $$1 - 0.4 = 0.6$$ units.

## Analysis

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression         | Input              | Elasticity ε |
| ------------------ | ------------------ | ------------ |
| `tick_alignment`   | limit\_price       | 0            |
| `tick_alignment`   | tick\_size         | 0            |
| `mid_price`        | best\_ask          | 0.5025       |
| `mid_price`        | best\_bid          | 0.4975       |
| `fok_availability` | available\_qty     | 0            |
| `fok_availability` | quantity           | 0            |
| `fill_quantity`    | taker\_remaining   | 1            |
| `fill_quantity`    | maker\_remaining   | 0            |
| `fill_price`       | maker\_price       | 1            |
| `stp_decrement`    | taker\_remaining   | 1            |
| `stp_decrement`    | maker\_remaining   | 0            |
| `slippage_span`    | mid\_price         | 1            |
| `slippage_span`    | max\_slippage\_bps | 1            |
| `running_vwap`     | running\_filled    | -0.8         |
| `running_vwap`     | running\_notional  | 0.7976       |
| `running_vwap`     | fill\_price        | 0.2024       |
| `running_vwap`     | fill\_qty          | 0.002381     |
| `vwap_estimate`    | notional           | 1            |
| `vwap_estimate`    | requested\_qty     | -1           |

![Sensitivity tornado — Fill quantity](/files/qDTRMyje3fsJvZfCzjpq)

![Sensitivity tornado — Slippage half-band](/files/mdvXDzmTx8lxoNnywEby)

![Sensitivity tornado — Running VWAP cap check](/files/fe0ySpWzIXQPgUww8dFK)

### Response curves

![The absolute price band a capped market order may traverse grows linearly in the basis-point cap, scaled by the submission-time mid-price held constant per series.](/files/6SRkzrCWnIcU1Nc9qpBY)

*The absolute price band a capped market order may traverse grows linearly in the basis-point cap, scaled by the submission-time mid-price held constant per series.*

![Each fill exchanges the minimum of the two remaining quantities: below the maker's size the taker binds, above it the maker binds (regions marked for the 1.0-unit maker).](/files/H1AjSOBVezm2gqWUpmlL)

*Each fill exchanges the minimum of the two remaining quantities: below the maker's size the taker binds, above it the maker binds (regions marked for the 1.0-unit maker).*

![With two units already filled at an average of 100,500, the cumulative VWAP climbs toward the third level's price as more of it is consumed; the worked example's 0.5-unit fill lands at 100,800.](/files/W3a1L107Kx4DYhNUVYqo)

*With two units already filled at an average of 100,500, the cumulative VWAP climbs toward the third level's price as more of it is consumed; the worked example's 0.5-unit fill lands at 100,800.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [funding-rate](/math-engine/funding-rate)
* Sibling model: [insurance-fund](/math-engine/insurance-fund)
* Sibling model: [liquidation-engine](/math-engine/liquidation-engine)
* Sibling model: [margin-math](/math-engine/margin-math)
* Sibling model: [oracle](/math-engine/oracle)
* Sibling model: [position-tracker](/math-engine/position-tracker)
* Sibling model: [settlement](/math-engine/settlement)


# Position Tracker

Every trade on the Exchange either opens, grows, shrinks, closes, or reverses a position, and the position tracker is the single bookkeeping authority for what that position is worth. Its central design choice is the split between money already banked and money still at risk: when a fill closes part of a position, the profit on the closed quantity is settled immediately at that fill's own price (realized PnL), while the remainder keeps floating with the mark price (unrealized PnL). The entry price is a volume-weighted average that only ever moves when the position grows — reducing a position never rewrites its history.

From the same state the tracker derives the two prices that govern an account's survival. The liquidation price is where the account's collateral plus floating PnL falls to exactly the maintenance-margin requirement — the point where the Exchange must step in. The bankruptcy price is more adverse still: it is where the collateral is fully consumed and equity reaches zero. The gap between the two is the buffer the liquidation engine has to unwind the position before losses spill beyond the account.

![Every fill branches on side into either an entry-price update or a realized-PnL close, and both risk prices are a fan-in of the same two state variables — entry price and collateral.](/files/82UtGCrGDedVQyeIXP0l)

*Every fill branches on side into either an entry-price update or a realized-PnL close, and both risk prices are a fan-in of the same two state variables — entry price and collateral.*

## Setting

A market is described by its parameters: the maintenance margin rate $$r\_m$$, the taker fee $$\beta\_t$$ and maker rebate $$\beta\_r$$ (both in basis points). A position carries a side $$\sigma \in {+1, -1}$$ (long, short), a positive size $$q$$, and a volume-weighted entry price $$P\_e > 0$$. Fills arrive with a price $$P\_f > 0$$ and quantity $$q\_f > 0$$, the market publishes a mark price $$P\_{mark} > 0$$, and the position is backed by account collateral $$C \geq 0$$. All quantities are exact decimals in the implementation; the formulas below are exact real arithmetic.

| Symbol        | Name                      | Description                                                                                                                 | Units                  | Domain   |
| ------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -------- |
| $$q$$         | size                      | Absolute position size in units of the underlying asset; direction is carried separately by the side.                       | units of asset         | (0, ∞)   |
| $$P\_e$$      | entry\_price              | Volume-weighted entry price of the position.                                                                                | USDX per base unit     | (0, ∞)   |
| $$P\_{mark}$$ | mark\_price               | The Exchange's mark price for the market at valuation time.                                                                 | USDX per unit of asset | (0, ∞)   |
| $$\sigma$$    | side\_sign                | Direction indicator for the position: $$+1$$ for a long position, $$-1$$ for a short position.                              | dimensionless          | \[-1, 1] |
| $$P\_f$$      | fill\_price               | Execution price of an individual fill applied to the position.                                                              | USDX per base unit     | (0, ∞)   |
| $$q\_f$$      | fill\_quantity            | Quantity of an individual fill applied to the position (always positive; direction comes from the fill side).               | base units             | (0, ∞)   |
| $$q\_c$$      | closed\_qty               | The portion of an opposing fill that closes existing position size: the smaller of the position size and the fill quantity. | base units             | (0, ∞)   |
| $$C$$         | account\_collateral       | Collateral backing the position being evaluated.                                                                            | USDX                   | \[0, ∞)  |
| $$r\_m$$      | maintenance\_margin\_rate | Market maintenance margin rate; strictly less than the initial margin rate.                                                 | dimensionless ratio    | (0, 1]   |
| $$\beta\_t$$  | taker\_fee\_bps           | Taker fee for the market, in basis points of fill notional.                                                                 | basis points           | \[0, ∞)  |
| $$\beta\_r$$  | maker\_rebate\_bps        | Maker rebate for the market, in basis points of fill notional; stored as a signed value whose magnitude is credited.        | basis points           | \[-∞, 0] |

## The mechanism

### Building the position

When a fill lands on the same side as an existing position, the position grows and its entry price becomes the volume-weighted average of the old position and the new fill: the new entry is the total cost of both legs divided by the total quantity. This is the only path on which the entry price changes — reductions, closes, and funding leave it untouched, so $$P\_e$$ always answers the question "what did the currently open size cost, on average?" A fill that opens a fresh position is the degenerate case with $$q = 0$$: the entry is simply the fill price.

$$
P\_e' = \frac{q , P\_e + q\_f , P\_f}{q + q\_f} \tag{T.1}
$$

### Valuing the position

The floating profit of the open position is the signed distance from entry to mark, scaled by size: a long gains when the mark rises above entry ($$\sigma = +1$$), a short when it falls below ($$\sigma = -1$$). This value is recomputed after every fill that leaves a position open, and again on every mark-price update; it is a pure revaluation and never moves collateral by itself.

$$
\text{uPnL} = \sigma ,(P\_{mark} - P\_e), q \tag{T.2}
$$

### Settling reductions

A fill on the opposing side first consumes existing position size. The quantity that closes is capped at the position size: a partial reduce closes the whole fill, a full close consumes exactly the position, and a flip closes the position and carries the excess into a new one. Only this closed portion settles PnL — the excess, if any, opens fresh exposure with no PnL of its own.

$$
q\_c = \min(q, , q\_f) \tag{T.3}
$$

Closing $$q\_c$$ units at the fill's own price banks the entry-to-fill difference, signed by the position side — the closing analogue of (T.2) with the fill price standing in for the mark. On a partial close this accumulates on the surviving position; on a full close or flip it is returned per fill so the risk layer can settle it into collateral, since the carried position (gone, or newly opened at (T.5)) cannot recover it. Fills that open or increase a position realize exactly zero.

$$
\text{rPnL} = \sigma ,(P\_f - P\_e), q\_c \tag{T.4}
$$

When an opposing fill exceeds the position size, the position reverses through zero: the old side closes fully (realizing (T.4) on $$q\_c = q$$), and the remainder opens a new position on the fill's side with the fill price as its fresh entry. The new position inherits none of the old accumulators.

$$
q\_{new} = q\_f - q \tag{T.5}
$$

### Fees and funding

Each taker fill charges a fee proportional to its notional value, at the market's taker rate in basis points. The tracker debits this amount from the position's fee PnL accumulator so the full P\&L breakdown (entry, funding, fee) is visible in one place; the cash movement itself is settled at the collateral layer. When a fill fully closes the position there is no surviving position to accumulate on, and the fee is settled entirely via collateral transfers.

$$
F\_t = \frac{q\_f , P\_f , \beta\_t}{10,000} \tag{T.6}
$$

Maker fills earn a rebate: the magnitude of the (negatively stored) maker rate, applied to the same fill notional as (T.6), is credited to the position's fee PnL. The absolute value guards against the sign convention — the stored rate is negative, but the credit is always positive.

$$
F\_m = \frac{q\_f , P\_f , |\beta\_r|}{10,000} \tag{T.7}
$$

### Risk prices

The liquidation price is the mark at which the account's equity — collateral plus the unrealized PnL of (T.2) evaluated at that mark — falls to exactly the maintenance-margin requirement $$P \cdot q \cdot r\_m$$. Solving $$C + \sigma(P - P\_e)q = P q r\_m$$ for $$P$$ gives a single closed form covering both sides: for a long ($$\sigma=+1$$) the price sits below entry, for a short ($$\sigma=-1$$) above it. The formula is meaningful for leveraged positions, where collateral is less than notional; a fully collateralized long yields a non-positive (unreachable) result.

$$
P\_{liq} = \frac{P\_e , q - \sigma, C}{q,(1 - \sigma, r\_m)} \tag{T.8}
$$

The bankruptcy price is where equity reaches zero: the entire collateral pool is consumed by the position's loss. Setting $$C + \sigma(P - P\_e)q = 0$$ and solving gives the entry price shifted adversely by the per-unit collateral $$C/q$$. Because the maintenance requirement in (T.8) is strictly positive, the bankruptcy price is always at least as adverse as the liquidation price — the gap is the liquidation engine's buffer to unwind the position before losses exceed the account.

$$
P\_{bkr} = P\_e - \sigma,\frac{C}{q} \tag{T.9}
$$

## Invariants

* Every open position has $$q > 0$$ and $$P\_e > 0$$. *Why it holds:* Opens and flips set size to a positive fill quantity (or positive excess $$q\_f - q$$) and entry to a positive fill price; increases add positive quantities; a reduction that would take size to zero returns no position at all rather than a zero-size one. So no path produces a live position with non-positive size or entry.
* Partial closes never change the entry price: after a reducing fill, $$P\_e' = P\_e$$ (see (T.1) for the only mutating path). *Why it holds:* The reduce branch subtracts from size and accumulates realized PnL but does not touch the entry field. Only the same-side (increase) branch recomputes the entry, and full closes and flips discard the old entry entirely.
* For leveraged positions ($$C < P\_e q$$), the bankruptcy price is at least as adverse as the liquidation price: $$\sigma P\_{bkr} \le \sigma P\_{liq}$$, i.e. $$P\_{bkr} \le P\_{liq}$$ for longs and $$P\_{bkr} \ge P\_{liq}$$ for shorts. *Why it holds:* At the liquidation price equity equals the maintenance requirement $$P\_{liq}, q, r\_m > 0$$; at the bankruptcy price equity is zero. Since equity is monotone in the mark (decreasing for longs, increasing for shorts), the zero-equity price lies strictly further in the adverse direction whenever the maintenance requirement is positive. Verified by the property test prop\_p005\_bankruptcy\_worse\_than\_liq over randomized sizes, entries, and collateral.
* A fill realizes PnL only on the quantity that closes existing opposite-side exposure: opens and increases return exactly zero realized PnL, and a flip realizes on $$q\_c = q$$, never on the newly opened $$q\_{new}$$ of (T.5). *Why it holds:* The open and increase branches return a zero realized amount unconditionally. The opposing-side branch computes (T.4) on $$\min(q, q\_f)$$ before any new position is constructed, so the reopened side starts with fresh, empty accumulators.
* Funding accrual is additive and orthogonal: applying a funding payment adds its amount to the funding accumulator and changes no other field — in particular fee PnL, entry, and size are preserved. *Why it holds:* apply\_funding performs a single addition to funding\_accrued and returns; the cash flow is handled at the collateral layer. Test f005\_funding\_tick\_preserves\_fee\_pnl confirms fee PnL survives funding ticks unchanged.

## Worked example

A trader opens a long with a buy of $$1$$ unit at $$P\_f = 45{,}000$$: the position is $$q = 1$$, $$P\_e = 45{,}000$$. A second buy of $$1$$ unit at $$47{,}000$$ increases the position, and (T.1) blends the two legs: $$P\_e' = (1 \cdot 45{,}000 + 1 \cdot 47{,}000)/2 = 46{,}000$$ on a size of $$q = 2$$. With the mark at $$P\_{mark} = 50{,}000$$, (T.2) values the position at $$\text{uPnL} = (50{,}000 - 46{,}000) \cdot 2 = 8{,}000$$ USDX of floating profit.

The trader now sells $$1$$ unit at $$50{,}000$$. The fill opposes the long, so (T.3) gives $$q\_c = \min(2, 1) = 1$$, and (T.4) banks $$(50{,}000 - 46{,}000) \cdot 1 = 4{,}000$$ USDX. The surviving position has $$q = 1$$ at the unchanged entry $$P\_e = 46{,}000$$. If instead the trader had sold $$3$$ units, the position would flip: $$q\_c = 2$$ closes the long (realizing $$8{,}000$$), and (T.5) opens a short of $$q\_{new} = 1$$ at entry $$50{,}000$$.

Suppose the surviving $$1$$-unit long is backed by $$C = 10{,}000$$ USDX in a market with $$r\_m = 0.005$$. (T.8) gives $$P\_{liq} = (46{,}000 - 10{,}000)/(1 \cdot 0.995) \approx 36{,}180.90$$, and (T.9) gives $$P\_{bkr} = 46{,}000 - 10{,}000 = 36{,}000$$. The mark reaches the liquidation threshold about $$181$$ USDX before equity would hit zero — that gap is the buffer within which the position must be unwound. Each taker fill along the way also cost (T.6): the opening $$45{,}000$$-notional buy, at $$\beta\_t = 5$$ bps, debited $$22.50$$ USDX of fee PnL.

## Analysis

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression          | Input                     | Elasticity ε |
| ------------------- | ------------------------- | ------------ |
| `vwap_entry`        | fill\_price               | 0.5109       |
| `vwap_entry`        | entry\_price              | 0.4891       |
| `vwap_entry`        | size                      | -0.01087     |
| `vwap_entry`        | fill\_quantity            | 0.01087      |
| `unrealized_pnl`    | mark\_price               | 12.5         |
| `unrealized_pnl`    | entry\_price              | -11.5        |
| `unrealized_pnl`    | side\_sign                | 1            |
| `unrealized_pnl`    | size                      | 1            |
| `closed_quantity`   | fill\_quantity            | 1            |
| `closed_quantity`   | size                      | 0            |
| `realized_pnl`      | fill\_price               | 11           |
| `realized_pnl`      | entry\_price              | -10          |
| `realized_pnl`      | side\_sign                | 1            |
| `realized_pnl`      | closed\_qty               | 1            |
| `flip_size`         | fill\_quantity            | 2            |
| `flip_size`         | size                      | -1           |
| `taker_fee`         | fill\_quantity            | 1            |
| `taker_fee`         | fill\_price               | 1            |
| `taker_fee`         | taker\_fee\_bps           | 1            |
| `maker_rebate`      | fill\_quantity            | 1            |
| `maker_rebate`      | fill\_price               | 1            |
| `maker_rebate`      | maker\_rebate\_bps        | 1            |
| `liquidation_price` | entry\_price              | 1.111        |
| `liquidation_price` | size                      | 0.1111       |
| `liquidation_price` | account\_collateral       | -0.1111      |
| `liquidation_price` | side\_sign                | -0.1061      |
| `liquidation_price` | maintenance\_margin\_rate | 0.005025     |
| `bankruptcy_price`  | entry\_price              | 1.111        |
| `bankruptcy_price`  | side\_sign                | -0.1111      |
| `bankruptcy_price`  | account\_collateral       | -0.1111      |
| `bankruptcy_price`  | size                      | 0.1111       |

![Sensitivity tornado — Volume-weighted entry price](/files/pKmXIHjUrOiRFVoVWRLu)

![Sensitivity tornado — Unrealized PnL](/files/WhQ07h2LV9mf4WFpAORx)

![Sensitivity tornado — Liquidation price](/files/E13CF6IJgS0ez5eTAbcp)

### Response curves

![More collateral pushes a long's liquidation price further below the 100,000 entry, and larger positions dilute each collateral dollar across more units; maintenance rate held at 0.5%.](/files/sg3FfX8xpEjJgom9jlJW)

*More collateral pushes a long's liquidation price further below the 100,000 entry, and larger positions dilute each collateral dollar across more units; maintenance rate held at 0.5%.*

![Floating PnL is linear in the mark and mirror-imaged between long and short around the 46,000 entry, with size held at 2 units.](/files/hIiy6TnpAex2Mqy974jT)

*Floating PnL is linear in the mark and mirror-imaged between long and short around the 46,000 entry, with size held at 2 units.*

![The volume-weighted entry moves toward the fill price in proportion to the fill's share of the combined size; existing position held at 2 units entered at 46,000.](/files/1IiRwL3FlzoDNTZqy3Lc)

*The volume-weighted entry moves toward the fill price in proportion to the fill's share of the combined size; existing position held at 2 units entered at 46,000.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [funding-rate](/math-engine/funding-rate)
* Sibling model: [insurance-fund](/math-engine/insurance-fund)
* Sibling model: [liquidation-engine](/math-engine/liquidation-engine)
* Sibling model: [margin-math](/math-engine/margin-math)
* Sibling model: [oracle](/math-engine/oracle)
* Sibling model: [order-book](/math-engine/order-book)
* Sibling model: [settlement](/math-engine/settlement)


# Insurance Fund

When a position is liquidated at a price worse than its bankruptcy price, the account's collateral is insufficient to make its counterparties whole — the difference is bad debt, a loss that must land somewhere. The Exchange's first line of defense is the insurance fund: a pool of USDX that grows when liquidations execute at better-than-bankruptcy prices (spread profit) and shrinks when it absorbs bad debt. The mechanism is deliberately simple and total: every unit of bad debt is either absorbed by the fund or passed on, with nothing lost and nothing created.

The fund cannot go negative. If a liquidation's bad debt exceeds the current balance, the fund contributes everything it has — draining to exactly zero — and the remainder becomes an auto-deleveraging instruction: the Exchange forcibly closes the most profitable, most leveraged opposing positions to settle the shortfall. This document derives the exact arithmetic of that waterfall — absorption, depletion, the ADL handoff amount, the trigger condition, and the counterparty ranking — together with the accounting identities the implementation preserves.

![A single bad-debt event splits exactly into a min-term the fund absorbs and a max-term ADL settles (their sum is D by construction), while spread profits close the replenishment loop back into the balance that determines the next split.](/files/3OhGNAzRmqXtUktkUAsk)

*A single bad-debt event splits exactly into a min-term the fund absorbs and a max-term ADL settles (their sum is D by construction), while spread profits close the replenishment loop back into the balance that determines the next split.*

## Setting

A market is parameterized by an ADL trigger threshold $$\kappa \ge 0$$. The fund's state is the triple $$(F, \Sigma\_{\mathrm{abs}}, \Sigma\_{\mathrm{rec}})$$ of current balance, cumulative bad debt absorbed, and cumulative funds received; all three are non-negative USDX amounts, and a fresh fund starts at $$(F\_0, 0, F\_0)$$. Each liquidation presents a bad debt $$D \ge 0$$; each ADL candidate carries an unrealized-PnL fraction $$\pi$$ and leverage $$L$$ from which its priority score $$\rho$$ is formed. The admissible region is $$F \ge 0$$, $$D \ge 0$$, $$g \ge 0$$, $$\kappa \ge 0$$.

| Symbol                     | Name            | Description                                                                                                                                                                           | Units         | Domain    |
| -------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | --------- |
| $$\Phi$$                   | fund\_balance   | Current insurance fund balance available to absorb bad debt.                                                                                                                          | USDX          | \[0, ∞)   |
| $$D$$                      | bad\_debt       | Bad debt produced by a liquidation: the shortfall between the bankrupt account's obligations and its collateral. Zero when the liquidation closed at or better than bankruptcy price. | USDX          | \[0, ∞)   |
| $$\Sigma\_{\mathrm{abs}}$$ | total\_absorbed | Cumulative bad debt the fund has absorbed over its lifetime; monotonically non-decreasing.                                                                                            | USDX          | \[0, ∞)   |
| $$\Sigma\_{\mathrm{rec}}$$ | total\_received | Cumulative funds the fund has received — the initial balance plus all spread profits; monotonically non-decreasing.                                                                   | USDX          | \[0, ∞)   |
| $$g$$                      | spread\_profit  | Liquidation spread profit credited to the fund when a liquidation fills at a better-than-bankruptcy price.                                                                            | USDX          | \[0, ∞)   |
| $$\kappa$$                 | adl\_threshold  | Per-market ADL trigger threshold on the fund balance. The default of zero means ADL only triggers when the fund is fully depleted; a positive value triggers ADL earlier.             | USDX          | \[0, ∞)   |
| $$\pi$$                    | pnl\_percent    | ADL candidate's unrealized PnL as a fraction of position value.                                                                                                                       | fraction      | unbounded |
| $$L$$                      | leverage        | ADL candidate's effective leverage.                                                                                                                                                   | multiplier    | (0, ∞)    |
| $$\rho$$                   | priority\_score | ADL ranking score for a counterparty candidate; candidates are deleveraged in descending order of this score.                                                                         | dimensionless | unbounded |

## The mechanism

### Bad-Debt Absorption

A liquidation presents bad debt $$D$$ to the fund, and the fund contributes as much as it can without going negative: the full debt when it is covered, and the entire balance otherwise. This is the amount by which the cumulative absorption ledger $$\Sigma\_{\mathrm{abs}}$$ advances, and its complement — (I.4) — is what escapes to auto-deleveraging. The case $$D = 0$$ is included: the fund absorbs nothing and no ADL is produced.

$$
D\_{\mathrm{abs}} = \min(D, \Phi) \tag{I.1}
$$

The balance after a liquidation is the previous balance less the absorbed amount (I.1), which collapses to a clamped subtraction: the fund keeps whatever the debt did not consume, and never dips below zero. When $$D > F$$ the two branches of the implementation meet exactly at zero — the fund is drained with no residual dust and no overdraw.

$$
\Phi' = \max(\Phi - D, 0) \tag{I.2}
$$

Every liquidation advances the lifetime absorption ledger by exactly the absorbed amount (I.1), never by the full bad debt when the fund cannot cover it. This keeps the ledger an honest record of what the fund itself paid, which is what makes the accounting-closure invariant hold.

$$
\Sigma\_{\mathrm{abs}}' = \Sigma\_{\mathrm{abs}} + \min(D, \Phi) \tag{I.3}
$$

### Auto-Deleveraging Handoff

When bad debt exceeds the fund balance, the excess is packaged into an ADL instruction whose settlement amount is exactly the shortfall the fund could not cover. Together with (I.1) this conserves the debt: $$D\_{\mathrm{abs}} + D\_{\mathrm{adl}} = D$$ always. No instruction is issued when $$D \le F$$ — the expression evaluates to zero there, and the implementation returns no ADL at all.

$$
D\_{\mathrm{adl}} = \max(D - \Phi, 0) \tag{I.4}
$$

Independently of any single liquidation, the Exchange can consult a market-level trigger: auto-deleveraging is armed whenever the fund balance has fallen to or below the market's threshold $$\kappa$$. With the default $$\kappa = 0$$ this arms only when the fund is fully depleted; a positive threshold arms ADL earlier as a safety margin. The comparison is inclusive — balance exactly at the threshold triggers.

$$
\mathrm{trigger}\_{\mathrm{adl}} = \mathbb{1}\left\[\Phi \le \kappa\right] \tag{I.5}
$$

The ADL instruction lists counterparties in the order they will be deleveraged: descending by priority score, the product of a candidate's unrealized-PnL fraction $$\pi$$ and its leverage $$L$$. The most profitable, most leveraged opposing positions absorb the shortfall first — the accounts that gained most from the move that bankrupted the liquidated trader. Ties on score are broken by ascending account identifier for deterministic replay.

$$
\rho = \pi \cdot L \tag{I.6}
$$

### Spread-Profit Crediting

The fund's only inflow after inception is liquidation spread profit: when a liquidation fills at a better-than-bankruptcy price, the surplus $$g$$ is credited to the fund. The balance increases by exactly the profit — no fee, no haircut — and $$g = 0$$ leaves the state unchanged.

$$
\Phi' = \Phi + g \tag{I.7}
$$

Each spread-profit credit also advances the lifetime receipts ledger, which was seeded with the fund's initial balance at construction. Liquidations never touch this ledger — bad debt moves value from $$F$$ to $$\Sigma\_{\mathrm{abs}}$$, not out of $$\Sigma\_{\mathrm{rec}}$$ — so receipts are monotonically non-decreasing and the closure identity with (I.7) and (I.3) is preserved on every path.

$$
\Sigma\_{\mathrm{rec}}' = \Sigma\_{\mathrm{rec}} + g \tag{I.8}
$$

## Invariants

* The fund balance is never negative: $$F \ge 0$$ after every operation, and it is exactly zero whenever an ADL instruction is emitted. *Why it holds:* Absorption subtracts $$D$$ only on the branch $$D \le F$$, so the result is non-negative; on the other branch the balance is assigned literally to zero after contributing everything ((I.2)). Spread-profit crediting only adds a non-negative amount. No other path writes the balance.
* Accounting closure: $$F = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}}$$ at all times, equivalently $$F + \Sigma\_{\mathrm{abs}} = \Sigma\_{\mathrm{rec}}$$. *Why it holds:* The initial state $$(F\_0, 0, F\_0)$$ satisfies it. Absorption moves the same amount $$\min(D,F)$$ out of $$\Phi$$ and into $$\Sigma\_{\mathrm{abs}}$$ ((I.1), (I.3)), leaving the difference unchanged; spread profit adds the same $$g$$ to both $$\Phi$$ and $$\Sigma\_{\mathrm{rec}}$$ ((I.7), (I.8)).
* Bad-debt conservation: every liquidation's debt splits exactly as $$D = D\_{\mathrm{abs}} + D\_{\mathrm{adl}}$$ with $$D\_{\mathrm{abs}} = \min(D,F)$$ and $$D\_{\mathrm{adl}} = \max(D-F,0)$$ — nothing is lost or double-counted between the fund and ADL. *Why it holds:* The identity $$\min(D,F) + \max(D-F,0) = D$$ holds pointwise. The covered branch sets $$D\_{\mathrm{adl}} = 0$$ implicitly (no instruction); the depleted branch computes the ADL amount as $$D - F$$ ((I.4)) after absorbing exactly $$\Phi$$.
* Both ledgers are monotone: $$\Sigma\_{\mathrm{abs}}$$ and $$\Sigma\_{\mathrm{rec}}$$ never decrease, and liquidations leave $$\Sigma\_{\mathrm{rec}}$$ untouched. *Why it holds:* Absorption adds a non-negative $$\min(D,F)$$ to $$\Sigma\_{\mathrm{abs}}$$ and never writes $$\Sigma\_{\mathrm{rec}}$$; spread profit adds a non-negative $$g$$ to $$\Sigma\_{\mathrm{rec}}$$ and never writes $$\Sigma\_{\mathrm{abs}}$$. ADL counterparty settlements happen outside the fund's books entirely.

## Worked example

Start a market's fund at $$F\_0 = 1{,}000$$ USDX, so the state is $$(F, \Sigma\_{\mathrm{abs}}, \Sigma\_{\mathrm{rec}}) = (1000, 0, 1000)$$. A first liquidation fills slightly better than bankruptcy and yields a spread profit of $$g = 250$$: by (I.7) and (I.8) the state becomes $$(1250, 0, 1250)$$, and closure $$F = \Sigma\_{\mathrm{rec}} - \Sigma\_{\mathrm{abs}}$$ holds.

A second liquidation leaves bad debt $$D = 1{,}500$$. Since $$D > F$$, the fund contributes everything: (I.1) gives $$D\_{\mathrm{abs}} = \min(1500, 1250) = 1250$$, the balance drops to $$\max(1250 - 1500, 0) = 0$$ by (I.2), and $$\Sigma\_{\mathrm{abs}}$$ advances to $$1250$$. The shortfall becomes an ADL instruction for $$D\_{\mathrm{adl}} = \max(1500 - 1250, 0) = 250$$ by (I.4). With the default threshold $$\kappa = 0$$, the trigger (I.5) now reads $$\mathbb{1}\[0 \le 0] = 1$$ — ADL is armed.

The instruction ranks counterparties by (I.6). Consider three profitable longs with $$(\pi, L)$$ of $$(0.02, 10)$$, $$(0.1, 15)$$, and $$(0.04, 10)$$: their scores are $$0.2$$, $$1.5$$, and $$0.4$$, so the second account — the most profitable and most leveraged — is deleveraged first to settle the $$250$$ USDX shortfall. Throughout, the final state $$(0, 1250, 1250)$$ still satisfies closure exactly.

## Analysis

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression                 | Input           | Elasticity ε |
| -------------------------- | --------------- | ------------ |
| `absorbed_amount`          | bad\_debt       | 1            |
| `absorbed_amount`          | fund\_balance   | 0            |
| `post_liquidation_balance` | fund\_balance   | 2            |
| `post_liquidation_balance` | bad\_debt       | -1           |
| `total_absorbed_update`    | bad\_debt       | 1            |
| `total_absorbed_update`    | total\_absorbed | 0            |
| `total_absorbed_update`    | fund\_balance   | 0            |
| `adl_settle_amount`        | bad\_debt       | 3            |
| `adl_settle_amount`        | fund\_balance   | -2           |
| `adl_priority_score`       | pnl\_percent    | 1            |
| `adl_priority_score`       | leverage        | 1            |
| `spread_profit_balance`    | fund\_balance   | 0.9756       |
| `spread_profit_balance`    | spread\_profit  | 0.02439      |
| `total_received_update`    | total\_received | 0.9756       |
| `total_received_update`    | spread\_profit  | 0.02439      |

![Sensitivity tornado — Fund balance after absorption](/files/jBRBlSFQ0PAjm0rAjsMv)

![Sensitivity tornado — ADL settlement amount](/files/ZdK3aVGaoRCarmm4FTFZ)

![Sensitivity tornado — ADL counterparty priority score](/files/xeHFeI7AIjoSoEuAPgcR)

### Response curves

![The balance falls one-for-one with bad debt until it clamps at exactly zero, where auto-deleveraging takes over; initial balance held at 10,000 USDX.](/files/7D3VYG7q7VQZvlKVKKor)

*The balance falls one-for-one with bad debt until it clamps at exactly zero, where auto-deleveraging takes over; initial balance held at 10,000 USDX.*

![The ADL settlement amount is zero while the fund covers the debt and grows one-for-one with the excess beyond the fund balance, shown for three fund sizes.](/files/AERStG69hKFHK6WnFT41)

*The ADL settlement amount is zero while the fund covers the debt and grows one-for-one with the excess beyond the fund balance, shown for three fund sizes.*

![Counterparties are deleveraged in descending score order, so more profitable and more leveraged positions absorb the shortfall first; three leverage levels shown.](/files/vaMWa2jCdlj1UE3Zew0Z)

*Counterparties are deleveraged in descending score order, so more profitable and more leveraged positions absorb the shortfall first; three leverage levels shown.*

### Parameter space

Joint parameter effects evaluated from the verified expressions over 2-D grids.

![The fund fully absorbs losses below the diagonal bad\_debt = fund\_balance; above it, the excess passes linearly to ADL counterparties, making the diagonal the socialization boundary.](/files/ziN2o3kvIIbU8xqwYkt8)

*The fund fully absorbs losses below the diagonal bad\_debt = fund\_balance; above it, the excess passes linearly to ADL counterparties, making the diagonal the socialization boundary.*

![Priority hyperbolas show that a modestly profitable high-leverage position outranks a highly profitable low-leverage one, while loss-making positions (negative scores) are deprioritized regardless of leverage.](/files/CS2T68msYstIM1YaXDYO)

*Priority hyperbolas show that a modestly profitable high-leverage position outranks a highly profitable low-leverage one, while loss-making positions (negative scores) are deprioritized regardless of leverage.*

![ADL arms exactly on and below the diagonal fund\_balance = adl\_threshold, showing how the threshold parameter directly sets the fund depletion level at which deleveraging begins.](/files/hOaz60ayMjFXRJLuUcZj)

*ADL arms exactly on and below the diagonal fund\_balance = adl\_threshold, showing how the threshold parameter directly sets the fund depletion level at which deleveraging begins.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [funding-rate](/math-engine/funding-rate)
* Sibling model: [liquidation-engine](/math-engine/liquidation-engine)
* Sibling model: [margin-math](/math-engine/margin-math)
* Sibling model: [oracle](/math-engine/oracle)
* Sibling model: [order-book](/math-engine/order-book)
* Sibling model: [position-tracker](/math-engine/position-tracker)
* Sibling model: [settlement](/math-engine/settlement)


# Settlement

Every trade on the Exchange ends in settlement: the moment abstract execution becomes actual transfers of USDX between accounts. The mechanism exists to make the Exchange's fee economics explicit and auditable — the taker, who consumes liquidity, pays a small fee on the trade's notional value; the maker, who provided the resting quote, receives a rebate funded out of that fee; and the difference is the Exchange's revenue on the fill. When the fill is a forced liquidation, an additional penalty on the same notional flows to the insurance fund, compensating the system for absorbing distressed risk.

Funding settlement is the second half of the story. Perpetual markets periodically charge one side of the market and pay the other to keep the contract price tethered to the index; settlement realizes those charges as transfers through a dedicated funding pool account. Throughout, the design commitment is conservation: every fee, rebate, penalty, and funding payment is a transfer between two named accounts, so money is moved, never created or destroyed.

![Each fill fans its notional out into fee, rebate, and penalty flows that recombine into ledger totals whose batch merge closes with V = F − R exactly.](/files/l6dGf425NiAtOD70buh1)

*Each fill fans its notional out into fee, rebate, and penalty flows that recombine into ledger totals whose batch merge closes with V = F − R exactly.*

## Setting

A market is parameterized by three fee rates quoted in basis points: the taker fee $$b\_t$$, the maker rebate $$b\_m$$ (stored as a negative number by convention; only its magnitude is charged), and the liquidation penalty $$b\_{liq}$$. The state being settled is a batch of fills, each with size $$q > 0$$ and price $$P > 0$$, plus a batch of signed funding payment amounts $$x$$. Three reserved accounts — the Exchange fee account, the insurance fund, and the funding pool — act as counterparties with untracked balances. All monetary results live in USDX and are truncated toward zero at 28 decimal places.

| Symbol       | Name                      | Description                                                                                                                                                           | Units              | Domain    |
| ------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | --------- |
| $$q$$        | size                      | Fill quantity in units of the underlying asset; always positive for an executed fill.                                                                                 | units of asset     | (0, ∞)    |
| $$P$$        | price                     | Execution price of the fill.                                                                                                                                          | USDX per base unit | (0, ∞)    |
| $$b\_t$$     | taker\_fee\_bps           | Market taker fee rate in basis points; divided by 10,000 to obtain the fee fraction.                                                                                  | basis points       | \[0, ∞)   |
| $$b\_m$$     | maker\_rebate\_bps        | Market maker rebate rate in basis points, stored as a negative number by convention; the settlement engine charges only its absolute value.                           | basis points       | unbounded |
| $$b\_{liq}$$ | liquidation\_penalty\_bps | Penalty rate in basis points applied to the notional of liquidation fills and routed to the insurance fund.                                                           | basis points       | \[0, ∞)   |
| $$F$$        | total\_fees               | Sum of taker fees collected across all fills in a settlement record.                                                                                                  | USDX               | \[0, ∞)   |
| $$R$$        | total\_rebates            | Sum of maker rebates paid across all fills in a settlement record.                                                                                                    | USDX               | \[0, ∞)   |
| $$x$$        | funding\_amount           | Signed funding payment amount for one account: positive means the account pays the funding pool, negative means the pool pays the account, zero produces no transfer. | USDX               | unbounded |
| $$V\_1$$     | volume\_1                 | An accumulator field of one constituent settlement record being merged (shown for volume; the same summation applies to fees, rebates, and net revenue).              | USDX               | \[0, ∞)   |
| $$V\_2$$     | volume\_2                 | An accumulator field of one constituent settlement record being merged (shown for volume; the same summation applies to fees, rebates, and net revenue).              | USDX               | \[0, ∞)   |

## The mechanism

### Fee Settlement

Every fee in this primitive is proportional to the same base quantity: the notional value of the fill, the size traded times the price it traded at. Computing it once per fill fixes the base on which the taker fee, maker rebate, and liquidation penalty are all assessed.

$$
n = q \cdot P \tag{S.1}
$$

The taker — the aggressing side that consumed resting liquidity — pays the Exchange a fee proportional to the fill's notional (S.1). The rate is the market's taker fee expressed in basis points, so a market with $$b\_t = 5$$ charges five hundredths of a percent of notional. The resulting amount moves from the taker's account to the Exchange fee account. The product is truncated toward zero at 28 decimal places, so the fee never rounds up against the taker.

$$
\text{fee}\_t = n \cdot \frac{b\_t}{10,000} = q \cdot P \cdot \frac{b\_t}{10,000} \tag{S.2}
$$

The maker, whose resting order provided the liquidity, is paid a rebate out of the Exchange fee account, again proportional to the notional (S.1). Market parameters store the maker rate as a negative number of basis points to signal that money flows toward the maker; the engine applies its absolute value, so the transferred amount is always non-negative. Like the taker fee (S.2), the amount is truncated toward zero at 28 decimal places.

$$
\text{rebate}\_m = n \cdot \frac{|b\_m|}{10,000} = q \cdot P \cdot \frac{|b\_m|}{10,000} \tag{S.3}
$$

When a fill is flagged as a liquidation — the taker side is a forced close of a distressed position — an additional penalty is assessed on the same notional (S.1) at the market's liquidation penalty rate. This transfer runs from the taker's account to the insurance fund, not to the Exchange fee account: the penalty capitalizes the backstop that absorbs losses when liquidations complete underwater. Non-liquidation fills incur no penalty, and the amount is truncated toward zero at 28 decimal places.

$$
\text{penalty} = n \cdot \frac{b\_{liq}}{10,000} = q \cdot P \cdot \frac{b\_{liq}}{10,000} \tag{S.4}
$$

### Batch Accounting

Across a batch of fills, the Exchange's revenue is exactly what came in as taker fees (S.2) minus what went out as maker rebates (S.3): $$F$$ is the sum of per-fill taker fees and $$R$$ the sum of per-fill rebates. Liquidation penalties (S.4) are deliberately excluded — they belong to the insurance fund, not the Exchange. Because $$F$$ and $$R$$ are sums of the already-truncated per-fill amounts, the identity holds exactly with no further rounding.

$$
V = F - R \tag{S.5}
$$

### Funding Settlement

Funding settlement converts each account's signed funding payment into a transfer against the funding pool. The transferred amount is always the magnitude $$|x|$$; the sign selects the direction. A positive $$x$$ means the account pays: the transfer runs account → funding pool. A negative $$x$$ means the account receives: the transfer runs funding pool → account, carrying $$|x|$$. A zero payment produces no transfer at all, and funding settlement contributes nothing to fee totals or revenue.

$$
\text{transfer} = |x|, \qquad \text{direction} = \begin{cases} \text{account} \to \text{pool} & x > 0 \ \text{pool} \to \text{account} & x < 0 \ \text{none} & x = 0 \end{cases} \tag{S.6}
$$

### Batch Accounting

Settlement records compose additively. Merging a set of records concatenates their transfer lists and sums their fee totals and revenue, while the merged period spans from the earliest period start to the latest period end. Because every total is a plain sum, the merged record's revenue is the sum of the constituent revenues — merging never changes any amount, only groups them.

$$
X\_{\text{merged}} = \sum\_j X\_j \quad \text{for } X \in {V, F, R, \text{net}} \tag{S.7}
$$

## Invariants

* Fee accounting closes exactly: for any settlement record, $$V = F - R$$, where $$F$$ is the sum of all taker-fee transfer amounts and $$R$$ the sum of all maker-rebate transfer amounts ((S.5)). *Why it holds:* Each fill appends exactly one taker-fee and one maker-rebate transfer and adds the same two truncated amounts to the running totals $$F$$ and $$R$$; revenue is computed once at the end as $$F - R$$. No other path mutates these totals, and merging sums them component-wise, so closure is preserved under batching.
* Taker fees, maker rebates, and liquidation penalties are all non-negative: $$\text{fee}\_t \ge 0$$, $$\text{rebate}\_m \ge 0$$, $$\text{penalty} \ge 0$$. *Why it holds:* Each amount is a product of a positive notional $$q \cdot P$$ and a non-negative rate — the maker rate enters through its absolute value $$|b\_m|$$ — and truncation toward zero cannot cross zero. Hence no transfer amount is ever negative.
* Liquidation penalties route only to the insurance fund, never to the Exchange fee account, so $$V$$ contains no penalty revenue ((S.4)). *Why it holds:* The penalty transfer is constructed with the insurance fund as its fixed destination, and the penalty amount is never added to $$F$$ or $$R$$. The revenue identity therefore sees only fees and rebates.
* Funding settlement is conservative through the pool: every nonzero payment produces exactly one transfer of magnitude $$|x|$$, directed by the sign of $$x$$, and funding records carry zero fees, rebates, and revenue ((S.6)). *Why it holds:* The settle-funding path branches only on the sign of the payment, emitting one transfer with amount $$x$$ or $$|x|$$ and skipping zero amounts; its record hard-codes all fee totals to zero. So funding moves money between accounts and the pool without creating revenue.

## Worked example

Consider a single fill of $$q = 1$$ BTC at $$P = 50{,}000$$ USDX in a market with $$b\_t = 5$$, $$b\_m = -2$$, and $$b\_{liq} = 5$$ basis points. The notional (S.1) is $$n = 1 \times 50{,}000 = 50{,}000$$ USDX. The taker fee (S.2) is $$50{,}000 \times 5/10{,}000 = 25$$ USDX, paid from the taker to the Exchange fee account. The maker rebate (S.3) uses the magnitude of the stored rate: $$50{,}000 \times 2/10{,}000 = 10$$ USDX, paid from the Exchange fee account to the maker.

If this were an ordinary fill, settlement would stop there and record net revenue (S.5) of $$V = 25 - 10 = 15$$ USDX. If instead the fill is a liquidation, one more transfer is added: a penalty (S.4) of $$50{,}000 \times 5/10{,}000 = 25$$ USDX from the taker to the insurance fund. Note that $$V$$ is still $$15$$ USDX — the penalty capitalizes the insurance fund and never counts as Exchange revenue.

Funding settles separately. Suppose the same account owes a funding payment of $$x = 100$$ USDX: since $$x > 0$$, settlement emits a transfer of $$100$$ USDX from the account to the funding pool (S.6). Had the payment been $$x = -75$$ USDX, the pool would instead pay the account $$75$$ USDX. Neither transfer touches $$F$$, $$R$$, or $$V$$.

## Analysis

### Sensitivity

Elasticities ε = (∂y/∂x)·(x/y), computed numerically from the verified expressions at each worked-example point. |ε| > 1 means the output moves more than proportionally with that input.

| Expression             | Input                     | Elasticity ε |
| ---------------------- | ------------------------- | ------------ |
| `fill_notional`        | size                      | 1            |
| `fill_notional`        | price                     | 1            |
| `taker_fee`            | size                      | 1            |
| `taker_fee`            | price                     | 1            |
| `taker_fee`            | taker\_fee\_bps           | 1            |
| `maker_rebate`         | size                      | 1            |
| `maker_rebate`         | price                     | 1            |
| `maker_rebate`         | maker\_rebate\_bps        | 1            |
| `liquidation_penalty`  | size                      | 1            |
| `liquidation_penalty`  | price                     | 1            |
| `liquidation_penalty`  | liquidation\_penalty\_bps | 1            |
| `net_exchange_revenue` | total\_fees               | 1.667        |
| `net_exchange_revenue` | total\_rebates            | -0.6667      |
| `funding_transfer`     | funding\_amount           | 1            |
| `merge_totals`         | volume\_1                 | 0.625        |
| `merge_totals`         | volume\_2                 | 0.375        |

![Sensitivity tornado — Taker fee](/files/uKkLBRpqMbhpbdV32wLV)

![Sensitivity tornado — Net exchange revenue](/files/Ykd9SkqGdTsksiuJnV4M)

### Response curves

![The taker fee grows linearly in price at each fill size, holding the taker rate fixed at 5 basis points.](/files/FbioQFdSF9zdQSZmvVwx)

*The taker fee grows linearly in price at each fill size, holding the taker rate fixed at 5 basis points.*

![Net revenue is fees minus rebates: each rebate level shifts the same unit-slope line downward, holding rebates constant along each series.](/files/mmQNkVBlFimrjzjAWhA6)

*Net revenue is fees minus rebates: each rebate level shifts the same unit-slope line downward, holding rebates constant along each series.*

![The transferred amount is the magnitude of the signed funding payment; the sign only selects the direction of the transfer, with zero producing no transfer.](/files/0S2jstRJhpo4c1qcLM10)

*The transferred amount is the magnitude of the signed funding payment; the sign only selects the direction of the transfer, with zero producing no transfer.*

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.
* Sibling model: [funding-rate](/math-engine/funding-rate)
* Sibling model: [insurance-fund](/math-engine/insurance-fund)
* Sibling model: [liquidation-engine](/math-engine/liquidation-engine)
* Sibling model: [margin-math](/math-engine/margin-math)
* Sibling model: [oracle](/math-engine/oracle)
* Sibling model: [order-book](/math-engine/order-book)
* Sibling model: [position-tracker](/math-engine/position-tracker)


# Performance

The matching engine is a message-passing machine: each market is an actor — a dedicated OS thread that exclusively owns its order book, position map, and trigger state — and the `MatchingEngine` is a router that maps a market id to that actor's bounded mpsc sender. Every operation the exchange performs is therefore decomposable into a routing step (which actors receive the message, and how the caller learns which one owns the answer), a queueing step (mailbox depth and reply via `oneshot`), and an on-actor computation step (book traversal, position mutation, cache refresh). Because these steps are structural, each benchmarked operation admits a falsifiable scaling law derived from the topology itself: direct dispatch is flat in market count, broadcast fan-out is linear in it, bulk cancellation is linear in resting orders, and concurrent submission into a single actor exhibits the serialization-plus-coherence shape of the Universal Scalability Law.

The models below carry only symbolic constants ($$c\_0, c\_1, \dots$$): the *form* of each law comes from reading the actor architecture and the benchmark's timed closure, while the constants are machine properties of the CI runner and are fitted separately against the measured benchmark series. Each operation corresponds exactly to one measured sweep, so the fit is a direct confrontation of theory with instrument.

The residuals are the signal. If direct routing drifts upward with market count, the router's market map is not $$O(1)$$; if the contention series needs a large quadratic coefficient, point-to-point serialization has acquired a coherence cost; if bulk cancel bends superlinearly, the book's removal path is not the linear scan the model claims. The model is the hypothesis; the CI bench is the experiment.

Provenance: the scaling laws are **derived from source** (the actor architecture and data structures of the hot paths); the constants are **fitted to CI measurement** (a benchmark run captured 2026-07-01); the residuals are the comparison signal — model error, or a caught regression.

## The architecture, mathematically

Formally, the engine's matching-engine service is a set of $$M$$ single-writer actors $${A\_1, \dots, A\_M}$$, one per market, each spawned with `std::thread::spawn` and owning its state exclusively. Tokio handler tasks never touch market state; they send a message carrying a `oneshot` reply channel into the actor's bounded mpsc mailbox and suspend until the reply arrives. The cost of any request is thus $$T = t\_{route} + t\_{queue} + t\_{work} + t\_{reply}$$, where $$t\_{route}$$ depends on how many senders the router must touch and $$t\_{work}$$ runs serially on the owning actor's thread.

Parallelism and contention follow directly. Requests targeting *different* markets never share a lock — throughput scales with cores up to $$M$$ — but requests targeting the *same* market are serialized by that market's single mailbox: the actor is an $$M/M/1$$-like server, and adding submitters to one market adds queueing, not parallelism. This is a deliberate trade: the previous mutex-sharded design allowed races between the book lock and the position lock and exhausted tokio workers with synchronous matching work; the actor design buys serialized market access and predictable latency at the price of a per-market serial ceiling.

Routing topology is the other axis. A call that names its market (`MatchingEngine::cancel_order_in_market`) resolves one sender — flat in $$M$$. A call that does not (`MatchingEngine::cancel_order` with only an order id) must fan out to every actor, paying $$M-1$$ lookup misses — linear in $$M$$. Cross-margin risk lives outside the actors in `risk_margin::RiskModule`, whose equity cache aggregates over an account's $$n$$ positions per refresh and whose liquidation path (`liquidate_portfolio`) closes each of $$n$$ positions through its owning market actor in turn. The six operations below each isolate one of these structural costs.

## Single-order cancel — direct dispatch

Each sample cancels a fixed batch of 400 pre-rested orders on MARKET-00 via `MatchingEngine::cancel_order_in_market(id, account, market_id)` while the engine hosts $$n$$ market actors (`bench_cancel_routing` in `tests/benchmarks/benches/cancel_order.rs`, mode="direct"). Because the caller names the owning market, the router resolves exactly one sender from its market map and dispatches to that single actor; the other $$n-1$$ actors are never messaged. The per-batch cost is therefore one map lookup, one mailbox round-trip, and one book removal per cancel — independent of how many markets exist. The predicted law is a constant.

$$
T(n) = c\_0
$$

*Architecture: Measures the flatness of keyed routing: a named-market dispatch touches one mailbox regardless of topology size, so any measured slope in n falsifies the O(1) market-map claim.*

Fitted to the CI series: $$c0 = \text{6.21ms}$$; $$R^2$$ n/a for a constant model (O(1)).

![Single-order cancel — direct dispatch model vs measurement](/files/23MllnLLSeRll2siMMfh)

*Dots: CI measurements. Curve: the source-derived model with fitted constants. Labels: residuals.*

## Single-order cancel — broadcast fan-out

Identical setup to the direct series, but each cancel goes through `MatchingEngine::cancel_order(id, account)` with no market id (`bench_cancel_routing`, mode="broadcast"). The router cannot know which actor owns the order, so it fans the cancel out to all $$n$$ market actors; $$n-1$$ of them perform a lookup miss and reply not-found, and one performs the removal. Each cancel thus pays $$n$$ mailbox round-trips instead of one, so the per-batch cost grows linearly in market count on top of the fixed removal work. The structural contrast with the direct series is the point: same book operation, different routing topology.

$$
T(n) = c\_0 + c\_1 , n
$$

*Architecture: Measures the linear price of ownership-blind routing: without a market hint, the actor topology forces an n-way fan-out with n−1 wasted mailbox round-trips per cancel.*

Fitted to the CI series: $$c0 = \text{1.19ms}$$, $$c1 = \text{1.17ms}$$; $$R^2 = 1.0000$$ (O(n)).

![Single-order cancel — broadcast fan-out model vs measurement](/files/7q6EcYpLL1tEo8P5KhGQ)

*Dots: CI measurements. Curve: the source-derived model with fitted constants. Labels: residuals.*

## Bulk cancel of all resting orders on a market

One book is seeded with $$n$$ non-crossing resting buys at distinct 0.5-tick prices, then a single `cancel_all_orders_for_market(account, market)` call is timed (`bench_cancel_all_for_market` in `cancel_order.rs`). The call routes once to the owning actor, which walks the account's resting orders and removes each from the book — one price-level removal and one order-state transition per order. The dominant cost is the per-order removal loop, so the law is affine in $$n$$; the intercept captures the single routing round-trip and call overhead. `Throughput::Elements(n)` in the bench exposes the same law as a per-order rate.

$$
T(n) = c\_0 + c\_1 , n
$$

*Architecture: Measures the on-actor serial work of a bulk book mutation: one routing hop amortized over n removals, isolating the per-order removal cost of the book data structure.*

Fitted to the CI series: $$c0 = \text{-654µs}$$, $$c1 = \text{1.21µs}$$; $$R^2 = 0.9996$$ (O(n)).

![Bulk cancel of all resting orders on a market model vs measurement](/files/i6sdxR9mPujzfhQdohOc)

*Dots: CI measurements. Curve: the source-derived model with fitted constants. Labels: residuals.*

## Concurrent submitters into one market — contention scaling

$$n$$ tokio tasks each submit 1000 crossing limit orders (distinct account pairs per task) into a single-market engine on a current-thread runtime, and the wall-clock for all tasks to drain is timed (`bench_contention` in `tests/benchmarks/benches/engine_throughput.rs`). Total work is $$1000n$$ orders, but the market is one actor: every order serializes through the same mailbox, so the linear term $$c\_1 n$$ is the actor's per-1000-order matching cost — the serialization term of the Universal Scalability Law. The quadratic term $$c\_2 n^2$$ captures the superlinear costs of adding submitters to a saturated single-writer pipeline: mailbox contention on the shared mpsc sender, task-switching and wakeup churn among $$n$$ suspended submitters per reply, and cache-line ping-pong on the shared channel state — the USL coherence term. $$c\_0$$ absorbs spawn/join overhead.

$$
T(n) = c\_0 + c\_1 , n + c\_2 , n^2
$$

*Architecture: Measures the single-writer ceiling of the per-market actor: work scales linearly with submitters (serialization) while cross-task coherence on the shared mailbox adds a quadratic penalty — the USL shape of the architecture's one deliberate serialization point.*

Fitted to the CI series: $$c0 = \text{17ms}$$, $$c1 = \text{1.68ms}$$, $$c2 = \text{77.6µs}$$; $$R^2 = 0.9994$$ (O(n^2)).

![Concurrent submitters into one market — contention scaling model vs measurement](/files/fhAy2xapNFpuRzKD0jqk)

*Dots: CI measurements. Curve: the source-derived model with fitted constants. Labels: residuals.*

## Equity cache refresh for a cross-margin account

A `RiskModule` is snapshot-built with one account holding $$n$$ cross-mode positions across $$n$$ markets, and the timed loop calls `risk.refresh_equity_for(ACCOUNT, "fill")` repeatedly on the hot cache (`bench_b7_equity_cache_update` in `tests/benchmarks/benches/cross_margin.rs`; setup via `build_account_snapshot`). A refresh recomputes account equity as collateral plus the sum of unrealized PnL over every open position, each mark-to-market against its market's oracle price — one position-map traversal and one oracle lookup per position. The work is a fold over $$n$$ positions with constant per-element cost, so the law is affine; the intercept is the fixed cost of the call, account lookup, and cache write.

$$
T(n) = c\_0 + c\_1 , n
$$

*Architecture: Measures the per-position cost of cross-margin aggregation in the risk module — the price every fill pays to keep account equity coherent grows linearly with portfolio breadth.*

Fitted to the CI series: $$c0 = \text{-12.3ns}$$, $$c1 = \text{278ns}$$; $$R^2 = 0.9997$$ (O(n)).

![Equity cache refresh for a cross-margin account model vs measurement](/files/qHJFsj2VihipSjk30A5h)

*Dots: CI measurements. Curve: the source-derived model with fitted constants. Labels: residuals.*

## Cross-margin portfolio liquidation roundtrip

Each iteration builds a fresh account holding $$n$$ cross-mode positions across $$n$$ markets (liquidation mutates state, so setup is per-iteration and outside the timed window) and times a single `risk.liquidate_portfolio(ACCOUNT).await` (`bench_b9_liquidation_roundtrip` in `cross_margin.rs`). The liquidation walks the account's portfolio and, for each of the $$n$$ positions, executes a closure through the owning market actor — a routing hop, a mailbox round-trip, and a position-close on that actor — plus the per-position risk accounting (equity/margin updates, insurance-fund interaction). Since each position lives on a distinct market actor and closures are issued sequentially by the risk module, the cost is one fixed portfolio-scan overhead plus $$n$$ per-market roundtrips: affine in $$n$$.

$$
T(n) = c\_0 + c\_1 , n
$$

*Architecture: Measures the cross-actor roundtrip cost of risk-driven control flow: liquidation crosses the risk-module/actor boundary once per position, so c1 is the price of one full risk→actor→risk closure hop.*

Fitted to the CI series: $$c0 = \text{3.92µs}$$, $$c1 = \text{1.07µs}$$; $$R^2 = 0.9990$$ (O(n)).

![Cross-margin portfolio liquidation roundtrip model vs measurement](/files/0MR5ltHePD3qJ0pxdPzF)

*Dots: CI measurements. Curve: the source-derived model with fitted constants. Labels: residuals.*

## Notes

* All constants are machine properties of the CI runner (shared GitHub-hosted hardware): absolute values are not portable across machines, and run-to-run variance on shared runners inflates residuals — the scaling shape, not the constant magnitudes, is the falsifiable claim.
* The concurrent\_submitters quadratic term is a USL-shaped phenomenological approximation of coherence costs (mailbox contention, scheduler churn), not a first-principles derivation; with only four swept points (n=1,2,4,8) the c2 fit is weakly identified.
* Batched benchmarks (routing series: 400 cancels/sample; throughput series: 1000 orders/task) report per-batch wall-clock, so fitted constants are per-batch, not per-operation; divide by the batch size for per-op costs.
* cancel\_all\_for\_market may hide a log(n) factor from ordered price-level removal inside c1 over the swept range (1000–16000); a systematic upward bend in residuals at large n would indicate it.
* The direct-routing model T(n)=c0 is deliberately slope-free: any statistically significant slope in the direct series is a falsification signal (router map not O(1) or idle-actor overhead), not something the model should absorb.
* Series sweep points are sparse (3–4 values each), so fits distinguish model families (constant vs linear vs quadratic) rather than resolving fine functional structure such as log factors.
* Timing on the CI runner uses coarse sample counts (sample\_size=10 for destructive benches), so per-point uncertainty should be propagated into the fit rather than assuming homoscedastic noise.

## References

* Derived from and adversarially verified against the Exchange's Rust implementation and its test suite.


# Building on Nexus

Nexus is fully EVM-compatible, so you can build with the tools you already know — Solidity, Hardhat, Foundry, Remix, and the standard libraries — and deploy to a chain with a high-performance financial engine built in.

Start here:

* [Getting Started](/network/building-on-nexus/getting-started) — deploy your first contract.
* [Developer Environment Setup](/network/building-on-nexus/developer-environment-setup) — networks, RPCs, and toolchain config.
* [Deploying on NexusEVM](/network/building-on-nexus/deploying-on-nexusevm) — deployment details and NexusCore composability.
* [Endpoints](/network/building-on-nexus/endpoints) — RPC and API endpoints, chain IDs, and limits.
* [Tokens and Bridges](/network/building-on-nexus/tokens-and-bridges) — NEX, WNEX, and bridging.

NEX is the native gas token. See [NEX](/network/building-on-nexus/tokens-and-bridges/usdnex) and the [Architecture](/architecture/architecture) section for how NexusEVM and NexusCore fit together.


# Getting Started

#### Prerequisites

* **Wallet** — MetaMask or any EVM-compatible wallet, configured for Nexus (See [Developer Environment Setup](https://docs.nexus.xyz/network/building-on-nexus/developer-environment-setup))
* **Testnet tokens** — NEX on testnet for gas ([faucet](https://faucet.nexus.xyz))
* **Toolchain** — Hardhat or Foundry

#### Quickstart: Deploy a contract on NexusEVM

**Step 1 — Create a Foundry project**

```bash
mkdir nexus-token && cd nexus-token
forge init
```

**Step 2 — Write the contract**

Replace `src/Counter.sol` with `src/NexusToken.sol`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract NexusToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("NexusToken", "NTK") {
        _mint(msg.sender, initialSupply);
    }
}
```

Install OpenZeppelin:

```bash
forge install OpenZeppelin/openzeppelin-contracts --no-commit
```

**Step 3 — Deploy to Nexus Testnet**

```bash
forge create src/NexusToken.sol:NexusToken \
  --rpc-url https://testnet.rpc.nexus.xyz \
  --chain-id 3945 \
  --private-key $PRIVATE_KEY \
  --constructor-args 1000000000000000000000000
```

This deploys 1,000,000 NTK to your address.

**Step 4 — Read on-chain state**

Check your balance:

```bash
cast call <CONTRACT_ADDRESS> "balanceOf(address)" $YOUR_ADDRESS \
  --rpc-url https://testnet.rpc.nexus.xyz
```

Transfer tokens:

```bash
cast send <CONTRACT_ADDRESS> "transfer(address,uint256)" \
  <RECIPIENT_ADDRESS> 1000000000000000000 \
  --rpc-url https://testnet.rpc.nexus.xyz \
  --private-key $PRIVATE_KEY
```

**Step 5 — Deploy to mainnet**

When ready, switch to mainnet:

```bash
forge create src/NexusToken.sol:NexusToken \
  --rpc-url https://mainnet.rpc.nexus.xyz \
  --chain-id 3946 \
  --private-key $PRIVATE_KEY \
  --constructor-args 1000000000000000000000000
```

Finality is single-slot — the deployment is confirmed as soon as the block is committed.

#### Quickstart: Interact with the chain using cast

No project setup needed. These commands work immediately with Foundry installed.

Get the latest block number:

```bash
cast block-number --rpc-url https://mainnet.rpc.nexus.xyz
```

Read any contract's state:

```bash
cast call <CONTRACT_ADDRESS> "symbol()" --rpc-url https://mainnet.rpc.nexus.xyz
```

Check an address balance:

```bash
cast balance <ADDRESS> --rpc-url https://mainnet.rpc.nexus.xyz
```

Send a transaction:

```bash
cast send <TO_ADDRESS> --value 0.1ether \
  --rpc-url https://mainnet.rpc.nexus.xyz \
  --private-key $PRIVATE_KEY
```

All transactions confirm with single-slot finality. No need to wait for additional block confirmations.


# Developer Environment Setup

### RPC Endpoints

<table><thead><tr><th>Network</th><th width="86.41796875">Chain ID</th><th width="276">HTTP RPC</th><th width="150.3046875">WebSocket RPC</th><th width="94.90234375">Status</th></tr></thead><tbody><tr><td>Nexus Mainnet</td><td>3946</td><td><code>https://mainnet.rpc.nexus.xyz</code></td><td><code>wss://mainnet.rpc.nexus.xyz</code></td><td>Live</td></tr><tr><td>Nexus Testnet</td><td>3945</td><td><code>https://testnet.rpc.nexus.xyz</code></td><td><code>wss://testnet.rpc.nexus.xyz</code></td><td>Live</td></tr></tbody></table>

Notes:

* WebSocket connections require an `X-Api-Key` header on the upgrade handshake. Since browsers cannot set custom headers on WebSocket connections, WSS is currently limited to server-side use (Node.js, Python, etc). Browser dApps should use HTTP polling or a server-side relay.
* OFAC-restricted jurisdictions are blocked at the edge. See the [Terms of Use](https://nexus.xyz/terms-of-use), which governs which jurisdictions are restricted.

### Request Limits

| Limit                  | Value  | Error                                 |
| ---------------------- | ------ | ------------------------------------- |
| Max payload size       | 2 MB   | JSON-RPC `-32005`                     |
| Max batch sub-requests | 10     | JSON-RPC `-32005`                     |
| Per-IP rate limiting   | Active | JSON-RPC error + `Retry-After` header |

### Testnet Faucet

Developers can acquire Testnet NEX here: <https://faucet.nexus.xyz>

### Wallet Setup

| Field              | Mainnet                         | Testnet                              |
| ------------------ | ------------------------------- | ------------------------------------ |
| Network Name       | Nexus Mainnet                   | Nexus Testnet                        |
| RPC URL            | `https://mainnet.rpc.nexus.xyz` | `https://testnet.rpc.nexus.xyz`      |
| Chain ID           | `3946`                          | `3945`                               |
| Currency Symbol    | `NEX`                           | `NEX`                                |
| Block Explorer URL | `https://explorer.nexus.xyz`    | `https://testnet.explorer.nexus.xyz` |

To add programmatically:

```javascript
await window.ethereum.request({
  method: "wallet_addEthereumChain",
  params: [{
    chainId: "0xF6A",  // 3946 in hex
    chainName: "Nexus Mainnet",
    nativeCurrency: {
      name: "NEX",
      symbol: "NEX",
      decimals: 18,
    },
    rpcUrls: ["https://mainnet.rpc.nexus.xyz"],
    blockExplorerUrls: ["https://explorer.nexus.xyz"],
  }],
});
```

### Hardhat

```bash
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
```

`hardhat.config.ts`:

```typescript
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: "0.8.24",
  networks: {
    nexusMainnet: {
      url: process.env.NEXUS_RPC_URL || "https://mainnet.rpc.nexus.xyz",
      chainId: 3946,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
    nexusTestnet: {
      url: process.env.NEXUS_TESTNET_RPC_URL || "https://testnet.rpc.nexus.xyz",
      chainId: 3945,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
  },
};

export default config;
```

### Foundry

```bash
curl -L https://foundry.paradigm.xyz | bash
foundryup
```

`foundry.toml`:

```toml
[profile.default]
solc_version = "0.8.24"

[rpc_endpoints]
nexus_mainnet = "https://mainnet.rpc.nexus.xyz"
nexus_testnet = "https://testnet.rpc.nexus.xyz"
```

```bash
forge create src/MyContract.sol:MyContract \
  --rpc-url nexus_testnet \
  --chain-id 3945 \
  --private-key $PRIVATE_KEY
```

### Remix

1. Open [Remix IDE](https://remix.ethereum.org/)
2. Deploy & Run tab → "Injected Provider — MetaMask"
3. Ensure MetaMask is connected to Nexus (chain ID 3946 or 3945)

### Environment verification

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
```

Expected: a valid hex block number in the `result` field.

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
```

Expected: `"0xf6a"` (3946) for mainnet, `"0xf69"` (3945) for testnet.


# Deploying on NexusEVM

NexusEVM is fully EVM-compatible.

#### Deployment with Hardhat

```bash
npx hardhat run scripts/deploy.ts --network nexusMainnet
```

Verify deployment:

```bash
cast code 0xYOUR_CONTRACT_ADDRESS --rpc-url https://mainnet.rpc.nexus.xyz
```

#### Deployment with Foundry

See [Developer Environment Setup](https://docs.nexus.xyz/network/building-on-nexus/developer-environment-setup) for configuration.

```bash
forge create src/MyContract.sol:MyContract \
  --rpc-url nexus_mainnet \
  --chain-id 3946 \
  --private-key $PRIVATE_KEY
```

With constructor arguments:

```bash
forge create src/MyContract.sol:MyContract \
  --rpc-url nexus_mainnet \
  --chain-id 3946 \
  --private-key $PRIVATE_KEY \
  --constructor-args "arg1" 42
```

#### Gas

Gas metering follows Ethereum's model. All opcodes cost the same gas as on Ethereum. Gas limits are enforced — out-of-gas transactions revert normally. **Gas is paid in NEX.**

#### Differences from Ethereum

| Difference           | Detail                                                                                                                                      |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Block time           | 1 second (vs. Ethereum's 12s). `block.timestamp` increments faster — time-based logic works, but blocks-as-time proxies behave differently. |
| Finality             | Single-slot. No reorgs, no uncle blocks. One confirmation is sufficient. `block.number` is always authoritative.                            |
| Transaction ordering | CometBFT default mempool (FIFO). No gas auction, no MEV competition.                                                                        |
| Native currency      | NEX. `msg.value` transfers NEX.                                                                                                             |
| ENS                  | Not available on NexusEVM.                                                                                                                  |

#### Composability with the Exchange

When the Exchange launches, NexusEVM contracts will be able to call into NexusCore — placing orders, reading market data, and managing margin atomically within a single EVM transaction.


# Tokens and Bridges

## NEX

NEX is the native token of the Nexus blockchain; gas is paid in NEX. WNEX is the wrapped ERC-20 version of NEX on the Nexus blockchain, and on Ethereum NEX also exists as an ERC-20. See [NEX](/network/building-on-nexus/tokens-and-bridges/usdnex) for details.

## USDX *(In Development)*

USDX will be the margin and quote currency of the Nexus Exchange — traders will use it to open positions and settle trades. It is in development in collaboration with M0 Protocol.

## Bridging

NEX moves across chains through two mechanisms:

* The **canonical NEX bridge** connects the Nexus blockchain and Ethereum.
* A **Hyperlane Warp Route** connects Ethereum and Binance Smart Chain (BSC): NEX is locked on Ethereum and a synthetic NEX is minted on BSC.

See [Bridging](/network/building-on-nexus/tokens-and-bridges/bridging) for contracts and step-by-step instructions.


# NEX

NEX is the native token of the Nexus blockchain. On Ethereum, it exists as an ERC-20. On BSC, it exists as a Hyperlane synthetic ERC-20.

#### What NEX does

| Role                | Detail                                                           |
| ------------------- | ---------------------------------------------------------------- |
| Gas                 | Every transaction on Nexus pays gas in NEX                       |
| Smart contracts     | Executing smart contracts deployed on the Nexus blockchain       |
| Network interaction | Interacting with the network's consensus and compute networks    |
| Fee discounts       | Holding NEX provides trading-fee discounts on the Nexus Exchange |

#### Supply

| Metric       | Value                      |
| ------------ | -------------------------- |
| Total supply | 100,000,000,000,000 (100T) |

See [Tokenomics](/overview/tokenomics) for total supply, token utility, distribution status, and the vesting schedule.

#### Contract addresses

| Network       | Address                                      | Type                       |
| ------------- | -------------------------------------------- | -------------------------- |
| Nexus Mainnet | NA                                           | Native gas token           |
| Ethereum      | `0xf57D49646621F563b0B905aFc8336923AC569Ec5` | ERC-20                     |
| BSC           | `0x365DE036A1F7dcCb621530d517133521debB2013` | Hyperlane synthetic ERC-20 |

The ERC-20 contract includes mint and burn functions used for bridge operations via the canonical NEX bridge.

#### Wrapped NEX

WNEX is a standard WETH9/ERC-20 wrapper — `deposit`, `withdraw`, `transfer`, `approve` all work as expected.

WNEX has 18 decimals.

| Contract             | Network              | Address                                      |
| -------------------- | -------------------- | -------------------------------------------- |
| WNEX (Wrapped Nexus) | Nexus Mainnet (3946) | `0x69BFfB3C9cb3aa81E8ab23dad470246956D2cC7F` |

#### Informational Links

<table><thead><tr><th width="284.21484375">Platform</th><th width="435.6953125">Link</th></tr></thead><tbody><tr><td>CoinMarketCap</td><td><a href="https://coinmarketcap.com/currencies/nexus-labs/">https://coinmarketcap.com/currencies/nexus-labs/</a></td></tr><tr><td>CoinGecko</td><td><a href="https://www.coingecko.com/en/coins/nexus-4">https://www.coingecko.com/en/coins/nexus-4</a></td></tr><tr><td>Etherscan</td><td><a href="https://etherscan.io/token/0xf57d49646621f563b0b905afc8336923ac569ec5">https://etherscan.io/token/0xf57d49646621f563b0b905afc8336923ac569ec5</a></td></tr></tbody></table>

#### Adding to your wallet

**Nexus (MetaMask or EVM wallet)**

Add the Nexus network to MetaMask. NEX appears automatically as the native gas token — no token import needed.

**Ethereum (ERC-20)**

Import the token in MetaMask:

1. Switch to Ethereum Mainnet
2. Click **Import tokens**
3. Enter contract address: `0xf57D49646621F563b0B905aFc8336923AC569Ec5`
4. Symbol and decimals auto-populate: `NEX`, `18`

**BSC (Hyperlane synthetic)**

Import the token in MetaMask:

1. Switch to BNB Smart Chain
2. Click Import tokens
3. Enter contract address: `0x365DE036A1F7dcCb621530d517133521debB2013`
4. Symbol and decimals auto-populate: NEX, 18


# Bridging

NEX moves between Ethereum and BSC via a Hyperlane Warp Route. On Ethereum, NEX is locked in a collateral contract. On BSC, a synthetic NEX is minted 1:1 against the locked balance.

You can bridge NEX between Ethereum and BSC: [Nexus Hyperlane Warp Route](https://nexus.hyperlane.xyz/?origin=ethereum\&originToken=NEX\&destination=bsc\&destinationToken=NEX).

**Ethereum → BSC**

1. Deposit ERC-20 NEX into the Hyperlane collateral contract on Ethereum.
2. Hyperlane validators attest to the deposit (6-of-9 multisig threshold).
3. Synthetic NEX is minted on BSC and delivered to your wallet.

**BSC → Ethereum**

1. Synthetic NEX is burned on BSC.
2. Hyperlane validators attest to the burn (4-of-6 multisig threshold).
3. ERC-20 NEX is released from the collateral contract on Ethereum and delivered to your wallet.

**Contract addresses**

| Contract          | Ethereum                                                  | BSC                                                      |
| ----------------- | --------------------------------------------------------- | -------------------------------------------------------- |
| Warp Route token  | `0xb71183f48F7f7789D40551149bE28a51f9A97F9A` (collateral) | `0x365DE036A1F7dcCb621530d517133521debB2013` (synthetic) |
| NEX ERC-20        | `0xf57D49646621F563b0B905aFc8336923AC569Ec5`              | —                                                        |
| Hyperlane Mailbox | `0xc005dc82818d67AF737725bD4bf75435d065D239`              | `0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4`             |

The collateral contract is a TransparentUpgradeableProxy pointing to the Hyperlane canonical HypERC20Collateral implementation. The BSC synthetic is a TransparentUpgradeableProxy pointing to the Hyperlane canonical HypERC20 implementation. Both are verified on Etherscan and BscScan.

**Fees and timing**

| Parameter              | Value                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------- |
| Bridge fee (ETH → BSC) | 6 bps + gas fees on both source and destination chains                                 |
| Bridge fee (BSC → ETH) | Gas fees on both source and destination chains                                         |
| Transfer time          | Typically under 5 minutes. Depends on validator attestation and source-chain finality. |

**Security**

The bridge uses Hyperlane's Interchain Security Module (ISM) stack:

* ETH → BSC: 6-of-9 validator multisig with dual MerkleRoot and MessageId verification.
* BSC → ETH: 4-of-6 validator multisig with dual MerkleRoot and MessageId verification.

Both warp route contracts are owned by the Nexus Foundation multisig. Proxy upgrades are controlled by the Foundation multisig via OpenZeppelin ProxyAdmin contracts.

**Troubleshooting**

Transfer pending — Transfers require attestation from Hyperlane validators after source-chain finality. If pending for more than 10 minutes, check the [Hyperlane Explorer](https://explorer.hyperlane.xyz/).

Insufficient gas — You need gas tokens on the source chain: ETH for Ethereum → BSC, BNB for BSC → Ethereum.

Transaction reverted — Check that you have approved sufficient token allowance for the Warp Route collateral contract (Ethereum side) and have enough gas on the source chain.

For Hyperlane protocol details, see [Hyperlane documentation](https://docs.hyperlane.xyz/).


# Endpoints

For RPC URLs, chain IDs, wallet setup, and developer tooling configuration, see [Developer Environment Setup](https://docs.nexus.xyz/network/building-on-nexus/developer-environment-setup).

## RPC

### Request Format

All requests use the standard JSON-RPC 2.0 envelope:

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_blockNumber",
    "params": [],
    "id": 1
  }'
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x2c620e"
}
```

#### Batch Requests

Send an array of request objects to execute multiple calls in a single HTTP round-trip. Maximum **10 sub-requests** per batch.

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '[
    {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1},
    {"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":2},
    {"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":3}
  ]'
```

### Request limits

| Limit                  | Value        | Error                                 |
| ---------------------- | ------------ | ------------------------------------- |
| Max payload size       | 2 MB         | JSON-RPC `-32005`                     |
| Max batch sub-requests | 10 per batch | JSON-RPC `-32005`                     |
| Per-IP rate limiting   | Active       | JSON-RPC error + `Retry-After` header |

All rejections return standard JSON-RPC error responses. When rate-limited, back off and retry after the duration specified in the `Retry-After` header.

### Supported Methods

#### `eth` Namespace

Standard Ethereum execution API methods. All methods accept `latest`, `earliest`, `pending`, or a hex block number as the block parameter unless noted otherwise.

**Reading Chain State**

| Method                     | Description                                                                             |
| -------------------------- | --------------------------------------------------------------------------------------- |
| `eth_blockNumber`          | Returns the current block height.                                                       |
| `eth_chainId`              | Returns the chain ID (`0xf6a` for mainnet, `0xf69` for testnet).                        |
| `eth_gasPrice`             | Returns the current gas price in wei.                                                   |
| `eth_maxPriorityFeePerGas` | Returns the suggested priority fee (tip) in wei.                                        |
| `eth_feeHistory`           | Returns base fee and gas usage history for a range of blocks.                           |
| `eth_blobBaseFee`          | Returns the blob base fee. Always returns `0x1` on Nexus (EIP-4844 blobs are not used). |
| `eth_syncing`              | Returns `false` when synced, or sync progress object when syncing.                      |

**Example: `eth_gasPrice`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}'
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x6b"
}
```

**Accounts and Balances**

| Method                    | Parameters                                | Description                                                       |
| ------------------------- | ----------------------------------------- | ----------------------------------------------------------------- |
| `eth_getBalance`          | `address`, `blockNumber`                  | Returns the NEX balance of an address in wei.                     |
| `eth_getTransactionCount` | `address`, `blockNumber`                  | Returns the nonce (number of transactions sent from the address). |
| `eth_getCode`             | `address`, `blockNumber`                  | Returns the contract bytecode at the address, or `0x` for EOAs.   |
| `eth_getStorageAt`        | `address`, `position`, `blockNumber`      | Returns the value in a contract's storage slot.                   |
| `eth_getProof`            | `address`, `storageKeys[]`, `blockNumber` | Returns the Merkle proof for an account and its storage slots.    |
| `eth_accounts`            | —                                         | Returns an empty array (no managed accounts on public RPC).       |

**Example: `eth_getBalance`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_getBalance",
    "params": ["0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18", "latest"],
    "id": 1
  }'
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x0"
}
```

**Blocks**

| Method                                 | Parameters                        | Description                                                                                                                                                     |
| -------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `eth_getBlockByNumber`                 | `blockNumber`, `fullTransactions` | Returns block data by number. Set `fullTransactions` to `true` for full tx objects, `false` for tx hashes only.                                                 |
| `eth_getBlockByHash`                   | `blockHash`, `fullTransactions`   | Returns block data by hash.                                                                                                                                     |
| `eth_getBlockTransactionCountByNumber` | `blockNumber`                     | Returns the number of transactions in a block by number.                                                                                                        |
| `eth_getBlockTransactionCountByHash`   | `blockHash`                       | Returns the number of transactions in a block by hash.                                                                                                          |
| `eth_getBlockReceipts`                 | `blockNumberOrTag`                | Returns all transaction receipts for a block. Accepts a block number, block hash, or block tag. (reth extension — not part of the base Ethereum JSON-RPC spec.) |

**Example: `eth_getBlockByNumber`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_getBlockByNumber",
    "params": ["latest", false],
    "id": 1
  }'
```

Returns the full block header plus an array of transaction hashes (or full transaction objects if the second parameter is `true`).

**Transactions**

| Method                                    | Parameters                   | Description                                                                           |
| ----------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------- |
| `eth_getTransactionByHash`                | `txHash`                     | Returns transaction data by hash, or `null` if not found.                             |
| `eth_getTransactionReceipt`               | `txHash`                     | Returns the receipt for a mined transaction, or `null` if pending/unknown.            |
| `eth_getTransactionByBlockHashAndIndex`   | `blockHash`, `index` (hex)   | Returns a transaction by block hash and position index (e.g., `"0x0"` for the first). |
| `eth_getTransactionByBlockNumberAndIndex` | `blockNumber`, `index` (hex) | Returns a transaction by block number and position index.                             |
| `eth_sendRawTransaction`                  | `signedTxData`               | Submits a signed transaction to the network. Returns the transaction hash.            |

**Example: `eth_sendRawTransaction`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_sendRawTransaction",
    "params": ["0x02f870..."],
    "id": 1
  }'
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x..."
}
```

Replace the params value with your full RLP-encoded signed transaction. The transaction must be signed with a valid chain ID (`3946` for mainnet, `3945` for testnet). EIP-1559 (type 2) transactions are supported. The transaction object accepts `maxFeePerGas` and `maxPriorityFeePerGas` fields for fee specification.

**Logs and Filters**

| Method                            | Parameters     | Description                                                                                           |
| --------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
| `eth_getLogs`                     | `filterObject` | Returns logs matching the filter. Large block ranges may be routed to the archive node automatically. |
| `eth_newFilter`                   | `filterObject` | Creates a log filter, returns a filter ID. Poll with `eth_getFilterChanges`.                          |
| `eth_newBlockFilter`              | —              | Creates a filter for new blocks. Poll with `eth_getFilterChanges`.                                    |
| `eth_newPendingTransactionFilter` | —              | Creates a filter for pending transactions. Poll with `eth_getFilterChanges`.                          |
| `eth_getFilterChanges`            | `filterId`     | Returns new entries since the last poll for the given filter ID.                                      |
| `eth_getFilterLogs`               | `filterId`     | Returns all logs matching the filter (equivalent to `eth_getLogs` for the filter's parameters).       |
| `eth_uninstallFilter`             | `filterId`     | Removes a filter. Filters also expire after a period of inactivity.                                   |

The filter object accepts `fromBlock`, `toBlock`, `address`, and `topics` fields. Keep block ranges small for best performance. For real-time log streaming, prefer WebSocket subscriptions over HTTP filter polling.

**Example: `eth_getLogs`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_getLogs",
    "params": [{
      "fromBlock": "0x2c6200",
      "toBlock": "0x2c620e",
      "address": "0x69BFfB3C9cb3aa81E8ab23dad470246956D2cC7F"
    }],
    "id": 1
  }'
```

**Execution**

| Method                 | Parameters                | Description                                                                      |
| ---------------------- | ------------------------- | -------------------------------------------------------------------------------- |
| `eth_call`             | `txObject`, `blockNumber` | Executes a call without creating a transaction. Used for reading contract state. |
| `eth_estimateGas`      | `txObject`, `blockNumber` | Estimates the gas required to execute a transaction.                             |
| `eth_createAccessList` | `txObject`, `blockNumber` | Creates an EIP-2930 access list for a transaction.                               |

**Example: `eth_call`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [{
      "to": "0x69BFfB3C9cb3aa81E8ab23dad470246956D2cC7F",
      "data": "0x18160ddd"
    }, "latest"],
    "id": 1
  }'
```

`0x18160ddd` is the function selector for `totalSupply()`. Replace `to` with any contract address and `data` with the appropriate ABI-encoded call data.

#### `net` Namespace

| Method          | Description                                                                                                                                                           |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `net_version`   | Returns the network ID as a decimal string (`"3946"` for mainnet, `"3945"` for testnet). Note: this returns a decimal string, unlike `eth_chainId` which returns hex. |
| `net_peerCount` | Returns the number of connected peers.                                                                                                                                |
| `net_listening` | Returns `true` if the node is listening for connections.                                                                                                              |

#### `web3` Namespace

| Method               | Description                                                                      |
| -------------------- | -------------------------------------------------------------------------------- |
| `web3_clientVersion` | Returns the client identifier (`reth/v1.10.2-8e3b5e6/x86_64-unknown-linux-gnu`). |
| `web3_sha3`          | Returns the Keccak-256 hash of the given data.                                   |

## WebSocket

WebSocket connections (`wss://mainnet.rpc.nexus.xyz`, `wss://testnet.rpc.nexus.xyz`) require an `X-Api-Key` header on the upgrade handshake.

Since browsers cannot set custom headers on WebSocket connections, WSS is currently limited to server-side use (Node.js, Python, etc.). Browser dApps should use HTTP polling or a server-side relay.

**Subscription Methods:**

| Method            | Description                                |
| ----------------- | ------------------------------------------ |
| `eth_subscribe`   | Opens a subscription for real-time events. |
| `eth_unsubscribe` | Closes an active subscription.             |

**Subscription Types:**

| Type                     | Description                                                                                        |
| ------------------------ | -------------------------------------------------------------------------------------------------- |
| `newHeads`               | Emits a header object each time a new block is produced (\~1 per second).                          |
| `logs`                   | Emits log entries matching a filter in real time. Accepts the same filter object as `eth_getLogs`. |
| `newPendingTransactions` | Emits transaction hashes as they enter the mempool.                                                |

**Example: Subscribe to new blocks (Node.js)**

```javascript
const WebSocket = require("ws");

const ws = new WebSocket("wss://mainnet.rpc.nexus.xyz", {
  headers: { "X-Api-Key": "YOUR_API_KEY" },
});

ws.on("open", () => {
  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    method: "eth_subscribe",
    params: ["newHeads"],
    id: 1,
  }));
});

ws.on("message", (data) => {
  const msg = JSON.parse(data);
  if (msg.params) {
    console.log("New block:", parseInt(msg.params.result.number, 16));
  }
});
```

**Example: Subscribe to new blocks (Python)**

```python
import asyncio
import json
import websockets

async def subscribe():
    async with websockets.connect(
        "wss://mainnet.rpc.nexus.xyz",
        additional_headers={"X-Api-Key": "YOUR_API_KEY"},
    ) as ws:
        await ws.send(json.dumps({
            "jsonrpc": "2.0",
            "method": "eth_subscribe",
            "params": ["newHeads"],
            "id": 1,
        }))
        async for message in ws:
            msg = json.loads(message)
            if "params" in msg:
                block_num = int(msg["params"]["result"]["number"], 16)
                print(f"New block: {block_num}")

asyncio.run(subscribe())
```

## Archive & Historical Data

The default RPC endpoint routes to full nodes. Debug, trace, and historical queries route through a separate archive path.

#### Method Routing

| Method category                                                     | Default RPC (`/`)               | Archive (`/archive`) |
| ------------------------------------------------------------------- | ------------------------------- | -------------------- |
| Standard methods (`eth_blockNumber`, `eth_getBalance@latest`, etc.) | Yes                             | No                   |
| `debug_*`                                                           | No (`-32601`)                   | Yes                  |
| `trace_*`                                                           | No (`-32601`)                   | Yes                  |
| `eth_getLogs` with block range                                      | Routed to archive automatically | Yes                  |

#### Archive and Debug Methods

Debug and trace methods are not available on the default RPC endpoint. They are served from a separate archive node at the `/archive` path.

| Method                     | Endpoint   | Description                                                         |
| -------------------------- | ---------- | ------------------------------------------------------------------- |
| `debug_traceTransaction`   | `/archive` | Returns the execution trace of a transaction.                       |
| `debug_traceBlockByNumber` | `/archive` | Returns execution traces for all transactions in a block.           |
| `debug_traceBlockByHash`   | `/archive` | Returns execution traces for all transactions in a block (by hash). |
| `trace_block`              | `/archive` | Returns Parity-style traces for all transactions in a block.        |
| `trace_transaction`        | `/archive` | Returns Parity-style traces for a single transaction.               |
| `trace_replayTransaction`  | `/archive` | Re-executes a transaction and returns the requested trace types.    |

Standard methods (`eth_blockNumber`, `eth_chainId`, etc.) are **rejected** on the `/archive` path. Use the default endpoint for those.

**Example: `debug_traceTransaction`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz/archive \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "debug_traceTransaction",
    "params": ["0xYOUR_TX_HASH", {"tracer": "callTracer"}],
    "id": 1
  }'
```

The `callTracer` and `prestateTracer` built-in tracers are both supported.

**Example: `trace_block`**

```bash
curl -s -X POST https://mainnet.rpc.nexus.xyz/archive \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "trace_block",
    "params": ["0x2c620e"],
    "id": 1
  }'
```

Pass a hex-encoded block number. Block tags like `"latest"` are not accepted by `trace_block`.

#### Archive endpoint

Append `/archive` to the RPC URL:

```
# Testnet archive
curl -s -X POST https://testnet.rpc.nexus.xyz/archive \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["<tx_hash>"],"id":1}'
```

```
# Mainnet archive
curl -s -X POST https://mainnet.rpc.nexus.xyz/archive \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"trace_block","params":["latest"],"id":1}'
```

Standard methods (`eth_blockNumber`, `eth_chainId`, etc.) are rejected on the `/archive` path — use the default endpoint for those.

The archive node is not directly addressable. All requests route through the gateway.

### Error Codes

| Code     | Message          | Cause                                                                     |
| -------- | ---------------- | ------------------------------------------------------------------------- |
| `-32700` | Parse error      | Malformed JSON in request body                                            |
| `-32600` | Invalid request  | Missing required JSON-RPC fields                                          |
| `-32601` | Method not found | Method not supported or called on wrong endpoint (e.g., `debug_*` on `/`) |
| `-32602` | Invalid params   | Wrong number or type of parameters                                        |
| `-32603` | Internal error   | Server-side failure                                                       |
| `-32005` | Limit exceeded   | Payload too large or batch size exceeded                                  |


# Nodes

#### Full nodes

Full nodes replicate chain state and serve RPC queries. No staking or proof generation required.

Get in touch with our team if you would like to run your own full node: <growth@nexus.xyz>

#### Provers

Provers generate validity proofs using the Nexus zkVM. The Compute Network is live on testnet and will expand to mainnet proof generation.

#### Validators

The Nexus blockchain is secured by a validator set running NexusBFT consensus. The set is permissioned at launch and will expand over time.


# Overview

A prover is a node in the Compute Network that generates validity proofs of execution. The Compute Network is live on testnet. Mainnet proof generation will launch alongside the Nexus Exchange.

To run a prover on testnet today, see [Proving via CLI](/network/nodes/proving-via-cli).

#### What provers will prove

* Exchange execution — order matching, positions, risk, liquidations (NexusCore, 200 ms blocks)
* Smart contract execution — EVM transactions (NexusEVM, 1s blocks)

Proof coverage will expand incrementally. Initial scope and rollout schedule will be published before mainnet proof generation begins.

#### Requirements

Hardware, network, and software requirements will be published when benchmarks are finalized. Proof generation is computationally intensive — expect requirements significantly higher than full node minimums.

#### Operational expectations

* **Availability** — must be online and ready to accept proof tasks
* **Task completion** — assigned tasks must be completed within the required time window
* **Monitoring** — operators should monitor performance, disk space, and connectivity
* **Upgrades** — prover software must be kept up to date

Provers that fail to meet these expectations will face slashing. Staking requirements and slashing parameters are not yet finalized.


# Proving via CLI

#### Introduction <a href="#introduction" id="introduction"></a>

The [Nexus Network CLI](https://github.com/nexus-xyz/nexus-cli) is a command-line tool for contributing compute resources to the network.

#### Install Script <a href="#install-script" id="install-script"></a>

Quick install (scripted):

```
curl https://cli.nexus.xyz/compute | sh
```

> The bare `cli.nexus.xyz` one-liner now installs the Nexus **Exchange** CLI. The compute/prover CLI installs from the `cli.nexus.xyz/compute` path shown above. Existing installs keep working — the `nexus-network` command is unchanged.

After installing, restart or refresh your terminal (e.g. `source ~/.bashrc`, `source ~/.zshrc`, etc.). To start with an existing node ID:

```
nexus-network start --node-id <your-node-id>
```

Alternatively, register your wallet address and create a node ID with the CLI, or at app.nexus.xyz:

```
nexus-network register-user --wallet-address <your-wallet-address>
nexus-network register-node
nexus-network start
```

The `register-user` and `register-node` commands will save your credentials to `~/.nexus/credentials.json`. To clear credentials, run:

```
nexus-network logout
```


# Tooling and Infrastructure

#### Development toolchains

| Tool                                                   | Status                                                     |
| ------------------------------------------------------ | ---------------------------------------------------------- |
| [Foundry](https://book.getfoundry.sh/)                 | Supported — forge, cast, anvil                             |
| [Hardhat](https://hardhat.org/)                        | Supported                                                  |
| [Remix](https://remix.ethereum.org/)                   | Supported via Injected Provider                            |
| [OpenZeppelin](https://www.openzeppelin.com/contracts) | Supported — ERC-20, ERC-721, ERC-1155, proxies, governance |

#### RPC providers

| Provider                                       | Mainnet                                                                                                                    | Testnet                         | Notes                |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------------------- |
| **Nexus (official)**                           | <p><code><https://mainnet.rpc.nexus.xyz></code><br><br><code>wss\://mainnet.rpc.nexus.xyz</code></p>                       | `https://testnet.rpc.nexus.xyz` | —                    |
| [Dteam](https://dteam.tech/)                   | <p><code><https://evm-rpc.nexus.mainnet.dteam.tech></code><br><br><code>wss\://evm-rpc.nexus.mainnet.dteam.tech</code></p> | —                               | Rate-limit: 1000 RPS |
| [ThirdWeb](https://thirdweb.com/nexus-mainnet) | `https://3946.rpc.thirdweb.com`                                                                                            | —                               | —                    |

#### Wallets

| Wallet         | Type                       | Notes                                                                              |
| -------------- | -------------------------- | ---------------------------------------------------------------------------------- |
| MetaMask       | Browser extension / mobile | Add network with chain ID 3946 (setup guide)                                       |
| Any EVM wallet | —                          | WalletConnect, Rabby, Coinbase Wallet — any wallet that supports custom EVM chains |

#### Bridges

| Bridge                                                                                                            | Route          | Link                                                                                                |
| ----------------------------------------------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------- |
| [Hyperlane](https://nexus.hyperlane.xyz/?origin=ethereum\&originToken=NEX\&destination=bsc\&destinationToken=NEX) | Ethereum ↔ BSC | <https://nexus.hyperlane.xyz/?origin=ethereum&originToken=NEX&destination=bsc&destinationToken=NEX> |

#### SAFE Multi-sig

| Provider | Link                                  | Notes                                        |
| -------- | ------------------------------------- | -------------------------------------------- |
| Palmera  | <https://safe.palmeradao.xyz/welcome> | UI and Backend for SAFE Multi-sig management |

#### Block explorers

| Explorer       | URL                          |
| -------------- | ---------------------------- |
| Nexus Explorer | <https://explorer.nexus.xyz> |

#### Libraries and SDKs

| Library                                                    | Language              | Notes     |
| ---------------------------------------------------------- | --------------------- | --------- |
| [ethers.js](https://docs.ethers.org/)                      | TypeScript/JavaScript | Supported |
| [viem](https://viem.sh/)                                   | TypeScript            | Supported |
| [web3.py](https://web3py.readthedocs.io/)                  | Python                | Supported |
| [Foundry cast](https://book.getfoundry.sh/reference/cast/) | CLI                   | Supported |


# Security Audits

Independent security reviews of Nexus components. The full reports are published on the [Nexus Security Audits page](https://docs.nexus.xyz/extra/security-audits) and can be downloaded there; each is listed below.

## NEX

Smart-contract audit of the NEX token and bridge contracts.

{% file src="/files/UNdFA8VldeOHmKg722WL" %}

## Nexus zkVM

Hybrid application security assessment of the Nexus zkVM (2025), with the follow-up remediation report.

{% file src="/files/LS1otMwjVtOjPodemAl1" %}

{% file src="/files/QyVyWYbxdBKMOew6swJL" %}


# The Nexus zkVM

![](/files/HE65vXy8PApI1XT5JYiK)

The Nexus zero-knowledge virtual machine is a modular, extensible, prover-optimized, fully-specified zkVM written in Rust, focused on performance and security. [Nexus zkVM v0.3.6](https://github.com/nexus-xyz/nexus-zkvm/releases/tag/v0.3.6) is the current stable release, implementing the [Nexus zkVM 3.0 machine](/zkvm/specifications/the-complete-specification).

## What is the Nexus zkVM?

Nexus zkVM is a [zero-knowledge virtual machine](/zkvm/overview/zkvm-overview) that enables developers to generate succinct proofs for any computation, demonstrating that a program executed every instruction and accessed memory correctly to produce a given output. Built with Rust and focused on performance and security, it provides a robust foundation for building applications.

## Proving Computation

The Nexus zkVM can generate proofs for any computation. For example, given a Rust program that calculates the Fibonacci sequence:

{% code title="fib.rs" %}

```rust
#![cfg_attr(target_arch = "riscv32", no_std, no_main)]

use nexus_rt::println;

fn fib(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n - 1) + fib(n - 2),
    }
}

#[nexus_rt::main]
fn main() {
    let n = 7;
    let result = fib(n);
    assert_eq!(result, 13);
    println!("fib({}) = {}", n, result);
}
```

{% endcode %}

the zkVM can generate a succinct, efficiently verifiable proof of its correct execution. To get started with the Nexus zkVM, check out the [Getting Started](/zkvm/development/getting-started) page.

{% hint style="warning" %}
The Nexus zkVM is in an experimental stage and is not currently recommended for production use.
{% endhint %}

## Key Features

### Security First

* The Nexus zkVM provides fully-specified cryptographic components with careful analysis of security and performance.
* The system includes no code obfuscation, no proprietary components, and no closed-source code.
* Source-available transparency ensures complete auditability for all users.

### Performance Optimized

* The prover-optimized architecture enables efficient proof generation across diverse workloads.
* Sensible defaults are configured to work out of the box without complex setup requirements.

### Developer Friendly

* Developers can write programs with execution proven to be correct.
* A comprehensive SDK and tooling ecosystem supports rapid development and deployment.
* Extensive documentation and examples guide users through implementation details.

### Extensible & Modular

* The system supports new languages, new precompiles, and new provers as the state-of-the-art advances.
* Configurable components prevent vendor lock-in and allow customization for specific use cases.
* Source-available code and consistent development by the Nexus zkVM team ensure long-term reliability.

## The Nexus Ethos: Assurance through Open Science

We believe a zkVM must provide an efficient proving mechanism without compromising on security and correctness. A zkVM cannot provide transparency without being transparent itself. Every component of a zkVM should be powered by fully and publicly specified cryptographic components, with careful analysis of security and performance. The Nexus zkVM features no code obfuscation, no proprietary components, and no closed-source code.

## Modular and Extensible Architecture

Built with a modular architecture at its core, the Nexus zkVM features independently optimized components that integrate smoothly. The system ships with carefully chosen defaults for the prover and memory model, allowing developers to start building immediately while maintaining confidence in both security and performance across diverse applications.

Extensibility is a fundamental design principle of the Nexus zkVM. The codebase and active development by the Nexus team ensures continuous evolution, supporting emerging languages, advanced precompiles, and cutting-edge proving systems as the field progresses—all without locking developers into proprietary solutions.

## Use Cases

The Nexus zkVM enables a wide range of applications:

* **Verifiable Computation**: Prove correct execution of any program
* **Privacy-Preserving Applications**: Compute on sensitive data without revealing it
* **Blockchain Scaling**: Generate proofs for off-chain computation
* **Compliance & Auditing**: Provide cryptographic guarantees for regulatory requirements
* **Trustless Systems**: Build applications that don’t require trusted intermediaries

## Getting Started

{% stepper %}
{% step %}

### Quick Start Guide

Get up and running in minutes: <https://docs.nexus.xyz/zkvm/proving/overview>
{% endstep %}

{% step %}

### SDK Documentation

Comprehensive API reference: <https://docs.nexus.xyz/zkvm/proving/sdk>
{% endstep %}

{% step %}

### Architecture Overview

Understand how it works: <https://docs.nexus.xyz/zkvm/architecture>
{% endstep %}

{% step %}

### Example Walkthroughs

Learn through practical examples: <https://docs.nexus.xyz/zkvm/walkthroughs/galeshapley>
{% endstep %}
{% endstepper %}

***

*Experience the future of verifiable computation with Nexus zkVM - where transparency meets performance.*


# zkVM Overview

Zero-Knowledge Virtual Machines (zkVMs) represent an approach to verifiable computation, enabling developers to generate cryptographic proofs that a computation was executed correctly.

## What is a zkVM?

A zero-knowledge virtual machine is a system that can execute programs and generate succinct, verifiable proofs of their correct execution. These proofs demonstrate that a program executed every line of code and accessed memory correctly, culminating in the expected output. Unlike traditional virtual machines that simply run code, zkVMs produce cryptographic evidence that the computation was performed accurately according to the specified program logic.

This fundamental capability transforms how we approach trust in computing. Instead of relying on individual parties or infrastructure to validate computations, zkVMs enable any computation execution to be independently verified. This opens up entirely new possibilities for preserving privacy and ensuring computational integrity across distributed networks.

## Key Concepts

### Zero-Knowledge Proofs

Succinct proofs of correct execution are cryptographic proofs that allow a prover to convince a verifier that a certain computation was performed correctly — without the verifier having to redo the computation themselves. These proofs are typically non-interactive, publicly verifiable, and much smaller and faster to verify than re-executing the original computation.

A zero-knowledge proof is a special type of such proof that reveals nothing beyond the correctness of the statement itself. That is, it allows the verifier to be convinced that the computation was carried out correctly, without learning anything about the inputs, intermediate steps, or any other private data.

Example: For an example of a zero-knowledge proof, see this description of Schnorr’s: <https://www.zkdocs.com/docs/zkdocs/zero-knowledge-protocols/schnorr/>

### Succinct Verification

zkVM proofs are “succinct,” meaning they can be verified much faster than re-executing the original computation. A proof for a program that takes hours to run can often be verified in milliseconds.

### Universal Computation

Modern zkVMs are designed to handle arbitrary computations, not just specific mathematical operations. This means developers can write programs in familiar languages and generate proofs for complex business logic.

## How zkVMs Work

{% stepper %}
{% step %}

### Program Execution

The zkVM executes a program step-by-step, tracking all operations and state changes and recording the computational trace.
{% endstep %}

{% step %}

### Proof Generation

The trace is converted into a cryptographic proof using advanced techniques like STARKs, SNARKs, or other proof systems.
{% endstep %}

{% step %}

### Verification

Anyone can verify the proof to confirm the correctness of the trace is proven.
{% endstep %}
{% endstepper %}

## Applications of zkVMs

### Privacy-Preserving Computation

Sensitive computations can be proven correct without revealing private inputs, enabling applications in healthcare, finance, and personal data processing. See walkthroughs at Gale-Shapley and Lambda Calculus:

* <https://docs.nexus.xyz/zkvm/walkthroughs/galeshapley>
* <https://docs.nexus.xyz/zkvm/walkthroughs/lambdacalculus>

### Verifiable AI/ML

Machine learning models can generate proofs of their inference results, ensuring AI decisions are transparent and auditable.

### Compliance and Auditing

Organizations can prove compliance with regulations or internal policies without exposing sensitive business data.

### Blockchain Scaling

zkVMs enable Layer 2 scaling solutions by allowing complex computations to be performed off-chain while maintaining on-chain verifiability.

***

*Zero-knowledge virtual machines are transforming how we think about computation, privacy, and trust in digital systems.*

Further reading:

* The Nexus zkVM: <https://docs.nexus.xyz/zkvm/nexus-zkvm>
* Architecture: <https://docs.nexus.xyz/zkvm/architecture>


# Architecture

The ethos of the Nexus project is a commitment to open and transparent science and engineering. For an in-depth look at the science behind the Nexus zkVM, see the [specification](/zkvm/specifications/the-complete-specification).

### Core Components

The Nexus zkVM features a modular and extensible architecture built from highly optimized components:

* **The Nexus Runtime**: A feature-rich runtime environment that streamlines guest program development for the Nexus zkVM, with full support for public and private inputs, public outputs, logging, and performance benchmarking — all in native Rust syntax.
* **The Nexus zkVM Machine Architecture**: A custom designed and from-scratch implemented and purpose-built RISC-V virtual machine in a modified Harvard architecture, designed to optimize prover performance through careful memory management in support of a “only prove what you use” model.
* **The Nexus zkVM**: A fully-specified Algebraic Intermediate Representation (AIR) arithmetization of the machine architecture, featuring comprehensive constraints for the full RISC-V32im instruction set alongside efficient offline memory checking.
* **The Stwo Prover:** An integration with StarkWare’s powerful [Stwo prover](https://github.com/starkware-libs/stwo) (which has since rebranded from Stwo to S-two), a state-of-the-art Circle STARK with excellent performance characteristics.

Every component within the Nexus zkVM has been carefully chosen or designed from scratch by the [Nexus team](https://nexus.xyz/aboutus) to maximize security, performance, modularity, and extensibility.

As a consequence of the architectural foundation, the Nexus zkVM is architected to support new theoretical developments led by our research team, as well as tooling to accelerate deploying the zkVM for a variety of use cases:

* **Proving Schemes**: Beyond the Stwo prover, the Nexus zkVM maintains compatibility with the Nova family of folding schemes and supports integration of emerging proving constructions as cryptographic research advances.
* **Precompiles**: Precompiles are extensions to the instruction set of the machine architecture, supporting common operations (e.g., cryptographic hashing like Keccak, or matrix multiplication) that the zkVM can use to accelerate specific computations. Developers will be able to extend the zkVM with their own custom precompiles, as well as import those published by other developers.
* **Developer Tooling:** The Nexus zkVM comes with a comprehensive SDK that streamlines application development with APIs that enable efficient development workflows regardless of use case complexity.
* **Language Support**: As the Nexus zkVM implements RISC-V ISA, the zkVM can run programs written in most high-level languages (e.g., Rust, C++, etc.) given its common availability as a computation target.

The zkVM aims to offer developers out-of-the-box prover performance and security, designed to power numerous applications.

### Proving Architectures

The Nexus zkVM turns programs into proofs, but the computational work of executing and proving the zkVM must be implemented by a proving architecture. The Nexus zkVM’s flexible design supports diverse proving architectures, ranging from local sequential execution on personal devices to the massively parallel [Nexus Network](https://docs.nexus.xyz/layer-1/vision/network), a globally-distributed proving infrastructure currently in active development.

Related:

* [zkVM Overview](/zkvm/overview/zkvm-overview)
* [Getting Started](/zkvm/development/getting-started)


# Getting Started

The Nexus zkVM provides both a comprehensive runtime for streamlined program development and a powerful SDK for configuring, executing, and generating proofs for your applications.

{% hint style="info" %}
Recommended starting points:

* SDK Quick Start — step-by-step guidance to begin proving with the Nexus zkVM.
* Runtime — guidance for writing guest programs for the zkVM and developing host programs to manage the proving process.
  {% endhint %}

## Key resources

* <a href="/pages/b513e7019934931e4eb130a2a505ee8de62ff8cd" class="button primary">SDK Quick Start</a>
* <a href="/pages/e75c829d318bf54f601847cafcef10908d241a05" class="button primary">Runtime</a>

Additional references:

* SDK Documentation: <https://docs.nexus.xyz/zkvm/proving/sdk-docs>
* Benchmarking your host project: <https://docs.nexus.xyz/zkvm/proving/sdk-benchmarking>
* Integrating Precompiles: <https://docs.nexus.xyz/zkvm/proving/precompiles>

(Links preserved with original query parameters and targets.)


# SDK Quick Start

The Nexus SDK provides simple, misuse-resistant programmatic use of the Nexus zkVM.

{% stepper %}
{% step %}

### Install the Nexus zkVM

First, install Rust: <https://www.rust-lang.org/tools/install>. Next, install the RISC-V target:

{% code title="Install RISC-V target" %}

```bash
$ rustup target add riscv32i-unknown-none-elf
```

{% endcode %}

Then, install the Nexus zkVM:

{% code title="Install cargo-nexus (v0.3.4)" %}

```bash
$ rustup run nightly-2025-05-09 cargo install --git https://github.com/nexus-xyz/nexus-zkvm cargo-nexus --tag 'v0.3.6'
```

{% endcode %}

And verify the installation:

{% code title="Verify installation" %}

```bash
$ rustup run nightly-2025-05-09 cargo nexus --help
```

{% endcode %}

This should print the available CLI commands. At present, the `cargo nexus` CLI is minimal, providing just a `cargo nexus host` command to setup an SDK based project.
{% endstep %}

{% step %}

### Create a new Nexus host project

To use the zkVM programmatically, you need two programs: a guest program that runs on the zkVM, and a host program that operates the zkVM. Create the project with:

{% code title="Generate host project" %}

```bash
$ rustup run nightly-2025-05-09 cargo nexus host nexus-host
```

{% endcode %}

This will create a new Rust project directory with the following structure:

{% code title="Project structure" %}

```
./nexus-host
├── Cargo.lock
├── Cargo.toml
├── rust-toolchain.toml
└── src
    ├── main.rs
    └── guest
        ├── Cargo.toml
        ├── rust-toolchain.toml
        └── src
            └── main.rs
```

{% endcode %}

Here, `./src/main.rs` is the host program, while `./src/guest/src/main.rs` is the guest program.

Replace `./src/guest/src/main.rs` with this guest program (takes two integers, one public and one private, logs values, returns their product):

{% code title="src/guest/src/main.rs" %}

```rust
#![cfg_attr(target_arch = "riscv32", no_std, no_main)]

use nexus_rt::println;

#[nexus_rt::main]
#[nexus_rt::public_input(x)]
fn main(x: u32, y: u32) -> u32 {
    println!("Read public input:  {}", x);
    println!("Read private input: {}", y);

    x * y
}
```

{% endcode %}

Then replace `./src/main.rs` with this host program (compiles guest, invokes it with public input x = 5 and private input y = 3, reads output and logs, and verifies the proof):

{% code title="src/main.rs" %}

```rust
use nexus_sdk::{
    compile::{cargo::CargoPackager, Compile, Compiler},
    stwo::seq::Stwo,
    ByGuestCompilation, Local, Prover, Verifiable, Viewable,
};

const PACKAGE: &str = "guest";

fn main() {
    println!("Compiling guest program...");
    let mut prover_compiler = Compiler::<CargoPackager>::new(PACKAGE);
    let prover: Stwo<Local> =
        Stwo::compile(&mut prover_compiler).expect("failed to compile guest program");

    let elf = prover.elf.clone(); // save elf for use with test verification

    print!("Proving execution of vm... ");
    let (view, proof) = prover
        .prove_with_input::<u32, u32>(&3, &5)
        .expect("failed to prove program"); // x = 5, y = 3

    assert_eq!(view.exit_code().expect("failed to retrieve exit code"), nexus_sdk::KnownExitCodes::EXIT_SUCCESS as u32);

    let output: u32 = view
        .public_output::<u32>()
        .expect("failed to retrieve public output");
    assert_eq!(output, 15); // z = 15

    println!("output is {}!", output);
    println!(
        ">>>>> Logging\n{}<<<<<",
        view.logs().expect("failed to retrieve debug logs").join("")
    );

    print!("Verifying execution...");
    proof
        .verify_expected::<u32, u32>(
            &5,   // x = 5
            nexus_sdk::KnownExitCodes::EXIT_SUCCESS as u32,
            &15,  // z = 15
            &elf, // expected elf (program binary)
            &[],  // no associated data,
        )
        .expect("failed to verify proof");

    println!("  Succeeded!");
}
```

{% endcode %}

This host program compiles the guest, runs the zkVM with supplied inputs, retrieves output and logs, and verifies the proof of correct execution.
{% endstep %}

{% step %}

### Run your program

Run the host program (which executes and proves the guest program) with:

{% code title="Run host" %}

```bash
$ cargo run -r
```

{% endcode %}

You should see output similar to:

{% code title="Expected output" %}

```
Proving execution of vm... output is 15!
>>>>> Logging
Read public input:  5
Read private input: 3
<<<<<
Verifying execution...  Succeeded!
```

{% endcode %}

For more examples using the SDK with more complicated guest programs, see the walkthroughs for Gale-Shapley stable matching and the lambda calculus:

* <https://docs.nexus.xyz/zkvm/walkthroughs/galeshapley>
* <https://docs.nexus.xyz/zkvm/walkthroughs/lambdacalculus>
  {% endstep %}

{% step %}

### Run in legacy mode

In addition to the Stwo-based Nexus zkVM 3.0 prover, the SDK supports a legacy mode that uses the Nova, HyperNova, and (experimental) Jolt-based Nexus zkVM 2.0 machine. This machine uses a different runtime and requires additional host-side configuration due to public parameters and reference strings.

To use legacy mode, activate the appropriate feature for the `nexus-sdk` dependency in the host program: `legacy-nova`, `legacy-hypernova`, or `legacy-jolt`. Examples of using legacy mode to prove legacy guest programs are provided in the examples folder on GitHub:

* Legacy guest examples: <https://github.com/nexus-xyz/nexus-zkvm/tree/main/examples/legacy/src>
* SDK examples: <https://github.com/nexus-xyz/nexus-zkvm/tree/main/sdk/examples>

The legacy-mode code corresponds to the Nexus zkVM v0.2.4 release:

* <https://github.com/nexus-xyz/nexus-zkvm/tree/releases/0.2.4>
  {% endstep %}
  {% endstepper %}


# SDK Documentation

The Nexus zkVM SDK documentation is available at:

* <https://sdk-docs.nexus.xyz/doc/nexus_sdk/index.html>


# Runtime

## What’s a Runtime?

In the vast majority of modern software development, a developer never writes code that directly interacts with the platform it’s running on. Instead, the platforms for which developers write code offer runtimes: easy-to-use (and often standardized) APIs that provide high-level functionality while abstracting away most platform-specific details. Prominent examples include POSIX and `glibc`. Runtimes also provide controlled and abstracted access to platform-specific functionality that developers do need access to, which is especially relevant for the Nexus zkVM.

## The Nexus Runtime

The zkVM runtime environment is most similar to a bare-metal or embedded environment, though it has properties specific to being a zkVM. As is typical for embedded platforms, interaction with the platform (zkVM) is done via raw `ecall`s, custom instructions, and memory-mapped I/O. Uniquely to zkVMs, however, [the memory model changes fundamentally between executions of the same guest program](/zkvm/overview/architecture) (see the aside below). The Nexus runtime is a set of libraries and macros that allows developers to write higher-level programs that can run efficiently and correctly on the Nexus zkVM, agnostic to the subtleties of the zkVM’s varying executions.

{% hint style="info" %}
Aside: the zkVM’s first pass uses a Harvard-like architecture with distinct memory spaces for code, data, inputs, and output. During the first pass, the zkVM collects statistics about exactly how much memory is used in each of these spaces. Before the second pass, the zkVM lays out the program’s memory in a single address space whose size is minimized based on the statistics collected during the first pass. Because proving memory is expensive, this process significantly reduces proof sizes and proving times. The second pass maintains memory protections but uses a single address space for all memory segments.
{% endhint %}

More specifically, the Nexus runtime consists of a few parts:

* Macros that allow developers to work easily with input and output, which are memory-mapped and directly managed by the zkVM.
* A `main` macro that ensures the guest program’s `main` function can be correctly located and run by the zkVM.
* `print!` / `println!` macros that write to an output log, implemented by the zkVM via an `ecall`.
* A `#[panic_handler]`, required by Rust’s `core`, implemented by the zkVM via an `ecall`.
* A `#[global_allocator]` that operates correctly across the zkVM’s memory models.
* An assembly entry point for the program that configures zkVM-specific global state and calls the guest program’s `main` function; this ensures stack, heap, and input/output segments are usable during both passes of execution.
* Well-known locations that correspond to particular memory-mapped values provided or read by the zkVM.

The rest of this section discusses these components in more detail and with examples. For even more detail, see [the VM specification](/zkvm/specifications/the-complete-specification).

### Notable Macros & Functions

#### Main

The `nexus_rt::main` attribute marks the entry point of the guest program. It is a procedural macro that:

* Ensures that the main function can be located and run by the runtime’s entry-point assembly by re-exporting the main function with a non-mangled, well-known name referenced by the runtime’s entry-point assembly.
* Generates code that automatically reads in all inputs and writes to the output. Unless specified otherwise, inputs default to private.
* Re-orders the function’s attributes to ensure that input type specifications are evaluated before the `main` macro generates code that reads them.

Example use:

```rust
#[nexus_rt::main]
fn main(x: u32, y: u32) -> bool {
    x > y
}
```

The `main` macro also handles the program’s public output, which is simply the value returned from `main`. The macro internally uses the `write_public_output` function to serialize the output and write it to the output tape. The public output is written by the Nexus runtime’s `write_public_output`, which is a function that serializes the program’s output in a [COBS](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) representation [generated by](https://docs.rs/postcard/latest/postcard/fn.to_allocvec_cobs.html) [`postcard`](https://crates.io/crates/postcard).

The serialized bytes are written to the output tape using the Nexus runtime’s `write_output!(byte_index, value)` macro, which writes a byte-addressed word to the output tape by:

{% stepper %}
{% step %}
Read the public output address from well-known location `PUBLIC_OUTPUT_ADDRESS_LOCATION`.
{% endstep %}

{% step %}
Add `byte_index` to that address.
{% endstep %}

{% step %}
Emit a `wou` instruction, whose behavior depends on zkVM execution mode:

* In the zkVM’s first pass (Harvard-like architecture), `wou` writes the word to a unique memory address space that only contains public output.
* In the second pass, the zkVM replaces the binary’s `wou` instructions with ordinary `sw` instructions that write the output values from a location calculated and populated by the zkVM using data collected from the first pass.
  {% endstep %}
  {% endstepper %}

#### Public Input

Public inputs are input values known to the verifier, specified as arguments to `#[nexus_rt::main]` and tagged with `#[nexus_rt::public_input]`.

Example use:

```rust
#[nexus_rt::main]
#[nexus_rt::public_input(x)]
fn main(x: u32, y: u32) -> bool {
    x > y
}
```

In this example, `x` is a public input while `y` is private.

Public inputs are read by the Nexus runtime’s `read_public_input`, which reads the entire public input tape and [deserializes](https://docs.rs/postcard/latest/postcard/fn.from_bytes_cobs.html) it from a [COBS](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) representation [produced by](https://docs.rs/postcard/latest/postcard/fn.to_allocvec_cobs.html) [`postcard`](https://crates.io/crates/postcard).

The serialized bytes are read using the Nexus runtime’s `read_input!(byte_index)` macro, which reads a byte-addressed word from the serialized public input by:

{% stepper %}
{% step %}
Read the public input address from well-known location `PUBLIC_INPUT_ADDRESS_LOCATION`.
{% endstep %}

{% step %}
Add `byte_index` to that address.
{% endstep %}

{% step %}
Emit a `rin` instruction, whose behavior depends on zkVM execution mode:

* In the zkVM’s first pass (Harvard-like architecture), `rin` reads the word from a unique memory address space which only contains public input.
* In the second pass, the zkVM replaces the binary’s `rin` instructions with ordinary `lw` instructions that read the input values from a location calculated and populated by the zkVM using data collected from the first pass.
  {% endstep %}
  {% endstepper %}

#### Private Input

Private inputs are input values known only to the prover. They are specified as arguments to `#[nexus_rt::main]` and optionally tagged with `#[nexus_rt::private_input]`. Arguments default to being private, but developers can explicitly mark them private for clarity.

Example use:

```rust
#[nexus_rt::main]
#[nexus_rt::private_input(y)]
fn main(x: u32, y: u32) -> bool {
    x > y
}
```

In this example, both `x` and `y` are private inputs.

Private inputs are read by the Nexus runtime’s `read_private_input`, which sequentially reads a single byte from the private input tape via an `ecall`, returning `None` after each byte has been read once. The runtime provides no further special handling for private inputs; guest program developers are expected to handle the interpretation of the private input tape themselves.

#### Host-Native Execution

It is often useful to run and debug guest programs in a native environment (e.g., `cargo run`). The Nexus runtime itself cannot run outside the zkVM, but its macros support native handlers—functions compiled and executed only when the guest program is run natively on the host. These functions generate inputs and handle outputs without the zkVM’s presence.

Example:

```rust
#![cfg_attr(target_arch = "riscv32", no_std, no_main)]

#[cfg(not(target_arch = "riscv32"))]
fn native_input_handler() -> Option<u32> {
    let mut input = String::new();

    std::io::stdin().read_line(&mut input).ok()?;

    Some(input.trim().parse().ok()?)
}

#[cfg(not(target_arch = "riscv32"))]
fn native_output_handler(output: &u32) -> Option<()> {
    println!("Output: {}", output);

    Some(())
}

#[nexus_rt::main]
#[cfg_attr(target_arch = "riscv32", nexus_rt::public_input(x))]
#[cfg_attr(not(target_arch = "riscv32"), nexus_rt::custom_input(x, native_input_handler))]
#[cfg_attr(not(target_arch = "riscv32"), nexus_rt::custom_input(y, native_input_handler))]
#[cfg_attr(not(target_arch = "riscv32"), nexus_rt::custom_output(native_output_handler))]
fn main(x: u32, y: u32) -> u32 {
    x ^ 0xdeadbeef
}
```

This allows running the program natively to ensure correctness without relying on the zkVM’s limited debugging capabilities.

#### Functionality in a `no_std` Environment

The Nexus zkVM has no operating system and cannot implement large portions of the Rust standard library, so guest programs must be `no_std`. To help developers, the Nexus runtime implements particular parts of the Rust runtime:

* A simple global allocator and implementations for `panic` and `abort`, as required by `core`.
* Simple `print!` and `println!` implementations that wrap zkVM-specific system calls for logging.

In the future, the team plans to enable `std` for the Nexus zkVM where appropriate by gating functionality dependent on an operating system or incompatible with provable computation. For example, there are no plans to enable multi-threading or general I/O, but certain `std` functionality useful for the zkVM (e.g., some system calls like `exit`, algorithms, and data structures) may be supported.

### Memory Allocation

The Nexus runtime implements a simple allocator required for any data structures that rely on heap allocations (i.e., those in `alloc`). Currently, it uses a naive bump allocator that allocates bottom-up and never deallocates.

## Assumptions

Despite the conveniences offered by the Nexus runtime, there are still some requirements for guest programs:

* The guest program must depend on `nexus-rt` and provide a `nexus_rt::main`-decorated function as the entry point. Without these, the zkVM can’t properly load the program and behavior is undefined.
* The guest program must be annotated with `no_std` and `no_main` when compiled for the zkVM (i.e., for a `riscv32` target architecture).
* The guest program’s code must be position-independent (compiled with `-fPIC`).

The Nexus `cargo` CLI tool ensures these requirements are met for projects it creates; initializing projects with it is highly recommended.

Further reading:

* [SDK Documentation](broken://pages/4e805822d78f1e65c696c75225e9afcd7b1e7dd6)
* [Precompiles](/zkvm/development/precompiles)


# Precompiles

## Introduction

**Precompiles** are custom extensions to the zkVM’s instruction set that accelerate complex operations most efficiently proved by custom circuits.

As a concrete example, consider [Keccak](https://keccak.team/keccak_specs_summary.html), which implements SHA-3 and is used prominently in some blockchains. The Keccak family of hash functions is optimized for performance on real CPUs—they make extensive use of bitwise operations that conventional CPUs can perform efficiently and in parallel. For current zkVM backends, however, proving a round of a Keccak hash function as a sequence of assembly instructions is quite expensive—that sequence of assembly instructions is long, and bitwise operations are each individually complex to express and expensive to constrain. Using a precompile, we can specifically design a monolithic, optimized circuit for proving a round of a Keccak hash function, potentially saving orders of magnitude in proving time for that operation. As it turns out, many common cryptographic and mathematical operations lend themselves to this kind of optimization.

Actually integrating precompiles into the zkVM is a complex task, however. Guest programs need to be able to use precompiles as if they were standard Rust libraries, and the VM needs to be able to seamlessly provide precompile evaluations to the guest and constraints to the prover. The rest of this documentation is concerned with how the Nexus zkVM actually achieves this.

## Architecture

Above all, the Nexus zkVM is designed to make guest program development as easy as possible. This means that, from the perspective of a developer writing a guest program, using precompiles should be just as easy as using any other Rust library. Using Keccak256 as a concrete example, we want developers to be able to write code like the following:

```rust
use_precompile!(nexus_keccak::Keccak256);

#[nexus_rt::public_input(x)]
#[nexus_rt::main]
fn main(x: &[u8]) -> [u8; 32] {
    Keccak256::hash(x)
}
```

Save for using the `use_precompile!` macro instead of the standard `use` statement, the guest program looks exactly as it would if it were using a standard Rust library. The guest program’s author needs no knowledge whatsoever about how precompiles work to use them correctly and efficiently.

This naturally requires trade-offs. Here, the precompile’s developer takes on the burden of ensuring that their precompile is able to provide a usable interface to the precompile’s functionality. The zkVM tooling provides a set of macros and library functions that make this as easy as possible for precompile developers, but precompile development is a much more advanced task than guest program development.

Consequently, the rest of this document primarily targets precompile developers and advanced users.

### Precompile Instructions

The atom of the Nexus zkVM is the RISC-V `RV32` assembly instruction, each of which the zkVM emulates and proves. A primary characteristic of a modern assembly language like RISC-V is that its instruction set is simple and minimal, making extensions to the instruction set something that needs to be approached carefully.

Fortunately, custom instructions are common, and RISC-V makes provisions for them. Specifically, there are two ways that the RISC-V ISA is designed to be extensible: syscalls (via `ecall`) and custom instructions with reserved opcodes.

We chose to use reserved custom instructions for precompiles. This is primarily because, conceptually, precompiles are *not* syscalls; their purpose is not to interact with the host environment in any particular way. Instead, they are simply a way to extend the functionality of the zkVM, which matches the intended use of reserved custom instructions by the RISC-V specification. These custom instructions make up the `RV32Nexus` extension to the `RV32` ISA, which, in addition to supporting third-party precompiles, will include a set of vetted and optimized first-party precompiles that address common use-cases.

This approach, however, creates some issues. System calls are fundamentally dynamic—one specifies desired behavior by setting register values, and there is only a single `ecall` instruction. As we use them, though, custom instructions, which are static in nature, also need to be dynamic in practice; we cannot simply reserve an instruction for every precompile in our ecosystem. Were we to do that, our ecosystem could only tolerate a small, fixed number of precompiles (1024, here), and developers would need to waste time best spent writing code and circuits fighting for a slot in the instruction set.

Our solution to this problem is multi-fold. At the moment, we have constrained Nexus precompiles to a single opcode (`0x0B`) reserved by the RISC-V specification—technically, this opcode is only reserved by the RISC-V specification for the 32- and 64-bit instruction sets, but, fortunately, Nexus has no plans to ever develop a 128-bit-addressed zkVM. We chose for instructions using this opcode to be R-type. A precompile instruction, then, looks like the following:

```
|--fn7--|-rs2-|-rs1-|fn3|-rd--|0001011|
 31---25       19-15     11--7
         24-20       14        6-----0
                     -12
```

| Our Use                    | `opcode` ( `inst[6:0]`) | `rd`         | `rs1`         | `rs2`         | `imm` | `fn`                                        |
| -------------------------- | ----------------------- | ------------ | ------------- | ------------- | ----- | ------------------------------------------- |
| Dynamic R-type Precompiles | `0001011`               | `inst[11:7]` | `inst[19:15]` | `inst[24:20]` | N/A   | `fn3 = inst[14:12]` and `fn7 = inst[31:25]` |

We generate concrete instructions via a procedural macro, which, for each precompile call, ultimately generates a corresponding custom instruction roughly as follows:

```rust
fn precompile_call<const FN3: u8, const FN7: u8>(rs1: u32, rs2: u32) -> u32 {
    let insn = format!(
        ".insn r 0x{R_TYPE_PRECOMPILE_OPCODE:x}, \
        0x{fn3:x}, 0x{fn7:x}, {{rd}}, {{rs1}}, {{rs2}}"
    );
    let mut rd: u32;
    unsafe {
        ::core::arch::asm!(
            ".insn r 0x0B 0x{FN3} 0x{FN7}, {rd}, {rs1}, {rs2}",
            rd = out(reg) rd,
            rs1 = in(reg) rs1,
            rs2 = in(reg) rs2,
        );
    };

    return rd;
}
```

This doesn’t precisely match our implementation (see [`precompiles/macros/src/generation.rs`](https://github.com/nexus-xyz/nexus-zkvm/blob/main/precompiles/macros/src/generation.rs) for that), but this example code is conceptually the same.

`FN3` and `FN7` are dynamically generated on a per-guest-program-compilation basis. Concretely, guest programs are limited to using no more than 1,024 precompiles, and a procedural macro is responsible for generating an integer index in `[0, 1024)` for each precompile and embedding an (index → precompile) mapping into the compiled program’s code (again, see `generation.rs` for specifics).

When the VM loads a guest program, it first uses the mapping embedded in the binary to discover which precompiles it will need to load in order to offer the guest program its desired functionality. The static reserved opcode and the mapping embedded in the guest program binary together encode all the instruction information needed for the zkVM to dynamically execute and prove the precompiles needed by a guest program.

In order to verify a proof, the verifier also has to be able to discover the precompiles used by the guest program. The verifier does this almost exactly the same as the zkVM does. The only difference is that the verifier ignores the precompile’s implementation, only verifying that the precompile’s constraints are satisfied and the same as the ones proved by the zkVM. The Nexus ecosystem ensures that the verifier is able to seamlessly access the same precompile implementations as the zkVM.

### Executing Precompiles

Precompiles are distributed as shared libraries compiled for the host’s native platform (e.g., Linux or macOS). On startup, the zkVM is configured to search for and load precompiles from a set of directories and files. Each of these is loaded into memory using the platform-appropriate dynamic library loading mechanism (e.g., `dlopen` on Unixes).

Precompile implementations adhere to the precompile interfaces specified in `nexus-precompiles` (see [`precompiles/src/traits.rs`](https://github.com/nexus-xyz/nexus-zkvm/blob/main/precompiles/src/traits.rs)). These traits mirror the traits that define native instructions as closely as possible, the only difference being that precompiles use dynamic dispatch instead of static dispatch via generics. Again, Nexus-provided macros automate exporting these implementations under well-known names which the zkVM can discover.

It's worth noting that while dynamically loaded libraries typically use the C ABI, we chose to use the Rust ABI for the time being. Nexus’ macros and zkVM implementation hide this from precompile developers and users, but there is one user-visible consequence: precompiles must be built with the same Rust version as the zkVM. This is because Rust has no ABI stability guarantees, and after precompiles stabilize, we may switch internally to the completely stable C ABI.

Upon loading a guest binary, the zkVM searches for well-known Nexus precompile symbols in that binary. If it finds any, it will construct a table mapping precompile instructions, discussed above, to the precompile implementations found in the loaded libraries. The zkVM will then use this table to dynamically dispatch precompile calls to the appropriate implementation during emulation.

### Proving Precompiles

Because precompile instructions are conceptually no different from native instructions, the zkVM’s proving process is able to integrate them easily. We need no special treatment for precompiles in the tracing process; they are simply another kind of instruction present in the trace that the emulator generates for the prover.

The prover, however, does need to be aware of precompiles in the same way that the emulator is. The same table constructed during guest binary loading is used by the prover, but instead of fetching instruction implementations, it fetches constraint circuits. The prover then uses these circuits to constrain the trace of the precompile instructions in the same way that it does for native instructions.

## Precompile Development

Because of Nexus’ choice to prioritize the precompile consumer’s experience, developing precompiles is a more advanced task than developing guest programs. This section is intended to help precompile developers understand the steps required to develop a precompile, each either on the host or guest side.

Fortunately, there are still only a few steps in developing a precompile, and Nexus provides tooling to assist with each. To create a precompile, a developer must:

{% stepper %}
{% step %}

### Implement the precompile’s functionality (host)

Write the host-side implementation that performs the operation exposed by the precompile.
{% endstep %}

{% step %}

### Create the precompile’s circuit (host)

Design and provide the constraint circuit that the prover will use to constrain traces for the precompile instruction.
{% endstep %}

{% step %}

### Provide precompile consumers with an idiomatic interface (guest)

Package a small, idiomatic Rust guest-side interface (crate) so guest programs can use the precompile like a normal library.
{% endstep %}
{% endstepper %}

The host functionality and circuit will be packaged in the precompile’s shared library, while the guest interface is packaged in a Rust crate imported by guest programs. The host implementation needs no awareness of the guest interface, and the guest interface needs no awareness of how the precompile is implemented.

### Host-Side Development

The bulk of the work necessary to create a precompile is its host-side implementation and constraint circuit. Concretely, precompile developers must write a `struct` which implements `PrecompileInstruction` (which will be specified in [`precompiles/src/traits.rs`](https://github.com/nexus-xyz/nexus-zkvm/blob/main/precompiles/src/traits.rs) once full precompile support is published). The source code for this trait (and its supertraits) is extensively documented, and for the most up-to-date guide, refer to the rustdoc for `traits.rs` and the example precompiles in `precompiles/examples`.

### Guest-Side Development

The developer work involved with creating a guest-side interface for a precompile is minimal: the developer only needs to write a single function that provides idiomatic access to the precompile’s functionality. This function has to be defined by a macro for code-generation reasons (only upon compilation will the precompile’s opcode be calculated) but is otherwise a simple wrapper around the precompile’s raw instruction.

As a concrete example, consider a simple hash. The host-side implementation and macro might write a function like the following:

```rust
impl MyHashInstruction {
    pub fn emit_instruction(rs1: u32, rs2: u32) -> u32 {
        let mut rd: u32;

        unsafe {
            ::core::arch::asm!(
                "/* inst. details */ {rd}, {rs1}, {rs2}",
                rd = out(reg) rd,
                rs1 = in(reg) rs1,
                rs2 = in(reg) rs2,
            );
        }

        return rd;
    }
}
```

Where `emit_instruction` computes the hash of `rs2` bytes starting at memory address `rs1` and writes it to `rd`. We don’t want guest program developers to have any awareness of how the underlying instruction works, so the precompile developer would write a macro like the following:

```rust
// Package: my-precompile
// File: my_precompile/src/guest.rs

#[cfg(target_arch = "riscv32")]
#[macro_export]
macro_rules! generate_instruction_caller {
    ($path:path) => {
        pub trait HashCaller {
            fn hash(data: &[u8]) -> u32;
        }

        impl HashCaller for $path {
            fn hash(data: &[u8]) -> u32 {
                let data_ptr = data.as_ptr() as u32;
                let data_len = data.len() as u32;

                Self::emit_instruction(data_ptr, data_len)
            }
        }
    };
}
```

Then, when the guest program developer wants to use the precompile, their `use_precompiles!` macro will automatically call the `generate_instruction_caller!` macro, which will generate an ad-hoc `HashCaller` trait implementation for the precompile’s instruction. The guest program developer can then use the precompile like this:

```rust
use_precompiles!(my_precompile::MyHash);

#[nexus_rt::public_input(x)]
#[nexus_rt::main]
fn main(x: &[u8]) -> u32 {
    MyHash::hash(x)
}
```

Which is exactly the kind of experience we set out to provide for guest program developers.


# Benchmarking

```bash
# Arrays for input and output
public_inputs=(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)
public_outputs=(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765)

# Loop through the arrays
for i in "${!public_inputs[@]}"; do
    public_input="${public_inputs[$i]}"
    public_output="${public_outputs[$i]}"

    echo "Running with PUBLIC_INPUT=$public_input and PUBLIC_OUTPUT=$public_output"

    # Run the Rust program with environment variables set
    # Ensure the Rust program is built in release mode
    PUBLIC_INPUT=$public_input PUBLIC_OUTPUT=$public_output cargo run --release

    echo "----------------------------------------"
done
```

Results (units in milliseconds)

| n-th Fibonacci | Compile (ms) | Prove (ms) | Verify (ms) | Total Time (ms) | Proof size (bytes) |
| -------------- | -----------: | ---------: | ----------: | --------------: | -----------------: |
| 0              |          161 |       1592 |          11 |            1764 |              51968 |
| 1              |          149 |       1522 |          11 |            1684 |              51968 |
| 2              |          148 |       1514 |          11 |            1675 |              51968 |
| 3              |          151 |       1538 |          11 |            1701 |              51440 |
| 5              |          151 |       1477 |          12 |            1641 |              51968 |
| 8              |          128 |       1465 |          11 |            1606 |              46008 |
| 13             |          136 |       1454 |          11 |            1602 |              49304 |
| 21             |          138 |       1440 |          12 |            1591 |              49880 |
| 34             |          149 |       1551 |          12 |            1713 |              51968 |
| 55             |          149 |       1422 |          13 |            1584 |              51440 |
| 89             |          151 |       1432 |          12 |            1596 |              51440 |
| 144            |          153 |       1427 |          11 |            1593 |              49460 |
| 233            |          149 |       1434 |          12 |            1596 |              50384 |
| 377            |          138 |       1417 |          11 |            1567 |              50368 |
| 610            |          148 |       1453 |          11 |            1613 |              51968 |
| 987            |          150 |       1510 |          12 |            1673 |              49280 |
| 1597           |          149 |       1443 |          11 |            1604 |              50384 |
| 2584           |          146 |       1460 |          13 |            1620 |              50864 |
| 4181           |          147 |       1449 |          11 |            1610 |              51968 |
| 6765           |          149 |       1440 |          11 |            1602 |              51968 |

Observation: Proving time is roughly constant for the first 20 Fibonacci numbers — likely because zkVM setup overhead dominates while the actual Fibonacci computation is still small.

Larger inputs (around the 200th Fibonacci)

| n-th Fibonacci | Compile (ms) | Prove (ms) | Verify (ms) | Total Time (ms) | Proof size (bytes) |
| -------------- | -----------: | ---------: | ----------: | --------------: | -----------------: |
| 200            |          156 |      17416 |         189 |           17762 |              58544 |
| 201            |          154 |      17179 |         183 |           17518 |              59904 |
| 202            |          152 |      17468 |         183 |           17804 |              59248 |
| 204            |          158 |      17238 |         181 |           17579 |              59248 |
| 205            |          151 |      17242 |         184 |           17579 |              57920 |

Observation: Proving time increases substantially with input size, while verification time remains small compared to proving time.

{% stepper %}
{% step %}

### Optimize Guest Programs

Focus on minimizing RISC-V cycles in guest programs. The benchmark shows the number of RISC-V cycles directly impacts proving time, which is the dominant component of total execution time.
{% endstep %}

{% step %}

### Consider Algorithmic Efficiency

For computationally demanding scenarios, prioritize efficient algorithms and implementations. The Fibonacci example demonstrates how complexity can rapidly increase proving time.
{% endstep %}

{% step %}

### Profile Regularly

Regularly profile Nexus zkVM projects to identify performance bottlenecks and optimization opportunities.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
Note: Verification time remains negligible in these benchmarks compared to proving time; optimizing the proving workload yields the largest gains.
{% endhint %}

Related links:

* <https://docs.nexus.xyz/zkvm/proving/precompiles>
* <https://docs.nexus.xyz/zkvm/walkthroughs/galeshapley>


# Use Case: Stable Matching

Every year in the United States, \~50,000 graduating medical students (doctors, for short) are placed into the residencies where they will begin their careers by the [National Resident Matching Program](https://www.nrmp.org/) (or The Match). To do so, the program finds a stable matching: an assignment of doctors to medical programs (hospitals, for short) such that there is no pair of doctor and hospital that would prefer to be assigned each other over the match(es) they ended up with. Each candidate doctor ranks their preferred hospitals and each hospital ranks their preferences in candidates, and the matching proceeds algorithmically.

One of the original problems in the field of algorithmic mechanism design, the matching program uses the [Roth-Peranson algorithm (1999)](https://www.aeaweb.org/articles?id=10.1257/aer.89.4.748), a modification of the earlier [Gale-Shapley algorithm (1962)](https://www.tandfonline.com/doi/pdf/10.1080/00029890.1962.11989827). These contributions in large part earned Roth & Shapley the 2012 Nobel Prize in Economics.

The Match is a classic example of a computation [whose transparency and accountability can benefit from private verifiable computation](https://scholarship.law.upenn.edu/penn_law_review/vol165/iss3/3/): at present, doctors receive no direct assurance that an algorithmic decision that will shape their careers (and their career earnings) is being executed correctly. However, The Match cannot simply publish a transcript of the matching, as it would harm the privacy of the doctors in particular (“Alice said she wanted to come back home for residency, but she didn’t rank any local hospitals” or “when we interviewed Bob he said fifteen years ago he ranked us highly in The Match, but he only had us seventh”). [Anonymizing the doctors can only accomplish so much](https://heinonline.org/HOL/LandingPage?handle=hein.journals/uclalr57\&div=48\&id=\&page=) as it offers only the illusion of privacy; modern re-identification techniques routinely pierce de-identified data. As a result, anonymization *fails to actually anonymize*. Our goal is to create a system that provides complete transparency about the correctness of the matching algorithm while preserving the privacy of all preference rankings.

Specifically, we want to generate a cryptographic proof that demonstrates the Gale-Shapley algorithm was executed correctly and produced a stable matching, without revealing any individual’s private preferences. The Nexus zkVM enables us to achieve exactly this balance between accountability and privacy.

***

{% stepper %}
{% step %}

### Implementation — initialize host and guest programs

Follow the [SDK Quick Start](/zkvm/development/sdk-quick-start). After installing the zkVM, to initialize the host and guest programs, run:

{% code title="Initialize host and guest" %}

```
$ cargo nexus host matching
```

{% endcode %}
{% endstep %}

{% step %}

### Guest Program

This guest program is the program to be proven; it implements the matching itself using the Gale-Shapley approach.

matching/src/guest/src/main.rs

{% code title="stable\_matching implementation" %}

```rust
#![cfg_attr(target_arch = "riscv32", no_std, no_main)]

extern crate alloc;
use alloc::collections::BTreeSet;
use alloc::{vec, vec::Vec};

fn stable_matching(
    n: usize,
    mut employers: BTreeSet<usize>,
    mut candidates: BTreeSet<usize>,
    employer_prefs: Vec<Vec<usize>>,
    candidate_prefs: Vec<Vec<usize>>,
) -> Vec<Option<usize>> {
    let mut hires: Vec<Option<usize>> = vec![None; n];
    while !employers.is_empty() {
        let next = employers.pop_first().unwrap();
        let prefs = &employer_prefs[next];

        for c in prefs {
            if candidates.contains(c) {
                hires[next] = Some(*c);
                assert!(candidates.remove(c));
                break;
            } else {
                let current = hires
                    .iter()
                    .position(|&x| x.is_some() && *c == x.unwrap())
                    .unwrap();
                if candidate_prefs[*c].iter().position(|&x| next == x)
                    < candidate_prefs[*c].iter().position(|&x| current == x)
                {
                    assert!(employers.insert(current));
                    hires[next] = Some(*c);
                    hires[current] = None;
                    break;
                }
            }
        }
    }

    hires
}
```

{% endcode %}

The function takes:

* a set of candidates (doctors),
* a set of employers (hospitals),
* preference rankings in matrix form (row i of `employer_prefs` encodes the ranking for the i-th employer),
* a parameter `n = employers.len()`.

It returns a vector `hires` of length `n`, where `hires[j] = i` means the j-th hospital hires the i-th doctor.

You also need a `main` to handle zkVM inputs/outputs and declare which inputs are public/private.

matching/src/guest/src/main.rs

{% code title="guest main" %}

```rust
#[nexus_rt::main]
#[nexus_rt::private_input(prefs)]
#[nexus_rt::public_input(lists)]
fn main(
    prefs: (Vec<Vec<usize>>, Vec<Vec<usize>>),
    lists: (BTreeSet<usize>, BTreeSet<usize>),
) -> Vec<Option<usize>> {
    let (mut employer_prefs, mut candidate_prefs) = prefs;
    let (mut employers, mut candidates) = lists;

    let n = employers.len();
    let m = candidates.len();

    nexus_rt::print!("Matching {} employers with {} candidates... ", n, m);

    let hires = stable_matching(n, employers, candidates, employer_prefs, candidate_prefs);

    nexus_rt::println!("completed.");

    hires
}
```

{% endcode %}

Note: inputs default to private unless explicitly declared public. The public inputs/outputs here leak only which hospital each candidate matched with (the `hires` vector), which is acceptable for auditing the algorithm while keeping preferences private.
{% endstep %}

{% step %}

### Host Program

The host program compiles the guest and runs it with the provided preferences (these preferences are private inside the host).

matching/src/main.rs

{% code title="host main" %}

```rust
use nexus_sdk::{
    compile::{cargo::CargoPackager, Compile, Compiler},
    stwo::seq::Stwo,
    ByGuestCompilation, Local, Prover, Viewable,
};
use std::collections::BTreeSet;

type Matches = Vec<Option<usize>>;
type Prefs = (Vec<Vec<usize>>, Vec<Vec<usize>>);
type Lists = (BTreeSet<usize>, BTreeSet<usize>);

const PACKAGE: &str = "guest";

fn main() {
    println!("Compiling guest program...");
    let mut prover_compiler = Compiler::<CargoPackager>::new(PACKAGE);
    let prover: Stwo<Local> =
        Stwo::compile(&mut prover_compiler).expect("failed to compile guest program");

    let n = 10;
    let m = 8;
    assert!(m < n);

    let mut employers = BTreeSet::<usize>::new();
    let mut candidates = BTreeSet::<usize>::new();

    for i in 0..n {
        employers.insert(i);
        if i < m {
            candidates.insert(i);
        }
    }

    let employer_prefs = vec![\
        vec![4, 1, 2, 7, 3, 0, 6, 5],\
        vec![0, 1, 4, 5, 7, 2, 6, 3],\
        vec![3, 7, 4, 6, 2, 0, 5, 1],\
        vec![4, 6, 0, 2, 7, 3, 5, 1],\
        vec![2, 6, 1, 3, 7, 0, 4, 5],\
        vec![1, 4, 5, 3, 7, 0, 6, 2],\
        vec![1, 5, 4, 0, 6, 2, 7, 3],\
        vec![3, 6, 1, 2, 7, 0, 5, 4],\
        vec![7, 5, 3, 4, 1, 6, 2, 0],\
        vec![0, 6, 1, 4, 5, 2, 7, 3],\
    ];

    let candidate_prefs = vec![\
        vec![0, 5, 7, 8, 3, 1, 4, 2, 6, 9],\
        vec![8, 2, 0, 9, 3, 4, 5, 6, 1, 7],\
        vec![3, 6, 4, 0, 5, 9, 7, 8, 2, 1],\
        vec![2, 9, 7, 3, 4, 1, 5, 8, 0, 6],\
        vec![7, 5, 9, 4, 6, 8, 0, 1, 3, 2],\
        vec![4, 1, 7, 6, 2, 9, 8, 0, 5, 3],\
        vec![0, 5, 3, 6, 8, 7, 4, 1, 9, 2],\
        vec![7, 5, 6, 1, 4, 0, 2, 8, 9, 3],\
    ];

    println!("Proving execution of vm...");
    let (view, proof) = prover.prove_with_input::<Prefs, Lists>(
        &(employer_prefs, candidate_prefs),
        &(employers, candidates),
    ).expect("failed to prove program");

    println!(
        ">>>>> Logging\n{}<<<<<",
        view.logs().expect("failed to retrieve debug logs").join("")
    );
    assert_eq!(view.exit_code().expect("failed to retrieve exit code"), nexus_sdk::KnownExitCodes::ExitSuccess as u32);

    let hires = view.public_output::<Matches>().expect("failed to retrieve public output");

    print!("Found matching (employer, candidate): ");
    for i in 0..n {
        if i > 0 {
            print!(", ")
        }
        print!(
            "({}, {})",
            i,
            if hires[i].is_some() {
                hires[i].unwrap().to_string()
            } else {
                "None".to_string()
            }
        );
    }
    println!("\n");

    /// the host program would go on to publish `view` and `proof` publicly.
}
```

{% endcode %}

The ranking matrices shown above are random example data for demonstration.
{% endstep %}

{% step %}

### Verification

Once the program, its public input/output (`view`) and the `proof` are published, any interested party can verify correctness without access to preferences.

{% code title="verifier snippet" %}

```rust
println!("Verifier recompiling guest program...");
let mut verifier_compiler = Compiler::<CargoPackager>::new(PACKAGE);
let path = verifier_compiler.build().expect("failed to (re)compile guest program");

print!("Verifying execution...");
proof.verify_expected_from_program_path::<&str, Lists, Matches>(
  &(employers, candidates),                       // at view.public_input
  nexus_sdk::KnownExitCodes::ExitSuccess as u32,  // at view.exit_code
  &hires,                                         // at view.public_output
  &path,                                          // path to program binary
  &[]                                             // no associated data,
).expect("failed to verify proof");
```

{% endcode %}

If the published matching (`hires`) has been tampered with in a way that violates Gale-Shapley guarantees, verification will fail.

Limitation: a verifier cannot directly confirm that the private preference matrices themselves are the ones originally provided by participants. The zkVM guarantees there exists some private input for which the execution is correct, not that the private input specifically matches an off-chain record. To mitigate this, participants can submit salted hashes of their preferences (with per-participant passphrases) that the guest program includes (hashed) in public output. Each participant can then verify their own preference hash matches what was used in the execution without revealing preferences to others.
{% endstep %}

{% step %}

### Conclusion

This walkthrough demonstrates how the Nexus zkVM can provide verifiable transparency for algorithmic decision-making while preserving privacy. Achievements:

* A publicly auditable guest program implementing stable matching.
* Proof generation that keeps preference rankings private.
* A verification mechanism for any party to confirm correct execution.
* A framework balancing individual privacy with institutional accountability.

The approach transforms the medical residency matching process from a “trust us” system to a “verify for yourself” system and can be applied to other algorithmic decision systems (financial algorithms, ML-based hiring/lending/healthcare) where transparency and privacy must coexist.
{% endstep %}
{% endstepper %}


# Use Case: Program Execution

### Introduction

*As this walkthrough describes a more advanced use case for the zkVM, we recommend reviewing both the* [SDK Quick Start](/zkvm/development/sdk-quick-start) *and the simpler* [Stable Matching](/zkvm/walkthroughs/use-case-stable-matching) *walkthrough first.*

By definition, a zkVM is designed to prove the correctness of a program’s execution. However, a more advanced use case arises when the **program to be proven** is not the code running directly in the zkVM, but is instead provided **as input** to the zkVM.

In this case, the zkVM executes an **interpreter** as its guest program, which then reads, interprets, and runs the input program inside the zkVM. This effectively allows the zkVM to generate a proof for the execution of arbitrary, dynamically supplied programs.

Consider the following guest program in which `program_to_be_proven` is nested inside as input to the zkVM:

```rust
fn digest(program: &ElfFile) -> Vec<u8> { ... }

fn rv32i_interpret(program: &ElfFile) -> Vec<u8> { ... }

#[nexus_rt::main]
#[nexus_rt::private_input(program_to_be_proven)]
#[nexus_rt::public_input(program_digest)]
fn main(program_to_be_proven: ElfFile, program_digest: Vec<u8>) -> Vec<u8> {
    assert_eq!(digest(&program_to_be_proven), &program_digest);

    nexus_rt::print!("Program digest {} validated, executing program... ", program_digest);

    let output = rv32i_interpret(&program_to_be_proven);

    nexus_rt::println!("completed.");

    output
}
```

At first glance, this pattern may seem redundant—why not just run `program_to_be_proven` directly as the guest program? However, this level of **indirection** unlocks powerful capabilities that significantly expand the utility of the zkVM.

#### Enabling Private Program Execution

The first benefit, illustrated by the example above, is the ability to treat the **program to be proven** as a **private input**. This allows the zkVM to prove the execution of programs **without revealing their source**, making it possible to build **private smart contracts** on-chain, among other use cases.

By comparing the program’s digest to a **public input**, verifiers can still check meaningful properties such as:

* Consistency of the private program across multiple invocations.
* Conformance to a known, audited version without exposing implementation details.

This digest check enables **trust minimization** while preserving **privacy**.

#### Generalization Across Programs with a Single zkVM Binary

A second, more technical benefit is that this structure enables the zkVM to handle a wide range of programs through a **single, fixed guest binary** — the interpreter.

With careful planning around memory constraints (e.g. stack, heap, and output size bounds), this pattern allows:

* The proving key and public parameters can be shared and reused across many programs, which simplifies definitions for [non-transparent provers which may require per-program setup](https://blog.nexus.xyz/the-murky-proof-system-waters-part-ii/).
* Deployment pipelines for applications and smart contracts verifying zkVM proofs on-chain can be simplified significantly.
* Systems using non-transparent proving systems can be managed more easily, even though they often require per-program setup (note: the `Stwo` prover used here is transparent).

This approach decouples the proving circuit from individual guest programs, significantly improving **composability** and **maintainability**.

### The Lambda Calculus Model

Instead of working through this design at the full complexity of the RISC-V32i instruction set, this walkthrough uses a simpler computational model: the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus). The example demonstrates the core concept with a minimal interpreter implemented as a guest program inside the zkVM.

***

### Implementation

To start, follow the [SDK Quick Start](/zkvm/development/sdk-quick-start). After installing the zkVM, initialize the host and guest programs:

```bash
$ cargo nexus host lambda_calculus
```

Begin by creating a shared library for the guest and host program, located within the guest program crate.

#### Shared Library

The first part of the library defines terms in the calculus:

lambda\_calculus/src/guest/src/common.rs

```rust
#![cfg_attr(no_std)]

extern crate alloc;

use alloc::boxed::Box;
use core::fmt::{Debug, Display};
use serde::{Serialize, Deserialize};

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(transparent)]
pub struct DeBruijnIndex(usize);

impl From<usize> for DeBruijnIndex {
    fn from(value: usize) -> Self {
        Self(value)
    }
}

impl Display for DeBruijnIndex {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
enum Term<R> {
    Var(DeBruijnIndex),
    Lambda(R),
    Apply(R, R),
}

impl<R: Display> Display for Term<R> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Term::Var(v) => write!(f, "{v}"),
            Term::Lambda(t) => {
                write!(f, "(\\")?;
                write!(f, "{t}")?;
                write!(f, ")")
            }
            Term::Apply(t1, t2) => {
                write!(f, "(")?;
                write!(f, "{t1}")?;
                write!(f, " ")?;
                write!(f, "{t2}")?;
                write!(f, ")")
            }
        }
    }
}
```

This defines an enum `Term` for possible term kinds: indexed `Var`, `Lambda`, and `Apply`, and includes trait implementations for construction and debugging. Next, full expressions and evaluation routines:

lambda\_calculus/src/guest/src/common.rs

```rust
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Expr(Term<Box<Self>>);

impl Display for Expr {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl PartialEq for Expr {
    fn eq(&self, other: &Self) -> bool {
        self.clone().eval().structural_eq(&other.clone().eval())
    }
}

impl Eq for Expr {}

impl Expr {
    pub fn var<T: Into<DeBruijnIndex>>(binding: T) -> Self {
        Self(Term::Var(binding.into()))
    }

    pub fn lambda<T: Into<Box<Self>>>(inner: T) -> Self {
        Self(Term::Lambda(inner.into()))
    }

    pub fn apply<T1, T2>(left: T1, right: T2) -> Self
    where
        T1: Into<Box<Self>>,
        T2: Into<Box<Self>>,
    {
        Self(Term::Apply(left.into(), right.into()))
    }

    pub fn identity() -> Self {
        Self::lambda(Self::var(0))
    }

    pub fn omega() -> Self {
        let expr = Self::lambda(Self::apply(Self::var(0), Self::var(0)));
        Self::apply(expr.clone(), expr)
    }

    pub fn step(&self) -> Self {
        fn subst(body: Expr, arg: Expr, depth: usize) -> Expr {
            match body.0 {
                Term::Var(i) => {
                    if i.0 == depth {
                        arg
                    } else {
                        Expr::var(i)
                    }
                }
                Term::Lambda(e) => subst(*e, arg, depth + 1),
                Term::Apply(e1, e2) => {
                    Expr::apply(subst(*e1, arg.clone(), depth), subst(*e2, arg, depth))
                }
            }
        }

        // attempt beta reduction
        if let Term::Apply(e1, e2) = &self.0 {
            if let Term::Lambda(e) = &e1.0 {
                return subst(*e.clone(), *e2.clone(), 0);
            }
        }

        self.clone()
    }

    pub fn structural_eq(&self, other: &Self) -> bool {
        match (&self.0, &other.0) {
            (Term::Var(i), Term::Var(j)) => *i == *j,
            (Term::Lambda(e), Term::Lambda(f)) => e.structural_eq(f),
            (Term::Apply(e1, e2), Term::Apply(f1, f2)) => {
                e1.structural_eq(f1) && e2.structural_eq(f2)
            }
            _ => false,
        }
    }

    pub fn eval(self) -> Self {
        let mut curr = self;
        loop {
            let next = curr.step();
            if next.structural_eq(&curr) {
                return curr.clone();
            } else {
                curr = next;
            }
        }
    }
}
```

This provides `step` and `eval` routines to reduce expressions into stable forms.

To make the common library work across both host and guest:

* Update the guest crate to depend on `serde` (used for serialization/deserialization of `Expr`):

lambda\_calculus/src/guest/Cargo.toml

```toml
...

[dependencies]
nexus-rt = { git = "https://github.com/nexus-xyz/nexus-zkvm.git", tag = "0.3.0", version = "0.3.0" }
postcard = { version = "1.1.1", default-features = false, features = ["alloc"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }

...
```

* Export the common code as a Rust library:

lambda\_calculus/src/guest/src/lib.rs

```rust
#![no_std]

pub mod common;
pub use common::*;
```

* Make the library visible to the host program:

lambda\_calculus/Cargo.toml

```toml
...

[dependencies]
nexus-sdk = { git = "https://github.com/nexus-xyz/nexus-zkvm.git", tag = "0.3.0", version = "0.3.0" }
guest = { path = "./src/guest" }

...
```

#### Guest Program

As an `Expr` is a program in the lambda calculus, implement a simple interpreter (here omitting hash-based checking):

lambda\_calculus/src/guest/src/main.rs

```rust
#![cfg_attr(target_arch = "riscv32", no_std, no_main)]

use guest::Expr;

#[nexus_rt::main]
#[nexus_rt::private_input(program_to_be_proven)]
fn main(program_to_be_proven: Expr) -> Expr {
    program_to_be_proven.eval()
}
```

#### Host Program

Create a host program that compiles the guest, provides a private program as input, and produces a proof of its execution:

lambda\_calculus/src/main.rs

```rust
use nexus_sdk::{
    compile::{cargo::CargoPackager, Compile, Compiler},
    stwo::seq::Stwo,
    ByGuestCompilation, Local, Prover, Viewable,
};

use guest::Expr;

const PACKAGE: &str = "guest";

fn main() {
    println!("Compiling guest program...");
    let mut prover_compiler = Compiler::<CargoPackager>::new(PACKAGE);
    let prover: Stwo<Local> =
        Stwo::compile(&mut prover_compiler).expect("failed to compile guest program");

    let program_to_be_proven = Expr::apply(Expr::identity(), Expr::var(42));

    println!("Proving execution of vm...");
    let (view, proof) = prover.prove_with_input::<Expr, ()>(
        &program_to_be_proven,
        (),
    ).expect("failed to prove program");

    assert_eq!(view.exit_code().expect("failed to retrieve exit code"), nexus_sdk::KnownExitCodes::ExitSuccess as u32);

    let output = view.public_output::<Expr>().expect("failed to retrieve public output");

    println!("Reduced expression: {}", output);
}
```

### Verification

Verification does not require access to the private program. A verifier can recompile the guest program and verify the proof against the expected outputs:

```rust
println!("Verifier recompiling guest program...");
let mut verifier_compiler = Compiler::<CargoPackager>::new(PACKAGE);
let path = verifier_compiler.build().expect("failed to (re)compile guest program");

print!("Verifying execution...");
proof.verify_expected_from_program_path::<&str, (), Expr>(
  &(),                                           // at view.public_input
  nexus_sdk::KnownExitCodes::ExitSuccess as u32, // at view.exit_code
  &output,                                       // at view.public_output
  &path,                                         // path to program binary
  &[]                                            // no associated data,
).expect("failed to verify proof");
```

This demonstrates using the zkVM to prove the correctness of executing a private program.

### Conclusion

This walkthrough demonstrates how the Nexus zkVM can be applied to fundamental computational models, illustrating versatility beyond practical applications. The implementation proves correct execution of lambda calculus programs while keeping program logic private.

Key achievements:

1. A shared library architecture enabling code reuse between guest and host programs.
2. A clean abstraction for lambda calculus expressions with evaluation semantics.
3. A zkVM-based interpreter that generates proofs for program execution.
4. A verification system that confirms correctness without exposing the original program.

This example shows that zkVMs can handle abstract computational models, not just concrete algorithms, and that privacy-preserving verification principles generalize across domains. The approach transforms program execution from “trust the interpreter” to “verify the computation,” providing mathematical assurance that programs are evaluated correctly according to their formal semantics.

Links for further reading:

* Use Case: Stable Matching: <https://docs.nexus.xyz/zkvm/walkthroughs/galeshapley>
* The Complete Specification: <https://docs.nexus.xyz/zkvm/specifications/zkvm-overview>


# The Nexus Whitepaper

**Nexus 1.0: Enabling Verifiable Computation**

The Nexus 1.0 whitepaper introduces the Nexus zkVM — a zero-knowledge virtual machine capable of proving any computation. It covers the full system design: the Nexus Virtual Machine (NVM), co-processors, recursive proof systems, and the Nexus Network's distributed prover architecture.

Key topics:

* **Nexus zkVM** — a universal proving machine for any stateful ISA (RISC-V, EVM, Wasm), architected for massively parallelised incremental proof generation
* **Nexus Virtual Machine (NVM)** — a minimal, extensible virtual CPU designed to maximise prover performance
* **Proof systems** — HyperNova, Parallel Nova, Parallel HyperNova; folding/accumulation schemes enabling IVC and PCD
* **Nexus Network** — a large-scale distributed prover network aggregating heterogeneous compute to prove 1B+ CPU cycles

{% file src="/files/J7FJzPwnFdmuZuyCOOE3" %}


# The Complete Specification

The Nexus zkVM 3.0 Specification provides an overview of the runtime and machine architecture of the zkVM, as well as a full, formal description of the constraints and proving integration.

In this Specifications section, we provide accessible introductions to some of the core concepts from the specification.

<table data-view="cards"><thead><tr><th>Title</th><th data-card-target data-type="content-ref">Target</th></tr></thead><tbody><tr><td>Home</td><td><a href="https://docs.nexus.xyz/home">https://docs.nexus.xyz/home</a></td></tr><tr><td>Nexus Layer 1 — Overview</td><td><a href="https://docs.nexus.xyz/layer-1/vision/overview">https://docs.nexus.xyz/layer-1/vision/overview</a></td></tr><tr><td>Nexus zkVM</td><td><a href="/pages/2fee3c5b002f76ad0b4f3fc5b1247c8207385ff6">/pages/2fee3c5b002f76ad0b4f3fc5b1247c8207385ff6</a></td></tr><tr><td>Web3 Fundamentals — Overview</td><td><a href="https://docs.nexus.xyz/essentials/web3-fundamentals/overview">https://docs.nexus.xyz/essentials/web3-fundamentals/overview</a></td></tr><tr><td>Use Case: Program Execution</td><td><a href="/pages/7d968cf98335e73d5f50320113f3836829feb400">/pages/7d968cf98335e73d5f50320113f3836829feb400</a></td></tr><tr><td>Machine Architecture</td><td><a href="/pages/d5430652a02631dc3db48dded6b2fb0695a0618f">/pages/d5430652a02631dc3db48dded6b2fb0695a0618f</a></td></tr><tr><td>Full zkVM Specification: zkVM Overview</td><td><a href="#content-area">#content-area</a></td></tr></tbody></table>


# Machine Architecture

The primary use of the Nexus zkVM machine architecture is to support the verifiable computation of the full zkVM. However, it is a well-defined virtual machine in its own right, and some of its key components are described below.

### Architectural overview

The machine architecture is built around an instruction set, not the same as, but close enough to the RISC-V RV32I instruction set. The primary difference is the lack of a few supported instructions, such as `fence` and `ebreak`. The machine uses a modified Harvard architecture in which the program, data, and input-output memory segments are distinct and permissioned with respect to whether they can be read from, written to, or both.

### Execution Model

The Nexus zkVM is designed around a “only prove what you use” memory architecture, where unused memory does not need to be proven. As a trade-off, the zkVM requires that the amounts of memory used by most of the program segments (all but the heap) must be known before execution — even though the sizes of the stack and output are often execution-dependent.

To avoid this chicken-and-egg problem, the machine architecture operates on a two-pass tracing model: the program is first executed in a (mostly) traditional Harvard architecture, and statistics are kept as to the resultant memory usage. The guest program is then executed again using the same inputs in a modified Harvard architecture with a fixed-memory organization determined from the statistics of the first execution, which is more conducive to proving.

### Execution Environment

Through environment calls the guest program can interact with the execution environment. The machine architecture supports six environment calls. Two of these calls are used for debugging (logging) and optimization (cycle counting), and these are no-ops during the second, proven tracing. The other four are used for setting up stack and heap pointers in the second, fixed memory model, as well as for reading off the private input tape and exiting the guest program, analogous to the exit system calls (like Rust’s `std::process::exit(code: i32) -> !}` provided by most programming languages).

### Memory Layout

The machine architecture uses distinct component memories. Each memory has three attributes: in which address space it exists, with what permission structure (read-write, read-only, write-only, or no access), and whether it is of fixed or variable size.

For the first pass, the organization of the memory is a mostly traditional Harvard architecture, with five distinct address spaces:

* (i) the cpu registers,
* (ii) the public input,
* (iii) the error code and public output,
* (iv) the associated data, and
* (v) the RAM containing both the program and data segments (including the stack and heap).

Other than the joining of the program and data memories this forms a relatively traditional Harvard architecture. In terms of permissions and sizes:

* (i) is read-write and fixed,
* (ii) is read-only and fixed,
* (iii) is write-only and variable,
* (iv) is no-access and fixed, and
* (v) is variable and mixed read-only (for static global variables) and read-write (for the remaining global variables and the entire data memory).

For (v) the guest program itself has no access to its instructions: they can only be accessed by the CPU.

For the second tracing pass, the memories are combined into a fixed-size, linear memory layout with one single, unified address space. As this is the memory layout used in tracing, the zkVM is best understood as a modified Harvard architecture, as the permission structures on the segments are maintained but they no longer exist in distinct memories. Also, a small additional read-only 8-byte segment containing well-known location pointers is introduced to enable the runtime to successfully access the now relocated public input and output segments.

### Registers

The zkVM machine has 32 registers that hold 32-bit word values. When tracing the program the zkVM stores the state of the registers separate from its record of memory operations. However, it also reserves the addresses `0x00-0x7F` so that prover integrations are able to identify the registers with those addresses should the prover need to consider the entire state of the machine to lie within a single address space.

### Well-Known Location Pointers

When tracing, the machine architecture also reserves two words of memory, the first from `0x80-0x83` and the second from `0x84-0x87`, which contain pointers to other memory locations for use by the runtime to manage input and output handling. These pointers existing in a well-known location for the runtime to access enables the zkVM to dynamically situate the input and output memory segments within the unified, linear architecture while still allowing the guest program easy access to their contents.

### Program Memory

The zkVM executes a guest program encoded in an ELF binary. The core of such a binary is the program consisting of a sequence of instructions and supporting read-only data (such as that contained in the `.rodata` segment). Before the zkVM executes the guest, this data must be loaded into a read-only segment of program memory, and after that remains unchanged during execution.

Additionally, the binary may contain read-write segments, such as the `.bss` and `.data` segments commonly used by programming languages to store global variables. Being writable, these segments must also be private over the course of the execution post-initialization. As such, the machine functionally treats these segments as a small part of the RAM that is non-contiguous with it, but with additional constraints to guarantee they are initialized as specified in the binary.

To simplify management of the dual-nature of these segments as both part of the program but also writable, the zkVM keeps the program memory and data memory in the same address space during the first-pass tracing, but with distinct permissions for the writable vs. read-only components. Those permissions are maintained in the unified address space of the linear memory model used in the second pass.

### Public Input

The public input segment contains an input of length `n` bytes prefaced by a four-byte (one 32-bit word) segment `n` so that the guest program can determine the length of the input made available to it. To read from the input the runtime invokes a custom instruction `rin`. During the second tracing pass — which is the one proven — `rin` is treated as a pseudoinstruction equivalent to `lb`, and the offset is loaded from the first word of the well-known segment (addresses `0x80-0x83` in the unified address space of the linear memory model). During both tracing passes, the contents of the segment are read-only — the zkVM will halt if an attempt is made by the guest program to write to those memory segments.

### Associated Data

For many zkVM use cases it can be useful to be able to bind arbitrary contextual information about an execution or the context of the proving into the proof itself. For example, from the perspective of the zkVM the program is just a compiled binary. By incorporating the hash of the program as originally written in a high-level language — such as Rust or Python — the proof can carry a reference to that code for use by the verifier, such as for auditing its functional correctness or the correctness of its compilation. In the particular case where the program forms a standalone software package, such as a Cargo crate or Python wheel, then binding in the hash of that package can even relate the proof to the broader software ecosystem.

To support binding arbitrary external information into the proof, the machine architecture contains an associated data memory segment that the prover can populate with an arbitrary bytestring. This segment is no-access within the Harvard architecture — it can neither be written to nor read from during an execution. But, the verifier otherwise treats it as a public input segment with checked contents that can then be used post-verification for application-focused infrastructure built on top of the proof, such as the aforementioned auditing. The associated data is placed before the public output and exit code, so that the memory regions after it form a contiguous space of writable segments.

### Public Output

The public output and exit code work in much the same way as the public input, except the relevant custom instruction is `wou` — interpreted on the second pass as `sb` — and the offset is loaded from the second word of the well-known segment (addresses `0x84-0x87` in the unified address space of the linear memory model). Otherwise, the most significant difference is that the single-word exit code segment and the public output segment are write-only, rather than read-only.

During the first tracing pass the public output segment can grow arbitrarily (up to addressing limits) to support additional written output. The length of the resultant output is then reserved ahead of time for the memory segment for the second and proven tracing pass.

### Data Memory

The zkVM includes a RAM. Instructions can read from the data memory, write to the data memory, or leave the data memory untouched. The primary use of the data memory is to store the stack and the heap.

During the first tracing pass, its size is variable and the stack and heap grow towards each other, as is standard. During the second tracing pass the stack and heap still grow towards each other, but are given fixed-size segments within which to do so. As a consequence, the size of the data memory is limited to only what is needed, enabling quicker proving and smaller proofs in the zkVM as only memory that is used is “paid for” by needing to prove its contents.


# Proving Instructions

The final component of the Nexus zkVM is the execution component, which is responsible for enforcing the correct execution of the instructions supported by the VM. It is designed with modularity and extensibility in mind.

Currently, this component provides support for the Nexus Virtual Machine (NVM) instruction set described in Section 2 of [the specification](/zkvm/specifications/the-complete-specification). The NVM instruction set is closely related to the RISC-V RV32I unprivileged instruction set found in [the RISC-V specification](https://drive.google.com/file/d/1s0lZxUZaa7eV_O0_WsZzaurFLLww7ou5/view). As a result, existing tooling for RISC-V RV32I can usually be used without modification.

We discuss some of the constraints for this component when we describe our [example](/zkvm/specifications/proving-an-example), but details of all the constraints for this component can be found in Section 8 of [the specification](/zkvm/specifications/the-complete-specification).


# Proving the CPU

The CPU component of the Nexus zkVM’s constraint system is responsible for ensuring that each state transition is correct. In particular, it is responsible for ensuring that the [instruction fetch](https://en.wikipedia.org/wiki/Classic_RISC_pipeline#Instruction_fetch), [instruction decode](https://en.wikipedia.org/wiki/Classic_RISC_pipeline#Instruction_decode), and [write-back](https://en.wikipedia.org/wiki/Classic_RISC_pipeline#Writeback) stages of the classic RISC pipeline are correct. It also facilitates connecting other stages of the pipeline with their respective constraint circuits.

Concretely, the CPU component performs the following tasks for each CPU cycle:

* Interacts with the program memory to fetch the next instruction pointed to by the program counter.
* Decodes that instruction and verifies that it is well-formed.
* Interacts with the register memory component to read the values associated with the instruction’s operands.
* Interacts with the execution component to execute the just-fetched instruction.
* Interacts with the register memory to perform write-back.

We discuss some of the constraints for this component when we describe our [example](/zkvm/specifications/proving-an-example), but details of all the constraints for this component can be found in Section 4 of [the specification](/zkvm/specifications/the-complete-specification).


# Proving Memory

The Nexus zkVM has three different components for handling the behavior of each type of memory used by the zkVM. Those types are:

* Program memory: a byte-addressed read-only memory space that contains the program being executed.
* Register memory: a word-addressed read-write memory space that contains the 32 32-bit registers specified by RISC-V RV32I.
* Data memory: a byte-addressed read-write memory space that accounts for all remaining memory used by the program.

To maintain consistency of accesses to the register and data memories, the Nexus zkVM uses a well-known offline memory checking technique (see [BEG+94](#references)). In this technique, each memory cell is associated with a timestamp which serves as a unique identifier for each memory access. In addition to the timestamps, the memory checking algorithm also maintains read and write sets, whose purpose is to record a trace of the program’s memory access pattern.

The main advantage of offline memory checking techniques is that one does not need to keep track of the actual status of the running memory. Instead, the memory checking algorithm only keeps a trace digest of memory accesses, which is inexpensive and can be updated at a cost that is independent of the size of the memory.

For program memory, a simpler memory checking technique can be used to maintain the consistency of the memory accesses. Instead of keeping a timestamp for each memory cell, it suffices to associate a counter to each memory cell to keep track of the number of times that each cell has been read.

In this version of the Nexus zkVM, the computation of the digest is implemented using logarithmic derivatives (also known as logups). See [EKRN24](#references) and [Hab22](#references).

We discuss some of the constraints for these components when we describe our [example](/zkvm/specifications/proving-an-example). Full details of all the constraints for these components can be found in:

* Section 5 of the specification for register memory: <https://docs.nexus.xyz/zkvm/specifications/zkvm-overview>
* Section 6 of the specification for program memory: <https://docs.nexus.xyz/zkvm/specifications/zkvm-overview>
* Section 7 of the specification for data memory: <https://docs.nexus.xyz/zkvm/specifications/zkvm-overview>

## References

* [BEG+94](https://doi.org/10.1007/BF01185212) Manuel Blum, William S. Evans, Peter Gemmell, Sampath Kannan, and Moni Naor. “Checking the correctness of memories”. Algorithmica, 12(2/3):225–244, 1994.
* [EKRN24](https://eprint.iacr.org/2022/510) Liam Eagen, Sanket Kanjalkar, Tim Ruffing, and Jonas Nick. “Bulletproofs++: Next Generation Confidential Transactions via Reciprocal Set Membership Arguments”. In EUROCRYPT 2024.
* [Hab22](https://eprint.iacr.org/2022/1530) Ulrich Haböck. “Multivariate lookups based on logarithmic derivatives”. In: Cryptology ePrint Archive (2022).

Related pages:

* [Proving the CPU](/zkvm/specifications/proving-the-cpu)
* [Proving Instructions](/zkvm/specifications/proving-instructions)
* [Nexus zkVM overview](/zkvm/specifications/the-complete-specification)
* Example: <https://docs.nexus.xyz/zkvm/specifications/example>


# Proving — An Example

In order to illustrate how these different components work together, let us consider an example in which the program counter pc points to memory location 0x00000004, containing the binary encoding of the instruction `ADDI x10 x8 3`. Moreover, for the sake of this example, let us assume the following about the state of the VM.

* Current clock cycle: 256
* Current trace row: 255
* Total number of rows: 2^16
* R\[x8] = 0x000000FF was last updated with timestamp 323
* R\[x10] = 0x00000005 was last updated with timestamp 77
* Prog\[0x00000004] has been accessed 222 times before the current clock cycle

In the following we describe the relevant trace columns associated with each component, their expected values at row 255 associated with the current clock cycle 256, and the associated constraints that they must satisfy.

### CPU Component Trace Columns and Constraints

In order to verify the correct execution of the `ADDI x10 x8 3` instruction at clock cycle 256, the CPU component will perform the following operations:

* Ensure a correct state transition;
* Fetch the instruction `ADDI x10 x8 3` from the program memory component;
* Decode the contents of the instruction and check the correctness of its format;
* Read contents of register x8 from the register memory component;
* Interact with the execution component to execute the instruction `ADDI x10 x8 3`; and
* Update the contents of register x10 based on the output of the execution component.

Let us now show how each of these operations is performed.

#### Ensuring a Correct State Transition

The CPU component ensures the state transition is performed correctly to guarantee correct ordering of instructions. It performs these checks:

{% stepper %}
{% step %}

### Verify program counter continuity

* Verify that the program counter in the present row matches the value of the next program counter from the preceding row (accounting for limb decomposition).
* Transition constraints (comparing two limbs at a time):
  * (1 - is\_first\[i]) \* (1 - is\_pad\[i]) \* (pc(1)\[i] + pc(2)\[i] \* 2^8 - pc\_next(1)\[i-1] - pc\_next(2)\[i-1] \* 2^8) = 0
  * (1 - is\_first\[i]) \* (1 - is\_pad\[i]) \* (pc(3)\[i] + pc(4)\[i] \* 2^8 - pc\_next(3)\[i-1] - pc\_next(4)\[i-1] \* 2^8) = 0

For our concrete state at row 255:

* i = 255
* pc(1)\[255] = 0x04, pc(2)\[255] = 0x00, pc(3)\[255] = 0x00, pc(4)\[255] = 0x00
* is\_pad\[255] = 0, is\_first\[255] = 0

Therefore pc\_next limbs on row 254 must equal 0x04, 0x00, 0x00, 0x00 respectively.
{% endstep %}

{% step %}

### Check clock update correctness

* Transition constraints for clk\[i] for row i > 0:
  * Use clk\_carry(1), clk\_carry(2) for carries.
  * Adding two limbs at a time:
    * clk(1)\[i] + clk(2)\[i] \* 2^8 + clk\_carry(1)\[i] \* 2^16 = clk(1)\[i-1] + clk(2)\[i-1] \* 2^8 + 1
    * clk(3)\[i] + clk(4)\[i] \* 2^8 + clk\_carry(2)\[i] \* 2^16 = clk(3)\[i-1] + clk(4)\[i-1] \* 2^8 + clk\_carry(1)\[i]
  * Enforce clk\_carry(j) in {0,1} via (clk\_carry(j))\*(1 - clk\_carry(j)) = 0
  * Range-check clk(j) ∈ \[0, 2^8 - 1] for each limb.

For our state, to increment clock from 255 → 256 it must hold that on row 254:

* clk(1)\[254] = 0xFF, clk(2)\[254] = 0x00, clk(3)\[254] = 0x00, clk(4)\[254] = 0x00
* clk\_carry(1)\[255] = 0, clk\_carry(2)\[255] = 0
  {% endstep %}

{% step %}

### Prevent padding rows from being followed by non-padding rows

* Enforce: (1 - is\_first\[i]) \* (1 - is\_pad\[i]) \* (is\_pad\[i-1]) = 0

With is\_pad\[255] = 0 and is\_first\[255] = 0, this implies is\_pad\[254] = 0.
{% endstep %}
{% endstepper %}

#### Fetching the Instruction

The CPU must read the instruction stored at the memory location pointed by pc and ensure pc is memory-aligned (multiple of 4).

Remark: Whenever constraints involve columns restricted to the same row, we omit explicit \[i] index for readability (values below apply to row 255 unless otherwise noted).

* The CPU interaction with program memory is captured by ReadProg interface with parameters (pc, clk) to obtain instr\_val. In the trace, instr\_val and pc are shared between CPU and program memory.

As a result at row 255:

* instr\_val(1) = Prog\[0x00000004] = 0b00010011
* instr\_val(2) = Prog\[0x00000005] = 0b00000101
* instr\_val(3) = Prog\[0x00000006] = 0b00110100
* instr\_val(4) = Prog\[0x00000007] = 0b00000000

Binary encoding breakdown for `ADDI x10 x8 3`:

* Bits 0–6: 0b0010011 (ADDI constant)
* Bits 7–11: 0b01010 → destination register x10 (op\_a)
* Bits 12–14: 0b000 (ADDI constant)
* Bits 15–19: 0b01000 → source register x8 (op\_b)
* Bits 20–31: 0b000000000011 → immediate 3 (op\_c)

Memory alignment constraint for pc:

* pc\_aux(1) \* 4 - pc(1) = 0
* pc\_aux(1) ∈ \[0, 2^6 - 1]

For this example, set pc\_aux(1) = 0x01 to show pc is multiple of 4.

#### Decoding the Instruction

The prover provides auxiliary values (advices) to help verify the binary encoding:

* op\_a = destination register (10)
* op\_b = source register (8)
* op\_c = immediate (3)
* op\_b\_flag = 1 (operand b used)
* imm\_c = 1 (operand c is immediate)
* is\_add = 1 (selector for ADD/ADDI)
* is\_alu\_imm\_no\_shift = 1
* is\_type\_i = 1
* is\_pad = 0, is\_first = 0, is\_last = 0

Operand decomposition advices (bits split across limbs):

* op\_a0 = 0 (bit 0 of op\_a)
* op\_a1\_4 = 5 (bits 1–4 of op\_a)
* op\_b0 = 0 (bit 0 of op\_b)
* op\_b1\_4 = 4 (bits 1–4 of op\_b)
* op\_c0\_3 = 3 (bits 0–3 of op\_c)
* op\_c4\_7 = 0 (bits 4–7 of op\_c)
* op\_c8\_10 = 0 (bits 8–10 of op\_c)
* op\_c11 = 0 (bit 11 of op\_c)

Convert the numbered set of constraints used to validate decoding into steps:

{% stepper %}
{% step %}

### 1) Exactly one instruction or padding flag is set

Enforce sum of instruction flags + is\_pad = 1. Since is\_add = 1, all other flags are 0.
{% endstep %}

{% step %}

### 2) op\_b\_flag correctness

Enforce op\_b\_flag = 1 for all instructions except {LUI, AUIPC, JAL, UNIMP}. This is satisfied by is\_add = 1 and op\_b\_flag = 1.
{% endstep %}

{% step %}

### 3) imm\_c correctness

Enforce imm\_c = 1 for all non-ALU instructions (constraint used to ensure correctness across instruction types). Given imm\_c = 1 in our example, constraints are satisfied.
{% endstep %}

{% step %}

### 4) Match instruction flag with opcode

For ADD/ADDI: (is\_add) \* (opcode - ADD) = 0. With is\_add = 1 and opcode set to ADD constant, satisfied.
{% endstep %}

{% step %}

### 5) ALU flags grouping

Define aggregated flags:

* is\_alu = sum of ALU instruction selectors
* is\_alu\_imm\_shift = imm\_c \* (is\_sll + is\_srl + is\_sra)
* is\_alu\_imm\_no\_shift = imm\_c \* (is\_add + is\_slt + is\_sltu + is\_xor + is\_or + is\_and)
* is\_type\_i\_no\_shift = is\_load + is\_alu\_imm\_no\_shift + is\_jalr
* is\_type\_i = is\_load + is\_alu\_imm\_no\_shift + is\_alu\_imm\_shift + is\_jalr

With is\_add = 1 and imm\_c = 1 we get:

* is\_alu = 1
* is\_alu\_imm\_shift = 0
* is\_alu\_imm\_no\_shift = 1
* is\_type\_i\_no\_shift = 1
* is\_type\_i = 1
  {% endstep %}

{% step %}

### 6) Operand decomposition consistency and range checks

* Ensure op\_a0 + op\_a1\_4 \* 2 - op\_a = 0 (when is\_type\_i\_no\_shift = 1)
* Range-check op\_a0 is binary, op\_a1\_4 ∈ \[0, 2^4 - 1]
* Ensure op\_b0 + op\_b1\_4 \* 2 - op\_b = 0
* Range-check op\_b parts
* Ensure op\_c0\_3 + op\_c4\_7 \* 2^4 + op\_c8\_10 \* 2^8 + op\_c11 \* 2^11 - op\_c = 0
* Range-check op\_c parts

With provided operand parts and is\_type\_i\_no\_shift = 1, these constraints hold for the example.
{% endstep %}

{% step %}

### 7) Sign-extension for operand c

Compute c\_val from op\_c using sign-extension constraints across limbs. Since op\_c11 = 0, higher limbs become 0. Prover sets:

* c\_val(1) = 0x03
* c\_val(2) = 0x00
* c\_val(3) = 0x00
* c\_val(4) = 0x00
  {% endstep %}

{% step %}

### 8) Instruction format checks across limbs

* Limb 1: (is\_alu\_imm\_no\_shift) \* (0b0010011 + op\_a0 \* 2^7 - instr\_val(1)) = 0
* Limb 2: (is\_add) \* (imm\_c) \* (op\_a1\_4 + 0b000 \* 2^4 + op\_b0 \* 2^7 - instr\_val(2)) = 0
* Limb 3: (is\_type\_i\_no\_shift) \* (op\_b1\_4 + op\_c0\_3 \* 2^4 - instr\_val(3)) = 0
* Limb 4: (is\_type\_i\_no\_shift) \* (op\_c4\_7 + op\_c8\_10 \* 2^4 + op\_c11 \* 2^7 - instr\_val(4)) = 0

With the previously set flags and operand parts, these constraints are satisfied for the given instr\_val limbs.
{% endstep %}
{% endstepper %}

#### Reading the Contents of Register x8

The interaction with register memory is captured by ReadReg interface with parameters (op\_b, clk, 1) where 1 indicates this is source register reg1. In the trace, fields are shared between CPU and register memory.

As a result of the interaction:

* reg1\_addr = op\_b
* b\_val limbs are set equal to reg1\_val\_cur limbs

Given the assumption R\[x8] = 0x000000FF before execution, the limbs are:

* b\_val(1) = reg1\_val\_cur(1) = 0xFF
* b\_val(2) = reg1\_val\_cur(2) = 0x00
* b\_val(3) = reg1\_val\_cur(3) = 0x00
* b\_val(4) = reg1\_val\_cur(4) = 0x00

#### Executing the Instruction

The CPU calls the execution component via exec(pc, opcode, a\_val, b\_val, c\_val) to obtain pc\_next. For ADD, execution updates a\_val.

After execution (ADDI x10 x8 3 → x10 := x8 + 3), the limbs are set as:

* a\_val(1) = 0x02
* a\_val(2) = 0x01
* a\_val(3) = 0x00
* a\_val(4) = 0x00

pc is incremented by 4, so:

* pc\_next(1) = 0x08
* pc\_next(2) = 0x00
* pc\_next(3) = 0x00
* pc\_next(4) = 0x00

(These a\_val limbs follow from b\_val = 0x000000FF and c\_val = 0x00000003.)

#### Updating the contents of register x10

To ensure x0 stays zero, CPU computes a\_val\_effective:

* a\_val\_effective = a\_val when op\_a ≠ 0, otherwise 0.
* Use auxiliary a\_val\_effective\_flag and supporting auxiliaries to enforce this (including multiplicative inverse aux variables).
* Enforce a\_val\_effective\_flag ∈ {0,1} and relation a\_val(limb) \* a\_val\_effective\_flag = a\_val\_effective(limb).

For op\_a = x10 (non-zero) the prover sets:

* a\_val\_effective\_flag = 1
* a\_val\_effective\_flag\_aux = 1 (non-zero aux)
* a\_val\_effective\_flag\_aux\_inv = appropriate inverse
* a\_val\_effective(limbs) = a\_val(limbs) = 0x02, 0x01, 0x00, 0x00 respectively

Then CPU interacts with register memory via WriteReg(op\_a, a\_val\_effective, clk, 3) to write to reg3 (destination). As a result reg3\_val\_cur limbs (for reg3\_addr = op\_a = x10) become:

* reg3\_val\_cur(1) = 0x02
* reg3\_val\_cur(2) = 0x01
* reg3\_val\_cur(3) = 0x00
* reg3\_val\_cur(4) = 0x00

### Execution Component Trace Columns and Constraints

To verify the ADD execution, the execution component enforces carry-handling constraints across limbs and that helper carries are binary.

Carry handling for ADD (per limb):

* (is\_add) \* (op\_a\_val(1) + h\_carry(1) \* 2^8 - op\_b\_val(1) - op\_c\_val(1)) = 0
* (is\_add) \* (op\_a\_val(2) + h\_carry(2) \* 2^8 - op\_b\_val(2) - op\_c\_val(2) - h\_carry(1)) = 0
* (is\_add) \* (op\_a\_val(3) + h\_carry(3) \* 2^8 - op\_b\_val(3) - op\_c\_val(3) - h\_carry(2)) = 0
* (is\_add) \* (op\_a\_val(4) + h\_carry(4) \* 2^8 - op\_b\_val(4) - op\_c\_val(4) - h\_carry(3)) = 0

And enforce (is\_add) \* h\_carry(j) \* (1 - h\_carry(j)) = 0 for each helper carry.

Given b\_val = 0x000000FF and c\_val = 0x00000003, to satisfy these constraints:

* a\_val limbs = 0x02, 0x01, 0x00, 0x00
* h\_carry(1) = 1, h\_carry(2) = 0, h\_carry(3) = 0, h\_carry(4) = 0

Next, execution determines whether pc is incremented by 4 (is\_pc\_inc\_std). It enforces:

* (is\_alu + is\_load + is\_type\_s + is\_type\_u + is\_type\_sys \* (1 - is\_sys\_halt) - is\_pc\_inc\_std) = 0

If is\_pc\_inc\_std = 1 then pc\_next must equal pc + 4, handled limb-wise with pc\_carry auxiliaries:

* (is\_pc\_inc\_std) \* (pc\_next(1) + pc\_next(2) \* 2^8 + pc\_carry(1) \* 2^16 - pc(1) - pc(2) \* 2^8 - 4) = 0
* (is\_pc\_inc\_std) \* (pc\_next(3) + pc\_next(4) \* 2^8 + pc\_carry(2) \* 2^16 - pc(3) - pc(4) \* 2^8 - pc\_carry(1)) = 0
* Enforce pc\_carry(j) binary via (is\_pc\_inc\_std) \* pc\_carry(j) \* (1 - pc\_carry(j)) = 0

For pc = 0x00000004, this yields pc\_next = 0x00000008 and pc\_carry(1) = pc\_carry(2) = 0.

### Program Memory Component Trace Columns and Constraints

The program memory component uses offline memory checking (simplified for read-only memory) with a counter per memory cell tracking read counts. Trace elements include:

* pc (word-aligned base address for instruction)
* instr\_val(1..4): instruction word bytes at pc, pc+1, pc+2, pc+3
* prog\_ctr\_prev (4 limbs): previous counter for base address pc
* prog\_ctr\_cur (4 limbs): current counter for base address pc
* prog\_read\_digest: digest of read set (logup)
* prog\_write\_digest: digest of write set (logup)

Actions per read:

* Check counter update correctness
* Verify read/write set digests updated correctly

#### Enforcing correct update of access counters

Enforce prog\_ctr\_cur = prog\_ctr\_prev + 1 using prog\_ctr\_carry auxiliaries across limbs. Carry bits must be binary.

Given Prog\[0x00000004] was accessed 222 times before current clock, set:

* prog\_ctr\_prev = 222 → limbs: prog\_ctr\_prev(1) = 2, prog\_ctr\_prev(2..4) = 0
* prog\_ctr\_cur = 223 → limbs: prog\_ctr\_cur(1) = 3, prog\_ctr\_cur(2..4) = 0
* prog\_ctr\_carry(1..4) = 0

#### Enforcing correct update of read- and write-set digests

Let fp(pc, instr\_val, prog\_ctr) be a fingerprint function (uses verifier-chosen β). Using random α chosen by verifier, enforce transition constraints for i > 0:

* prog\_read\_digest\[i] - prog\_read\_digest\[i-1] = 1 / (fp(pc\[i], instr\_val\[i], prog\_ctr\_prev\[i]) + α)
* prog\_write\_digest\[i] - prog\_write\_digest\[i-1] = 1 / (fp(pc\[i], instr\_val\[i], prog\_ctr\_cur\[i]) + α)

For row 255 with known pc and instr\_val limbs, these constraints must hold with the corresponding prog\_ctr\_prev and prog\_ctr\_cur.

Remark: instr\_val, prog\_ctr\_prev and prog\_ctr\_cur limbs also have range checks in the formal spec; the values above satisfy those ranges.

### Register Memory Component Trace Columns and Constraints

The register memory component uses offline memory checking for read/write memory and associates a timestamp per cell. It also uses logups for read/write set digest consistency. Each access maintains tuples of (reg\_addr, reg\_val\_prev, reg\_val\_cur, reg\_ts\_prev, reg\_ts\_cur). Up to three register addresses can be accessed in one execution cycle; the trace contains three such sets.

Trace elements include:

* clk
* reg1\_addr, reg2\_addr, reg3\_addr
* reg1\_val\_cur, reg2\_val\_cur, reg3\_val\_cur (32-bit values)
* reg1\_ts\_cur, reg2\_ts\_cur, reg3\_ts\_cur (current timestamps)
* reg1\_val\_prev, reg2\_val\_prev, reg3\_val\_prev
* reg1\_ts\_prev, reg2\_ts\_prev, reg3\_ts\_prev
* reg\_read\_digest, reg\_write\_digest
* reg1\_accessed, reg2\_accessed, reg3\_accessed flags (indicate whether each set is used)

Register memory enforces:

1. Current timestamps for reg1/2/3 satisfy:
   * reg1\_ts\_cur = 3 \* clk - 2
   * reg2\_ts\_cur = 3 \* clk - 1
   * reg3\_ts\_cur = 3 \* clk
2. Previous timestamps precede current timestamps:
   * regj\_ts\_prev ∈ {0, …, regj\_ts\_cur - 1}
3. Read/write digest updates via logup contributions (described below).

Remark: reg1\_addr, reg2\_addr, reg3\_addr should be accessed in order and only reg3\_addr can be modified during a clock cycle.

#### Enforcing the Correct Update of Read- and Write-Set Digests (Registers)

Let fp(reg\_addr, reg\_val, reg\_ts) be a fingerprint function (uses verifier-chosen β). For row index i > 0 and random α:

* reg\_read\_digest\[i] - reg\_read\_digest\[i-1] = reg1\_accessed\[i] / (fp(reg1\_addr\[i], reg1\_val\[i], reg1\_ts\_prev\[i]) + α)
  * reg2\_accessed\[i] / (fp(reg2\_addr\[i], reg2\_val\[i], reg2\_ts\_prev\[i]) + α)
  * reg3\_accessed\[i] / (fp(reg3\_addr\[i], reg3\_val\[i], reg3\_ts\_prev\[i]) + α)
* reg\_write\_digest\[i] - reg\_write\_digest\[i-1] = reg1\_accessed\[i] / (fp(reg1\_addr\[i], reg1\_val\[i], reg1\_ts\_cur\[i]) + α)
  * reg2\_accessed\[i] / (fp(reg2\_addr\[i], reg2\_val\[i], reg3\_ts\_cur\[i]) + α)
  * reg3\_accessed\[i] / (fp(reg3\_addr\[i], reg3\_val\[i], reg3\_ts\_cur\[i]) + α)

For this example, the assumptions and derived values for row 255 are:

* R\[x8] = 0x000000FF with last timestamp 323
* R\[x10] = 0x00000005 with last timestamp 77
* clk\[255] = 256
* reg1\_ts\_cur\[255] = 3 \* 256 - 2 = 766
* reg3\_ts\_cur\[255] = 3 \* 256 = 768
* reg1\_accessed\[255] = 1, reg2\_accessed\[255] = 0, reg3\_accessed\[255] = 1
* R\[x10] updated to 0x00000102 at current clock

To satisfy logup constraints at i = 255, the following must be set consistently (limb decompositions shown):

* reg1\_ts\_prev\[255] limbs: (1) = 32, (2..4) = 0
* reg1\_val\_prev\[255] = reg1\_val\_cur\[255] = 0xFF, 0x00, 0x00, 0x00
* reg1\_ts\_cur\[255] limbs: (1) = 254, (2) = 2, (3..4) = 0 (representation of 766)
* reg3\_ts\_prev\[255] limbs: (1) = 7, (2..4) = 0
* reg3\_val\_prev\[255] limbs: 0x05, 0x00, 0x00, 0x00
* reg3\_ts\_cur\[255] limbs: (1) = 0, (2) = 3, (3..4) = 0 (representation of 768)
* reg3\_val\_cur\[255] limbs: 0x02, 0x01, 0x00, 0x00

With these values, the register read/write digest transition constraints hold for the example.

***

References (kept intact):

* Proving Memory: <https://docs.nexus.xyz/zkvm/specifications/memory-checking>
* Proving Instructions: <https://docs.nexus.xyz/zkvm/specifications/instructions>
* Licensing: <https://docs.nexus.xyz/zkvm/license>

(End of cleaned/import-optimized page content.)


# Licensing

## Licensing

The Nexus zkVM is licensed under the source-available Business Source License (BUSL):\
<https://github.com/nexus-xyz/nexus-zkvm/blob/main/LICENSE>

This license will convert to an open-source dual Apache 2.0 and MIT licensing on February 10th, 2029.


