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

# Build with the L2 Order Book

> The Hyperliquid L2 order book over WebSocket - build top-of-book quote feeds, depth-weighted mid prices, slippage and market-impact estimators, and liquidity heatmaps from self-contained L2 snapshots, no diff replay or snapshot bootstrap required.

The `l2Book` channel on `wss://hypercore.goldrushdata.com/ws?key=<GOLDRUSH_API_KEY>` is wire-equal to the public Hyperliquid feed but with `coin` made optional and the per-IP subscription cap removed. This recipe shows how to turn that stream into common trading and analytics building blocks. For the raw subscription shape see the [`l2Book` reference](/api-reference/hyperliquid-websocket/l2-book); for the connection model see the [WebSocket API overview](/goldrush-hyperliquid/websocket-api/overview).

## What you get

* **Complete snapshots, not diffs.** Every `l2Book` message contains the current `time`, `coin`, and a full `[bids, asks]` tuple in best-first order, with `px` / `sz` / `n` per level. Consume each message in isolation - no sequence numbers to track, no diff replay buffer, no REST snapshot to bootstrap.
* **Self-healing on packet loss.** Drop a message, reconnect mid-session, or restart your process - the next message arrives with the full book state, so your in-memory view is correct on the very next tick.
* **Upstream-compatible aggregation knobs.** `nSigFigs` accepts `2`, `3`, `4`, `5`, or `null` (full precision). `mantissa` accepts `1`, `2`, or `5`, and is only valid when `nSigFigs` is `5`.
* **Wildcard coverage.** Omit `coin` to stream every asset's book over a single subscription, instead of fanning out one subscription per asset. The wildcard defaults to perps only (`marketTypes: ["perp"]`); pass `["spot"]`, `["outcome"]`, a mix, or `["*"]` to opt into spot, outcome, and future market types.

## Pair address format

Hyperliquid markets use a **deployer-prefix naming scheme**. The exact string differs by surface, so pass the value each API expects - matching is case- and format-sensitive.

| Where it's used                                                       | Format                                                                   | Examples                                    |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------- |
| WebSocket `coin` (`l2Book`, `l2BookDiff`, `l4Book`) - canonical perps | `<symbol>`                                                               | `BTC`, `ETH`, `HYPE`                        |
| WebSocket `coin` - HIP-3 builder markets                              | `<deployer>:<symbol>`                                                    | `xyz:GOLD`, `flx:OIL`                       |
| Streaming `pair_addresses` (`ohlcvCandlesForPair`)                    | `<deployer>:<symbol>-<quote>`, or `<symbol>-<quote>` for canonical pairs | `xyz:GOLD-USDC`, `flx:OIL-USDH`, `BTC-USDC` |
| Streaming `token_addresses` (`ohlcvCandlesForToken`)                  | `<symbol>` (no prefix)                                                   | `GOLD`, `OIL`, `BTC`                        |

* `deployer` - wallet address of the HIP-3 builder that deployed the market. Omitted for canonical Hyperliquid markets.
* `symbol` - market ticker (e.g. `GOLD`, `OIL`).
* `quote` - the margin / quote currency, usually `USDC` (some HIP-3 markets use `USDH`).

<Note>HIP-4 outcome markets use a separate `#<encoding>` scheme (e.g. `#1230`), not the deployer-prefix - see [HIP-4 Markets](/goldrush-hyperliquid/streaming/hip4-markets).</Note>

**Discovering markets:** a dedicated market-list endpoint (the `perpDexs` Info API type) is on the roadmap. Until it ships, call [`metaAndAssetCtxs`](/api-reference/hyperliquid-info/meta-and-asset-ctxs) - the `name` field on each `universe` entry is the canonical pair address.

## Subscribe and hold book state

The pattern below keeps a `Map<coin, snapshot>` in memory. Each incoming message replaces the entry for its `coin`, so the map is always current and never needs reconciliation.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import WebSocket from "ws";

  type Level = { px: string; sz: string; n: number };
  type Snapshot = { time: number; bids: Level[]; asks: Level[] };

  const books = new Map<string, Snapshot>();

  const ws = new WebSocket(
    `wss://hypercore.goldrushdata.com/ws?key=${process.env.GOLDRUSH_API_KEY}`,
  );

  ws.on("open", () => {
    // Omit `coin` to stream every asset on one subscription.
    ws.send(JSON.stringify({
      method: "subscribe",
      subscription: { type: "l2Book", coin: "BTC" },
    }));
  });

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

    const { coin, time, levels: [bids, asks] } = msg.data;
    books.set(coin, { time, bids, asks });

    console.log(coin, time, "bid:", bids[0]?.px, "ask:", asks[0]?.px);
  });
  ```

  ```python Python theme={null}
  import asyncio, json, os
  import websockets

  books: dict[str, dict] = {}

  async def main():
      uri = f"wss://hypercore.goldrushdata.com/ws?key={os.environ['GOLDRUSH_API_KEY']}"
      async with websockets.connect(uri) as ws:
          await ws.send(json.dumps({
              "method": "subscribe",
              "subscription": {"type": "l2Book", "coin": "BTC"},
          }))
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get("channel") != "l2Book":
                  continue
              data = msg["data"]
              coin, time = data["coin"], data["time"]
              bids, asks = data["levels"]
              books[coin] = {"time": time, "bids": bids, "asks": asks}
              print(coin, time, "bid:", bids[0]["px"], "ask:", asks[0]["px"])

  asyncio.run(main())
  ```
</CodeGroup>

## Patterns

### Top-of-book tracker

Read `bids[0]` and `asks[0]` directly from each message. The spread is `Number(asks[0].px) - Number(bids[0].px)`. No state required - every message is self-contained, so a single-line transformation gives you a live ticker.

### Depth-weighted mid quote

Sum `px * sz` across the first `K` levels on each side, then average. This produces a fair-value mid that's robust to thin top-of-book liquidity, and is useful as a hedging or pricing reference.

```typescript TypeScript theme={null}
function depthWeightedMid(snap: Snapshot, k = 5): number {
  const side = (levels: Level[]) => {
    let num = 0, den = 0;
    for (const { px, sz } of levels.slice(0, k)) {
      const p = Number(px), s = Number(sz);
      num += p * s;
      den += s;
    }
    return den > 0 ? num / den : NaN;
  };
  return (side(snap.bids) + side(snap.asks)) / 2;
}
```

### Slippage / impact estimator

Walk levels on the relevant side until cumulative `sz` covers the requested notional. Return the size-weighted average fill price - the difference vs the top-of-book is your expected slippage.

```typescript TypeScript theme={null}
function estimateFill(levels: Level[], targetSize: number): number {
  let remaining = targetSize, notional = 0;
  for (const { px, sz } of levels) {
    const take = Math.min(remaining, Number(sz));
    notional += take * Number(px);
    remaining -= take;
    if (remaining <= 0) break;
  }
  return remaining > 0 ? NaN : notional / targetSize;
}

// Buying 10 BTC against current asks:
const avgFill = estimateFill(books.get("BTC")!.asks, 10);
```

### Liquidity heatmap

On each message, append `[time, coin, side, px, sz]` rows to your time-series store (Clickhouse, TimescaleDB, Parquet). Because every message is a complete snapshot of the top levels, the heatmap rebuilds correctly from any contiguous slice of history - you don't need a separate "initial book" record to seed the visualisation.

## Handling reconnects

Reconnect logic is a one-liner: open a new socket and resend the same `subscribe` payload. The first message after subscribe is a full book snapshot, so your `books` map is correct on the next tick - there's nothing to replay, nothing to buffer, and no sequence numbers to reconcile against a separately-fetched REST snapshot.

```typescript TypeScript theme={null}
function connect() {
  const ws = new WebSocket(
    `wss://hypercore.goldrushdata.com/ws?key=${process.env.GOLDRUSH_API_KEY}`,
  );
  ws.on("open", () => ws.send(JSON.stringify({
    method: "subscribe",
    subscription: { type: "l2Book", coin: "BTC" },
  })));
  ws.on("close", () => setTimeout(connect, 1000));
  return ws;
}
```

## Related

* [`l2Book` API reference](/api-reference/hyperliquid-websocket/l2-book) - full subscription and message schema.
* [WebSocket API overview](/goldrush-hyperliquid/websocket-api/overview) - endpoint URL, auth, and limits.
* [`clearinghouseState`](/api-reference/hyperliquid-info/clearinghouse-state) - pair the live book with per-account position and margin state.
* [OHLCV pairs stream](/api-reference/streaming-api/subscriptions/ohlcv-pairs-stream) - candles instead of raw book state.
