Products
Datacenter proxies from $19/mo Rotating proxies from $49/mo ISP proxies from $33/mo Dedicated proxies from $3.50/mo UDP proxies from $5/mo Try proxies
Use cases
Data & scraping AI services Social media & messaging E-commerce & finance Media & entertainment Marketing & ads Automation & tools All use cases →
Pricing
Full pricing table All 20 locations Money-back guarantee
Resources
Blog Proxy API MCP server Setup guides FAQ For business About us Affiliate program
English
English Русский
← PapaProxy.net Blog

How to read the Hyperliquid leaderboard and wallet positions

Hyperliquid publishes every wallet’s positions, but the leaderboard that names those wallets lives on a separate host and isn’t in the official reference. Here’s where it sits, how to read a wallet with clearinghouseState, and where the pairing runs out of room.

API & data Original research August 7, 2026 6 min read Alex Young Alex Young Technical specialist
Key takeawaysThe leaderboard is served from stats-data.hyperliquid.xyz/Mainnet/leaderboard as a plain GET and isn’t in the official reference — treat its shape as unstable, cache it hourly, and health-check that host separately from api.hyperliquid.xyz.
  • The leaderboard is served from stats-data.hyperliquid.xyz/Mainnet/leaderboard as a plain GET and isn’t in the official reference — treat its shape as unstable, cache it hourly, and health-check that host separately from api.hyperliquid.xyz.
  • clearinghouseState returns a wallet’s open positions with no key or signature, at weight 2 — the cheap tier; an empty assetPositions means flat, not failed.
  • Per-user WebSocket subscriptions cap at 10 unique users per IP — beyond that, either distribute them across addresses or filter the market-wide trades stream (every fill carries a users array) and reconcile the affected wallets through clearinghouseState.
  • userFills returns at most 2,000 recent fills (10,000 available via userFillsByTime); a full 2,000-row response costs about 120 weight — roughly 60 clearinghouseState calls — so page with an overlapping cursor and deduplicate by tid.
  • There’s no copy-trading endpoint and no dedicated close: you place your own reduce-only order as an aggressive limit with a slippage bound. Vaults are the native alternative — exposure to a leader’s strategy, not mirroring an arbitrary wallet.
  • In our August 2026 test, the first 429 appeared at 1,212–1,381 calls per minute, about double the nominal arithmetic — but the rate we sustained, 574/min per datacenter address, sits close to the documented 600. Plan from the docs and treat the headroom as burst tolerance; limits are counted per address rather than per /24, and no response exposes the remaining quota.

This summary was created with AI.

Where the leaderboard actually lives

The main API answers on POST https://api.hyperliquid.xyz/info, and the info API documentation covers it thoroughly — every wallet-level query in this article is there. The leaderboard is the exception: it is served from https://stats-data.hyperliquid.xyz/Mainnet/leaderboard as a plain GET, and you will not find that path in the reference. Public SDKs treat it as a second base URL alongside the main API rather than as an info request, which is the clearest signal of how it’s built: a different host, a different response shape, no type field, no POST body.

Two consequences follow, and both are operational rather than theoretical. First, availability is independent: the two hosts are separate services, so a health check against api.hyperliquid.xyz tells you nothing about whether the stats host is answering. Monitor them separately, or your leaderboard job will fail silently while every other call keeps working. Second, the response is heavy and slow compared with an info call. Measuring straight from our own server, the leaderboard’s median response time was roughly 0.7 seconds against about 0.3 seconds for a clearinghouseState request — and it returns the full ranking rather than a page.

The response is a ranking, one object per wallet, and it’s worth knowing its shape before you build on it:

JSON
{
  "ethAddress": "0x...",
  "accountValue": "1234567.89",
  "displayName": "trader name or null",
  "prize": 0,
  "windowPerformances": [
    ["day",     {"pnl": "12345.6", "roi": "0.0123", "vlm": "9876543.2"}],
    ["week",    {"pnl": "...", "roi": "...", "vlm": "..."}],
    ["month",   {"pnl": "...", "roi": "...", "vlm": "..."}],
    ["allTime", {"pnl": "...", "roi": "...", "vlm": "..."}]
  ]
}

ethAddress is the key you carry into every wallet-level call below. Performance arrives as window/metrics pairs rather than flat fields, so “top by weekly PnL” means selecting the right window first, and every number is a string — consistent across the whole API and a routine source of comparison bugs. displayName is optional and frequently null.

That shapes how you should use it. Per Hyperliquid’s API announcements channel, the stats snapshot refreshes on an hourly cadence, so polling it more often normally buys you no fresher data — while the positions behind those wallets change constantly. So fetch the leaderboard rarely, cache it, and build your fast loop around wallet-level calls using the addresses you already extracted:

Python
import json, time, requests

STATS = "https://stats-data.hyperliquid.xyz/Mainnet/leaderboard"
CACHE, TTL = "leaderboard.json", 3600          # matches the snapshot's refresh cadence

def leaderboard():
    try:                                        # serve from cache while it's fresh
        with open(CACHE) as f:
            blob = json.load(f)
        if time.time() - blob["fetched"] < TTL:
            return blob["rows"]
    except (FileNotFoundError, json.JSONDecodeError, KeyError):
        pass
    r = requests.get(STATS, timeout=30)          # generous: this host is slower than /info
    r.raise_for_status()
    rows = r.json().get("leaderboardRows", [])
    with open(CACHE, "w") as f:
        json.dump({"fetched": time.time(), "rows": rows}, f)
    return rows

rows = leaderboard()
print(len(rows), "wallets")
print(rows[0]["ethAddress"], rows[0]["accountValue"])

What to look at: the timeout is deliberately generous because this host is slower than the main API, and the cache is what keeps a slow, undocumented dependency off your critical path. Validate the fields you depend on rather than assuming them — an endpoint absent from the formal reference carries no compatibility promise, even though its move to this host was announced publicly. What this snippet does not do: it has no fallback if the stats host is down, so production code should keep serving the last good cache and alert instead of failing.

Reading the state of a single wallet

Once you have addresses, everything else is the documented API. The clearinghouseState request returns a wallet’s perpetuals state: margin summary, withdrawable balance, and the open positions for a user in assetPositions. It weighs 2 — the cheap tier, alongside l2Book and allMids — and needs no key, no signature, and no account. One scoping detail that older guides miss: the request accepts an optional dex parameter, and with it omitted you get the first perp DEX. Positions on builder-deployed HIP-3 DEXes have to be queried separately when they matter to you.

Python
import requests

INFO = "https://api.hyperliquid.xyz/info"

def wallet_state(address):
    r = requests.post(INFO, json={"type": "clearinghouseState", "user": address}, timeout=10)
    r.raise_for_status()
    return r.json()

state = wallet_state("0x0000000000000000000000000000000000000000")   # user or sub-account address
for item in state["assetPositions"]:
    p = item["position"]
    print(p["coin"], p["szi"], "entry", p["entryPx"], "uPnL", p["unrealizedPnl"])

Reading positions by address is that direct: szi is signed size, so a negative value is a short; entryPx is the average entry; unrealizedPnl and marginUsed come alongside. For a correctly resolved user or sub-account address, an empty assetPositions list means no open positions rather than an error — which matters when you iterate over hundreds of addresses and need to tell “flat” apart from “failed.” One caveat the documentation calls out: don’t point this at an agent or API-wallet address, since that can return an empty result for a wallet that plainly holds positions.

Two limits define what you can build on this call. It’s a snapshot, not a feed — you learn that a position changed only by asking again, so your resolution equals your polling interval. And per-user WebSocket subscriptions stop scaling on a single address: Hyperliquid’s documented limits allow a maximum of 10 unique users across user-specific WebSocket subscriptions per IP.

That ceiling shapes the architecture rather than closing it. Three patterns work, and they combine:

  • Up to ten wallets per address — user-specific subscriptions, the lowest-latency option and the right choice for a dashboard.
  • Distribute the subscriptions — ten unique users per egress address, so the watchlist grows with the number of addresses.
  • Watch the market instead of the wallets — the market-wide trades subscription carries a users array naming the buyer and seller of each fill, so you can filter every trade against your watchlist for change detection and then reconcile only the affected wallets through clearinghouseState. Several current wallet trackers are built exactly this way, and it converts a per-wallet subscription problem into a filtering problem.

The third pattern doesn’t remove polling — it retargets it. Instead of asking every wallet on a timer, you ask the wallets that just traded, plus a slow full sweep to catch whatever the stream missed. The question then becomes how fast that reconciliation can run, which is where budgets come in.

Pulling the fills behind a position

A position tells you where a wallet stands; fills tell you how it got there. The userFills endpoint returns up to 2,000 most recent fills for an address, with aggregateByTime available to combine partial fills of one crossing order. For anything time-bounded, userFillsByTime takes startTime in milliseconds — required — plus an optional endTime that defaults to now, and the documentation notes at most 2,000 fills per response with only the 10,000 most recent available.

The startTime behavior on fills is where a first implementation usually goes wrong, and the trap isn’t window size — it’s cursor handling around truncated responses. startTime is inclusive and expressed in milliseconds, and several fills can share the same millisecond, so advancing to last_time + 1 can silently drop rows that sat behind the last one you saw. The safe pattern is an overlapping cursor: restart from the last timestamp rather than past it, deduplicate on a fill identifier such as tid or the transaction hash, and advance only after processing everything the response returned. Mind the horizon too — at most 2,000 fills per response and only the 10,000 most recent overall, so deep history isn’t available here at all, and pretending otherwise produces silently truncated backfills.

Their cost is easy to misjudge in both directions. Both fills endpoints carry weight 20 — the default tier for info requests — plus an additional weight per 20 items returned. A full 2,000-fill response therefore costs about 120 weight under the documented formula, roughly the equivalent of 60 clearinghouseState calls: expensive enough that fill history belongs in a slow background job, cheap enough that a single such call won’t wreck a minute’s budget on its own.

Following a wallet in practice

Two honest notes before any architecture, because the phrase “copy trading” promises more than the platform exposes. There is no copy trading API on Hyperliquid in the sense of an endpoint that mirrors another trader’s actions to your account: what exists is public read access to every wallet’s state and a normal exchange API for your own orders. Any “following” system you build is a pipeline you own — read a wallet, decide, then place your own order — and that means the latency, the slippage, and the decision to trade are all yours.

Vaults are the closest native equivalent, and they work differently: a depositor puts funds into a vault and shares proportionally in the profits and losses of the vault leader’s strategy. That gives you exposure to a strategy natively — it does not let you pick an arbitrary leaderboard wallet and mirror its individual trades.

Closing a position with a market order is the same story from the other side. Hyperliquid’s exchange API has no dedicated close endpoint; you close by submitting an order in the opposite direction with reduce_only set, and market execution is expressed as an aggressive limit order with a slippage bound rather than a MARKET type. The official Python SDK’s market_close helper does exactly that under the hood — read it before writing your own.

So the architecture reduces to a polling budget, and the budget is documented: REST requests share an aggregated weight limit of 1,200 per minute per IP address. With clearinghouseState at weight 2, arithmetic gives 600 wallet checks per minute from one address. Before you plan capacity around that number, three software levers matter more than any of them:

  • Cache what doesn’t move. The leaderboard hourly, meta daily. Those are weight-20 calls with no reason to be in a loop.
  • Poll by tier, not uniformly. Wallets with open positions deserve seconds; flat wallets deserve minutes. Uniform polling spends most of its budget confirming that nothing happened.
  • Back off on the observed signal, not the assumed one. No response carries a remaining-quota header, so track spend yourself and treat 429 as data — our measurements below show the recovery is far shorter than the “per minute” phrasing implies.

Our test: how many wallets one address covers

Hypothesis. The documented budget describes real behavior, and one egress address is not enough to watch a large wallet set.

Environment and profile. PapaProxy.net addresses over SOCKS5 with IP whitelisting, targeting the public POST /info on api.hyperliquid.xyz; no keys and no signing, with wallet addresses taken from the leaderboard response itself. Datacenter and ISP pools were run as separate series and never merged into a single figure. We raised in-flight requests per address in 150-second stages — each covering a full clock minute — and stopped a ladder at the first HTTP 429. Run dated August 6–7, 2026.

The first observed 429 point. Note the wording: a ladder that stops at the first refusal measures where enforcement began, not a confirmed sustained ceiling. Those are different measurements, and this run made the first one.

Pool clearinghouseState calls/min at first observed 429 Concurrency Latency p50
Datacenter 1,212 8 284 ms
ISP 1,381 12 332 ms

Enforcement started well above the nominal arithmetic. A 1,200 weight/min budget at weight 2 implies 600 calls; the first refusal came at roughly twice that, and the same pattern appeared on every endpoint where we reached a limit:

Endpoint Pool Calls before 429 Documented weight Weight spent per the docs
clearinghouseState Datacenter 1,212 2 2,424
clearinghouseState ISP 1,381 2 2,762
allMids ISP 1,089 2 2,178
meta ISP 131 20 2,620

In all four cases, the consumed budget came out 1.8–2.3× above the documented 1,200. The measured behavior broadly preserved the documented weight tiers, but the first-429 point varied materially between runs and endpoints — around 21% between two weight-2 endpoints on the same pool.

And here is the conclusion that matters more than the headroom. The rate we settled on for sustained multi-address work — 574 calls per minute per datacenter address — lands close to the documented planning figure of 600. So the documentation remains a sensible capacity-planning baseline, and the extra room before the first refusal is best treated as burst tolerance rather than as capacity you can plan to buy less of.

Three findings that change how you write the client. Remaining quota is not exposed: no response carried a rate-limit header, on success or on 429, so maintain a client-side counter from the documented weights — a 429 only tells you that enforcement has already been reached. Recovery was quick in these runs: after a 429, the address answered again within 5.3–5.4 seconds. Treat that as an observation rather than a guaranteed cooldown, and keep backoff plus your own weight accounting in place. And the limit is counted per address, not per subnet — four addresses inside one /24 sustained 2,278 calls/min on datacenter against 2,274 for four addresses in separate /24s (2,052 against 2,044 on ISP), with zero refusals in either group, so there’s nothing to gain from scattering addresses across blocks.

One anomaly we could not explain. Converted to documented weight, three of the four ceilings cluster around 2,500 (±12%). But allMids from datacenter ran to 1,622 calls per minute — 3,244 weight — without a single refusal, while the same call from ISP hit 429 at 1,089. The gap points the opposite way from the clearinghouseState result, so no systematic pool advantage is visible. Plausible explanations, in order: the ceiling isn’t a constant and drifts between runs; the larger allMids payload kept us from reaching the required rate before the ladder ended; the limit is counted differently on that endpoint. Our data can’t separate them. The practical lesson outranks the puzzle: the ceiling isn’t a constant, so don’t plan against the measured maximum — leave margin.

The sustained operating point. This is the number to plan from — the rate we held, not the one that broke:

Pool Sustained calls/min per address Against the documented 600
Datacenter 574 ~96%
ISP 514 ~86%

Interactive capacity planner

How many addresses does your wallet watchlist need?

The calculator uses the sustained rates we held in the test, not the higher and less stable point where the first 429 appeared.

Documented planning rate 600
Sustained · Datacenter 574
Sustained · ISP 514
First 429 · Datacenter 1,212
First 429 · ISP 1,381

Calls per minute per address. The first-429 values describe observed burst headroom, not a safe operating rate.

Refresh each wallet
Required calls/min 2,000
Datacenter addresses 4
ISP addresses 4

Rounded up to whole addresses. This estimate assumes one clearinghouseState call per wallet per refresh and no other REST spend from the same IPs.

Aggregate throughput scaled with address count: exactly linear to five addresses, and close to it beyond. One clearinghouseState call equals one wallet, so the addresses you need are simply wallets divided by refresh interval:

Wallets watched Refresh every 10s every 30s every 60s
100 2 1 1
250 3 1 1
500 6 2 1
1,000 11 4 2
5,000 53 18 9

Put plainly: one address covers roughly 574 wallets at a 60-second refresh, 287 at 30 seconds, and 95 at 10 seconds. So a couple of hundred wallets at a half-minute refresh needs exactly one address — while five thousand wallets need nine even at a leisurely one-minute cadence, or more than fifty at ten seconds. And if you use the market-wide trade stream for change detection, these numbers describe your reconciliation sweep rather than a per-wallet timer, which is usually a much smaller bill.

Where does that leave the pairing this article started with? Everything up to a few hundred wallets is a software problem — cache the leaderboard, filter the trade stream instead of polling every wallet, tier whatever polling remains, and back off on your own counter. Past that, the constraint is the documented per-IP budget, and capacity is added the same way you’d add workers: more egress addresses, each with its own budget, sized from the table above. That’s the part our proxies for crypto projects cover — dedicated datacenter and ISP IPv4 addresses, static for the whole plan term, with contiguous blocks carrying no penalty since the limit is counted per address. Check the platform’s current terms of use before scaling a polling fleet, and keep the leaderboard call out of the fast loop regardless of how many addresses you have.