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.
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

Account value is the headline metric and comes straight from the batch:
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

Account value ranks wealth. 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, account value (live), 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