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

Pair address format

Hyperliquid markets use a deployer-prefix naming scheme. The exact string differs by surface, so pass the value each API expects - matching is case- and format-sensitive.
Where it’s usedFormatExamples
WebSocket coin (l2Book, l2BookDiff, l4Book) - canonical perps<symbol>BTC, ETH, HYPE
WebSocket coin - HIP-3 builder markets<deployer>:<symbol>xyz:GOLD, flx:OIL
Streaming pair_addresses (ohlcvCandlesForPair)<deployer>:<symbol>-<quote>, or <symbol>-<quote> for canonical pairsxyz:GOLD-USDC, flx:OIL-USDH, BTC-USDC
Streaming token_addresses (ohlcvCandlesForToken)<symbol> (no prefix)GOLD, OIL, BTC
  • deployer - wallet address of the HIP-3 builder that deployed the market. Omitted for canonical Hyperliquid markets.
  • symbol - market ticker (e.g. GOLD, OIL).
  • quote - the margin / quote currency, usually USDC (some HIP-3 markets use USDH).
HIP-4 outcome markets use a separate #<encoding> scheme (e.g. #1230), not the deployer-prefix - see HIP-4 Markets.
Discovering markets: a dedicated market-list endpoint (the perpDexs Info API type) is on the roadmap. Until it ships, call metaAndAssetCtxs - the name field on each universe entry is the canonical pair address.

Subscribe and maintain book state

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

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

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

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

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

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

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

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

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

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

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
function sizeByUser(orders: Map<number, Order>) {
  const totals = new Map<string, number>();
  for (const o of orders.values()) {
    if (!o.user) continue;
    totals.set(o.user, (totals.get(o.user) ?? 0) + Number(o.sz));
  }
  return totals;
}

Reconstruct aggregated price levels

If a downstream consumer expects an l2Book-style aggregated view, sum sz across all orders sharing a limitPx on the same side. Going the other way isn’t possible - l2Book collapses the per-order detail you’d lose.
TypeScript
function aggregate(orders: Map<number, Order>) {
  const bids = new Map<string, number>();
  const asks = new Map<string, number>();
  for (const o of orders.values()) {
    const book = o.side === "B" ? bids : asks;
    book.set(o.limitPx, (book.get(o.limitPx) ?? 0) + Number(o.sz));
  }
  return { bids, asks };
}

Handling reconnects

On reconnect, resend the same subscribe payload. The first message back is always a fresh Snapshot - drop your local orders map and re-seed from it. Do not attempt to replay missed Updates - the snapshot is authoritative and supersedes anything you held before the disconnect.
TypeScript
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;
}