← Whose wall

Methodology

Every figure on the page is computed in the request that serves it, from two live sources read at the same moment: the L4 order book from Alchemy's HyperCore websocket (every resting order with its wallet) and Hyperliquid's public info API (the 20-level l2Book, the HIP-3 market list and mids). Nothing is cached beyond a few seconds and nothing is mocked. Every figure carries its HyperCore block time (UTC), block number and stream sequence.

Windows

Depth ladder±50 bp around mid, 1 bp bins
Concentration (HIP-3 table)±100 bp (1%) around mid
Majority booktop-1 share > 50%
Wallets listed and coloured10
Pulled sizecancelled within 10 s of placement and within 10 bp of mid, never filled
Public viewl2Book, 20 levels a side, full precision

Formulas

Mid(best bid + best ask) / 2, both from the same L4 state (same HyperCore block)
Distance from mid(px − mid) / mid × 10,000, in basis points
Notionalpx × remaining size, USD
Sharewallet notional / total notional inside the window
HHIΣ share² × 10,000 over wallets inside the window (10,000 = one wallet)
Top-1 sharelargest wallet share inside ±1% of mid
Top-3 sharesum of the three largest wallet shares inside ±1% of mid
Book extentdistance from mid of the farthest resting bid and ask, in bp
Public viewl2Book, 20 levels a side, full precision: price range = lowest bid to highest ask shown, orders = Σ n, notional = Σ px × sz
Pulled orderplaced during the sample within ±10 bp of the mid at placement, removed within 10 s with no fill for its oid (allFills) and no size change (update); notional = px × sz at placement
Pulled sharepulled notional / notional placed within the window during the sample
Same key delta(precompile px − stream px) / stream mid × 10,000 per side; time delta = HyperEVM block timestamp − HyperCore stream block time
Majority bookscount of HIP-3 markets whose top-1 share inside ±1% of mid exceeds 50%, over markets with at least one resting order in the window

How a request works

  1. Open Alchemy's HyperCore websocket, subscribe to l4BookUpdates for the market (and bbo), take the snapshot: every resting order as a new diff with coin, oid, user, side, px, sz.
  2. The instant the snapshot lands, read the public l2Book three times over the next second. Keep applying stream diffs block by block (sequence numbers must be contiguous; a gap ends the capture and nothing after it is applied).
  3. Pick the L4 state whose 40 price levels best reproduce one of the public books (size to 1e-6, order count exact). Every figure in sections A and B is computed from that state, so both views refer to the same block. The page shows how many levels matched and the time offset.
  4. The HIP-3 table is one subscription over every live market (up to 200 per subscription; batched beyond that), one snapshot, concentration inside ±1% of each market's own L4 mid.
  5. Pulled size holds the stream for 30 s with allFills on the same socket. An order placed in the sample within ±10 bp of the mid at that block, then removed within 10 s with no fill carrying its oid and no size change, counts as pulled. The sample is what it says: 30 seconds, re-read every minute.
  6. The same-key strip reads HyperEVM (eth_getBlockByNumber and an eth_call to the BBO precompile at 0x…080e, asset index from the public meta, price = uint64 / 10^(6 − szDecimals)) and the HyperCore bbo stream through the same Alchemy key, in one request.
  7. The 24-hour trend is section C stored every 5 minutes (a Vercel cron on /api/cron/hip3, plus a write from /api/hip3 whenever the newest stored snapshot is older than the cadence) in Postgres, one row per snapshot and one per market, kept 7 days. Each stored point carries its block time and block.
  8. The stream carries no placement times. The age of a top wallet's oldest order comes from the public API's open-orders read for that wallet, matched by oid, cached two minutes.

Wallet labels

Labels are attached server-side from public directories and always carry their source. Nothing is inferred here. Deployer names in section 3 come from the exchange's own perpDexs listing. An unlabelled wallet is simply unknown to these lists.

hypurrscan aliasespublic alias map from api.hypurrscan.io/globalAliases, refreshed hourly
cryexc address directorycommunity-sourced, best-effort; the directory itself says labels may be inaccurate, outdated or speculative · 668 entries, snapshot 2026-10-26

QA report

Written by npm run qa: L4 reconciles to l2Book per level, BBO agrees across the stream, L4 and the public book, HIP-3 coverage equals the live universe.

{
 "at": "2026-10-26T17:22:20.038Z",
 "q1_l4_reconciles_to_l2": [
  {
   "coin": "BTC",
   "block": 1198170524,
   "at": "2026-10-26T17:21:52.110Z",
   "seq": 43,
   "levels": 40,
   "matched": 40,
   "szResidual": 0,
   "nResidual": 0,
   "dtMs": 0,
   "statesTried": 156,
   "attempts": 1,
   "mismatches": [],
   "gap": false
  },
  {
   "coin": "ETH",
   "block": 1198170652,
   "at": "2026-10-26T17:22:01.391Z",
   "seq": 44,
   "levels": 40,
   "matched": 40,
   "szResidual": 0,
   "nResidual": 0,
   "dtMs": 0,
   "statesTried": 147,
   "attempts": 1,
   "mismatches": [],
   "gap": false
  },
  {
   "coin": "HYPE",
   "block": 1198170753,
   "at": "2026-10-26T17:22:08.404Z",
   "seq": 33,
   "levels": 40,
   "matched": 40,
   "szResidual": 0,
   "nResidual": 0,
   "dtMs": 0,
   "statesTried": 126,
   "attempts": 1,
   "mismatches": [],
   "gap": false
  }
 ],
 "q2_bbo": [
  {
   "coin": "BTC",
   "block": 1198170524,
   "stream": {
    "bid": 80447,
    "ask": 80448,
    "block": 1198170524
   },
   "l4": {
    "bid": 80447,
    "ask": 80448
   },
   "public": {
    "bid": 80447,
    "ask": 80448
   },
   "ok": true
  },
  {
   "coin": "ETH",
   "block": 1198170652,
   "stream": {
    "bid": 2332.4,
    "ask": 2332.5,
    "block": 1198170652
   },
   "l4": {
    "bid": 2332.4,
    "ask": 2332.5
   },
   "public": {
    "bid": 2332.4,
    "ask": 2332.5
   },
   "ok": true
  },
  {
   "coin": "HYPE",
   "block": 1198170753,
   "stream": {
    "bid": 78.858,
    "ask": 78.859,
    "block": 1198170748
   },
   "l4": {
    "bid": 78.858,
    "ask": 78.859
   },
   "public": {
    "bid": 78.858,
    "ask": 78.859
   },
   "ok": true
  }
 ],
 "q3_hip3_coverage": {
  "universe": 165,
  "dexsListed": 10,
  "dexsWithLiveMarkets": 4,
  "perDex": {
   "xyz": 117,
   "flx": 0,
   "vntl": 0,
   "hyna": 0,
   "km": 0,
   "abcd": 0,
   "cash": 0,
   "para": 32,
   "mkts": 5,
   "io": 11
  },
  "coinsInSnapshot": 165,
  "measured": 164,
  "majority": 46,
  "failed": [
   {
    "coin": "xyz:CAMBRICON",
    "reason": "no resting orders within ±1% of mid"
   }
  ],
  "block": 1198170867,
  "at": "2026-10-26T17:22:16.261Z",
  "ms": 8040,
  "batches": 1,
  "ok": true
 },
 "q4_no_mock_data": "No fixture or constant feeds a figure; fixtures/ holds raw frames used only to write the parser. Failed markets render 'no data' with the reason."
}

Decisions log

# Decisions

Spec items that were ambiguous or that the live data contradicted. Each was decided here and the build continued.

D-001 · Kickoff file. `CLAUDE-CODE-KICKOFF.md` was not in the working directory, the PAPERSCOPE repo or anywhere on disk. The four rules the spec names were applied as written: secrets stay server-side (the key is read only in route handlers; `.env*` is gitignored), no mock data, this log, QA before done.

D-002 · Channel name and filter. Alchemy's HyperCore websocket rejects `l4Book`; its own error lists the valid types: `userFills, allFills, builderFills, liquidationFills, l2Book, l2BookDiff, bbo, allMids, l4BookUpdates`. The filter is `coins` (an array, up to 200); the first probe sent `coin` and got a subscription acknowledgement but no data within 6 s, because unknown fields are ignored and the subscription was unfiltered (every market). Raw frames are in `fixtures/l4-raw-BTC.json` and `fixtures/l4-raw-hip3-snapshot-head.json`.

D-003 · Schema, as received. `data: { blockNumber, blockTime (ms), isSnapshot, diffs[] }`. Diffs: `new { coin, oid, user, side B|A, px, sz, insertBefore? }`, `update { coin, oid, sz }`, `remove { coin, oid }`. The snapshot lists every resting order as a `new` diff, grouped by price level, bids first (best to worst) then asks, with queue order preserved inside a level (oid ascending in 40,636 of 40,654 adjacent pairs). `insertBefore` appears only in updates. `seq` is per subscription from 1; a skipped value means dropped frames, and the capture stops applying diffs at a gap (never apply updates to a stale state). No `book_reset` was observed.

D-004 · No order timestamps. Neither snapshot nor update diffs carry a placement time, so "age of oldest order" cannot come from the stream alone. For the ten listed wallets it is read from the public API's `frontendOpenOrders` (which has `timestamp` per oid), matched by oid, cached 2 minutes per wallet; a wallet whose read fails shows n/a.

D-005 · Block numbers. `blockNumber` is the HyperCore block (about 1.198 billion on 27 Oct), not the HyperEVM block (about 49.6 million). Figures carry HyperCore block, block time and stream seq. The HyperEVM block joins in section E.

D-006 · HIP-3 universe. `perpDexs` lists 10 deployers, but 6 of them (flx, vntl, hyna, km, abcd, cash) have only delisted markets on 27 Oct; `metaAndAssetCtxs` with `dex` gives 315 markets of which 165 are live: xyz 117, para 32, io 11, mkts 5. N in the summary line is the live count, and delisted markets are excluded (they have no book). The spec's "130+ across 10 deployers" was read as "every live HIP-3 market", which is what the page shows.

D-007 · One subscription covers every HIP-3 market. 165 coins in one `l4BookUpdates` subscription returned the snapshot in 5.4 s (17.5 MB, 123,920 orders, all 165 coins present), so section C is one pass, not batched. Batching at 200 coins per subscription is implemented for when the universe grows.

D-008 · "Same instant". Vercel functions cannot hold the socket, so each `/api/wall` call opens the stream, takes the snapshot, holds 1.5 s of updates, and reads the public l2Book three times from the moment the snapshot lands. Every (L4 state, l2Book) pair is reconciled per level and the best pair wins; sections A and B are computed from that L4 state, so "public" and "L4" refer to the same block. In testing the match is exact (40/40 levels, zero residual) with Δt = 0 ms (block time equals the l2Book `time`).

D-009 · Book extent. The L4 book for BTC extends from a bid at $0.1 to an ask at $1.1M. The hero shows this as is ("book extends −100% to +1,279% from mid"), since it is the honest answer to "how far from mid the book actually extends"; the notional figures include those orders because they are resting.

D-010 · HIP-3 mid. Mid for the concentration window is the market's own L4 best bid/ask. If one side is empty, the public `midPx` is used and the row is marked `midSource: public` in the API. A market with no resting orders inside ±1% renders "no data" with that reason and is excluded from the summary's denominator (the summary states both counts).

D-011 · Function budgets. `/api/hip3` declares `maxDuration` 60 and completes in 4 to 10 s, `/api/wall` declares 30 and completes in 4 to 7 s, `/api/pulls` declares 60 and takes about 35 s. (The team is on the Pro plan, checked through the Vercel API on 27 Oct, so these are far inside the limit.)

D-012 · Logos. Hyperliquid: `Hyperliquid_Blob_Green.svg` and `Hyperliquid_Light.svg` from the official brand kit zip (hyperliquid.gitbook.io/hyperliquid-docs/brand-kit). Alchemy: `alchemy-mark-white.svg` and `alchemy-logo-white.svg` from alchemy.com/brand's asset zip. Files were inspected for scripts before being copied to `public/logos`.

D-013 · First production deploy failed with "HyperCore stream: socket error" on both stream routes while the same code worked locally and PAPERSCOPE's stream route worked in production. The key had been added with `vercel env add` reading stdin; re-adding it with `--value … --force --sensitive` and redeploying fixed it. `vercel env pull` cannot show a sensitive value, so the stored value could not be compared directly; the socket error now carries the underlying message and code so a repeat is diagnosable from the response.

D-014 · Pulled vs filled. A `remove` diff means "filled or cancelled", so the stream alone cannot say which. The 30 s sample also subscribes to `allFills` for the market (`data.fills: [[user, { coin, px, sz, side, oid, crossed, … }]]`, raw frames in `fixtures/fills-raw-BTC.json`); an order removed with no fill carrying its oid and no `update` (partial fill) in between is counted as pulled. Mid at placement is the L4 mid of the block the `new` diff arrived in. The label is "pulled", never "spoof".

D-015 · Same-key strip. HyperEVM is read with raw JSON-RPC through the same Alchemy key (no viem): `eth_getBlockByNumber latest` and `eth_call` to the BBO precompile 0x…080e with the asset index as a uint32, decoded as two uint64 words divided by 10^(6 − szDecimals) (BTC: index 0, divisor 10, confirmed against the stream BBO). The strip is BTC only, as the spec asks.

D-016 · Function budget. `/api/pulls` holds the socket 30 s plus the snapshot (about 35 s total) under a 60 s `maxDuration`, cached 60 s; the client polls it once a minute. `/api/samekey` opens a BBO-only socket and resolves on the first snapshot (under a second), cached 2 s.

D-017 · Storage. Section C snapshots go to the PAPERSCOPE Neon database (same `DATABASE_URL`) in two new tables, `ww_hip3_snapshots` (summary per snapshot: block time, block, majority, measured, universe, capture ms, source) and `ww_hip3_markets` (one row per market per snapshot: status, top-1, top-3, HHI, notional, wallets). About 165 rows per snapshot, 288 snapshots a day, pruned after 7 days by the cron. Schema in `db/schema.sql`, applied with `npm run migrate`.

D-018 · Cadence. `vercel.json` declares `*/5 * * * *` on `/api/cron/hip3` (protected by `CRON_SECRET`, the header Vercel sends). The team is on Pro, so the 5-minute schedule is accepted and shows as enabled on the project. As a second path, `/api/hip3` also writes a snapshot after responding whenever the newest stored one is older than 4 min 45 s, so the trend keeps filling while anyone has the page open. The `source` column says which path wrote each row.

D-019 · Trend chart. One SVG: books with a majority wallet (accent) against books measured (grey) over the last 24 hours, points shown while there are fewer than 40. When a HIP-3 market is selected, its top-1 share is overlaid as a dashed line scaled to the count axis (1.0 = top of axis). The chart says "no data" until the first snapshot is stored.

D-020 · Wallet labels. The stream carries addresses only. Labels are attached server-side from two public sources, each shown with its source and a link: hypurrscan's global alias map (`api.hypurrscan.io/globalAliases`, 463 entries on 27 Oct, refreshed hourly with a vendored fallback) and the cryexc "Hyperliquid Address Directory" (668 entries, community-sourced, scraped once on 27 Oct into `src/lib/labels/cryexc.json`; the page says it is free to copy and that labels may be inaccurate). hypurrscan's per-address `/tags` endpoints returned empty for every address tried, so they are not used. Hyperliquid Names (.hl) was not added: it needs an API key and names are self-claimed. Deployer names in section 3 come from `perpDexs.fullName`. The footer and methodology say labels are best-effort and nothing is an accusation.

D-021 · Price overlay. The ladder can overlay the last 60 minutes of 1-minute closes (public `candleSnapshot`, proxied by `/api/candles`, cached 30 s) on the same price axis, time left to right, high-low as a faint band. Candles outside ±50 bp of the current mid are clamped to the edge and counted in the caption. It is a toggle, off by default, so the ladder stays the hero of the section.

D-022 · Copy. Sections are numbered with an eyebrow, a plain-English line and a "why it matters" line, and the hero carries a four-term reading guide (resting order, wall, bp, L2 vs L4). The claim line is unchanged. Branding: "Whose Wall", tagline "who is actually standing in the Hyperliquid book", a wall-bars favicon in the mint theme, byline in the footer.

D-023 · Live liquidations. Vercel cannot hold a socket, so `/api/liquidations` is a server-sent-events relay: each connection takes a BBO snapshot for the head block, subscribes to `liquidationFills` (all markets, no filter exists) with `fromBlock` set to the client's last seen block or the head minus 900, forwards every frame for 55 s with a 15 s heartbeat, then closes; the page reconnects itself with its last block, so nothing is missed between connections. The stream replays roughly the last 1,000 blocks (about a minute); asking for more answers with a `gap` frame (`{"seq":1,"channel":"liquidationFills","gap":{"fromBlock","toBlock"}}`, no `data`), which the page shows. There is no longer history anywhere, so the feed starts with the last minute. Frames are `data.fills: [[user, fill]]` with `fill.liquidation { liquidatedUser, markPx, method }`, `dir` and `closedPnl`, both sides of each liquidation delivered (fixtures/liquidation-raw.json; first live row seen: xyz:GME, 26 Oct 18:27 UTC). Liquidations were rare while building (none in about seven minutes of watching every market around 18:10 UTC), so the counter is honest about being "since you opened this page".

Disclosure

Built on Alchemy HyperCore streaming (paid collab), on a private-preview key. Wallet addresses are public onchain data, shown without labels. Concentration describes who is quoting, not why. Nothing here is financial advice.