All examples use the
info() helper and the key-holding proxy from the
series overview.1. Read account state
You have two options, and the choice is about how much normalization you want.portfolioState is a Uniblock synthetic type: one call returns perps, spot, and
account-abstraction mode composed into a single object, with each section isolated so a
failure in one doesn’t take down the others.
perpetuals and spot are nullable. A section that failed upstream comes
back null rather than throwing, so guard before you read into it.
clearinghouseState and spotClearinghouseState are the native Hyperliquid
reads, if you’d rather compose them yourself:
2. Render the positions table
Positions live one level down, underassetPositions[].position. The field that trips
people up is szi, which is the signed position size, so its sign is what tells you
long from short. There is no side field.
liquidationPx is legitimately null for some positions. Cross-margin positions
don’t each have their own liquidation price, since the whole account is collateral.
Render a dash, not a zero.3. Show open orders
side is "B" for bid (buy) and "A" for ask (sell), Hyperliquid’s convention
throughout, in fills as well as orders. sz is what’s left unfilled; origSz is what
the order was placed for, so origSz - sz is the filled portion.
4. Keep mid prices live
Positions are only half the picture:unrealizedPnl in the snapshot was true at
time, and it drifts the moment the market moves. To keep it live you need a current
mark for each coin.
There is no allMids WebSocket channel. This is the single most common wrong
assumption when building on this stack: the REST type exists, so people look for the
matching stream and don’t find one. The working pattern is a hybrid:
- Seed and correct from REST
allMidson a slow interval (30 seconds is plenty). - Update tick-by-tick from the
allFillsWebSocket channel, taking each fill’s price as the latest trade price for its coin.
allFills needs no parameters (it’s the whole venue), and its payload arrives as
[address, fill] tuples, which is why the loop destructures past the address.
With a live price map, unrealized PnL becomes a derivation rather than a fetch:
szi is signed, that one expression is correct for both directions: a short has
negative szi, so a falling price yields a positive product.
5. Stream this wallet’s fills
Finally, subscribe to the address’s own fills for a live activity feed.[address, fill] tuples under a fills
key, not in Hyperliquid’s native { data: { user, fills } } envelope:
allFills and userFills share that envelope, one parser handles both:
dir field is worth surfacing directly in the feed. It is a pre-formatted
description of what the fill did to the position, which saves you inferring it from
startPosition and szi.
Subscriptions are matched for unsubscribe by their exact body. Send
{ method: "unsubscribe", subscription: { type: "userFills", addresses: [address] } }
with the identical addresses array you subscribed with, or the subscription (and its
per-minute billing) stays open. When a user switches wallets, unsubscribe the old
address before subscribing the new one.