Guides
Guides
Dwellir GuidesAgent CookbookWhen to use IP whitelistingeth_getLogs LimitsBackfill the Full Hyperliquid OHLCV ArchiveExport Hyperliquid OHLCV Candles to CSV or ParquetBuild a Hyperliquid Trading App with Builder CodesStream Order Book Data with the Dwellir WebSocketBuild a Real-Time Base Flashblocks Visualizer with Next.jsHow to Build a Polymarket Copy-Trading BotHyperliquid Funding Rates: Hourly Formula and APIHow to Track Hyperliquid Whales in Real TimeHow to Find the Wallet Behind a Hyperliquid OrderTrade post-mortemPolymarket API: Endpoints, API Keys and Trading GuideKalshi API: Keys, Endpoints, WebSocket and PythonBuild a Real-Time Robinhood Chain Whale Tracker in PythonHow to Build a Copy Trading Bot on Arcus (Robinhood Chain)grpcurlVaults and leaderboard
Hyperliquid Vaults and Leaderboard Data via API in Python cover

Hyperliquid Vaults and Leaderboard Data via API in Python

Rank every Hyperliquid vault, inspect one before you deposit, pull the trader leaderboard and profile the top wallets live, all in Python.

Published November 5, 2026

hyperliquid logohyperliquid logo
LanguagePython 3.10+
NetworkHyperliquid
DataInfo API, stats-data feeds

Hyperliquid's vault page and leaderboard are two of the most visited screens on Hyperliquid, and neither has a documented API that returns the data you see on screen. vaultSummaries comes back as []. leadingVaults returns a 422 error if you copy the request from the docs. The leaderboard has no Info query at all. People end up scraping the website, or they give up and read screenshots.

In this guide we build three small Python programs that get the real data: a vault ranker that sorts every open vault by TVL, APR or 30-day PnL, a vault inspector that shows a vault's leader, fees, returns, biggest depositors and live positions before we put money in, and a leaderboard tool that ranks 48,000 traders and then profiles the top five with one Dwellir call. We also add a short script that shows a wallet's vault deposits and when they unlock.

The interesting part is the routing. Some queries work through Dwellir's Info proxy, two only work on Hyperliquid's public endpoint, and the two big lists live in JSON feeds the app itself loads. One helper sends each request to the right place, and everything else is sorting and printing.

The code is in dwellir-examples/hyperliquid-vaults-leaderboard. Every number in this guide comes from running it on 5 and 6 November 2026.

What You Will Learn

  • Download and cache the two feeds behind the Hyperliquid vault list and leaderboard
  • Rank vaults by TVL, APR or window PnL, and filter out the 6,400 closed ones
  • Read a vault's leader, commission, window returns, largest followers and open positions
  • List the vaults an address leads with leadingVaults, with the user field it silently requires
  • Rank traders by PnL, ROI or volume over a day, week, month or all time, and filter the noise out of ROI
  • Profile the top wallets' live positions in one batchClearinghouseStates request
  • Show a wallet's vault deposits, APR and lockups with userVaultEquities

Prerequisites

  • Python 3.10 or newer. Run python3 --version to check.
  • A Dwellir API key from the dashboard. The free plan allows 20 responses per second, which is more than this guide needs. No gRPC or Order Book add-on is required.
  • One package:
Bash
pip install httpx
  • Comfort with argparse and dict comprehensions. There is no async code here; every call is a plain POST.

We build the four files from scratch below. Clone the finished example to compare against, and set the key:

Bash
git clone https://github.com/dwellir-public/dwellir-examples
cd dwellir-examples/examples/hyperliquid-vaults-leaderboard
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export DWELLIR_API_KEY=your-api-key

Note: vault names are set by their leaders, and some of the real names in the outputs below include emoji. We print them as they are.

Where the Vault and Leaderboard Data Actually Lives

Before writing code, we need to know which request returns what. We tested every candidate on 5 November 2026 against both Hyperliquid's public endpoint and Dwellir's Info proxy at https://api-hyperliquid-mainnet-info.n.dwellir.com/YOUR_API_KEY/info.

QueryPublic endpointDwellir Info proxyWhat it returns
vaultSummaries[][]Nothing useful right now
leadingVaults without user422422Failed to deserialize the JSON body into the target type
leadingVaults with user200200Vaults that address leads: [{address, name}]
vaultDetails200, 56 KB403 type not allowedOne vault: leader, fees, returns, followers, child relationship
portfolio200403 type not allowedEquity, PnL and volume history for any address
userVaultEquities200200A wallet's vault deposits, equity and lockup
clearinghouseState200200Open positions for any address, vaults included
batchClearinghouseStatesnot available200Many wallets' positions in one call, Dwellir only
GET stats-data.hyperliquid.xyz/Mainnet/vaults200, 14 MBEvery vault ever created, 9,478 on the day
GET stats-data.hyperliquid.xyz/Mainnet/leaderboard200, 40 MBEvery ranked trader, 48,192 on the day

Three things follow from that table. The per-wallet queries go through Dwellir, where the rate limit is the plan's own rather than a shared per-IP cap. vaultDetails and portfolio go to the public endpoint. The two lists come from stats-data.hyperliquid.xyz, which is unauthenticated, undocumented, and large enough that we download each file once per run and cache it on disk.

Step 1: One Helper That Routes Each Query

Create hl_api.py. It holds the three hosts, the routing rule and the feed cache, and nothing else:

Python
"""Read helpers for Hyperliquid vault and leaderboard data.

Three sources, one module:

- Dwellir's Hyperliquid Info proxy for the per-user queries it serves
  (clearinghouseState, batchClearinghouseStates, userVaultEquities, leadingVaults, ...).
- Hyperliquid's public Info endpoint for the two vault queries the proxy does not serve
  (vaultDetails, portfolio). Checked on 5 November 2026.
- The stats-data feeds behind app.hyperliquid.xyz: /Mainnet/vaults (every vault, about
  14 MB) and /Mainnet/leaderboard (every ranked trader, about 40 MB). Both are cached on
  disk because you want to download them once per run, not once per lookup.
"""

import json
import os
import time
from pathlib import Path

import httpx

API_KEY = os.environ.get("DWELLIR_API_KEY", "")
DWELLIR_INFO = f"https://api-hyperliquid-mainnet-info.n.dwellir.com/{API_KEY}/info"
PUBLIC_INFO = "https://api.hyperliquid.xyz/info"
STATS_BASE = "https://stats-data.hyperliquid.xyz/Mainnet"

# Info types the Dwellir proxy answers with 403 "type not allowed". Route these
# to the public endpoint. Everything else goes through Dwellir.
PUBLIC_ONLY = {"vaultDetails", "portfolio"}

CACHE_DIR = Path(__file__).parent / ".cache"


def info(query: dict, timeout: float = 30.0):
    """POST one Info query to the right host and return the parsed JSON."""
    if query["type"] in PUBLIC_ONLY:
        url = PUBLIC_INFO
    elif API_KEY:
        url = DWELLIR_INFO
    else:
        raise RuntimeError("Set DWELLIR_API_KEY to query the Dwellir Hyperliquid Info endpoint")
    resp = httpx.post(url, json=query, timeout=timeout)
    if resp.status_code >= 400:
        # The key sits in the Dwellir URL, so never let httpx print it.
        raise RuntimeError(f"{query['type']} failed with HTTP {resp.status_code}: {resp.text[:200]}")
    return resp.json()


def feed(name: str, max_age_s: int = 600):
    """Download /Mainnet/<name> once and reuse it for max_age_s seconds."""
    CACHE_DIR.mkdir(exist_ok=True)
    path = CACHE_DIR / f"{name}.json"
    if path.exists() and time.time() - path.stat().st_mtime < max_age_s:
        return json.loads(path.read_text())
    resp = httpx.get(f"{STATS_BASE}/{name}", timeout=120.0)
    resp.raise_for_status()
    data = resp.json()  # parse before caching, so a bad body is never reused
    path.write_bytes(resp.content)
    return data

info() is the only place a host name appears. If Dwellir adds vaultDetails to the proxy later, we delete one string from PUBLIC_ONLY and nothing else changes. Leaving DWELLIR_API_KEY unset raises an error for the Dwellir queries instead of quietly sending them to the public endpoint, where batchClearinghouseStates does not exist. The error path re-raises without the URL, because the key is part of it.

feed() writes the raw bytes to .cache/ and reuses them for ten minutes. The leaderboard file downloaded in about 1.2 seconds on our connection, so the cache is less about speed than about not fetching 40 MB every time we change a sort flag.

Add three formatting helpers at the bottom of the same file. Hyperliquid returns every number as a string, and the window data comes as lists of [name, value] pairs rather than objects:

Python
def windows(pairs):
    """Turn Hyperliquid's [["day", {...}], ["week", {...}]] lists into a dict."""
    return {name: value for name, value in pairs}


def usd(value) -> str:
    """Format a numeric string as a dollar amount with thousands separators."""
    return f"${float(value):,.0f}"


def pct(value, signed: bool = True) -> str:
    """Format a fraction (0.12 = 12%) as a percentage, signed for returns."""
    return f"{float(value) * 100:{'+' if signed else ''}.1f}%"

Verify the routing from a Python shell:

Python
>>> from hl_api import info
>>> info({"type": "userRole", "user": "0xdfc24b077bc1425ad1dea75bcb6f8158e10df303"})
{'role': 'vault'}

That ran through Dwellir and confirms the HLP address is a vault.

Step 2: Rank Every Vault

The vaults feed is a list of entries, each with a summary object and two extras: apr as a fraction, and pnls, a short cumulative PnL series for each of day, week, month and allTime. The last point in each series is the PnL over that window.

Create vaults.py and start with a loader that flattens the feed. The leader example in the docstring is HLP's leader address:

Python
"""Rank Hyperliquid vaults, inspect one, or list the vaults an address leads.

    python vaults.py top --by tvl --n 20
    python vaults.py top --by apr --min-tvl 250000
    python vaults.py show 0xdfc24b077bc1425ad1dea75bcb6f8158e10df303
    python vaults.py leader 0x677d831aef5328190852e24f13c46cac05f984e7
"""

import argparse
from datetime import datetime, timezone

from hl_api import feed, info, pct, usd, windows


def last(series: list) -> float:
    """Last value of a cumulative PnL series, or 0 when the window has no points yet."""
    return float(series[-1]) if series else 0.0


def load_vaults(open_only: bool = True, min_tvl: float = 0.0) -> list[dict]:
    """Flatten the /Mainnet/vaults feed into one dict per vault."""
    rows = []
    for entry in feed("vaults"):
        summary = entry["summary"]
        if open_only and summary["isClosed"]:
            continue
        tvl = float(summary["tvl"])
        if tvl < min_tvl:
            continue
        pnls = windows(entry["pnls"])
        rows.append({
            "name": summary["name"],
            "address": summary["vaultAddress"],
            "leader": summary["leader"],
            "tvl": tvl,
            "apr": float(entry["apr"]),
            # Each window is a short series of cumulative PnL points. The last
            # point is the PnL over that window; a brand-new vault has none yet.
            "pnl_day": last(pnls["day"]),
            "pnl_week": last(pnls["week"]),
            "pnl_month": last(pnls["month"]),
            "pnl_all": last(pnls["allTime"]),
            "created": datetime.fromtimestamp(summary["createTimeMillis"] / 1000, timezone.utc),
            "kind": summary["relationship"]["type"],
        })
    return rows

Two filters do most of the work. open_only drops the 6,416 closed vaults, which still sit in the feed with a TVL of a few cents. min_tvl drops the long tail: only 48 open vaults held more than $250,000.

Now the ranking and the table:

Python
SORT_KEYS = {"tvl": "tvl", "apr": "apr", "day": "pnl_day", "week": "pnl_week",
             "month": "pnl_month", "all": "pnl_all"}


def print_top(rows: list[dict], by: str, n: int) -> None:
    rows = sorted(rows, key=lambda r: r[SORT_KEYS[by]], reverse=True)[:n]
    print(f"{'#':>3}  {'Vault':<34} {'TVL':>14} {'APR':>9} {'30d PnL':>13}  Address")
    for i, r in enumerate(rows, 1):
        print(f"{i:>3}  {r['name'][:34]:<34} {usd(r['tvl']):>14} {pct(r['apr']):>9} "
              f"{usd(r['pnl_month']):>13}  {r['address']}")

Add the command-line entry point at the bottom of the file. The show and leader subcommands call show_vault and show_leader, which we write in Steps 3 and 4; define the functions above main() as we go:

Python
def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    sub = parser.add_subparsers(dest="command", required=True)

    top = sub.add_parser("top", help="rank vaults")
    top.add_argument("--by", choices=SORT_KEYS, default="tvl")
    top.add_argument("--n", type=int, default=20)
    top.add_argument("--min-tvl", type=float, default=0.0)
    top.add_argument("--include-closed", action="store_true")

    sub.add_parser("show", help="inspect one vault").add_argument("address")
    sub.add_parser("leader", help="vaults an address leads").add_argument("address")

    args = parser.parse_args()
    if args.command == "top":
        rows = load_vaults(open_only=not args.include_closed, min_tvl=args.min_tvl)
        print(f"{len(rows)} vaults after filters")
        print_top(rows, args.by, args.n)
    elif args.command == "show":
        show_vault(args.address)
    else:
        show_leader(args.address)


if __name__ == "__main__":
    main()

Run the ranking:

Bash
python vaults.py top --by tvl --n 10
Text
3062 vaults after filters
  #  Vault                                         TVL       APR       30d PnL  Address
  1  Hyperliquidity Provider (HLP)        $151,014,571     -0.0%      $122,470  0xdfc24b077bc1425ad1dea75bcb6f8158e10df303
  2  HLP Strategy X                       $100,778,255     +4.3%      $356,963  0x469f690213c467c39a23efacfd2816896009d7d8
  3  HLP Liquidator 2                      $30,000,002     +0.0%            $2  0xb0a55f13d22f66e6d495ac98113841b2326e9540
  4  Growi HF                              $12,496,442     -0.6%      $-82,557  0x1e37a337ed460039d1b15bd3bc489de789768d5e
  5  [ Systemic Strategies ] L/S Grids     $11,103,636   +137.0%    $1,246,919  0x07fd993f0fa3a185f7207adccd29f7a87404689d
  6  drkmttr                                $7,213,316    -36.2%   $-4,388,417  0xc179e03922afe8fa9533d3f896338b9fb87ce0c8
  7  DOUBLETOP Vault                        $7,182,580    -11.8%     $-960,705  0x53f8f390fd4f70941c5d160a964f6893c8dbceff
  8  [ Systemic Strategies ] ♾️ HyperGr     $6,132,482   +169.4%      $902,959  0xd6e56265890b76413d1d527eb9b75e334c0c5b42
  9  BredoStrategy                          $5,124,118    -39.2%   $-3,299,440  0x654016a8c9fcf0c4cb7ed6078aba21f7f399f7b7
 10  HLP Strategy A                         $3,060,457     -2.1%     $-107,633  0x010461c14e146ac35fe42271bdc1134ee31c703a

TVL ranks size, not skill. Sort by APR with a TVL floor to find vaults that are both large enough to matter and actually making money:

Bash
python vaults.py top --by apr --min-tvl 250000 --n 5
Text
48 vaults after filters
  #  Vault                                         TVL       APR       30d PnL  Address
  1  Elsewhere                                $808,791   +208.4%      $119,663  0x8fc7c0442e582bca195978c5a4fdec2e7c5bb0f7
  2  [ Systemic Strategies ] ♾️ HyperGr     $6,132,482   +169.4%      $902,959  0xd6e56265890b76413d1d527eb9b75e334c0c5b42
  3  [ Systemic Strategies ] L/S Grids     $11,103,636   +137.0%    $1,246,919  0x07fd993f0fa3a185f7207adccd29f7a87404689d
  4  Orbit Value Strategies                 $1,512,052   +117.0%      $134,293  0x115849ce84370f25cadcf0d348510d73837e1aa5
  5  AceVault Hyper01                         $900,790    +94.5%       $73,641  0x9e02aca9865e1859bb7865f6f64801e804a173df

Note: apr in the feed is annualised from recent performance, so a vault that had one good month shows a triple-digit APR. The --by month sort uses realised 30-day PnL in dollars and is harder to flatter.

Step 3: Inspect One Vault Before Depositing

The ranking gives us candidates. vaultDetails tells us what we would be buying: who runs it, what they take, how it has done over each window, who else is in it, and what it holds right now. The first four come from one public call. The positions come from clearinghouseState through Dwellir, because a vault is just an address.

Add this to vaults.py:

Python
def show_vault(address: str) -> None:
    """Everything the vault page shows, from vaultDetails plus live positions."""
    details = info({"type": "vaultDetails", "vaultAddress": address})
    portfolio = windows(details["portfolio"])
    print(f"{details['name']}  ({address})")
    print(f"  leader        {details['leader']}")
    print(f"  APR           {pct(details['apr'])}")
    print(f"  leader share  {pct(details['leaderFraction'], signed=False)} of equity, "
          f"{pct(details['leaderCommission'], signed=False)} commission")
    print(f"  deposits      {'open' if details['allowDeposits'] else 'closed'}"
          f"{', vault closed' if details['isClosed'] else ''}")
    print(f"  followers     {len(details['followers'])} listed, "
          f"max distributable {usd(details['maxDistributable'])}")

    print("  returns")
    for window in ("day", "week", "month", "allTime"):
        history = portfolio[window]["accountValueHistory"]
        if not history:
            print(f"    {window:<8} no history yet")
            continue
        pnl = portfolio[window]["pnlHistory"][-1][1]
        start, end = float(history[0][1]), float(history[-1][1])
        print(f"    {window:<8} equity {usd(start):>14} -> {usd(end):>14}   "
              f"pnl {usd(pnl):>13}   volume {usd(portfolio[window]['vlm'])}")

    print("  largest followers")
    followers = sorted(details["followers"], key=lambda f: float(f["vaultEquity"]), reverse=True)
    for f in followers[:5]:
        print(f"    {f['user']}  equity {usd(f['vaultEquity']):>12}  "
              f"all-time pnl {usd(f['allTimePnl']):>12}  {f['daysFollowing']} days")

portfolio carries eight windows: day, week, month, allTime and a perp-prefixed copy of each. Each window has accountValueHistory and pnlHistory as lists of [timestamp_ms, value] pairs plus a vlm total. The first and last equity points show how much money came and went, which pnl alone hides: a vault can lose depositors while making money.

followers is capped at 100 entries, sorted by the server, so for a vault with thousands of depositors we see the biggest ones. Each follower carries vaultEquity, pnl, allTimePnl, daysFollowing and lockupUntil.

Now the positions. Append this to the end of show_vault:

Python
    # HLP is a parent vault: its capital sits in child vaults, which show up in
    # the vaults feed with the parent as their leader.
    children = [v for v in load_vaults(open_only=False) if v["leader"].lower() == address.lower()]
    if children:
        print(f"  child vaults ({len(children)})")
        for c in sorted(children, key=lambda c: c["tvl"], reverse=True):
            print(f"    {c['name']:<24} TVL {usd(c['tvl']):>13}  APR {pct(c['apr']):>7}  {c['address']}")

    # A vault is an address, so its open positions come from clearinghouseState
    # like any other wallet. This call runs through Dwellir.
    state = info({"type": "clearinghouseState", "user": address})
    positions = [p["position"] for p in state["assetPositions"] if float(p["position"]["szi"]) != 0]
    print(f"  open positions ({len(positions)})  account value {usd(state['marginSummary']['accountValue'])}")
    for p in sorted(positions, key=lambda p: abs(float(p["positionValue"])), reverse=True)[:10]:
        side = "LONG " if float(p["szi"]) > 0 else "SHORT"
        print(f"    {side} {p['coin']:<8} {usd(p['positionValue']):>13}  "
              f"{p['leverage']['value']}x {p['leverage']['type']}  "
              f"upnl {usd(p['unrealizedPnl'])}")

Run it on the Systemic Strategies grid vault from the ranking:

Bash
python vaults.py show 0x07fd993f0fa3a185f7207adccd29f7a87404689d
Text
[ Systemic Strategies ] L/S Grids  (0x07fd993f0fa3a185f7207adccd29f7a87404689d)
  leader        0x2b804617c6f63c040377e95bb276811747006f4b
  APR           +137.8%
  leader share  7.9% of equity, 10.0% commission
  deposits      open
  followers     100 listed, max distributable $5,496,930
  returns
    day      equity    $11,240,855 ->    $11,111,113   pnl     $-161,380   volume $6,961,193
    week     equity    $11,048,770 ->    $11,111,113   pnl      $444,003   volume $20,822,926
    month    equity    $10,797,571 ->    $11,111,113   pnl    $1,254,395   volume $82,254,487
    allTime  equity             $0 ->    $11,111,113   pnl    $2,666,403   volume $1,072,362,168
  largest followers
    0xa9ff08af55b24bb5d064d776a078e8a292b8dfe2  equity   $3,401,808  all-time pnl     $391,981  641 days
    Leader  equity     $877,263  all-time pnl     $720,683  651 days
    0x8fec806c9e94ff7ab2af3d7e4875c2381413f98e  equity     $534,659  all-time pnl      $84,659  146 days
    0xbdfa4f4492dd7b7cf211209c4791af8d52bf5c50  equity     $483,231  all-time pnl      $19,210  83 days
    0xa69d62573ff153390148bf41b233e65901993b48  equity     $386,911  all-time pnl     $158,769  504 days
  open positions (60)  account value $11,111,113
    LONG  XMR         $3,074,524  5x cross  upnl $167,158
    LONG  ETH         $2,649,361  25x cross  upnl $-174,937
    LONG  AAVE        $1,840,490  10x cross  upnl $-197,783
    LONG  BTC         $1,400,222  40x cross  upnl $-18,806
    LONG  POL         $1,264,704  5x cross  upnl $-88,820
    LONG  BNB         $1,233,521  10x cross  upnl $-36,679
    LONG  PENDLE      $1,158,061  5x cross  upnl $-293,465
    LONG  LDO         $1,155,351  5x cross  upnl $-224,220
    LONG  PAXG          $947,295  5x cross  upnl $-79,053
    LONG  HYPE          $930,902  10x cross  upnl $-70,543

That is the whole due-diligence screen in one command. The leader has $877,000 of their own money in the vault and takes 10% of profits. The vault made $1.25M over the month on $82M of volume. One depositor holds 31% of the equity. And right now it holds 60 positions on cross margin, and the ten largest are all longs, which is a different risk from what the name "L/S Grids" suggests.

Run the same command on HLP and the output is almost empty: 0 open positions and an account value of $11.3M against $151M of TVL. That is the child vaults block earning its place. HLP's relationship.type is parent, and seven child vaults list HLP's address as their leader:

Text
  child vaults (7)
    HLP Strategy X           TVL  $100,778,255  APR   +4.3%  0x469f690213c467c39a23efacfd2816896009d7d8
    HLP Liquidator 2         TVL   $30,000,002  APR   +0.0%  0xb0a55f13d22f66e6d495ac98113841b2326e9540
    HLP Strategy A           TVL    $3,060,457  APR   -2.1%  0x010461c14e146ac35fe42271bdc1134ee31c703a
    HLP Strategy B           TVL    $3,027,313  APR   -0.7%  0x31ca8395cf837de08b24da3f660e77761dfb974b
    HLP Liquidator 4         TVL    $1,000,000  APR   +0.0%  0x2ed5c4484ea3ff8b57d5f2fb152a40d9f2b68308
    HLP Liquidator           TVL      $999,999  APR   +0.0%  0x2e3d94f0562703b25c83308a05046ddaf9a8dd14
    HLP Liquidator 3         TVL      $844,213  APR  -39.5%  0x5e177e5e39c0f4e421f5865a6d8beed8d921cb70
  open positions (0)  account value $11,257,378

To see what HLP is actually trading, run show on HLP Strategy X.

Step 4: The Vaults an Address Leads

leadingVaults has the most misleading name in the Info API. It does not rank vaults. It returns the vaults that one address leads, and it needs that address in a user field. Send it without one and both the public endpoint and Dwellir return HTTP 422 with Failed to deserialize the JSON body into the target type.

Python
def show_leader(address: str) -> None:
    """Vaults this address leads. leadingVaults needs a user; without one it returns 422."""
    vaults = info({"type": "leadingVaults", "user": address})
    if not vaults:
        print(f"{address} leads no vaults")
        return
    for v in vaults:
        print(f"{v['name']}  {v['address']}")

The leader of the grid vault from Step 3 (the leader address on its screen) runs three:

Bash
python vaults.py leader 0x2b804617c6f63c040377e95bb276811747006f4b
Text
[ Systemic Strategies ] L/S Grids  0x07fd993f0fa3a185f7207adccd29f7a87404689d
📉 Inverse Andrew Tate  0x7f6ce756bdee0db22a8fbda15aa48023aae5a8be
[ Systemic Strategies ] ♾️ HyperGrowth ♾️  0xd6e56265890b76413d1d527eb9b75e334c0c5b42

This is the query to run on any leaderboard address before copying it. A top trader who leads a vault can be followed with a deposit instead of a bot.

Step 5: Rank the Leaderboard

The leaderboard feed is one object with a leaderboardRows list. Each row has ethAddress, accountValue, an optional displayName (1,482 of 48,192 rows had one) and windowPerformances, a list of [window, {pnl, roi, vlm}] pairs for day, week, month and allTime.

Create leaderboard.py:

Python
"""Rank Hyperliquid traders from the leaderboard feed and profile the top ones live.

    python leaderboard.py top --window week --by pnl --n 20
    python leaderboard.py top --window month --by roi --min-value 100000 --profile
    python leaderboard.py top --vault-leaders --n 10
    python leaderboard.py addresses --out addresses.txt
"""

import argparse

from hl_api import feed, info, pct, usd, windows
from vaults import load_vaults

WINDOWS = ("day", "week", "month", "allTime")
METRICS = ("pnl", "roi", "vlm")


def load_leaderboard(min_value: float = 0.0, min_volume: float = 0.0, window: str = "month") -> list[dict]:
    """Flatten /Mainnet/leaderboard into one dict per trader.

    min_value drops small accounts. min_volume drops accounts that did not trade
    in the window: deposits and transfers can show up as PnL on a tiny starting
    balance, which makes their ROI meaningless.
    """
    rows = []
    for row in feed("leaderboard")["leaderboardRows"]:
        value = float(row["accountValue"])
        if value < min_value:
            continue
        perf = windows(row["windowPerformances"])
        if float(perf[window]["vlm"]) < min_volume:
            continue
        rows.append({
            "address": row["ethAddress"],
            "name": row.get("displayName") or "",
            "value": value,
            # perf[window] is {"pnl": ..., "roi": ..., "vlm": ...} as strings. Flattened
            # to pnl_day, roi_day, vlm_day, pnl_week, ... pnl_allTime, roi_allTime, vlm_allTime.
            **{f"{m}_{w}": float(perf[w][m]) for w in WINDOWS for m in METRICS},
        })
    return rows

The dict comprehension flattens twelve numbers per trader into keys like pnl_week and roi_allTime, so sorting is one sorted() call with a composed key.

Add the printer. It takes a live flag we will wire up in Step 6:

Python
def print_top(rows: list[dict], window: str, by: str, n: int, live: bool) -> None:
    key = f"{by}_{window}"
    rows = sorted(rows, key=lambda r: r[key], reverse=True)[:n]
    states = profile([r["address"] for r in rows]) if live else {}
    print(f"{'#':>3}  {'Trader':<42} {'Account':>13} {'PnL':>13} {'ROI':>8} {'Volume':>16}")
    for i, r in enumerate(rows, 1):
        print(f"{i:>3}  {r['address']:<42} {usd(r['value']):>13} {usd(r[f'pnl_{window}']):>13} "
              f"{pct(r[f'roi_{window}']):>8} {usd(r[f'vlm_{window}']):>16}")
        notes = [r["name"]] if r["name"] else []
        if r["address"] in states:
            notes.append(describe(states[r["address"]]))
        if notes:
            print(f"     {' | '.join(notes)}")

Add the entry point at the bottom of the file. The --profile flag calls profile() and describe(), which we write in Step 6:

Python
def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    sub = parser.add_subparsers(dest="command", required=True)

    top = sub.add_parser("top", help="rank traders")
    top.add_argument("--window", choices=WINDOWS, default="week")
    top.add_argument("--by", choices=METRICS, default="pnl")
    top.add_argument("--n", type=int, default=20)
    top.add_argument("--min-value", type=float, default=0.0,
                     help="ignore accounts below this value in USD")
    top.add_argument("--min-volume", type=float, default=0.0,
                     help="ignore accounts that traded less than this in the window (ROI on untraded deposits is noise)")
    top.add_argument("--vault-leaders", action="store_true",
                     help="only traders who lead at least one open vault")
    top.add_argument("--profile", action="store_true",
                     help="add live positions from Dwellir batchClearinghouseStates")

    dump = sub.add_parser("addresses", help="write every leaderboard address to a file")
    dump.add_argument("--out", default="addresses.txt")

    args = parser.parse_args()
    if args.command == "addresses":
        rows = load_leaderboard()
        with open(args.out, "w") as fh:
            fh.write("\n".join(r["address"] for r in rows) + "\n")
        print(f"wrote {len(rows)} addresses to {args.out}")
        return

    rows = load_leaderboard(min_value=args.min_value, min_volume=args.min_volume, window=args.window)
    print(f"{len(rows)} traders after filters")
    if args.vault_leaders:
        # A leader can run several vaults (HLP's leader runs eight). Name the
        # largest and count the rest.
        vaults_by_leader: dict[str, list[dict]] = {}
        for v in sorted(load_vaults(open_only=True), key=lambda v: v["tvl"], reverse=True):
            vaults_by_leader.setdefault(v["leader"], []).append(v)
        rows = [dict(r, name=f"leads {vs[0]['name']} ({usd(vs[0]['tvl'])} TVL"
                             + (f", and {len(vs) - 1} more)" if len(vs) > 1 else ")"))
                for r in rows if (vs := vaults_by_leader.get(r["address"]))]
        print(f"{len(rows)} of them lead an open vault")
    print_top(rows, args.window, args.by, args.n, live=args.profile)


if __name__ == "__main__":
    main()

Rank by ROI over a month with only an account-value floor and the result is useless:

Bash
python leaderboard.py top --window month --by roi --min-value 100000 --n 2
Text
14386 traders after filters
  #  Trader                                           Account           PnL      ROI           Volume
  1  0x03a66ab07ef5851ab95ce65aaf7f35226a6c9111      $848,500      $848,000 +169600.0%               $0
  2  0xbfce143acf73a34a1f8b2e2da64736146035c732    $1,697,000    $1,696,000 +169600.0%               $0

Volume is zero. These accounts started the window with $500 or $1,000 and received a transfer, which the feed counts as PnL against the starting balance. --min-volume removes them: adding --min-volume 1000000 to the same command leaves 3,791 traders and a top of the table made of people who traded.

Step 6: Profile the Top Wallets in One Call

A leaderboard row is history. What the trader holds now is a different question, and asking it one wallet at a time for twenty wallets is twenty round trips. Dwellir's Info proxy adds batchClearinghouseStates, which is not in Hyperliquid's own API: it takes a list of addresses and returns every state in one response.

Python
def profile(addresses: list[str]) -> dict[str, dict]:
    """Live account state for many wallets in one Dwellir request.

    batchClearinghouseStates is a Dwellir-only Info type. It is billed per wallet,
    so 20 addresses cost 20 responses, the same as 20 single calls, in one round trip.
    """
    result = info({"type": "batchClearinghouseStates", "users": addresses}, timeout=60.0)
    return {address: state for address, state in result["successful_states"]}


def describe(state: dict) -> str:
    """One line: biggest position, leverage, margin use."""
    summary = state["marginSummary"]
    value = float(summary["accountValue"])
    used = float(summary["totalMarginUsed"])
    positions = [p["position"] for p in state["assetPositions"] if float(p["position"]["szi"]) != 0]
    if not positions:
        return f"flat, margin used {used / value * 100 if value else 0:.0f}%"
    big = max(positions, key=lambda p: abs(float(p["positionValue"])))
    side = "long" if float(big["szi"]) > 0 else "short"
    return (f"{len(positions)} positions, largest {side} {big['coin']} "
            f"{usd(big['positionValue'])} at {big['leverage']['value']}x, "
            f"margin used {used / value * 100 if value else 0:.0f}%")

The response has successful_states, a list of [address, state] pairs in input order, and failed_wallets for any upstream lookups that timed out. Each state is the same object clearinghouseState returns, so describe() works on either.

The week's biggest earners, with their live books:

Bash
python leaderboard.py top --window week --by pnl --n 5 --profile
Text
48192 traders after filters
  #  Trader                                           Account           PnL      ROI           Volume
  1  0x5b5d51203a0f9079f8aeb098a6523a13f298c060  $220,434,589   $58,999,938   +35.1%     $145,461,895
     23 positions, largest short ETH $184,268,026 at 5x, margin used 100%
  2  0xb83de012dba672c76a7dbbbf3e459cb59d7d6e36  $196,167,837   $51,772,639   +35.4%     $229,779,403
     30 positions, largest short BTC $147,935,229 at 5x, margin used 100%
  3  0x024294c53a69c1e6b39810b3fac87385752fffa1   $46,014,702   $26,138,317  +183.1%     $370,469,635
     flat, margin used 0%
  4  0xf5d81a135f756ca16544e53c20fc20643ec3ad53   $97,862,218   $20,575,626   +24.1%  $13,401,831,903
     29 positions, largest long BTC $279,299 at 3x, margin used 9%
  5  0x7fdafde5cfb5465924316eced2d3715494c517d1   $44,531,145   $16,380,053   +28.8%     $766,542,921
     BobbyBigSize | 35 positions, largest short BTC $37,965,921 at 30x, margin used 53%

Five wallets, five different stories. The top two made $111M between them in a week and are both fully margined short, one in ETH and one in BTC. Number three made $26M and is sitting in cash. Number four turned over $13.4B for a 24% return and runs 9% margin, which looks like a market maker rather than a directional trader. Those distinctions are invisible in the leaderboard and obvious in the batch call.

The whole command, including parsing 40 MB of cached JSON and the batch request, took 2.2 seconds.

Step 7: Your Own Vault Deposits

userVaultEquities runs through Dwellir and returns a wallet's vault positions. It only carries the vault address, the equity and lockedUntilTimestamp, so we join the vaults feed for names and APR.

Create my_vaults.py:

Python
"""Show a wallet's vault deposits with names, lockups and vault performance.

    python my_vaults.py 0x677d831aef5328190852e24f13c46cac05f984e7
"""

import sys
from datetime import datetime, timezone

from hl_api import info, pct, usd
from vaults import load_vaults


def main(address: str) -> None:
    # userVaultEquities runs through Dwellir. It returns address, equity and
    # lockup only, so we join the vaults feed for names and APR.
    equities = info({"type": "userVaultEquities", "user": address})
    if not equities:
        print(f"{address} has no vault deposits")
        return
    vaults = {v["address"]: v for v in load_vaults(open_only=False)}
    now = datetime.now(timezone.utc)

    total = 0.0
    print(f"{'Vault':<34} {'Equity':>12} {'APR':>9} {'TVL':>14}  Lockup")
    for e in equities:
        v = vaults.get(e["vaultAddress"])
        name = v["name"] if v else e["vaultAddress"]
        apr = pct(v["apr"]) if v else "n/a"
        tvl = usd(v["tvl"]) if v else "n/a"
        unlock = datetime.fromtimestamp(e["lockedUntilTimestamp"] / 1000, timezone.utc)
        lock = "unlocked" if unlock <= now else f"locked until {unlock:%Y-%m-%d %H:%M} UTC"
        total += float(e["equity"])
        print(f"{name[:34]:<34} {usd(e['equity']):>12} {apr:>9} {tvl:>14}  {lock}")
    print(f"{'total':<34} {usd(total):>12}")


if __name__ == "__main__":
    if len(sys.argv) != 2:
        sys.exit(__doc__)
    main(sys.argv[1])

load_vaults(open_only=False) matters here. A deposit in a vault that has since closed still shows up in userVaultEquities, and we want its name. Run it on a wallet from the grid vault's follower list (it sits below the five printed in Step 3):

Bash
python my_vaults.py 0x0293eb1f9ec324555629d38f97ffbaaaa43770f5
Text
Vault                                    Equity       APR            TVL  Lockup
[ Systemic Strategies ] L/S Grids      $159,699   +137.0%    $11,103,636  unlocked
Growi HF                               $240,598     -0.6%    $12,496,442  unlocked
total                                  $400,297

Putting It All Together

The question this guide set out to answer is "who is worth following, and can I follow them with a deposit?" One flag joins the two feeds: keep only leaderboard rows whose address leads an open vault.

Bash
python leaderboard.py top --window month --by pnl --vault-leaders --min-volume 1000000 --n 5
Text
9076 traders after filters
103 of them lead an open vault
  #  Trader                                           Account           PnL      ROI           Volume
  1  0x49e96e255ba418d08e66c35b588e2f2f3766e1d0   $30,940,980   $14,356,501   +36.7%   $1,410,528,403
     leads TEST VAULT DO NOT SEND FUNDS HERE ($100 TVL)
  2  0xf517639a8872e756ac98d3c65507d2ebc25cc032   $11,304,504    $1,579,753   +17.0%     $755,212,366
     leads 100x degen Vault ($5 TVL)
  3  0x94dac1facdc4afd2614c3119447187b4143bc449   $10,354,584      $636,752   +50.0%      $14,828,128
     leads Big Brother Capital ($1,345 TVL)
  4  0x7facb3ec0415d6605e0cf5dff744f1108224ff4d      $951,789      $415,872  +137.1%       $2,352,282
     leads Long HYPE | Short Garbage ($1,192 TVL)
  5  0xf04ac903c91a028c9a6de6dcb86875920f02206c       $97,468       $88,102  +158.0%       $3,451,895
     leads Special Sits 50x ($0 TVL)

The TVL in brackets is the point of the join. The five best monthly PnLs among vault leaders all run vaults holding under $1,500, so this flag finds traders who opened a vault, not vaults worth a deposit. For candidates, go the other way: python vaults.py top --by month --min-tvl 250000 ranks funded vaults by 30-day PnL, and python vaults.py show <address> on one of them gives the fee, lockup, follower and position screen from Step 3 before any money moves.

The last tool answers a question that shows up in our Search Console queries: how to get every address on the leaderboard.

Bash
python leaderboard.py addresses --out addresses.txt
Text
wrote 48192 addresses to addresses.txt

Feed that file to batchClearinghouseStates in chunks and you have a snapshot of every ranked trader's book.

Going to Production

Cache the feeds, and decide how stale is acceptable. The two stats-data files are rebuilt by Hyperliquid on their own schedule, not on request, and the vault feed's 30-day PnL for the grid vault was $1,246,919 while vaultDetails said $1,254,395 a minute later. Treat the feeds as a few minutes behind. Ten minutes of cache is right for a dashboard; a copy-trading signal should use vaultDetails and clearinghouseState for the vaults it cares about and the feed only for discovery.

Keep the per-wallet calls on Dwellir. The public Info endpoint applies a shared rate limit per IP address, and every service behind one NAT gateway shares it. Through Dwellir the limit is the plan's responses per second, counted per account, and batchClearinghouseStates keeps the round trips down. It is billed per distinct wallet in the batch, so profiling 1,000 addresses costs 1,000 responses, the same as 1,000 single calls, in one request. See the Info endpoint docs for the per-plan limits.

Expect PUBLIC_ONLY to shrink. vaultDetails and portfolio return 403 on the proxy today. If you want them routed through Dwellir, ask support@dwellir.com; the routing table in hl_api.py is the only line to change when they land.

Handle the two feed failures. A 40 MB download will occasionally fail halfway. feed() only writes the cache after raise_for_status(), so a failed download leaves the old file in place, but on a first run there is no old file. Wrap the call in a retry with backoff before you schedule it.

Treat ROI as a screen, not a score. ROI in the feed is PnL over the window's starting equity. Deposits inflate PnL, small starting balances inflate ROI, and neither is a trading result. Rank by PnL with an account-value floor, or by ROI with --min-volume, and then look at the positions.

Vault terms are in vaultDetails, not the feed. leaderCommission, leaderFraction, allowDeposits and each follower's lockupUntil only come from the detail call. Deposits lock for one day in user vaults and four days in HLP, measured as lockupUntil minus vaultEntryTime across the followers of both on 6 November 2026, so read lockupUntil before assuming you can withdraw today.

This is not investment advice. The vaults and wallets shown here were picked because they topped a sort on one afternoon. A vault holding 60 cross-margin positions can lose a year of APR in a day.

Next Steps

  • Stream vault deposits and withdrawals as they happen. Every vaultTransfer action is decoded in Dwellir's gRPC block stream, so a flow monitor for the vaults you follow is a filter on StreamBlocks.
  • Replay a leader's trades. The trade post-mortem guide rebuilds any wallet's closed trades from userFillsByTime on the Hyperliquid Index, which works on a vault leader's address as well as your own.
  • Turn a leaderboard wallet into a signal. The whale tracking guide streams large fills over gRPC and enriches them with the same clearinghouseState call used here, and the copy-trading bot post takes it from signal to order.

This guide ran every per-wallet query through Dwellir's Hyperliquid Info endpoint. To run it yourself, create a free account and set DWELLIR_API_KEY.

References

  • dwellir-examples/hyperliquid-vaults-leaderboard
  • Dwellir Hyperliquid Info endpoint
  • userVaultEquities, leadingVaults, batchClearinghouseStates
  • Hyperliquid Info endpoint documentation

Trading cryptocurrencies involves significant risk. This content is for educational purposes only and does not constitute financial advice.

Frequently Asked Questions

Is there a Hyperliquid leaderboard API?

Not in the documented Info API. The leaderboard on app.hyperliquid.xyz loads one JSON file, https://stats-data.hyperliquid.xyz/Mainnet/leaderboard, with every ranked trader (48,192 rows and about 40 MB on 5 November 2026). Each row has the address, account value and PnL, ROI and volume for day, week, month and all time. Download it once, cache it, and rank it locally. Then use Dwellir's batchClearinghouseStates to pull live positions for the top wallets in one call.

How do I get a list of all Hyperliquid vaults?

vaultSummaries is the documented query, but it returned an empty array on both the public endpoint and Dwellir on 5 November 2026. The vault list the app shows comes from https://stats-data.hyperliquid.xyz/Mainnet/vaults, about 14 MB with 9,478 vaults, 3,062 of them open. Each entry has the name, leader, TVL, APR, open or closed flag and a short cumulative PnL series per window.

What does leadingVaults return?

The vaults an address leads, not a ranking of the best vaults. It needs a user field. Without one the endpoint returns HTTP 422 'Failed to deserialize the JSON body into the target type', which is why it looks broken if you copy the bare request from older docs.

How do I see what a Hyperliquid vault is holding?

A vault is an address. Query clearinghouseState with the vault address as the user and you get its open positions, leverage, unrealized PnL and margin use, the same as any wallet. For HLP that returns almost nothing, because HLP is a parent vault whose capital sits in child vaults such as HLP Strategy X; query the children instead.

Which vault queries work through Dwellir's Info proxy?

clearinghouseState, batchClearinghouseStates, userVaultEquities, leadingVaults and userRole all run through api-hyperliquid-mainnet-info.n.dwellir.com with your API key. vaultDetails and portfolio return 403 'type not allowed' there, so send those two to the public api.hyperliquid.xyz/info endpoint. The guide's helper routes each query automatically.

grpcurl

grpcurl is curl for gRPC. Install it on macOS, Linux or Windows, then list, describe, call and stream real gRPC APIs with headers, API keys and jq.

On this page

What You Will LearnPrerequisitesWhere the Vault and Leaderboard Data Actually LivesStep 1: One Helper That Routes Each QueryStep 2: Rank Every VaultStep 3: Inspect One Vault Before DepositingStep 4: The Vaults an Address LeadsStep 5: Rank the LeaderboardStep 6: Profile the Top Wallets in One CallStep 7: Your Own Vault DepositsPutting It All TogetherGoing to ProductionNext StepsReferences