> ## 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.

# Websocket api

# Hyperliquid WebSocket API

## Critical Rules

1. WebSocket URL: `wss://hypercore.goldrushdata.com/ws?key=<GOLDRUSH_API_KEY>` (note: `hypercore`, not `hyperliquid`; auth is a `?key=` query parameter, not an `Authorization` header).
2. Wire-compatible with `wss://api.hyperliquid.xyz/ws` for shared subscription types (e.g. `l2Book`).
3. No 1000-subscription-per-IP cap. Multiplex many subscriptions on one connection.
4. **`l2Book`** - aggregated price-level snapshots `{px, sz, n}`. `coin` is **optional** - omit it to stream every asset over a single subscription. When `coin` is omitted, `marketTypes` defaults to `["perp"]` (perps only); pass `["spot"]`, `["outcome"]`, a mix, or `["*"]` to opt into spot, outcome, and any future market types.
5. **`l2BookDiff`** - GoldRush-native L2 diff transport. Same aggregated `{px, sz, n}` shape as `l2Book`, but emits one `Snapshot` per subscribed coin and then per-block `Updates` carrying only changed levels. `coin` accepts a single asset, an array of assets, or can be omitted for wildcard (same `marketTypes` default — perps only — applies when `coin` is omitted). Treat `sz: "0"` (with `n: 0`) as level removal. Not available on the public Hyperliquid WebSocket.
6. **`l4Book`** - GoldRush-native order-level stream with `user`, `oid`, `cloid`, `tif`, and trigger metadata per order. `coin` is **required**. Emits a single `Snapshot` on subscribe, then per-block `Updates` (`order_statuses` + `book_diffs`). Not available on the public Hyperliquid WebSocket.

## Available Subscriptions

| Channel      | Subscription body                                                                                                       | Returns                                                                                                                                                                                                                      |
| ------------ | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `l2Book`     | `{"type":"l2Book","coin":"BTC"}` (or omit `coin`)                                                                       | Full L2 book snapshot per tick with bids/asks aggregated by significant figures. Self-healing - every message is a complete snapshot.                                                                                        |
| `l2BookDiff` | `{"type":"l2BookDiff","coin":"HYPE"}`, `{"type":"l2BookDiff","coin":["HYPE","BTC","ETH"]}`, or omit `coin` for wildcard | Initial `Snapshot` per coin (aggregated `{px, sz, n}`), then per-block `Updates` with `book_diffs` grouped by coin carrying only changed levels. `sz: "0"` means level removed. **GoldRush-native, no upstream equivalent.** |
| `l4Book`     | `{"type":"l4Book","coin":"BTC"}`                                                                                        | Initial `Snapshot` of every resting order, then per-block `Updates` with `order_statuses` (lifecycle) and `book_diffs` (per-order changes). **GoldRush-native, no upstream equivalent.**                                     |

## Subscribe / Unsubscribe Pattern

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

// Unsubscribe (same subscription body, method swapped)
{"method":"unsubscribe","subscription":{"type":"l2Book","coin":"BTC"}}
```

## When to use which channel

| Need                                                                                        | Use                                                      |
| ------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| Top-of-book, spread, depth-weighted mid, slippage / impact estimator                        | `l2Book`                                                 |
| Stream every asset on one subscription                                                      | `l2Book` (omit `coin`) or `l2BookDiff` (omit `coin`)     |
| Aggregated `{px, sz, n}` book state with diff-only bandwidth, single coin / list / wildcard | `l2BookDiff`                                             |
| Multi-coin coverage with a fixed list of assets in one subscription                         | `l2BookDiff` with `coin: ["HYPE","BTC","ETH"]`           |
| Queue position, per-user flow attribution, microstructure analytics                         | `l4Book`                                                 |
| Reconstruct an L2-style aggregated view but keep order-level detail                         | `l4Book` (aggregate client-side)                         |
| OHLCV candles instead of raw book state                                                     | Streaming API `ohlcvCandlesForPair` (see `streaming.md`) |

***

The GoldRush Hyperliquid WebSocket API is a **drop-in replacement** for `wss://api.hyperliquid.xyz/ws`. Subscription payloads, channel names, and message shapes are byte-for-byte identical to the public Hyperliquid feed. The only difference is the connection URL - authentication is a required `key` query parameter, so no header changes are needed in your client.

## Endpoint

```
wss://hypercore.goldrushdata.com/ws?key=
```

Your GoldRush API key. Passed as a query parameter at connection time.

## Comparison with the public Hyperliquid WebSocket

|                      | Public WebSocket                                                                                     | GoldRush                                                |
| -------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| URL                  | `wss://api.hyperliquid.xyz/ws`                                                                       | `wss://hypercore.goldrushdata.com/ws?key=`              |
| Auth                 | None                                                                                                 | `key` query parameter (required)                        |
| Subscriptions per IP | 1000                                                                                                 | No cap                                                  |
| Wire compatibility   | n/a (it's the source)                                                                                | Byte-for-byte                                           |
| Available channels   | See [Hyperliquid Docs](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket) | See [Available subscriptions](#available-subscriptions) |

## Available subscriptions

| Channel          | Subscription body                        | Returns                                                                                                                                                                                                                                                                                            |
| ---------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`l2Book`**     | `{"type": "l2Book", "coin": "BTC"}`      | Real-time L2 order book snapshots - bids and asks aggregated by significant figures. `coin` is optional - omit it to stream every asset on one subscription.                                                                                                                                       |
| **`l2BookDiff`** | `{"type": "l2BookDiff", "coin": "HYPE"}` | GoldRush-native L2 diff transport - initial `Snapshot` per coin plus per-block `Updates` carrying only changed `{px, sz, n}` levels. `coin` accepts a single asset, an array of assets, or can be omitted to stream every asset. Not available on the public Hyperliquid WebSocket.                |
| **`l4Book`**     | `{"type": "l4Book", "coin": "BTC"}`      | GoldRush-native order-level book stream - initial `Snapshot` of every resting order plus per-block `Updates` with `order_statuses` and `book_diffs`. Exposes `user`, `oid`, `cloid`, `tif`, and trigger metadata per order. `coin` is required. Not available on the public Hyperliquid WebSocket. |

## Limits

No 1000-subscription-per-IP cap. On `l2Book` and `l2BookDiff`, filter parameters are optional - omit `coin` to stream the full L2 book across every asset on a single subscription. The wildcard defaults to **perps only** (`marketTypes: ["perp"]`); pass `["spot"]`, `["outcome"]`, a mix, or `["*"]` to opt into spot, outcome, and future market types. `l2BookDiff` additionally accepts an array of coin symbols for a fixed multi-asset subscription. `l4Book` requires `coin` and is one-asset-per-subscription. See **Limits & Connections** for details.

For richer real-time analytics (pre-decoded HyperCore fills, liquidations, vault events, OHLCV across every HIP-3/HIP-4 market), pair the WebSocket with the GraphQL **Streaming API**.

***

Moving from the public Hyperliquid WebSocket to GoldRush is one change:

1. **URL** - replace `wss://api.hyperliquid.xyz/ws` with `wss://hypercore.goldrushdata.com/ws?key=`.

That's it. Subscription payloads, channel names, and the streamed message shape are byte-for-byte identical. Authentication is a required `key` query parameter, so no header swap is needed in your client.

## Side-by-side

### wscat

```bash Public Hyperliquid theme={null}
wscat -c wss://api.hyperliquid.xyz/ws

> {"method":"subscribe","subscription":{"type":"l2Book","coin":"BTC"}}
```

```bash GoldRush theme={null}
wscat -c "wss://hypercore.goldrushdata.com/ws?key=$GOLDRUSH_API_KEY"

> {"method":"subscribe","subscription":{"type":"l2Book","coin":"BTC"}}
```

### JavaScript / TypeScript

```typescript Public Hyperliquid theme={null}
import WebSocket from "ws";

const ws = new WebSocket("wss://api.hyperliquid.xyz/ws");

ws.on("open", () => {
  ws.send(JSON.stringify({
    method: "subscribe",
    subscription: { type: "l2Book", coin: "BTC" },
  }));
});

ws.on("message", (raw) => {
  console.log(JSON.parse(raw.toString()));
});
```

```typescript GoldRush theme={null}
import WebSocket from "ws";

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("message", (raw) => {
  console.log(JSON.parse(raw.toString()));
});
```

### Python

```python Public Hyperliquid theme={null}
import asyncio, json
import websockets

async def main():
    async with websockets.connect("wss://api.hyperliquid.xyz/ws") as ws:
        await ws.send(json.dumps({
            "method": "subscribe",
            "subscription": {"type": "l2Book", "coin": "BTC"},
        }))
        async for raw in ws:
            print(json.loads(raw))

asyncio.run(main())
```

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

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:
            print(json.loads(raw))

asyncio.run(main())
```

## Behavioral notes

Things to be aware of when you cut over.

### Stream payloads are byte-equal, modulo live drift

The `channel` name and `data` shape match Hyperliquid byte-for-byte - same keys, same nesting, same value types. Numeric fields update independently on each side, so a price level may differ by tens of milliseconds, but the schema is identical.

### Auth errors close the connection

A missing or invalid `key` query param returns HTTP `401` on the upgrade handshake, so the WebSocket never opens. Public Hyperliquid has no auth and never rejects the handshake.

### Filter parameters are optional on GoldRush

Parameters that the public Hyperliquid WebSocket **requires** (e.g. `coin` on `l2Book`) are **optional** on GoldRush. Omit them to stream the entire channel on a single subscription instead of fanning out one subscription per asset. See **Limits**.

### Existing SDKs work after a `wsUrl` override

Most popular Hyperliquid SDKs accept a WebSocket base URL override - point them at `wss://hypercore.goldrushdata.com/ws?key=` and the rest of the SDK works unchanged. See **SDK compatibility** for the override snippets.

## Authentication

The WebSocket API uses your standard GoldRush API key, passed as a `key` query parameter at connection time. The same key works against the **Foundational API**, the **Streaming API**, the **Pipeline API**, and the **Info API**. If you don't have one yet, [sign up here](https://goldrush.dev/platform/auth/register/).

Never hardcode keys in source. Use environment variables or a secrets manager.

## What you gain

* **No subscription cap.** No 1000-subscription-per-IP limit; multiplex hundreds of subscriptions on a single connection.
* **Wildcard subscriptions.** Omit filter parameters to stream the entire channel - e.g. the full L2 order book across every asset on one subscription.
* **One key for everything Hyperliquid.** The same API key unlocks Streaming, Pipeline, the Info API, and HyperEVM via the Foundational API.

***

The most popular Hyperliquid SDKs work against the GoldRush WebSocket API after a one-line URL override. Authentication is a `key` query parameter on the connection URL - no header injection is needed.

## JavaScript / TypeScript: `nomeida/hyperliquid`

### Install

```bash npm theme={null}
npm install hyperliquid
```

```bash yarn theme={null}
yarn add hyperliquid
```

### Configure

```typescript theme={null}
import { Hyperliquid } from "hyperliquid";

const sdk = new Hyperliquid({
  // Point at GoldRush - REST and WebSocket
  baseUrl: "https://hypercore.goldrushdata.com",
  wsUrl: `wss://hypercore.goldrushdata.com/ws?key=${process.env.GOLDRUSH_API_KEY}`,
  // REST still needs the Authorization header
  headers: {
    Authorization: `Bearer ${process.env.GOLDRUSH_API_KEY}`,
  },
});

// Existing subscription methods work unchanged
sdk.subscriptions.subscribeToL2Book("BTC", (book) => {
  console.log(book.coin, book.time, book.levels[0][0]);
});

sdk.subscriptions.subscribeToL2Book("ETH", (book) => {
  console.log(book.coin, book.time, book.levels[0][0]);
});
```

> **Note:** If your SDK version doesn't expose a `wsUrl` option, instantiate the WebSocket client manually and pass it to the SDK, or patch the constant the SDK uses. See the override fallback below.

### Manual WebSocket fallback

When the SDK doesn't expose a `wsUrl` knob, bypass it and drive the raw socket yourself:

```typescript theme={null}
import WebSocket from "ws";

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("message", (raw) => {
  const msg = JSON.parse(raw.toString());
  if (msg.channel === "l2Book") {
    // Hand off to your application
  }
});
```

## Python: `hyperliquid-dex/hyperliquid-python-sdk`

### Install

```bash theme={null}
pip install hyperliquid-python-sdk
```

### Configure

```python theme={null}
import os
from hyperliquid.info import Info

# Point Info at GoldRush. skip_ws=False opens the WebSocket on init.
info = Info(
    base_url="https://hypercore.goldrushdata.com",
    skip_ws=False,
)

# Override the WebSocket URL on the underlying client so it includes the key
info.ws_manager.ws_url = (
    f"wss://hypercore.goldrushdata.com/ws?key={os.environ['GOLDRUSH_API_KEY']}"
)

# Inject the Authorization header for REST calls
info.session.headers.update({
    "Authorization": f"Bearer {os.environ['GOLDRUSH_API_KEY']}"
})

# Existing subscription methods work unchanged
def on_book(msg):
    print(msg["data"]["coin"], msg["data"]["time"], msg["data"]["levels"][0][:1])

info.subscribe({"type": "l2Book", "coin": "BTC"}, on_book)
```

> **Tip:** The SDK's `Info` class manages both REST and WebSocket. If you only need WebSocket, you can skip the `session.headers.update(...)` line. If you only need REST, pass `skip_ws=True` instead.

## Verification

After cutover, confirm everything is wired correctly:

1. **Diff a known subscription** - subscribe to `l2Book` for the same coin against both endpoints; the streamed `channel` and `data` shape (keys, nesting, types) should match exactly.
2. **Confirm auth** - remove the `key` query parameter and confirm the WebSocket upgrade fails with HTTP `401`. If the socket opens, your request isn't reaching GoldRush.
3. **Confirm wildcard** - subscribe to `l2Book` **without** a `coin` and confirm you receive book snapshots for multiple assets. This call would be rejected on the public Hyperliquid WebSocket.

## Other SDKs

The pattern is the same for any WebSocket client: override the connection URL to `wss://hypercore.goldrushdata.com/ws?key=`. If you run into a specific SDK that doesn't expose a URL override, **email us** - we'll publish a recipe.

***

## No subscription cap

The GoldRush Hyperliquid WebSocket API has **no per-IP, per-key, or per-connection subscription cap**. The 1000-subscription-per-IP limit on the public Hyperliquid WebSocket does not apply.

You can:

* Open as many concurrent subscriptions as your client supports.
* Multiplex hundreds of `l2Book` subscriptions on a single connection.
* Track every active wallet, market, and asset from one process.

## Wildcard subscriptions

Filter parameters that the public Hyperliquid WebSocket **requires** are **optional** on GoldRush. Omit them to stream the entire channel on one subscription instead of fanning out one subscription per asset.

| Channel  | Public Hyperliquid                           | GoldRush                                                                                                   |
| -------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `l2Book` | `coin` required - one subscription per asset | `coin` optional - omit it to stream the **full L2 order book across every asset** on a single subscription |

So a single connection can stream Hyperliquid's full live book state without any client-side fan-out logic.

## How streaming works

Messages are pushed directly from a live Hyperliquid ingestion pipeline - no polling, no cache delay. Latency from upstream Hyperliquid event to your client is dominated by network round-trip from our Tokyo nodes.

| Channel  | Push trigger                                                         |
| -------- | -------------------------------------------------------------------- |
| `l2Book` | Every L2 update on the subscribed coin (or every coin, if wildcard). |

## Connection management

You're not rate-limited, but a few client-side defaults are worth tuning.

| Behavior             | Recommended client setting                                                                                                                                                                     |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Reconnect**        | On unexpected close, reconnect with exponential backoff capped at \~30 seconds. Re-send your subscription messages after the new socket opens.                                                 |
| **Heartbeat**        | Send an application-level `ping` every 30 seconds. The server replies with `pong`. Most WebSocket libraries handle this automatically; verify yours does.                                      |
| **Max message size** | L2 book snapshots for wildcard subscriptions can exceed 1 MB. Raise your client's `maxPayload` (Node `ws` library) or `max_size` (Python `websockets`) if you're receiving truncated messages. |
| **Backpressure**     | If your handler can't keep up with incoming messages, your client buffer will fill. Drain to a queue or downstream consumer; don't block the read loop on application work.                    |

### Reconnect sketch

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

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

  ws.on("open", () => {
    ws.send(JSON.stringify({
      method: "subscribe",
      subscription: { type: "l2Book" }, // wildcard - all assets
    }));
  });

  ws.on("close", () => setTimeout(connect, Math.min(30_000, backoff *= 2)));
  ws.on("error", () => ws.close());
  ws.on("message", handle);
}

let backoff = 1_000;
connect();
```

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

async def consume():
    uri = f"wss://hypercore.goldrushdata.com/ws?key={os.environ['GOLDRUSH_API_KEY']}"
    backoff = 1
    while True:
        try:
            async with websockets.connect(uri, max_size=8 * 1024 * 1024, ping_interval=30) as ws:
                backoff = 1
                await ws.send(json.dumps({
                    "method": "subscribe",
                    "subscription": {"type": "l2Book"},  # wildcard
                }))
                async for raw in ws:
                    handle(json.loads(raw))
        except Exception:
            await asyncio.sleep(min(30, backoff))
            backoff *= 2

asyncio.run(consume())
```

## Watch out for client-side limits

GoldRush has no caps, but the rest of your stack might:

* **OS file descriptor limits** - if you're opening many connections in parallel, raise `ulimit -n`.
* **Reverse proxy idle timeouts** - if you're terminating WS through nginx, HAProxy, or a cloud load balancer, set the idle timeout above your heartbeat interval (typically 60 seconds minimum).
* **Browser concurrency** - browsers limit WebSocket connections per origin; one connection multiplexing many subscriptions is always preferable to many connections.

## Network and TLS

* **HTTP/1.1 Upgrade** to WSS is supported (standard WebSocket handshake).
* **TLS 1.2+** required.
* **Compression (`permessage-deflate`)** is negotiated when offered by the client.

## Need higher guarantees?

Enterprise SLA, dedicated capacity, regional pinning, and on-prem options are all available. **Email sales**.

***

# WebSocket API Reference

## L2 Order Book Diff

**Credit Cost:** 0.1 per coin per minute

**Processing:** Realtime

> **Note:** - GoldRush-native. `l2BookDiff` is not exposed on `wss://api.hyperliquid.xyz/ws`. Pointing a client at the public endpoint with this subscription type will fail.

* Snapshot, then diffs. The first message is always a `Snapshot`; every message thereafter is an `Updates`. Clients must seed local book state from the snapshot and apply diffs from there. On reconnect, drop local state and re-seed from the next snapshot.
* `coin` is optional. Omit it to stream the entire L2 order book across every asset on a single subscription.
* When `coin` is omitted, the optional `marketTypes` filter selects which market families to include. It defaults to `["perp"]` only.
* Pass `"marketTypes": ["spot"]`, or `"marketTypes":["outcome"]`, or a mix (e.g. `"marketTypes": ["perp","spot"]`), or use the wildcard `"marketTypes": ["*"]` to opt into spot, perps, outcome, and any future market types.
* No 1000-subscription-per-IP cap - multiplex hundreds of `l2BookDiff` subscriptions on a single connection.
* For OHLCV candles instead of raw book state, use the [Streaming API OHLCV streams](https://goldrush.dev/docs/api-reference/streaming-api/subscriptions/ohlcv-pairs-stream).

**Note:** When `coin` is omitted, a credit rate of `10` **credits per minute subscribed** is applied.

## Endpoint

```
wss://hypercore.goldrushdata.com/ws?key=
```

Your GoldRush API key. Passed as a query parameter at connection time - no `Authorization` header is used.

## Subscribe

Send this JSON message after the connection is established:

| Parameter      | Type     | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| -------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `method`       | `string` | Yes      | Always `"subscribe"`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `subscription` | `object` | Yes      | \_\_RESPONSE\_ROW\_\_type	string	Always `"l2BookDiff"`. \_\_RESPONSE\_ROW\_\_coin	string \| string\[]	Asset filter. Accepts three shapes: - **String** - a single asset symbol (e.g. `"HYPE"`, `"BTC"`, `"@107"` for spot pairs). For HIP-3 markets, include the deployer prefix. - **Array of strings** - a fixed list of asset symbols (e.g. `["HYPE", "BTC", "ETH"]`). Each listed coin gets its own initial `Snapshot`; subsequent `Updates` may bundle diffs for any subset of the list per block. - **Omitted** - wildcard. Streams the full L2 book across **every perp asset** by default on one subscription (see `marketTypes` to include spot or outcome markets). The server emits one `Snapshot` per live coin, then per-block `Updates` covering only the coins that changed. \_\_RESPONSE\_ROW\_\_marketTypes	string\[]	Optional. Selects which market families a wildcard subscription includes. **Only valid when `coin` is omitted.** **Defaults to `["perp"]`** — when omitted, only perp markets stream. Spot and outcome markets require explicit opt-in. Accepted values: - `"perp"` *(default)* — vanilla perps and HIP-3 deployer-perps (e.g. `BTC`, `ETH`, `HYPE`, `SOL`, `cash`, `abcd`, `USA500`). - `"spot"` — `@` spot markets and legacy spot pairs (e.g. `@1`, `@107` for HYPE spot, `PURR/USDC`). Not included by default. - `"outcome"` — HIP-4 prediction-market outcomes (e.g. `#700`, `#710`, `#741`). Not included by default. - `"*"` — every current type (`"perp"` + `"spot"` + `"outcome"`) and auto-opt-in to any future types the server adds. Mix and match in one subscription, e.g. `["perp", "outcome"]`. A second `subscribe` with a different `marketTypes` value **replaces** the previous filter rather than coexisting with it. |

### Example

Pick the subscription shape that matches the coverage you want. Every subscription starts with one `Snapshot` per coin in scope, then per-block `Updates` carrying only changed levels:

| Subscribe with                                        | What you receive                                                               |
| ----------------------------------------------------- | ------------------------------------------------------------------------------ |
| `{"type":"l2BookDiff","coin":"HYPE"}`                 | Snapshot + diffs for **HYPE only**                                             |
| `{"type":"l2BookDiff","coin":["HYPE","BTC","ETH"]}`   | Snapshot + diffs for **a fixed list of coins**                                 |
| `{"type":"l2BookDiff"}`                               | Snapshot + diffs for **every perp coin** (default: `marketTypes: ["perp"]`)    |
| `{"type":"l2BookDiff","marketTypes":["spot"]}`        | Snapshot + diffs for **every spot coin**                                       |
| `{"type":"l2BookDiff","marketTypes":["outcome"]}`     | Snapshot + diffs for **every HIP-4 outcome market**                            |
| `{"type":"l2BookDiff","marketTypes":["perp","spot"]}` | Snapshot + diffs for **perps + spot**                                          |
| `{"type":"l2BookDiff","marketTypes":["*"]}`           | Snapshot + diffs for **every coin** (perp + spot + outcome, plus future types) |

```bash wscat theme={null}
wscat -c "wss://hypercore.goldrushdata.com/ws?key=$GOLDRUSH_API_KEY"

> {"method":"subscribe","subscription":{"type":"l2BookDiff","coin":"HYPE"}}
```

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

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: "l2BookDiff", coin: "HYPE" },
  }));
});

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

  if (msg.data.Snapshot) {
    const { coin, time, block_height, levels: [bids, asks] } = msg.data.Snapshot;
    console.log("snapshot", coin, time, block_height, "bids:", bids.length, "asks:", asks.length);
  } else if (msg.data.Updates) {
    const { time, block_height, book_diffs } = msg.data.Updates;
    console.log("updates", time, block_height, "coins:", book_diffs.length);
  }
});
```

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

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": "l2BookDiff", "coin": "HYPE"},
        }))
        async for raw in ws:
            msg = json.loads(raw)
            if msg.get("channel") != "l2BookDiff":
                continue
            data = msg["data"]
            if "Snapshot" in data:
                snap = data["Snapshot"]
                bids, asks = snap["levels"]
                print("snapshot", snap["coin"], snap["time"], snap["block_height"], "bids:", len(bids), "asks:", len(asks))
            elif "Updates" in data:
                upd = data["Updates"]
                print("updates", upd["time"], upd["block_height"], "coins:", len(upd["book_diffs"]))

asyncio.run(main())
```

## Unsubscribe

Send the same `subscription` body with `method: "unsubscribe"`:

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

## Streamed messages

Every message has `channel: "l2BookDiff"`. The `data` payload contains **exactly one** of two variants: a `Snapshot` (emitted once per subscribed coin, immediately after subscribe) or an `Updates` (emitted on each subsequent HyperCore block where the book for at least one subscribed coin changed).

### Initial snapshot

After subscribe, the server emits one `Snapshot` message per coin currently in scope. For a single-coin subscription that is one message; for a list or wildcard subscription that is one message per asset. Each entry in `levels[0]` (bids) and `levels[1]` (asks) is an aggregated price level, sorted best-first.

```json theme={null}
{
  "channel": "l2BookDiff",
  "data": {
    "Snapshot": {
      "coin": "HYPE",
      "time": 1779220051027,
      "block_height": 1002862373,
      "levels": [
        [
          { "px": "48.601", "sz": "51.26", "n": 1 }
        ],
        [
          { "px": "48.614", "sz": "12.34", "n": 1 }
        ]
      ]
    }
  }
}
```

### Incremental updates

Subsequent messages carry only the levels that changed since the previous block, grouped by coin. A level entry with `sz: "0"` and `n: 0` means **the level at that price has been removed**; any other entry replaces the current state at that `px` with the new `{sz, n}`.

```json theme={null}
{
  "channel": "l2BookDiff",
  "data": {
    "Updates": {
      "time": 1779220051224,
      "block_height": 1002862374,
      "book_diffs": [
        {
          "coin": "HYPE",
          "levels": [
            [
              { "px": "48.601", "sz": "60.00", "n": 2 }
            ],
            [
              { "px": "48.614", "sz": "0", "n": 0 }
            ]
          ]
        }
      ]
    }
  }
}
```

### Response fields

| Field               | Type     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel`           | `string` | Always `"l2BookDiff"`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `data`              | `object` | Contains exactly one of `Snapshot` or `Updates`.                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `data.coin`         | `string` | Asset symbol the snapshot belongs to.                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `data.time`         | `int`    | HyperCore block timestamp in milliseconds.                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `data.block_height` | `int`    | HyperCore block height the snapshot was taken at.                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `data.levels`       | `array>` | Tuple `[bids, asks]`. Each side is an array of aggregated **Level** objects in best-first order.  \_\_RESPONSE\_ROW\_\_px	string	Price for this level (decimal string). \_\_RESPONSE\_ROW\_\_data.sz	string	Aggregate size resting at this level (decimal string, base units). \_\_RESPONSE\_ROW\_\_data.n	int	Number of orders aggregated into this level.   HyperCore block timestamp in milliseconds.                                                                                    |
| `block_height`      | `int`    | HyperCore block height.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `book_diffs`        | `array`  | Per-coin lists of changed price levels. One entry per coin that had changes at this block.                                                                                                                                                                                                                                                                                                                                                                                                  |
| `book_diffs.coin`   | `string` | Asset symbol the diff applies to.                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `book_diffs.levels` | `array>` | Tuple `[changed_bids, changed_asks]`. Each entry replaces the current state at its `px`. An entry with `sz: "0"` and `n: 0` removes the level at that price.  \_\_RESPONSE\_ROW\_\_px	string	Price for this level (decimal string). \_\_RESPONSE\_ROW\_\_book\_diffs.sz	string	New aggregate size at this level (decimal string). `"0"` means the level is removed. \_\_RESPONSE\_ROW\_\_book\_diffs.n	int	New number of orders aggregated into this level. `0` means the level is removed. |

***

## L2 Order Book

**Credit Cost:** 0.5 per coin per minute

**Processing:** Realtime

> **Note:** - Wire-compatible with `wss://api.hyperliquid.xyz/ws` `l2Book` subscriptions - same channel name, same `levels` shape.

* `coin` is optional on GoldRush. Omit it to stream the entire L2 order book across every asset on a single subscription. The public Hyperliquid API requires `coin` and locks each subscription to one asset at a time.
* When `coin` is omitted, the optional `marketTypes` filter selects which market families to include. It defaults to `["perp"]` only.
* Pass `"marketTypes": ["spot"]`, or `"marketTypes":["outcome"]`, or a mix (e.g. `"marketTypes": ["perp","spot"]`), or use the wildcard `"marketTypes": ["*"]` to opt into spot, perps, outcome, and any future market types.
* No 1000-subscription-per-IP cap - multiplex hundreds of `l2Book` subscriptions on a single connection.
* For OHLCV candles instead of raw book state, use the [Streaming API OHLCV streams](https://goldrush.dev/docs/api-reference/streaming-api/subscriptions/ohlcv-pairs-stream).

**Note:** When `coin` is omitted, a credit rate of `50` **credits per minute subscribed** is applied.

## Endpoint

```
wss://hypercore.goldrushdata.com/ws?key=
```

Your GoldRush API key. Passed as a query parameter at connection time - no `Authorization` header is used.

## Subscribe

Send this JSON message after the connection is established:

| Parameter      | Type     | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| -------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `method`       | `string` | Yes      | Always `"subscribe"`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `subscription` | `object` | Yes      | \_\_RESPONSE\_ROW\_\_type	string	Always `"l2Book"`. \_\_RESPONSE\_ROW\_\_coin	string	Asset symbol - e.g. `"BTC"`, `"ETH"`, `"@107"` for spot pairs. For HIP-3 markets, include the deployer prefix. Omit to receive snapshots for **all perp assets** (the default; see `marketTypes` to include spot or outcome markets). \_\_RESPONSE\_ROW\_\_marketTypes	string\[]	Optional. Selects which market families a wildcard subscription includes. **Only valid when `coin` is omitted.** **Defaults to `["perp"]`** — when omitted, only perp markets stream. Spot and outcome markets require explicit opt-in. Accepted values: - `"perp"` *(default)* — vanilla perps and HIP-3 deployer-perps (e.g. `BTC`, `ETH`, `HYPE`, `SOL`, `cash`, `abcd`, `USA500`). - `"spot"` — `@` spot markets and legacy spot pairs (e.g. `@1`, `@107` for HYPE spot, `PURR/USDC`). Not included by default. - `"outcome"` — HIP-4 prediction-market outcomes (e.g. `#700`, `#710`, `#741`). Not included by default. - `"*"` — every current type (`"perp"` + `"spot"` + `"outcome"`) and auto-opt-in to any future types the server adds. Mix and match in one subscription, e.g. `["perp", "outcome"]`. A second `subscribe` with a different `marketTypes` value **replaces** the previous filter rather than coexisting with it. \_\_RESPONSE\_ROW\_\_nSigFigs	int	Significant figures used for price aggregation. One of `2`, `3`, `4`, `5`, or `null` for full precision. Defaults to `null`. \_\_RESPONSE\_ROW\_\_mantissa	int	When `nSigFigs` is 5, controls the mantissa rounding. One of `1`, `2`, or `5`. Not allowed for other `nSigFigs` values. |

### Example

Pick the subscription shape that matches the coverage you want:

| Subscribe with                                    | What you receive                                                           |
| ------------------------------------------------- | -------------------------------------------------------------------------- |
| `{"type":"l2Book","coin":"BTC"}`                  | L2 snapshots for **BTC only**                                              |
| `{"type":"l2Book"}`                               | L2 snapshots for **every perp coin** (default: `marketTypes: ["perp"]`)    |
| `{"type":"l2Book","marketTypes":["spot"]}`        | L2 snapshots for **every spot coin**                                       |
| `{"type":"l2Book","marketTypes":["outcome"]}`     | L2 snapshots for **every HIP-4 outcome market**                            |
| `{"type":"l2Book","marketTypes":["perp","spot"]}` | L2 snapshots for **perps + spot**                                          |
| `{"type":"l2Book","marketTypes":["*"]}`           | L2 snapshots for **every coin** (perp + spot + outcome, plus future types) |

```bash wscat theme={null}
wscat -c "wss://hypercore.goldrushdata.com/ws?key=$GOLDRUSH_API_KEY"

> {"method":"subscribe","subscription":{"type":"l2Book","coin":"BTC"}}
```

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

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("message", (raw) => {
  const msg = JSON.parse(raw.toString());
  if (msg.channel === "l2Book") {
    const { coin, time, levels: [bids, asks] } = msg.data;
    console.log(coin, time, "top bid:", bids[0], "top ask:", asks[0]);
  }
});
```

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

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":
                print(msg["data"]["coin"], msg["data"]["time"], msg["data"]["levels"][0][:1])

asyncio.run(main())
```

## Unsubscribe

Send the same `subscription` body with `method: "unsubscribe"`:

```json theme={null}
{
  "method": "unsubscribe",
  "subscription": { "type": "l2Book", "coin": "BTC" }
}
```

## Streamed message

Each message has `channel: "l2Book"` and a `data` payload with the current book snapshot for the subscribed coin.

```json theme={null}
{
  "channel": "l2Book",
  "data": {
    "coin": "BTC",
    "time": 1762450000123,
    "block_height": 996629014,
    "levels": [
      [
        { "px": "68210.0", "sz": "1.2345", "n": 3 },
        { "px": "68209.5", "sz": "4.5670", "n": 5 },
        { "px": "68209.0", "sz": "2.1100", "n": 2 }
      ],
      [
        { "px": "68215.0", "sz": "0.8900", "n": 2 },
        { "px": "68215.5", "sz": "3.4500", "n": 4 },
        { "px": "68216.0", "sz": "1.2300", "n": 1 }
      ]
    ]
  }
}
```

| Field               | Type     | Description                                                                                                                                                                                                                                                                                                                                 |
| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel`           | `string` | Always `"l2Book"`.                                                                                                                                                                                                                                                                                                                          |
| `data`              | `object` |                                                                                                                                                                                                                                                                                                                                             |
| `data.coin`         | `string` | Asset symbol the snapshot belongs to.                                                                                                                                                                                                                                                                                                       |
| `data.time`         | `int`    | HyperCore block timestamp in milliseconds.                                                                                                                                                                                                                                                                                                  |
| `data.block_height` | `int`    | HyperCore block height the snapshot was taken at.                                                                                                                                                                                                                                                                                           |
| `data.levels`       | `array>` | Tuple `[bids, asks]`. Each side is an array of price levels in best-first order.  \_\_RESPONSE\_ROW\_\_px	string	Price for this level (decimal string). \_\_RESPONSE\_ROW\_\_data.sz	string	Aggregate size resting at this level (decimal string, base units). \_\_RESPONSE\_ROW\_\_data.n	int	Number of orders aggregated into this level. |

***

## L4 Order Book Diff

**Credit Cost:** 5 per coin per minute (except BTC which is 60 per minute)

**Processing:** Realtime

> **Note:** - GoldRush-native. `l4Book` is not exposed on `wss://api.hyperliquid.xyz/ws`. Pointing a client at the public endpoint with this subscription type will fail.

* Perps only. No spot assets.
* `coin` is required. Unlike `l2Book` and `l2BookDiff`, you cannot omit `coin` to stream every asset. Open one subscription per asset.
* Snapshot, then diffs. The first message is always a `Snapshot`; every message thereafter is an `Updates`. Clients must seed local book state from the snapshot and apply diffs from there. On reconnect, drop local state and re-seed from the next snapshot.
* Per-order detail. Each entry exposes `user`, `oid`, `cloid`, `tif`, and trigger metadata - enabling queue-position reconstruction, per-trader flow attribution, and microstructure analytics that are not possible with `l2Book`.
* See the [L4 Order Book recipe](https://goldrush.dev/docs/goldrush-hyperliquid/websocket-api/l4-order-book-diff) for patterns to maintain book state, attribute flow by `user`, or reconstruct aggregated price levels.

**Note:** When `"coin":"BTC"` is used, a credit rate of `60` **credits per minute subscribed** is applied.

## Endpoint

```
wss://hypercore.goldrushdata.com/ws?key=
```

Your GoldRush API key. Passed as a query parameter at connection time - no `Authorization` header is used.

## Subscribe

Send this JSON message after the connection is established:

| Parameter      | Type     | Required | Description                                                                                                                                                                                                                                                                                                                          |
| -------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `method`       | `string` | Yes      | Always `"subscribe"`.                                                                                                                                                                                                                                                                                                                |
| `subscription` | `object` | Yes      | \_\_RESPONSE\_ROW\_\_type	string	Always `"l4Book"`. \_\_RESPONSE\_ROW\_\_coin	string	Asset symbol - e.g. `"BTC"`, `"ETH"`, `"@107"` for spot pairs. For HIP-3 markets, include the deployer prefix. **Required** - unlike `l2Book`, `l4Book` does not support wildcard subscriptions; each subscription is locked to a single asset. |

### Example

```bash wscat theme={null}
wscat -c "wss://hypercore.goldrushdata.com/ws?key=$GOLDRUSH_API_KEY"

> {"method":"subscribe","subscription":{"type":"l4Book","coin":"BTC"}}
```

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

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) {
    const { coin, time, block_height, levels: [bids, asks] } = msg.data.Snapshot;
    console.log("snapshot", coin, time, block_height, "bids:", bids.length, "asks:", asks.length);
  } else if (msg.data.Updates) {
    const { time, block_height, order_statuses, book_diffs } = msg.data.Updates;
    console.log("updates", time, block_height, "statuses:", order_statuses.length, "diffs:", book_diffs.length);
  }
});
```

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

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:
                snap = data["Snapshot"]
                bids, asks = snap["levels"]
                print("snapshot", snap["coin"], snap["time"], snap["block_height"], "bids:", len(bids), "asks:", len(asks))
            elif "Updates" in data:
                upd = data["Updates"]
                print("updates", upd["time"], upd["block_height"], "statuses:", len(upd["order_statuses"]), "diffs:", len(upd["book_diffs"]))

asyncio.run(main())
```

## Unsubscribe

Send the same `subscription` body with `method: "unsubscribe"`:

```json theme={null}
{
  "method": "unsubscribe",
  "subscription": { "type": "l4Book", "coin": "BTC" }
}
```

## Streamed messages

Every message has `channel: "l4Book"`. The `data` payload contains **exactly one** of two variants: a `Snapshot` (emitted once, immediately after subscribe) or an `Updates` (emitted on each subsequent HyperCore block where the book for `coin` changed).

### Initial snapshot

The first message after subscribe carries the full resting book at the current block. Each entry in `levels[0]` (bids) and `levels[1]` (asks) is an **individual order** - not an aggregated price level.

```json theme={null}
{
  "channel": "l4Book",
  "data": {
    "Snapshot": {
      "coin": "BTC",
      "time": 1778865761968,
      "block_height": 997719816,
      "levels": [
        [
          {
            "user": "0xa62b923a112d50d03e1e096bbd53422490dac104",
            "coin": "BTC",
            "side": "B",
            "limitPx": "79242",
            "sz": "0.74831",
            "oid": 427632406005,
            "timestamp": 1778865761305,
            "triggerCondition": "N/A",
            "isTrigger": false,
            "triggerPx": "0.0",
            "isPositionTpsl": false,
            "reduceOnly": false,
            "orderType": "Limit",
            "tif": "Alo",
            "cloid": "0x00000000000000000000019e2c8b7d66"
          }
        ],
        [
          {
            "user": "0xfcf104006bfff47695c1dc21dad3e9de1e72098e",
            "coin": "BTC",
            "side": "A",
            "limitPx": "79250",
            "sz": "0.2961",
            "oid": 427632406032,
            "timestamp": 1778865761305,
            "triggerCondition": "N/A",
            "isTrigger": false,
            "triggerPx": "0.0",
            "isPositionTpsl": false,
            "reduceOnly": false,
            "orderType": "Limit",
            "tif": "Gtc",
            "cloid": null
          }
        ]
      ]
    }
  }
}
```

### Incremental updates

Subsequent messages carry only what changed since the previous block. `order_statuses` describes order lifecycle events (`open`, etc.); `book_diffs` carries the corresponding price-level changes.

```json theme={null}
{
  "channel": "l4Book",
  "data": {
    "Updates": {
      "time": 1778865761768,
      "block_height": 997719813,
      "order_statuses": [
        {
          "time": "2026-05-15T17:22:41.768005701",
          "user": "0x31ca8395cf837de08b24da3f660e77761dfb974b",
          "status": "open",
          "order": {
            "user": null,
            "coin": "BTC",
            "side": "B",
            "limitPx": "79242.0",
            "sz": "0.00867",
            "oid": 427632416336,
            "timestamp": 1778865761768,
            "triggerCondition": "N/A",
            "isTrigger": false,
            "triggerPx": "0.0",
            "isPositionTpsl": false,
            "reduceOnly": false,
            "orderType": "Limit",
            "tif": "Alo",
            "cloid": null
          }
        }
      ],
      "book_diffs": [
        {
          "user": "0x31ca8395cf837de08b24da3f660e77761dfb974b",
          "oid": 427632416336,
          "px": "79242.0",
          "coin": "BTC",
          "raw_book_diff": { "new": { "sz": "0.00867" } }
        }
      ]
    }
  }
}
```

### Response fields

| Field                 | Type     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| --------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel`             | `string` | Always `"l4Book"`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `data`                | `object` | Contains exactly one of `Snapshot` or `Updates`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `data.coin`           | `string` | Asset symbol the snapshot belongs to.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `data.time`           | `int`    | HyperCore block timestamp in milliseconds.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `data.block_height`   | `int`    | HyperCore block height the snapshot was taken at.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `data.levels`         | `array>` | Tuple `[bids, asks]`. Each side is an array of individual **Order** objects (see below), in queue order at their respective price.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `data.time`           | `int`    | HyperCore block timestamp in milliseconds.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `data.block_height`   | `int`    | HyperCore block height.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `data.order_statuses` | `array`  | Order lifecycle events at this block.  \_\_RESPONSE\_ROW\_\_time	string	ISO-8601 timestamp with nanosecond precision. \_\_RESPONSE\_ROW\_\_data.user	string	Wallet address that owns the order. \_\_RESPONSE\_ROW\_\_data.status	string	Lifecycle status (e.g. `"open"`). \_\_RESPONSE\_ROW\_\_data.order	Order	The order, in the same shape as a snapshot entry. `user` inside this nested object is `null` because it duplicates the parent `user`. \_\_RESPONSE\_ROW\_\_book\_diffs	array	Per-order book changes at this block. \_\_RESPONSE\_ROW\_\_book\_diffs.user	string	Wallet address that owns the order. \_\_RESPONSE\_ROW\_\_book\_diffs.oid	int	Order id the diff applies to. \_\_RESPONSE\_ROW\_\_book\_diffs.px	string	Price level the diff applies to (decimal string). \_\_RESPONSE\_ROW\_\_book\_diffs.coin	string	Asset symbol. \_\_RESPONSE\_ROW\_\_book\_diffs.raw\_book\_diff	object	The change descriptor. Observed shape: `{ "new": { "sz": "" } }` for a newly resting order. Other shapes may carry size deltas or cancellations - inspect the keys to discriminate. |

### Order object

The **Order** type appears inside `Snapshot.levels[*][*]` and `Updates.order_statuses[*].order`.

| Field              | Type      | Description                                                     |                                                                                                                               |
| ------------------ | --------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `user`             | \`string  | null\`                                                          | Wallet address that owns the order. `null` when the order is nested inside an `order_status` (the parent already carries it). |
| `coin`             | `string`  | Asset symbol.                                                   |                                                                                                                               |
| `side`             | `string`  | `"B"` for bid, `"A"` for ask.                                   |                                                                                                                               |
| `limitPx`          | `string`  | Limit price (decimal string).                                   |                                                                                                                               |
| `sz`               | `string`  | Resting size (decimal string, base units).                      |                                                                                                                               |
| `oid`              | `int`     | Hyperliquid order id - stable for the lifetime of the order.    |                                                                                                                               |
| `timestamp`        | `int`     | Order-placement timestamp in HyperCore milliseconds.            |                                                                                                                               |
| `triggerCondition` | `string`  | Trigger condition string (e.g. `"N/A"` for plain limit orders). |                                                                                                                               |
| `isTrigger`        | `boolean` | True if this is a stop / take-profit trigger order.             |                                                                                                                               |
| `triggerPx`        | `string`  | Trigger price (decimal string, `"0.0"` for non-trigger orders). |                                                                                                                               |
| `isPositionTpsl`   | `boolean` | True if this is a position-level TP/SL.                         |                                                                                                                               |
| `reduceOnly`       | `boolean` | True if the order is flagged reduce-only.                       |                                                                                                                               |
| `orderType`        | `string`  | Hyperliquid order type (e.g. `"Limit"`).                        |                                                                                                                               |
| `tif`              | `string`  | Time-in-force (e.g. `"Alo"`, `"Gtc"`, `"Ioc"`).                 |                                                                                                                               |
| `cloid`            | \`string  | null\`                                                          | Client-supplied order id (hex string), or `null` if none was provided.                                                        |

***

# Recipes

The `l2Book` channel on `wss://hypercore.goldrushdata.com/ws?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**; for the connection model see the **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.

## Subscribe and hold book state

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

```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();

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())
```

## 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 ? 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** - full subscription and message schema.
* **WebSocket API overview** - endpoint URL, auth, and limits.
* **`clearinghouseState`** - pair the live book with per-account position and margin state.
* **OHLCV pairs stream** - candles instead of raw book state.

***

The `l2BookDiff` channel on `wss://hypercore.goldrushdata.com/ws?key=` is a GoldRush-exclusive stream - it has no equivalent on `wss://api.hyperliquid.xyz/ws`. After subscribing, the server emits one `Snapshot` per subscribed coin, then per-block `Updates` carrying only the price levels that changed. The shape per level is the same `{px, sz, n}` you already get from `l2Book`, so book-state code only needs to learn how to apply diffs - and `coin` accepts a single asset, a list, or can be omitted to stream every asset on one subscription. For the raw subscription shape see the **`l2BookDiff` reference**; for the connection model see the **WebSocket API overview**.

## What you get

* **Snapshot + diff transport.** One full `Snapshot` per subscribed coin on subscribe, then per-block `Updates` containing only the levels that changed. Apply diffs to local state.
* **Aggregated `{px, sz, n}` shape.** Identical to `l2Book` levels - reuse your existing aggregated-book types and just add a level-apply function.
* **Multi-coin / wildcard friendly.** `coin` accepts a single asset, an array of assets, or can be omitted to stream every asset on one subscription. When `coin` is omitted, `marketTypes` defaults to `["perp"]` — pass `["spot"]`, `["outcome"]`, a mix, or `["*"]` to opt into spot, outcome, and future market types. `l4Book` is one-coin-per-subscription; `l2Book` requires per-asset fan-out on the public feed.
* **Bandwidth proportional to change.** Quiet markets cost almost nothing; only the levels that actually moved arrive over the wire, instead of a full re-snapshot every tick.
* **GoldRush-exclusive.** Public Hyperliquid has no L2-diff transport - `l2Book` is full-snapshot only.

## Subscribe and maintain book state

The pattern below keeps a `Map, asks: Map }>`. Each `Snapshot` seeds the per-coin entry; each `book_diff` inside `Updates` either deletes a level (when `sz === "0"`) or replaces it with the new `{sz, n}`.

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

type Level = { px: string; sz: string; n: number };
type Side = Map;
type Book = { bids: Side; asks: Side };

const books = new Map();

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

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: "l2BookDiff", coin: "HYPE" },
  }));
});

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

  if (msg.data.Snapshot) {
    const { coin, levels: [bids, asks] } = msg.data.Snapshot;
    const book: Book = { bids: new Map(), asks: new Map() };
    for (const l of bids) book.bids.set(l.px, l);
    for (const l of asks) book.asks.set(l.px, l);
    books.set(coin, book);
    return;
  }

  if (msg.data.Updates) {
    for (const diff of msg.data.Updates.book_diffs) {
      const book = books.get(diff.coin);
      if (!book) continue;
      const [bidLevels, askLevels] = diff.levels;
      applyLevels(book.bids, bidLevels);
      applyLevels(book.asks, askLevels);
    }
  }
});
```

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

books: dict[str, dict] = {}

def apply_levels(side: dict, levels: list[dict]) -> None:
    for lvl in levels:
        if lvl["sz"] == "0":
            side.pop(lvl["px"], None)
        else:
            side[lvl["px"]] = lvl

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

            if "Snapshot" in data:
                snap = data["Snapshot"]
                bids, asks = snap["levels"]
                books[snap["coin"]] = {
                    "bids": {l["px"]: l for l in bids},
                    "asks": {l["px"]: l for l in asks},
                }
                continue

            if "Updates" in data:
                for diff in data["Updates"]["book_diffs"]:
                    book = books.get(diff["coin"])
                    if not book:
                        continue
                    bid_levels, ask_levels = diff["levels"]
                    apply_levels(book["bids"], bid_levels)
                    apply_levels(book["asks"], ask_levels)

asyncio.run(main())
```

## Patterns

### Single coin, list, or wildcard

All three subscription shapes share the same handler - only the payload sent at `subscribe` time changes. Use whichever matches your coverage requirements.

```typescript TypeScript theme={null}
// Single coin
ws.send(JSON.stringify({
  method: "subscribe",
  subscription: { type: "l2BookDiff", coin: "HYPE" },
}));

// Fixed list of coins
ws.send(JSON.stringify({
  method: "subscribe",
  subscription: { type: "l2BookDiff", coin: ["HYPE", "BTC", "ETH"] },
}));

// Every perp asset on one subscription (perps is the default when coin is omitted)
ws.send(JSON.stringify({
  method: "subscribe",
  subscription: { type: "l2BookDiff" },
}));

// Spot only, or any mix; pass ["*"] for perps + spot + outcomes plus auto-opt-in to future types
ws.send(JSON.stringify({
  method: "subscribe",
  subscription: { type: "l2BookDiff", marketTypes: ["spot"] },
}));
ws.send(JSON.stringify({
  method: "subscribe",
  subscription: { type: "l2BookDiff", marketTypes: ["*"] },
}));
```

A list or wildcard subscription receives one `Snapshot` per live coin before diffs begin, so the `books` map fills in over the first few messages rather than all at once. `marketTypes` is only valid when `coin` is omitted; it defaults to `["perp"]`, so spot and outcome markets require explicit opt-in. A second `subscribe` with a different `marketTypes` value replaces the previous filter rather than coexisting with it.

### Reconstruct a top-of-book stream

After each diff is applied, the best bid is the highest `px` in `book.bids` and the best ask is the lowest `px` in `book.asks`. Emit only when the top level changes to avoid noise.

```typescript TypeScript theme={null}
function bestLevels(book: Book) {
  let bestBid: string | null = null;
  for (const px of book.bids.keys()) if (!bestBid || Number(px) > Number(bestBid)) bestBid = px;
  let bestAsk: string | null = null;
  for (const px of book.asks.keys()) if (!bestAsk || Number(px)  ws.send(JSON.stringify({
    method: "subscribe",
    subscription: { type: "l2BookDiff", coin: "HYPE" },
  })));
  ws.on("close", () => {
    books.clear();
    setTimeout(connect, 1000);
  });
  return ws;
}
```

## Related

* **`l2BookDiff` API reference** - full subscription, snapshot, and update schema.
* **`l2Book` reference** - full-snapshot transport when diff replay isn't desirable.
* **`l4Book` reference** - order-level stream with `user`, `oid`, `cloid`, `tif`, and trigger metadata.
* **WebSocket API overview** - endpoint URL, auth, and limits.

***

The `l4Book` channel on `wss://hypercore.goldrushdata.com/ws?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, 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**; for the connection model see the **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.

## Subscribe and maintain book state

The pattern below keeps a `Map` 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.

```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();

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())
```

## 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) {
  const totals = new Map();
  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) {
  const bids = new Map();
  const asks = new Map();
  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** - full subscription, snapshot, and update schema.
* **`l2Book` reference** - aggregated price-level snapshots when per-order detail isn't needed.
* **WebSocket API overview** - endpoint URL, auth, and limits.
* **`clearinghouseState`** - pair per-user resting orders with position and margin state.
