Wallet liquidation-risk monitor
A warning when a watched position comes within 10 % or 3 % of its liquidation price.
What you get
A monitor for a list of Hyperliquid wallets: for every open position it compares the mark with the liquidation price and warns once when the distance falls below your threshold, and again when it gets dangerous.
DANGER 0x6791…8bbc xyz:SP500 short $134.4K 50x: mark 7,728, liquidation 7,745 (0.2 %)
ok 0x5b5d…c060 ETH short $252.22M 5x: mark 2,370, liquidation 3,707 (56.4 %)
ok 0x5b5d…c060 BTC short $212.92M 5x: mark 81,476, liquidation 136,370 (67.4 %)
ok 0x5b5d…c060 SOL short $74.05M 7x: mark 102.87, liquidation 297.4 (189.1 %)
ok 0x5b5d…c060 ZEC short $53.70M 3x: mark 1,319, liquidation 4,601 (248.8 %)
ok 0x5b5d…c060 HYPE short $39.40M 5x: mark 82.23, liquidation 361.08 (339.1 %)
ok 0x5b5d…c060 xyz:GOLD short $27.97M 4x: mark 4,055, liquidation 24,953 (515.4 %)
ok 0x5b5d…c060 XRP short $17.93M 5x: mark 1.33, liquidation 11.76 (784.0 %)
ok 0x5b5d…c060 xyz:SILVER short $9.85M 5x: mark 53.86, liquidation 842.5 (1464.2 %)
ok 0x5b5d…c060 WLD short $6.36M 2x: mark 0.43862, liquidation 9.7 (2111.7 %)
ok 0x5b5d…c060 PUMP short $6.21M 5x: mark 0.004351, liquidation 0.098388 (2161.3 %)
ok 0x5b5d…c060 ENA short $5.85M 3x: mark 0.20768, liquidation 4.98 (2295.9 %)
ok 0x5b5d…c060 XPL short $5.45M 3x: mark 0.076145, liquidation 1.94 (2454.3 %)
… 13 more linesReal output of the program below, run against our API on 2026-10-28 10:38 UTC.
Build it step by step
Read one wallet
Each open position has mark, liqPx and liqDistance = (liquidation price - mark) / mark. Negative for longs (price must fall), positive for shorts.
curl -s -H "Authorization: Bearer $BLOCKMETRIC_API_KEY" \ "$BLOCKMETRIC_BASE_URL/wallets/0xbc1bc64be47052fbab69d12fe5da1c3327788d1b"Choose two thresholds
WARN_AT=0.10 warns within 10 %, DANGER_AT=0.03 within 3 %. The monitor alerts on each change of level only, not on every poll.
List the wallets
WALLETS=0x...,0x... Untracked wallets answer 404; the monitor says so and moves on.
Send the warning somewhere
Replace console.log with a message to your chat (see the alert bot recipe) or your paging tool.
The complete monitor
Set this once in the shell that runs the code:
export BLOCKMETRIC_API_KEY=bm_live_… # create one on /account/api
export BLOCKMETRIC_BASE_URL=https://api.blockmetric.io/v1// Wallet liquidation-risk monitor: watches the open positions of Hyperliquid wallets and warns when the mark comes
// within a set distance of a position's liquidation price. Node 18+, no dependencies. One GET /wallets/{address} per
// wallet and poll (each answer has the positions with liqPx and the signed distance liqDistance).
//
// export BLOCKMETRIC_API_KEY=bm_live_... # optional: without a key it uses keyless public access
// export BLOCKMETRIC_BASE_URL=https://<domain>/api/v1
// export WALLETS=0xabc...,0xdef... # the wallets to watch (yours, or any tracked wallet)
// node risk-monitor.mjs
//
// Options: WARN_AT (0.10 = warn within 10 %), DANGER_AT (0.03), POLL_SECONDS (60), ONCE=1 (one check, then exit).
// Distance = (liquidation price - mark) / mark, so -0.08 means the price may fall 8 % before a long is liquidated.
const BASE = (process.env.BLOCKMETRIC_BASE_URL ?? '').replace(/\/+$/, '')
const KEY = process.env.BLOCKMETRIC_API_KEY
if (!BASE) throw new Error('Set BLOCKMETRIC_BASE_URL, e.g. https://<domain>/api/v1')
const WALLETS = (process.env.WALLETS ?? '').split(',').map((a) => a.trim()).filter(Boolean)
if (!WALLETS.length) throw new Error('Set WALLETS=0x...,0x... (Hyperliquid addresses)')
const WARN_AT = Number(process.env.WARN_AT ?? 0.1)
const DANGER_AT = Number(process.env.DANGER_AT ?? 0.03)
const POLL_SECONDS = Number(process.env.POLL_SECONDS ?? 60)
const ONCE = process.env.ONCE === '1'
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
async function api(path) {
for (let attempt = 1; ; attempt++) {
const res = await fetch(BASE + path, { headers: KEY ? { Authorization: `Bearer ${KEY}` } : { 'X-Blockmetric-Access': 'public' }, signal: AbortSignal.timeout(30_000) })
if (res.status === 429 && attempt < 5) {
await sleep(Number(res.headers.get('Retry-After') ?? 5) * 1000)
continue
}
const body = await res.json()
return { status: res.status, body }
}
}
const usd = (n) => (n >= 1e6 ? `$${(n / 1e6).toFixed(2)}M` : `$${(n / 1e3).toFixed(1)}K`)
const px = (p) => p.toLocaleString('en-US', { maximumFractionDigits: p >= 1000 ? 0 : p >= 1 ? 2 : 6 })
const short = (a) => `${a.slice(0, 6)}…${a.slice(-4)}`
/** ok | warn | danger for one position (no liquidation price, e.g. fully collateralised: ok). */
export function level(p) {
if (typeof p.liqDistance !== 'number') return 'ok'
const d = Math.abs(p.liqDistance)
return d <= DANGER_AT ? 'danger' : d <= WARN_AT ? 'warn' : 'ok'
}
const lastLevel = new Map() // wallet:coin -> level, so each change alerts once
async function check() {
for (const address of WALLETS) {
const { status, body } = await api(`/wallets/${address}`)
if (status === 404) {
console.log(`${short(address)}: not tracked by the API (404). For a wallet of your own, Guard on the site (Alerts > Guard) watches it live.`)
continue
}
if (status !== 200) throw new Error(`${status} ${body.error?.code}: ${body.error?.message}`)
const { wallet, positions } = body.data
if (!positions.length) console.log(`${short(address)}: no open positions (account ${usd(wallet.accountValue ?? 0)})`)
for (const p of positions) {
const key = `${address}:${p.coin}`
const now = level(p)
const dist = typeof p.liqDistance === 'number' ? `${(p.liqDistance * 100).toFixed(1)} %` : 'no liquidation price'
const text = `${short(address)} ${p.coin} ${p.side} ${usd(p.positionUsd)} ${p.leverage}x: mark ${px(p.mark)}, liquidation ${p.liqPx ? px(p.liqPx) : '-'} (${dist})`
if (lastLevel.get(key) !== now && now !== 'ok') console.log(`${now === 'danger' ? 'DANGER' : 'WARNING'} ${text}`)
else if (ONCE || !lastLevel.has(key)) console.log(`ok ${text}`)
lastLevel.set(key, now)
}
}
}
for (;;) {
await check()
if (ONCE) break
await sleep(POLL_SECONDS * 1000)
}
Run it:
WALLETS=0x... node risk-monitor.mjs
WALLETS=0x... python risk_monitor.py
WALLETS=0x... ONCE=1 node risk-monitor.mjs # one checkDownload: risk-monitor.mjs · risk_monitor.py · requests.sh. MIT-0: use it as you like.
Real responses
What the API answered when we ran this recipe, with long lists shortened.
One wallet with its positions: GET /wallets/0x6791f31ebe6e6fc39a26c83160da54a175648bbc?limit=1
{
"data": {
"wallet": {
"address": "0x6791f31ebe6e6fc39a26c83160da54a175648bbc",
"label": null,
"accountValue": 0,
"pnlMonth": null,
"vlmMonth": null,
"source": "trades",
"priority": 3,
"firstSeen": "2026-10-27T06:27:31.649Z",
"lastPolledAt": "2026-10-27T11:31:39.861Z",
"positionCount": 1
},
"positions": [
{
"address": "0x6791f31ebe6e6fc39a26c83160da54a175648bbc",
"label": null,
"accountValue": 0,
"coin": "xyz:SP500",
"side": "short",
"size": 17.469,
"entryPx": 7671.055,
"positionUsd": 134430.94,
"leverage": 50,
"leverageType": "cross",
"liqPx": 7745.44511698,
"liqDistance": 0.0023092703,
"mark": 7727.6,
"marginUsed": 2688.62,
"unrealizedPnl": -425.28,
"updatedAt": "2026-10-27T11:31:39.861Z"
}
],
"totals": {
"positionUsd": 134430.94,
"longUsd": 0,
"shortUsd": 134430.94,
"unrealizedPnl": -425.28,
"unrealizedPnlMissing": 0
}
},
"meta": {
"updatedAt": "2026-10-27T11:31:39.861Z",
"sources": [
"hyperliquid"
],
"plan": "api_dev",
"delayMinutes": 0,
"attribution": {
"text": "Data by Blockmetric · Source: Hyperliquid",
"url": "https://blockmetric.io/"
},
"license": {
"id": "internal",
"attributionRequired": false
}
}
}Rate limits and pagination
- One request per wallet per poll. A free key's 1,000 requests a day cover one wallet every 90 seconds, or 10 wallets every 15 minutes.
- Positions are refreshed by our position polls; a check more often than once a minute rarely shows new numbers.
- sort=near_liq on GET /whales lists the tracked wallets closest to liquidation right now: a quick way to find positions at risk.
- Cross-margin positions share the account margin: a loss on one coin moves the liquidation price of the others.
Common pitfalls
- A position without liqPx (for example fully collateralised) has no distance; treat it as safe, not as zero.
- The mark moves between polls. For a position at 1 % distance a one-minute poll is too slow; use Guard or a shorter interval.
Who it is for
- Traders who watch their own or a fund’s wallets from a script
- Risk teams that want a log of near-liquidation moments
- Builders of portfolio tools
Plan and limits
Honest limits
- The API answers for the wallets we track (404 otherwise). For a wallet of your own, Guard on the site (Alerts › Guard) watches it live without code.
- Liquidation prices are the ones Hyperliquid reports for the position at our last poll.
Next steps
More recipes like this
/liquidations/meCascade and cluster alertsLiquidation cascades pushed to your server, plus a warning before price hits a big cluster.API Dev30 min to buildwebhooks/radar/events+1Whale position trackerOne line every time a large wallet opens, adds, cuts, flips or closes a position.Free15 min to build/whales/feed/whales+2