Skip to main content
A leaderboard ranks traders against each other. The approach here is watchlist-based: you define the set of addresses to track (traders you’ve discovered from the fill tape, a curated list, addresses your users follow) and rank them with a single batched read per refresh, plus live volume from the stream you already have open. This scales cleanly and costs one request per cycle rather than one per trader.

1. Batch the portfolio reads

batchPortfolioStates takes a users array and returns one result slot per address, in request order.
marginSummary.accountValue is perps margin only: it excludes vault equity, same as on the wallet tracker. On a leaderboard this is worse than on a single wallet page: a vault-heavy trader doesn’t just display low, they can be ranked out of the board entirely, and the mis-ranking is invisible because there’s nothing on screen to contradict it.Folding vault equity in requires one additional userVaultEquities call per watchlist address on top of the existing batched read, so one extra Info call per wallet on every refresh. At 50 wallets polled every 60 seconds that’s a real, ongoing CU cost, not a one-time fix, so it’s a judgment call rather than a strict correctness bug to patch silently:
  • if your watchlist is small and user-pinned (a personal watchlist, not a leaderboard), the scan is usually worth paying for: sum perps margin and vault equity into a combined figure (not “Total Value”, since it still excludes spot balances, see the wallet tracker), and keep Perps Margin as its own column so the two numbers aren’t buried into each other.
  • if the set is large and refreshed often (a public leaderboard), label the column Perps Margin rather than “Account Value”, and say plainly that the ranking excludes vault equity. Don’t rank on a number you haven’t actually computed.
Whichever you choose, don’t call this column “Account Value” or “Total Value” unqualified, since either name overclaims what a perps-only (or perps-plus-vault) figure represents.
Two properties of that response shape matter: Failure is isolated per user. A slot that failed has result: null and a populated error; the rest of the batch still succeeds. One bad address never takes down the board, but you must handle the null, or your ranking will throw on the first slot that didn’t resolve. Slots come back in request order, with user echoed on each, so you can either zip by index or key by user. Keying by user is the more robust of the two.
The limit is 50 users per request. Beyond that the request fails; it does not silently truncate. Chunk larger watchlists, and remember that each chunk is a separate billed request.

2. Rank

Perps margin is the headline metric and comes straight from the batch. Label it accordingly. As covered above, it excludes vault equity:
Keep the failed rows rather than dropping them silently. Render them unranked with a “data unavailable” state. A trader vanishing from the board because of one timed-out request looks like a bug to anyone watching a specific address.

3. Keep it live between polls

Refetching the batch every few seconds is the expensive way to make a leaderboard feel live. The cheap way is to poll slowly and recompute unrealized PnL locally against live mids in between. You already have the positions from the batch, and the price store from the wallet tracker. That’s everything needed:
A 60-second poll with live uPnL in between gives a board that ticks continuously at 1/30th the request cost of a 2-second poll.
Pause polling when the tab is hidden. A leaderboard left open in a background tab overnight is hundreds of requests nobody looked at.

4. Rank by live volume

Perps margin ranks wealth (with the vault-equity caveat above). To rank activity, accumulate traded notional per address from the allFills stream, the same subscription already powering prices and the tape.
Because allFills is venue-wide, this observes every trader, not just your watchlist, which makes it a discovery mechanism as much as a ranking one:
This volume is session-scoped: it counts from when your stream connected, and resets on reconnect. Label it accordingly (“volume this session”, not “24h volume”). If you need a persistent figure, accumulate into a datastore server-side with one long-lived connection shared across all users, rather than one per browser tab.

5. Assemble the surface

Sensible columns: rank, address, perps margin (live) with an “excludes vault equity” label, unrealized PnL, open position count, and session volume, with the address linking through to the wallet tracker and PnL surfaces for a full history on whoever’s at the top. Render a rank: null row with a dash and a “data unavailable” state rather than a number. A trader whose batch slot timed out on this cycle will be ranked normally on the next one, and showing them in last place in the meantime reads as a real ranking change to anyone watching that address.

Cost summary

Putting the whole dashboard together, the steady-state cost of a running instance is small and, importantly, flat in the number of traders you display: The single allFills subscription doing triple duty is the load-bearing design decision across this series. Adding traders to the watchlist grows only the batch; adding surfaces to the dashboard costs nothing extra at all.

Where to go next