> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aultblockchain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

Each data stream is a **subscription type**. You subscribe by type, and the server tags every frame
it sends back with the `channel` field. This page covers the Spot `/ws` protocol and payloads. For Testnet-Only Perpetuals, use [Perpetual Subscriptions](https://docs.aultblockchain.com/dex-api/websocket/perpetuals.md).

## Subscribe and Unsubscribe

```json theme={null}
{
  "method": "subscribe",
  "subscription": {
    "type": "l2Book",
    "coin": "AULT/USDC"
  }
}
```

`method` is `subscribe` or `unsubscribe`. To unsubscribe, send the same `subscription` object you
subscribed with. Unsubscribing from one type does not affect other subscriptions.

Only `subscribe` and `unsubscribe` are supported. This API has no equivalent of posting REST
requests over the WebSocket.

### Subscription Fields

| Field | Applies to | Description |
| - | - | - |
| `type` | all | Subscription type. Required |
| `coin` | market types | `BASE/QUOTE` symbol, for example `AULT/USDC`. Hex market ids are not accepted |
| `user` | user types | Address as `ault1...` or `0x...`, any case |
| `ttl` | optional, all | Subscription lifetime in seconds. `0` means no expiry. Capped at 3,600 |

These fields are rejected rather than silently ignored: `nSigFigs`, `mantissa`, and
`aggregateByTime`. These are accepted and ignored, and echoed back in the acknowledgement: `dex`
(single DEX) and `isPortfolioMargin` (spot only).

### Limits

| Limit | Value |
| - | - |
| Commands | 10 per second, burst of 20 |
| Simultaneous subscriptions per client | 100 |
| Maximum `ttl` | 3,600 seconds |

Resubscribing to a type the client already holds refreshes its TTL and does not consume a second
slot.

## Server Messages

Every server message is a two-field envelope:

```json theme={null}
{
  "channel": "l2Book",
  "data": {}
}
```

`channel` is the subscription `type` that produced the frame, with one exception: `userEvents` data
arrives with `channel` set to `user`.

Three channels carry control messages rather than data:

| Channel | Sent when | Data |
| - | - | - |
| `subscriptionResponse` | After every subscribe and unsubscribe | The echoed subscription, plus `ttl` or `error` |
| `error` | Protocol failure not tied to one subscription | `{ "message": "..." }` |
| `subscriptionExpired` | A TTL subscription expired | `{ "subscription": {...}, "reason": "ttl_expired" }` |

The acknowledgement echoes the subscription back, including ignored fields. On user types the `user`
value is echoed in the format you sent it, even though the server normalizes it internally.

```json theme={null}
{ "channel": "subscriptionResponse", "data": { "type": "l2Book", "coin": "AULT/USDC", "ttl": 300 } }
```

Failures attach `error` to the echoed subscription. See [Errors](https://docs.aultblockchain.com/dex-api/websocket/errors.md).

### Snapshot-First Delivery

`l2Book`, `candle`, `openOrders`, `userFills`, `spotState`, and `userTwapHistory` send an initial snapshot on subscribe. The
server acknowledges, sends the snapshot, and only then starts streaming. Live updates that arrive
while the snapshot is being prepared are buffered and flushed after it, so a consumer never sees an
update before the baseline it applies to.

This ordering applies only within one connection. It does not give you a block height for the snapshot or a sequence number shared with REST. See [Maintaining a Local Book](https://docs.aultblockchain.com/dex-api/rest/market-data.md#maintaining-a-local-book).

## Types at a Glance

Every type is a read-only view of public chain state, the same data REST serves without
authentication. Subscribing to a user type exposes no keys and cannot place orders.

Field encodings are listed in [Overview](https://docs.aultblockchain.com/dex-api/websocket/overview.md).

| Type | Key | Snapshot | Emitted |
| - | - | - | - |
| [`l2Book`](#l2book) | `coin` | yes | Every block with order book changes |
| [`bbo`](#bbo) | `coin` | no | Only when the best bid or offer changes |
| [`trades`](#trades) | `coin` | no | Every block with fills |
| [`orderUpdates`](#orderupdates) | `user` | no | Every block with order changes for the user |
| [`openOrders`](#openorders) | `user` | yes | Snapshot only, no streaming updates |
| [`userFills`](#userfills) | `user` | yes | Every block with fills for the user |
| [`userEvents`](#userevents) | `user` | no | Every block with fills or non-user cancels |
| [`spotState`](#spotstate) | `user` | yes | Every block with balance changes for the user |
| [`allMids`](#allmids) | none | no | Every block where any market's mid changes |
| [`candle`](#candle) | `coin`, `interval` | yes | Blocks with trades |
| [`activeAssetCtx`](#activeassetctx) | `coin` | no | Trades or book changes |
| [`userTwapHistory`](#usertwaphistory) | `user` | yes | Slice execution blocks |
| [`chainBlock`](#chainblock) | none | no | Each block |

## Market Types

### l2Book

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "l2Book", "coin": "AULT/USDC" } }
```

`levels[0]` is bids, descending by price. `levels[1]` is asks, ascending. Each frame replaces both
published sides; it is not a delta to merge by price. `sz` preserves raw base units. `n` is currently a zero placeholder, not a
usable count of resting orders.

```json theme={null}
{
  "coin": "AULT/USDC",
  "levels": [
    [{ "px": "1.5", "sz": "100", "n": 0 }, { "px": "1.49", "sz": "200", "n": 0 }],
    [{ "px": "1.51", "sz": "50", "n": 0 }, { "px": "1.52", "sz": "100", "n": 0 }]
  ],
  "time": 1700000000000
}
```

Levels use the same ordering as `GET /api/v1/orderbook/{symbol}`, so switching between the REST
snapshot and this stream needs no re-sorting. See [Market Data](https://docs.aultblockchain.com/dex-api/rest/market-data.md).

The frame has no block height or sequence. Its initial snapshot uses server time; live frames use
block time. Do not compare `time` with a REST `nonce` or use it to promise lossless recovery.

### bbo

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "bbo", "coin": "AULT/USDC" } }
```

Top of book only. `bbo[0]` is the best bid or `null`, `bbo[1]` is the best ask or `null`. Emitted
only when the top of book changed from the previous block.

```json theme={null}
{
  "coin": "AULT/USDC",
  "time": 1700000000000,
  "bbo": [{ "px": "1.5", "sz": "100", "n": 0 }, { "px": "1.51", "sz": "50", "n": 0 }]
}
```

### trades

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "trades", "coin": "AULT/USDC" } }
```

Executions in the block.

```json theme={null}
[
  { "coin": "AULT/USDC", "side": "buy", "px": "1.5", "sz": "100", "time": 1700000000000, "tid": 12345678901234 }
]
```

`tid` is derived from the order id, block height, and post-fill remaining size, so two same-size
fills on one order in one block stay distinct. It is not the REST trade `id` and cannot be passed to
`GET /api/v1/trade/{id}`.

## User Types

User types take a `user` address, given as `ault1...` or `0x...`.

### orderUpdates

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "orderUpdates", "user": "ault1..." } }
```

Array of changed orders.

```json theme={null}
[
  {
    "order": {
      "coin": "AULT/USDC",
      "side": "buy",
      "limitPx": "1.5",
      "sz": "100",
      "oid": 562949953421312,
      "oidHex": "abcdef0123456789abcdef0123456789",
      "timestamp": 1700000000000,
      "origSz": "100"
    },
    "status": "open",
    "chainStatus": "partially_filled",
    "statusTimestamp": 1700000000000
  }
]
```

| Field | Description |
| - | - |
| `status` | Collapsed status: `open`, `filled`, `canceled`, `triggered`, or `unknown` |
| `chainStatus` | Ault DEX extension with the granular chain status: `open`, `partially_filled`, `filled`, `cancelled`, `expired`, `triggered_pending`, or `unknown` |
| `sz` | Current remaining size |
| `origSz` | Original size |
| `oid` | 50-bit numeric id |
| `oidHex` | Full chain order id |

### openOrders

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "openOrders", "user": "ault1..." } }
```

Snapshot only, with no streaming updates. It returns at most **500 executable orders**, excludes
conditional (Testnet-Only) and TWAP parents, and has no continuation token or truncation flag. A successful
subscription does not mean you have every order. Each order uses the same shape as
`orderUpdates.order`; `dex` is always `"ault"`.

For a complete inventory, follow every page of [REST orders](https://docs.aultblockchain.com/dex-api/rest/orders.md), which includes
conditional and TWAP parents, for each wallet or derived account you control. Reconcile changes
with `orderUpdates` and subsequent reads; these interfaces do not share one point-in-time view.

```json theme={null}
{
  "dex": "ault",
  "user": "ault1...",
  "orders": [
    { "coin": "AULT/USDC", "side": "buy", "limitPx": "1.5", "sz": "100", "oid": 562949953421312, "oidHex": "abcdef0123456789abcdef0123456789", "timestamp": 1700000000000, "origSz": "100" }
  ]
}
```

### userFills

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "userFills", "user": "ault1..." } }
```

The first frame is a snapshot of up to 200 recent fills with `isSnapshot: true`. Live frames carry
`isSnapshot: false`.

```json theme={null}
{
  "isSnapshot": false,
  "user": "ault1...",
  "fills": [
    {
      "coin": "WETH/USDC",
      "px": "2462.076",
      "sz": "0.02",
      "side": "buy",
      "time": 1787306957000,
      "oid": 281474976711128,
      "oidHex": "00010000000001d831db000000000006",
      "tid": 189635840429351,
      "crossed": false,
      "fee": "0.00738622",
      "feeToken": "USDC"
    }
  ]
}
```

| Field | Description |
| - | - |
| `crossed` | `true` when the order took liquidity, `false` when it was the resting maker side |
| `fee` | Indexed realized amount when supplied; otherwise price times size times maker/taker rate |
| `feeToken` | Realized fee asset when supplied; quote currency for the fallback estimate |
| `cloid` | Client order id, present when the order carried one |

Realized fee amount and asset are used when available. Older records fall back to quote estimates and do not indicate that the value is an estimate. Use
[User Fills](https://docs.aultblockchain.com/dex-api/rest/user-fills.md#reconciling-an-order) and confirmed chain records to reconcile fees by currency. Native event granularity depends on the chain
version; see [Trade and Fill Data](https://docs.aultblockchain.com/dex-api/advanced/chain-queries.md#trade-and-fill-data) before
building an indexer or treating raw events as these API records.

> [!WARNING]
>
> Correlate fills to orders with `oidHex`, not `oid`. `oid` is a lossy 50-bit derivation and cannot be
> mapped back to the chain order id.

Against older chain nodes, `crossed` may not reflect the actual leg: live fills default to
`crossed: true` with a taker-rate fee, and older snapshot rows read as `crossed: false` with a
maker-rate fee. `cloid` may also be absent on live fills from those nodes. For authoritative fees,
read [User Fills](https://docs.aultblockchain.com/dex-api/rest/user-fills.md).

### userEvents

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "userEvents", "user": "ault1..." } }
```

Frames arrive with `channel` set to `user`, not `userEvents`. Each frame is one of two shapes: a
`nonUserCancel` array, or a `fills` array with the same shape as `userFills`.

```json theme={null}
{ "nonUserCancel": [ { "coin": "AULT/USDC", "oid": 12345 } ] }
```

Subscribe to this type for `nonUserCancel`, which is the only place expiry is reported. A non-user
cancel is currently emitted only for expired orders. The `fills` shape duplicates `userFills`, so
there is no need to consume both for fills.

> [!WARNING]
>
> The frame carries `oid` and no `oidHex`, and `oid` is not unique enough to identify one order, since
> orders from the same block can share a value. Treat the frame as a signal that something expired on
> that market, then reconcile which order it was through [Orders](https://docs.aultblockchain.com/dex-api/rest/orders.md).

### spotState

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "spotState", "user": "ault1..." } }
```

The first frame is the full balance set. Subsequent frames are deltas containing only the balances
that changed in the block, so merge them into local state by `coin`. There is no `isSnapshot` flag.

```json theme={null}
{
  "user": "ault1...",
  "spotState": {
    "balances": [
      { "coin": "AULT", "token": 0, "hold": "50.25", "total": "150.75", "entryNtl": "0" },
      { "coin": "USDC", "token": 0, "hold": "500", "total": "1500", "entryNtl": "0" }
    ]
  }
}
```

| Field | Description |
| - | - |
| `hold` | Amount locked in open orders |
| `total` | Total balance |
| `token` | Always `0`. Ault DEX does not assign numeric token ids |
| `entryNtl` | Always `"0"`. Cost basis is not tracked |

`hold` and `total` correspond to `used` and `total` on [Balances](https://docs.aultblockchain.com/dex-api/rest/balances.md). Free
balance is `total` minus `hold`.

## Keyless Types

### allMids

```json theme={null}
{ "method": "subscribe", "subscription": { "type": "allMids" } }
```

Every market's current mid price. The mid is the book midpoint when both sides exist, and falls back
to the last trade price when the book has one side or none.

```json theme={null}
{ "mids": { "AULT/USDC": "1.5", "WETH/USDC": "2462.076" } }
```

A market that has had neither a book nor a trade never appears. Once a market goes quiet it keeps its
last published mid.

## Additional Market and Strategy Channels

### candle

Subscribe with `{type: "candle", coin: "AULT/USDC", interval: "1m"}`. Accepted intervals are
`1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`.
The initial array contains up to 300 bars; updates contain one bar. Each bar has
`t`, `T` (open/close milliseconds), `s` (symbol), `i` (interval), numeric `o`, `c`, `h`, `l`, `v`,
and `n` (trade count, zero for snapshot bars). These intervals differ from REST candle support.

### activeAssetCtx

Subscribe with `{type: "activeAssetCtx", coin: "AULT/USDC"}`. The payload is `{coin, ctx}`.
Spot `ctx.markPx`, `midPx` and `circulatingSupply` are JSON numbers; `last`, `bid`, `bidVolume`,
`ask`, `askVolume` are decimal strings. `circulatingSupply` is a zero placeholder.
`markPx` is a Spot display fallback from mid/book/last and is not a Perp risk mark.
There are no 24-hour aggregates: fetch REST tickers. A missing bid/ask means that side is empty;
do not retain a previous quote. Missing 24-hour fields do not erase the separately loaded ticker.

### userTwapHistory

Subscribe with `{type: "userTwapHistory", user: "ault1..."}`. Payload:
`{isSnapshot, dex, user, history}`. The snapshot has up to 100 newest parents. Each item has
`coin`, `side`, `sz`, `depositAsset`, `numSlices`, `sliceIndex`, `oid`, `oidHex`, `status`,
`chainStatus`, `startTime`, `deadline`, `timestamp`; `priceTolerance`, `worstPx`, `closeReason`
are optional. `sz` is human units of the payment asset: base for sells, quote for buys.
It is a strategy budget, not proof of full-parent escrow.

Live frames are emitted only in blocks with a slice execution. Placement and closure produce
no parent frame, so query [REST orders](https://docs.aultblockchain.com/dex-api/rest/orders.md) to confirm those transitions.

### chainBlock

Subscribe with `{type: "chainBlock"}`. Each frame contains numeric `height` and `timestamp`
(milliseconds). Optional `hash`, `proposer`, `txCount`, `gasUsed`, `gasWanted` may be absent.
`hash` is bare uppercase hex. A block frame cannot be used to replay missed data or to mark a point at which all subscriptions are in sync.

## Ordering Across Types

Each subscription is delivered independently, so a fill can arrive before the order update that
reflects it. Treat each type as authoritative for its own data, and reconcile against REST after any
reconnection.

## Example Session

```
// subscribe to a market type
> { "method": "subscribe", "subscription": { "type": "l2Book", "coin": "AULT/USDC" } }
< { "channel": "subscriptionResponse", "data": { "type": "l2Book", "coin": "AULT/USDC" } }
< { "channel": "l2Book", "data": { "coin": "AULT/USDC", "levels": [[...],[...]], "time": 1700000000000 } }

// subscribe with a TTL
> { "method": "subscribe", "subscription": { "type": "trades", "coin": "AULT/USDC", "ttl": 300 } }
< { "channel": "subscriptionResponse", "data": { "type": "trades", "coin": "AULT/USDC", "ttl": 300 } }

// subscribe to a user type, 0x address echoed back as sent
> { "method": "subscribe", "subscription": { "type": "orderUpdates", "user": "0xAbC..." } }
< { "channel": "subscriptionResponse", "data": { "type": "orderUpdates", "user": "0xAbC..." } }

// unsubscribe
> { "method": "unsubscribe", "subscription": { "type": "trades", "coin": "AULT/USDC" } }
< { "channel": "subscriptionResponse", "data": { "type": "trades", "coin": "AULT/USDC" } }
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.