A live interactive explainer of Hyperliquid's two priority-fee markets. Six tabbed chapters, one big figure each, every figure running on mainnet data as you read it.
A trade has two latency legs: hearing the market and acting on it. Hyperliquid prices them separately and burns the proceeds of both. The chapters:
- The queue everyone stands in: live mempool transactions drifting toward block inclusion.
- Paying to cut the line: the centerpiece: a fee slider that slides your ghost order back through real arriving traffic, 45 ms per basis point, with the fee computed on a worked $10,000 example.
- Who actually pays: the dust cloud vs the IOC tail, plus the measured surprise: inclusion time is flat for everyone, because the fee's reordering happens inside the matching engine where no public stream can follow it.
- The other market, paying to hear: the gossip Dutch auction's ask, falling in real time, with slot holders and handover history.
- Where the money goes: the burn, counted since you opened the page.
- Build on this: the two Quicknode streams and one info call behind everything.
Design: warm paper, ink, Fraunces. Orange is always the write side, blue is always the read side. If it looks like a dashboard, something has gone wrong.
Quicknode Hyperliquid endpoint
├─ gRPC :10000 ORDER_PRIORITY + GOSSIP_PRIORITY streams
└─ /info gossipPriorityAuctionStatus (the live ask)
│
▼
server/index.js (Node relay, ~600 lines)
│ WebSocket, one compact JSON message per event
▼
Vite + TypeScript + PixiJS v8
A few things that took measuring rather than guessing:
One transaction, hundreds of orders. Mainnet order priority is dominated by market makers re-quoting in bulk: a single transaction can carry 400+ priority orders across 16 markets, at under one transaction per second. Per-order rendering would be 120 identical dots a second, so the relay groups events by tx_hash and emits one message per transaction. IOC-bearing batches ship on a shorter timer because they are the interesting ones.
Mainnet priority is mostly dust. Most orders carry a raw p in the single or low triple digits, well under a basis point, and they are ALO rather than IOC. Speed and colour are log-scaled or the whole screen looks identical.
The two streams cannot be joined on hash. A confirmed event carries a different tx_hash than its mempool twin, because Hyperliquid mints the canonical hash at inclusion. Measured live: zero overlap between the two sets. So latency comes from matching orders on their parameters instead, which recovers about a quarter of confirmations, and that is plenty for a distribution. On the queue tab, outcomes attach to squares by position rather than identity, and the caption says so.
The streams never carry the auction price. GOSSIP_PRIORITY reports bids. The falling ask is state, not an event, so it comes from polling gossipPriorityAuctionStatus every few seconds and interpolating between polls. The decay is linear from startGas to a 0.1 HYPE floor over 180 seconds, verified against live samples. Slot handovers are detected from the holder IP changing between cycles, which is a far better win signal than guessing from a price reset.
Silence is normal here. Unlike a mempool firehose, these streams are legitimately quiet for long stretches, so the watchdogs wait tens of minutes before recycling and never restart the process over quiet alone. Pongs are not counted as liveness, because a zombie subscription answers pings forever while delivering nothing.
Credit-polite. Upstream subscriptions and the auction poller only run while at least one browser is connected, and stop 30 seconds after the last one leaves.
You need Node 20 or newer.
npm install
npm run dev # → http://localhost:5173With no credentials it runs in demo mode, generating traffic shaped like the real thing so you can see everything working immediately.
- Create a Quicknode Hyperliquid endpoint.
- Copy
.env.exampleto.envand fill it in:
QN_GRPC_URL=your-endpoint.hype-mainnet.quiknode.pro:10000
QN_GRPC_TOKEN=your-token
QN_HTTP_URL=https://your-endpoint.hype-mainnet.quiknode.pro/YOUR_TOKEN- Restart. The badge flips from
DEMOtoMAINNET.
| Variable | What it does |
|---|---|
QN_GRPC_URL |
Endpoint host with :10000, where the gRPC streams live |
QN_GRPC_TOKEN |
Endpoint token. Leave empty for demo mode |
QN_HTTP_URL |
Full HTTP URL of the same endpoint, used for the auction status poll |
POLL_MS |
Auction poll interval, defaults to 4000 |
PORT |
Relay port, defaults to 8790 |
HOST |
Bind address. Set 127.0.0.1 when behind a reverse proxy |
Priority events are sparse, and gossip bids can be minutes apart. Record a busy session once and replay it whenever you want a consistent dataset:
node server/index.js --record # writes recordings/session-<ts>.ndjson
node server/index.js --replay recordings/session-<ts>.ndjson --speed 2Replay is announced in the relay's hello message and the badge reads REPLAY, so replayed data is never presented as live.
npm run build # type-check + bundle to dist/
npm start # serves dist/ and the WebSocket relay on :8790GET /healthz returns viewer count, stream state, and the age of the last event from each stream, which is enough for an external staleness alert.
Anything that runs npm start hosts the whole thing as one process: Railway, Render, Fly.io, a VPS. Static-only platforms cannot run it alone, because the relay has to stay alive to hold its gRPC streams open.
To split it, put the frontend on a CDN and the relay behind a reverse proxy: build with VITE_WS_URL=wss://your-relay-host/ws and set HOST=127.0.0.1 on the relay. With Caddy the proxy is one block:
relay.example.com {
reverse_proxy 127.0.0.1:8790
}
The app subscribes to exactly two gRPC streams, plus one info call. It never opens the raw
MEMPOOL_TXS stream: both priority streams are derived feeds where Quicknode has already done
the parsing, and each emits every action twice: once when it is seen pre-consensus (tagged
source: "mempool_txs") and again when it is confirmed (tagged source: "replica_cmds").
Those tags name where the event was derived from; they are fields inside the stream, not
separate subscriptions.
| Tab | What you see | Where it comes from |
|---|---|---|
| 01 The queue | squares drifting toward inclusion, their outcomes | ORDER_PRIORITY, both sources, batched per transaction |
| 02 Cutting the line | the tick marks your ghost order races against | ORDER_PRIORITY, pre-consensus events |
| 03 Who pays | every dot, and the flat inclusion median | ORDER_PRIORITY, pre-consensus and confirmed events matched on order parameters (FIFO per parameter key, or identical re-quotes mismeasure) |
| 04 Paying to hear | bidders | GOSSIP_PRIORITY |
| 04 Paying to hear | the falling ask, slot holders, handovers | gossipPriorityAuctionStatus via /info, polled while a viewer is connected |
| 05 The burn | the counter | confirmed order fees plus estimated winning bids |
Both streams are gRPC only and support server-side filtering on fields like coin, tif, outcome and user. For history rather than live flow, see SQL Explorer.
MIT, see LICENSE.
