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

# Build with the L2 Order Book Diff

> Track live Hyperliquid order book state across many assets with bandwidth proportional to changes — an initial snapshot plus per-block diffs.

The `l2BookDiff` channel is a bandwidth-efficient way to track order book changes. Instead of a full book on every update, you get an **initial snapshot** per coin followed by **per-block `Updates`** carrying only the price levels that changed — the same `{ px, sz, n }` structure as [`l2Book`](/guides/hyperliquid/websockets/l2-book). It's a GoldRush-native channel, unavailable on the public Hyperliquid feed, and the cheapest way to keep a live book across many assets.

<Info>
  `l2BookDiff` is billed at **0.1 credits/min per coin** (500 CU/min) — a fifth
  of `l2Book`. See [pricing](/guides/hyperliquid/websockets/overview#pricing).
</Info>

## Subscribe

```json theme={null}
{
  "method": "subscribe",
  "subscription": { "type": "l2BookDiff", "coin": "HYPE" }
}
```

`coin` accepts:

* a single asset: `"HYPE"`
* an array: `["HYPE", "BTC", "ETH"]`
* omitted entirely — streams all perps on one connection. When omitted, specify `marketTypes` like `["spot"]` or `["*"]` for multi-market coverage.

## Payload shapes

The first message per coin is a `Snapshot`; subsequent messages are `Updates`.

### Snapshot

```json theme={null}
{
  "channel": "l2BookDiff",
  "data": {
    "Snapshot": {
      "coin": "HYPE",
      "levels": [
        [{ "px": "0.50", "sz": "100", "n": 5 }],
        [{ "px": "0.51", "sz": "200", "n": 3 }]
      ]
    }
  }
}
```

`levels` is `[bids, asks]` — each entry is a price object.

### Update

```json theme={null}
{
  "channel": "l2BookDiff",
  "data": {
    "Updates": {
      "book_diffs": [
        {
          "coin": "HYPE",
          "levels": [
            [{ "px": "0.50", "sz": "50", "n": 4 }],
            [{ "px": "0.51", "sz": "0", "n": 0 }]
          ]
        }
      ]
    }
  }
}
```

| Field        | Meaning                                               |
| ------------ | ----------------------------------------------------- |
| `px`         | Price level, as a string.                             |
| `sz`         | Size at that level; `"0"` means **delete** the level. |
| `n`          | Number of individual orders at the level.             |
| `coin`       | Asset symbol (e.g. `HYPE`, `BTC`).                    |
| `levels`     | Two-element array: `[bidLevels, askLevels]`.          |
| `book_diffs` | Array of diff objects, one per affected coin.         |

## Maintain a local book

1. On `Snapshot`: initialize (or reset) the coin's book with bid and ask maps.
2. On `Updates`: for each `book_diff`, apply the changed levels to the matching side.
3. When `sz === "0"`, delete that price level; otherwise set or update it.

```typescript theme={null}
type Level = { px: string; sz: string; n: number };

function applyLevels(side: Map<string, Level>, levels: Level[]) {
  for (const lvl of levels) {
    if (lvl.sz === "0") side.delete(lvl.px);
    else side.set(lvl.px, lvl);
  }
}

const books = new Map<string, { bids: Map<string, Level>; asks: Map<string, Level> }>();

ws.on("message", (raw) => {
  const msg = JSON.parse(raw.toString());
  if (msg.channel !== "l2BookDiff") return;

  if (msg.data.Snapshot) {
    const { coin, levels } = msg.data.Snapshot;
    const book = { bids: new Map(), asks: new Map() };
    applyLevels(book.bids, levels[0]);
    applyLevels(book.asks, levels[1]);
    books.set(coin, book);
  } else if (msg.data.Updates) {
    for (const diff of msg.data.Updates.book_diffs) {
      const book = books.get(diff.coin);
      if (!book) continue; // wait for the snapshot first
      applyLevels(book.bids, diff.levels[0]);
      applyLevels(book.asks, diff.levels[1]);
    }
  }
});
```

Reconstruct top-of-book by scanning your local maps for the highest bid and lowest ask.

## Reconnection

On reconnect, resend your original subscription. Fresh snapshots arrive first — **discard local state for affected coins and re-seed from those snapshots** rather than attempting to replay missed updates.
