> ## 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 L4 Order Book

> Build per-trader flow attribution, queue-position estimators, smart-money trackers, and microstructure analytics from individual Hyperliquid orders - with `user`, `oid`, `cloid`, `tif`, and trigger metadata exposed on every resting order.

The `l4Book` channel on `wss://hypercore.goldrushdata.com/ws?key=<GOLDRUSH_API_KEY>` is a GoldRush-exclusive stream - it has no equivalent on `wss://api.hyperliquid.xyz/ws`. After subscribing, the server sends a single `Snapshot` of every resting order, then per-block `Updates` carrying lifecycle events and per-order book changes. Each order arrives with its `user`, `oid`, `cloid`, `tif`, and trigger metadata, so you can reconstruct queue position and price-time priority, attribute flow to specific wallets, and run microstructure analytics that `l2Book`'s aggregated `{px, sz, n}` view hides. For the raw subscription shape see the [`l4Book` reference](/api-reference/hyperliquid-websocket/l4-book); for the connection model see the [WebSocket API overview](/goldrush-hyperliquid/websocket-api/overview).

## What you get

* **Per-order visibility.** Every level in the snapshot is an individual order keyed by `oid`, with `user`, `cloid`, `tif`, `orderType`, and trigger metadata attached. `l2Book` only exposes `{px, sz, n}` per price level.
* **Snapshot + diff transport.** One full `Snapshot` on subscribe, then per-block `Updates` containing `order_statuses` (lifecycle events) and `book_diffs` (per-order changes). Apply diffs to local state.
* **Per-block cadence.** Updates fire on each HyperCore block where the book for the subscribed `coin` changed. The `time` and `block_height` fields anchor each message to a specific block.
* **GoldRush-exclusive.** Not available on the public Hyperliquid WebSocket - this stream surfaces user attribution and per-order metadata the public feed never exposes.
* **One coin per subscription.** Unlike `l2Book`, `coin` is required. To cover multiple assets, open one `l4Book` subscription per asset on the same connection.

## 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 maintain book state

The pattern below keeps a `Map<oid, Order>` per coin. The snapshot seeds the map; each `Updates` message applies `book_diffs` against it. Reconnects drop the map and re-seed from the next snapshot.

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

  type Order = {
    user: string | null;
    coin: string;
    side: "B" | "A";
    limitPx: string;
    sz: string;
    oid: number;
    timestamp: number;
    triggerCondition: string;
    isTrigger: boolean;
    triggerPx: string;
    isPositionTpsl: boolean;
    reduceOnly: boolean;
    orderType: string;
    tif: string;
    cloid: string | null;
  };

  const orders = new Map<number, Order>();

  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: "l4Book", coin: "BTC" },
    }));
  });

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

    if (msg.data.Snapshot) {
      orders.clear();
      const [bids, asks] = msg.data.Snapshot.levels;
      for (const o of [...bids, ...asks]) orders.set(o.oid, o);
      console.log("seeded from snapshot:", orders.size, "orders");
      return;
    }

    if (msg.data.Updates) {
      const { order_statuses, book_diffs } = msg.data.Updates;

      for (const s of order_statuses) {
        // Re-attach the parent `user` since the nested order has user=null.
        orders.set(s.order.oid, { ...s.order, user: s.user });
      }

      for (const d of book_diffs) {
        const existing = orders.get(d.oid);
        if (!existing) continue;
        if (d.raw_book_diff.new) {
          orders.set(d.oid, { ...existing, sz: d.raw_book_diff.new.sz });
        }
        // Other raw_book_diff shapes (deletes, modifies) belong here.
      }
    }
  });
  ```

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

  orders: dict[int, 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": "l4Book", "coin": "BTC"},
          }))
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get("channel") != "l4Book":
                  continue
              data = msg["data"]

              if "Snapshot" in data:
                  orders.clear()
                  bids, asks = data["Snapshot"]["levels"]
                  for o in bids + asks:
                      orders[o["oid"]] = o
                  print("seeded from snapshot:", len(orders), "orders")
                  continue

              if "Updates" in data:
                  for s in data["Updates"]["order_statuses"]:
                      o = {**s["order"], "user": s["user"]}
                      orders[o["oid"]] = o
                  for d in data["Updates"]["book_diffs"]:
                      existing = orders.get(d["oid"])
                      if not existing:
                          continue
                      new = d["raw_book_diff"].get("new")
                      if new:
                          existing["sz"] = new["sz"]

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

## Patterns

### Track individual orders by `oid`

`oid` is the Hyperliquid order id, stable for the lifetime of the order - use it as the primary key in your local map. `cloid` is the client-supplied id (may be `null`); index on it when you need to correlate fills back to a specific trading bot's instructions.

### Per-user flow attribution

Every order entry carries `user`. Group orders by wallet to surface market-maker behavior, identify spoofing patterns, or build a per-trader heatmap of resting size. Pair this with `clearinghouseState` for per-user position and margin context.

```typescript TypeScript theme={null}
function sizeByUser(orders: Map<number, Order>) {
  const totals = new Map<string, number>();
  for (const o of orders.values()) {
    if (!o.user) continue;
    totals.set(o.user, (totals.get(o.user) ?? 0) + Number(o.sz));
  }
  return totals;
}
```

### Reconstruct aggregated price levels

If a downstream consumer expects an `l2Book`-style aggregated view, sum `sz` across all orders sharing a `limitPx` on the same `side`. Going the other way isn't possible - `l2Book` collapses the per-order detail you'd lose.

```typescript TypeScript theme={null}
function aggregate(orders: Map<number, Order>) {
  const bids = new Map<string, number>();
  const asks = new Map<string, number>();
  for (const o of orders.values()) {
    const book = o.side === "B" ? bids : asks;
    book.set(o.limitPx, (book.get(o.limitPx) ?? 0) + Number(o.sz));
  }
  return { bids, asks };
}
```

## Handling reconnects

On reconnect, resend the same `subscribe` payload. The first message back is always a fresh `Snapshot` - drop your local `orders` map and re-seed from it. **Do not attempt to replay missed `Updates`** - the snapshot is authoritative and supersedes anything you held before the disconnect.

```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: "l4Book", coin: "BTC" },
  })));
  ws.on("close", () => {
    orders.clear();
    setTimeout(connect, 1000);
  });
  return ws;
}
```

## Related

* [`l4Book` API reference](/api-reference/hyperliquid-websocket/l4-book) - full subscription, snapshot, and update schema.
* [`l2Book` reference](/api-reference/hyperliquid-websocket/l2-book) - aggregated price-level snapshots when per-order detail isn't needed.
* [WebSocket API overview](/goldrush-hyperliquid/websocket-api/overview) - endpoint URL, auth, and limits.
* [`clearinghouseState`](/api-reference/hyperliquid-info/clearinghouse-state) - pair per-user resting orders with position and margin state.
