Skip to content

quicknode/Hyperliquid-Orderbook-Radar

Repository files navigation

Hyperliquid Orderbook Radar

A live, CEX-grade Hyperliquid L2 order book with a liquidation-radar overlay — a full book, every block, powered entirely by Quicknode HyperCore gRPC (StreamL2Book). No REST bootstrap. No incremental diff-stitching. No sequence numbers to reconcile.

Hyperliquid Orderbook Radar

The pitch in one line: with StreamL2Book every message is a complete aggregated book for that block, so you replace local state wholesale on each update — there is nothing to stitch. That is what lets you stand up a real depth view in an afternoon.


Why this exists

On June 9, 2026 the Hyperliquid core team announced that several public WebSocket feeds will push less frequently after the next network upgrade. After the June 11 adjustment, the standard public l2Book feed:

Public WebSocket l2Book Levels Cadence
without fast: true 20 every 2s (→ 5s later)
with fast: true 5 every 0.5s

If your strategy quotes, sizes, cancels, or routes from public snapshots, that throttle changes your data plane. The gRPC alternative does not throttle:

Quicknode gRPC StreamL2Book Levels Cadence
StreamL2Book up to 100 every block

The angle here is completeness + freshness — a complete real-time book without building one from diffs — not a latency race.


60-second quickstart

npm install
npm run dev          # replay mode — no credentials, no credits

Then open http://localhost:5173. That streams a bundled sample recording through the exact same backend → WebSocket → UI pipeline as live mode, so you can explore the whole app offline. To connect to a real endpoint, see Live mode.

Requires Node ≥ 20.


What you get

  • Live ladder — asks (red) above, bids (green) below, a centred mid/spread row, and columns for price, size, cumulative size, order count (n), and a depth bar per level. Up to 100 levels.
  • Depth chart — cumulative bid/ask curves, redrawn every block, with liquidation markers overlaid on the price axis (sized by USD notional, fading by age).
  • Freshness HUD — the money shot: current block number, book age (ms since last update), and updates/sec. Visual proof the book is fresh every block, with an inline l2Book vs StreamL2Book comparison.
  • Liquidation radar — a live feed of forced-liquidation fills (side, size, USD notional, price, age), flashing on arrival, with an optional sound toggle (off by default).
  • Controls — coin selector (BTC / ETH / SOL / … + free input), depth slider (n_levels), optional price bucketing (n_sig_figs / mantissa), and pause/resume (freezes the ladder while the HUD keeps proving the feed is live).

Architecture

Browsers can't speak raw gRPC (HTTP/2 trailers + server streaming aren't reachable from the browser), so this is a two-tier app. Secrets live only in the backend.

                  TLS · port 10000 · x-token            WebSocket (JSON)
  ┌────────────────┐   metadata header     ┌───────────────────────────┐    book / liq / status   ┌──────────────────┐
  │   Quicknode     │ ───────────────────►  │      Backend  (server/)    │ ───────────────────────► │  Browser  (web/) │
  │  HyperCore gRPC │                       │  ┌──────────────────────┐  │                          │  ┌────────────┐  │
  │                 │  StreamL2Book ──────► │  │ book state (full snap)│  │                          │  │ ladder      │  │
  │                 │                       │  │ replace wholesale     │  │                          │  │ depth chart │  │
  │                 │  StreamData(TRADES) ► │  │ liquidation extract   │  │  ◄───── subscribe ────── │  │ HUD · radar │  │
  │                 │                       │  │ zstd · reconnect/relay│  │      coin / levels       │  │ controls    │  │
  └────────────────┘                       │  └──────────────────────┘  │                          │  └────────────┘  │
                                            └───────────────────────────┘                          └──────────────────┘
       React + Vite frontend subscribes to the backend WebSocket — it never holds a secret or speaks gRPC.

A Source abstraction is the seam that makes replay possible: live and replay both implement the same interface and feed the identical relay → WebSocket pipeline, so the UI behaves the same with or without a connection.

server/src/
  index.ts            entry — parses mode/flags, wires a Source into the Relay
  config.ts           env loading + credential parsing (QN_HL_ENDPOINT / QN_HL_TOKEN / QN_HL_URL)
  protocol.ts         the server↔web wire contract (mirrored in web/src/protocol.ts)
  relay.ts            WebSocket relay; caches latest book/status; optional static serving
  source.ts           Source interface + subscription helpers
  sources/live.ts     gRPC streams → normalized messages
  sources/replay.ts   NDJSON fixture → same messages, re-stamped to stay "live"
  record.ts           capture live messages to an NDJSON fixture
  book.ts             normalize L2BookUpdate → compact wire frame (wholesale replace)
  liquidations.ts     pull liquidation fills out of TRADES JSON
  grpc/
    proto.ts          load the official protos, typed via proto-loader-gen-types
    client.ts         TLS + x-token + channel options
    l2book.ts         StreamL2Book consumer (reconnect, DATA_LOSS backoff)
    trades.ts         StreamData(TRADES) consumer (bidi, ping keepalive, zstd)
  generated/          TypeScript types generated from the protos (checked in)
  tools/genFixture.ts deterministic synthetic-fixture generator
web/src/
  useRelay.ts         the store: WebSocket client + reconnect + freshness stats
  derive.ts           book math (cumulative depth, mid/spread)
  components/         Ladder · DepthChart · FreshnessHUD · LiquidationRadar · Controls
proto/                orderbook.proto + hyperliquid.proto (pulled verbatim from the official repo)
fixtures/             sample-btc.ndjson (bundled, committed)

Modes

Mode Command Needs credentials? What it does
replay npm run dev no Streams the bundled fixture (zero-config default).
live npm run dev:live yes Connects to Quicknode HyperCore gRPC.
record npm run record -w server -- … yes Connects live and writes a fixture you can replay.

Live mode

  1. Get an endpoint. HyperCore gRPC requires a Quicknode Build plan or higher with the Hyperliquid add-on. Create a Hyperliquid endpoint in the Quicknode dashboard — your HTTP provider URL looks like:

    https://<endpoint-name>.hype-mainnet.quiknode.pro/<token>/
    
  2. Configure .env.

    cp .env.example .env

    Then set either the split values or the full URL:

    # derive from the HTTP URL above:
    QN_HL_ENDPOINT=<endpoint-name>.hype-mainnet.quiknode.pro:10000   # port 10000, TLS
    QN_HL_TOKEN=<token>                                              # the path segment
    
    # …or just paste the whole URL and the server splits it:
    # QN_HL_URL=https://<endpoint-name>.hype-mainnet.quiknode.pro/<token>/

    Use .hype-testnet.quiknode.pro for testnet. Never commit .env (it's git-ignored).

  3. Run it.

    npm run dev:live

You can pick the starting market/depth with env (QN_HL_COIN, QN_HL_LEVELS) or change them live in the UI.


Record your own fixture

Capture a few minutes of live StreamL2Book + liquidation messages, then replay them — great for a reproducible demo without burning credits:

# capture (Ctrl-C to stop, or set a duration)
npm run record -w server -- --coin=BTC --levels=100 --duration=60s --out=fixtures/btc-demo.ndjson

# replay your recording
npm run replay -w server -- --fixture=fixtures/btc-demo.ndjson

(Ad-hoc recordings under fixtures/ are git-ignored; only sample-btc.ndjson is committed.) Regenerate the bundled synthetic sample with npm run gen:fixture.


Scripts

Script What it does
npm run dev Replay backend + Vite dev server (default; no credentials).
npm run dev:live Live backend + Vite dev server. Needs .env.
npm run build Type-check the server and build the web bundle to web/dist.
npm start Replay backend serving the built web on one port (http://localhost:8787).
npm run replay -w server Replay backend only (headless). Add -- --fixture=….
npm run record -w server Record a fixture. Add -- --coin=… --duration=… --out=….
npm run proto:gen Regenerate server/src/generated from the protos.
npm run gen:fixture Rebuild the bundled synthetic sample fixture.
npm run typecheck Type-check both workspaces.

Ground-truth API notes

Everything below is verified against the official protos (proto/) and the Quicknode docs — nothing is hand-waved.

  • StreamL2Book (orderbook.proto, service OrderBookStreaming): request L2BookRequest { coin, n_levels (default 20, max 100), n_sig_figs?, mantissa? }; response stream of L2BookUpdate { coin, time, block_number, bids[], asks[] } where each L2Level = { px, sz, n } and index 0 is the best level. Each message is the complete book for that block → replace state wholesale.
  • Liquidations come from StreamData(TRADES). Each TRADES fill optionally carries a liquidation object — { liquidatedUser, markPx, method } — present only on forced-liquidation fills. That is the marker this app keys on. (The EVENTS stream's ledgerLiquidation is account-level and lacks per-fill price/size, so TRADES is the right feed; the official trading dashboard filters TRADES the same way.) Subscribe shape: Subscribe { stream_type: TRADES, filters: { coin: { values: [...] } } }.
  • Connection: <endpoint>.hype-mainnet.quiknode.pro:10000, TLS, API token in the x-token metadata header. Channel options match the official example: grpc.max_receive_message_length = 100MB, grpc.keepalive_time_ms = 30s.
  • zstd: payloads may be zstd-compressed (magic bytes 28 B5 2F FD). Billing is on uncompressed volume, so compression is free bandwidth — leave it on. Handled in server/src/util/zstd.ts.
  • DATA_LOSS status = "server reinitialized" → reconnect with exponential backoff (counter resets on the first healthy message). gRPC is real-time only: there's no historical backfill — on reconnect we just resubscribe and resume from the next snapshot, which is cheap because each snapshot is a full book.
  • StreamL2Book vs StreamL2BookDiff: this demo uses StreamL2Book (full snapshot per block) on purpose — that's what eliminates stitching. StreamL2BookDiff is the incremental/sequence-number stream; it's a bandwidth-optimized advanced path, not the default here.

Notes

  • Self-hosted assets only (system fonts, hand-rolled SVG depth chart) — no external CDN calls.
  • The frontend never holds a secret and never speaks gRPC; all credentials stay in server/.
  • Next.js would be a fine single-process alternative; this repo uses the Vite + Node split because it matches the team's stack and keeps the gRPC layer obvious.

License

MIT.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors