Solana Tracker LogoSolana Tracker
Swap
Developers
⌘K
Affiliate
All Resources
Solana PnL Leaderboard API: Top Traders and KOL Rankings
Data APITypeScriptJune 9, 202611 min readUpdated October 7, 2026Solana Tracker

Solana PnL Leaderboard API: Top Traders and KOL Rankings

Use the Solana PnL leaderboard API to rank wallets by realized PnL, ROI or win rate over 1-90 day windows, filter out bots, and rank KOLs by period.

  • pnl,
  • leaderboard,
  • kol,
  • data-api,
  • typescript
Solana Data API›Enterprise›

The Solana PnL leaderboard API is GET /v2/pnl/leaderboard/top on the Solana Tracker Data API, called as client.getPnlV2TopTraders() in @solana-tracker/data-api 0.5.0. It ranks wallets by realized PnL, volume, ROI, win rate, trades, tokens or active days over a rolling 1, 7, 30 or 90 day window, with filters that remove arbitrage bots and one-token wonders. For the tracked KOL roster, /v2/pnl/leaderboard/kols ranks all-time and /v2/pnl/leaderboard/kols/period ranks a chosen window.

You can see both rankings rendered live on the Solana PnL leaderboard and the KOL leaderboard. This guide is about ranking many wallets. To analyze one wallet's PnL history in depth, use the Solana wallet PnL API; for current holdings and value, use the wallet portfolio API.

Which Solana PnL leaderboard API endpoint to use

All leaderboard routes belong to PnL v2 on the Solana Data API. They differ by population and time scope.

QuestionSDK methodEndpointPopulationWindow
Who made the most in the last N days?getPnlV2TopTraders/v2/pnl/leaderboard/topTop 500k wallets per period1, 7, 30 or 90 days
Which KOLs are up all-time?getPnlV2KOLLeaderboard/v2/pnl/leaderboard/kolsTracked KOL rosterAll-time
Which KOLs led this week or month?getPnlV2KOLPeriodLeaderboard/v2/pnl/leaderboard/kols/periodTracked KOL rosterperiod or start/end
How did KOLs do each day this month?getPnlV2KOLCalendar/v2/pnl/leaderboard/kols/calendarAggregateOne month
Who did what on one date?getPnlV2KOLByDate/v2/pnl/leaderboard/kols/dateTracked KOL rosterOne day
Who profited on one token?getPnlV2TokenTraders/v2/pnl/tokens/{token}/tradersWallets that traded the tokenToken lifetime

The top-trader board is not limited to KOLs. Coverage is capped at the top 500,000 wallets per period, so a wallet can trade and still be absent. Query it directly with the wallet endpoints in that case. For the earliest buyers of a token rather than its most profitable traders, see the pump.fun first buyers guide.

Parameters, sorting and time windows

getPnlV2TopTraders accepts:

ParameterDefaultValues and meaning
days901, 7, 30 or 90. Other values resolve to the nearest supported window
sortrealizedrealized, volume, days, roi, win_percentage, trades, tokens
directiondescasc or desc
limit100Rows per page
cursornonepagination.nextCursor from the previous page, unchanged
pnlModestrictstrict, adjusted or raw
excludeArbitrage"true"String "false" includes arbitrage wallets
platformnoneaxiom, axiom-flash (alias of axiom), bloom, photon; comma-separated
minTrades20Minimum trades in the window
minInvested1Minimum USD invested in the window
minDays3Minimum active trading days
minWinRate, minRoi, minClosedTokensnoneFloors on win rate %, ROI % and closed positions
maxSingleTokenPctnoneMaximum share of PnL from one token

The KOL routes are simpler. getPnlV2KOLLeaderboard takes sort (total, realized, unrealized, invested, proceeds, value, trades, tokens, roi, win_percentage, last_trade; default realized), direction, limit and cursor. getPnlV2KOLPeriodLeaderboard takes sort (realized, volume, days, ending_total), period (1d, 7d, 14d, 30d, 90d, all; default 30d) or explicit start and end dates in YYYY-MM-DD, which override period. Neither KOL route accepts pnlMode; they return a fixed server-side view.

Response shape

Each top-trader row is a PnlV2Top90dTrader. Trimmed from the documented example:

{
  "traders": [
    {
      "wallet": "HkFGQsW8mr8DTC2AE2WcC7MzwSnynfEryGMQSht271nf",
      "period": {
        "realized": 29253686.75,
        "realizedRaw": 29511824.12,
        "volume": 53377270.16,
        "tradingDays": 79,
        "roi": 345.12,
        "days": { "profitable": 58, "losing": 21, "maxSinglePnl": 891245.33, "winRate": 73.42 }
      },
      "pnlAdjustments": { "mode": "adjusted", "invalidPnl": 29511824.12, "adjustedCorrection": 258137.37 },
      "ending": { "pnl": { "realized": 29642991.91, "total": 29684935.07 } },
      "invested": 8476262.08,
      "counts": { "buys": 13842, "sells": 27968, "trades": 41810, "tokensTraded": 4689 },
      "tokens": { "profitable": 2441, "losing": 159, "closed": 2600 },
      "winRate": 93.88,
      "lastSnapshotDate": "2026-04-19",
      "identity": { "name": null, "twitter": null, "avatar": "https://kol-avatar.solanatracker.io/HkFG…" }
    }
  ],
  "pagination": { "hasMore": true, "nextCursor": "eyJzb3J0VmFsdWUiOj…", "count": 2, "total": 2, "pnlMode": "adjusted" }
}

Read the fields carefully:

  • period.* is scoped to the window. period.realized is mode-aware; period.realizedRaw is the unfiltered figure.
  • ending.pnl is the wallet's cumulative PnL as of lastSnapshotDate, not the window's PnL.
  • There are two win rates. Top-level winRate is profitable closed tokens over closed tokens (2441 / 2600 = 93.88). period.days.winRate is profitable days over active days (58 / 79 = 73.42). period.days is optional and absent on some rows.
  • roi is a percentage, consistent with realized over invested in the example (29.25M / 8.48M is about 3.45x).
  • identity can be null, and a present identity can still have name: null with only an avatar.

KOL all-time rows carry lifetime pnl { realized, unrealized, total }, roi, winRate, openPositions and a non-optional identity that may be null. KOL period rows carry period { realized, volume, tradingDays }, ending.pnl and identity.

Build a leaderboard in TypeScript

Install the SDK and put your key from the Data API dashboard in .env:

npm init -y && npm pkg set type=module
npm install @solana-tracker/data-api@^0.5.0
npm install --save-dev tsx typescript @types/node
node --env-file=.env --import tsx index.ts
# .env.example
ST_API_KEY=
LEADERBOARD_DAYS=30
LEADERBOARD_SORT=realized
PNL_MODE=strict
LIMIT=20

The script validates every input against the documented enums before spending a request, retries 429 and 5xx with backoff, and prints the top traders followed by the KOL board for the same window.

import { Client, DataApiError, RateLimitError, ValidationError } from '@solana-tracker/data-api';
import type { PnlMode, PnlV2Identity, PnlV2TopTradersParams } from '@solana-tracker/data-api';

const apiKey = process.env.ST_API_KEY?.trim();
if (!apiKey) { console.error('Missing ST_API_KEY'); process.exit(1); }

const WINDOWS = { 1: '1d', 7: '7d', 30: '30d', 90: '90d' } as const;
const SORTS = ['realized', 'volume', 'days', 'roi', 'win_percentage', 'trades', 'tokens'] as const;
const MODES = ['strict', 'adjusted', 'raw'] as const;

const days = Number(process.env.LEADERBOARD_DAYS ?? 30);
const sort = (process.env.LEADERBOARD_SORT ?? 'realized') as (typeof SORTS)[number];
const pnlMode = (process.env.PNL_MODE ?? 'strict') as PnlMode;
const limit = Number(process.env.LIMIT ?? 20);
if (!(days in WINDOWS)) { console.error('LEADERBOARD_DAYS must be 1, 7, 30 or 90'); process.exit(1); }
if (!SORTS.includes(sort)) { console.error(`LEADERBOARD_SORT must be one of ${SORTS.join(', ')}`); process.exit(1); }
if (!MODES.includes(pnlMode)) { console.error('PNL_MODE must be strict, adjusted or raw'); process.exit(1); }
if (!Number.isInteger(limit) || limit < 1 || limit > 100) { console.error('LIMIT must be 1-100'); process.exit(1); }

const client = new Client({ apiKey });
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function withRetry<T>(label: string, fn: () => Promise<T>, attempts = 4): Promise<T> {
  for (let i = 1; ; i++) {
    let timer: NodeJS.Timeout | undefined;
    try {
      const timeout = new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error(`${label} timed out`)), 15_000); });
      return await Promise.race([fn(), timeout]);
    } catch (err) {
      const retryable = err instanceof RateLimitError
        || (err instanceof DataApiError && !(err instanceof ValidationError) && (err.status ?? 500) >= 500)
        || !(err instanceof DataApiError);
      if (!retryable || i >= attempts) throw err;
      const retryAfterMs = err instanceof RateLimitError && err.retryAfter ? err.retryAfter * 1000 : 0;
      await sleep(Math.max(retryAfterMs, Math.min(8_000, 500 * 2 ** (i - 1))));
    } finally {
      clearTimeout(timer);
    }
  }
}

const label = (id: PnlV2Identity | null | undefined) => id?.name || id?.sns?.domain || id?.type || '';
const pct = (n: number | null | undefined) => (typeof n === 'number' ? `${n.toFixed(1)}%` : 'n/a');
const usd = (n: number | null | undefined) => (typeof n === 'number' ? `$${Math.round(n).toLocaleString('en-US')}` : 'n/a');

try {
  const params: PnlV2TopTradersParams = { days, sort, direction: 'desc', limit, pnlMode, excludeArbitrage: 'true' };
  const top = await withRetry('top traders', () => client.getPnlV2TopTraders(params));
  console.log(`Top traders, ${days}d by ${sort}, mode ${top.pagination.pnlMode ?? pnlMode}`);
  top.traders.forEach((t, i) => {
    console.log(`${String(i + 1).padStart(3)} ${t.wallet.slice(0, 6)}… ${label(t.identity).padEnd(16)}`
      + ` realized ${usd(t.period.realized)}  roi ${pct(t.period.roi)}`
      + `  token win ${pct(t.winRate)}  day win ${pct(t.period.days?.winRate)}  trades ${t.counts.trades}`);
  });
  if (top.pagination.hasMore) console.log(`next cursor: ${top.pagination.nextCursor}`);

  const kols = await withRetry('kol period', () =>
    client.getPnlV2KOLPeriodLeaderboard({ period: WINDOWS[days as keyof typeof WINDOWS], sort: 'realized', limit: 10 }));
  console.log(`\nKOLs, ${WINDOWS[days as keyof typeof WINDOWS]}`);
  kols.traders.forEach((k, i) => {
    console.log(`${String(i + 1).padStart(3)} ${(label(k.identity) || k.wallet.slice(0, 6) + '…').padEnd(20)}`
      + ` realized ${usd(k.period.realized)}  volume ${usd(k.period.volume)}  lifetime ${usd(k.ending.pnl.total)}`);
  });
} catch (err) {
  console.error('Leaderboard request failed:', err instanceof DataApiError ? `HTTP ${err.status} ${err.message}` : (err as Error).message);
  process.exitCode = 1;
}

The companion project adds the platform, trade-count, active-day and concentration filters, follows the cursor for several pages, and prints the all-time KOL table from getPnlV2KOLLeaderboard.

Pagination

Every leaderboard response includes pagination { hasMore, nextCursor, count, total }. Pass nextCursor back unchanged as cursor and keep every other parameter identical; treat the cursor as opaque. Changing sort, days or a filter between pages produces a list that is not one ranking. Stop when hasMore is false or nextCursor is null.

pnlMode on the leaderboard

Some positions have more recorded sells than recorded buys, for example when tokens arrived without an indexed buy, so their realized PnL rests on an incomplete cost basis. pnlMode decides how those positions count:

ModeTreatmentUse it for
strict (default)Flagged positions are excluded (zeroed)Conservative, comparable rankings
adjustedKept, with realized PnL recalculated on an adjusted cost basisShowing wallets strict mode would understate
rawNo filteringInspection and debugging

The response echoes the mode in pagination.pnlMode, and rows expose period.realizedRaw and, where applied, pnlAdjustments. Store the mode with any cached ranking. A wallet's rank can change substantially between modes, so a table that does not state its mode is not reproducible.

Production pitfalls

State the window, sort and mode above the table. A 7-day realized ranking and a 90-day ROI ranking are different lists. Showing lifetime ending.pnl.total next to a 30-day period.realized without labels misleads readers.

Dollar profit, ROI and win rate each favor a different wallet. Dollar rankings favor large capital. ROI can be dominated by a few small positions. Token win rate hides payoff size, and many small wins can coexist with one large loss. Use minInvested, minClosedTokens and maxSingleTokenPct to remove degenerate cases.

Leaderboards are survivorship-selected. Ranking after the fact does not mean the same wallets were identifiable in advance. A follower also trades later, at worse prices, with different fees. Do not present a rank as a prediction.

Labels are presentation, not joins. Join on wallet. Treat identity as optional, and render the address when it is null or has no name. The wallet identity guide covers how labels resolve and why they can change.

Arbitrage is excluded by default. If your totals look low against another source, check excludeArbitrage before anything else.

Cache, do not hammer. Rows carry lastSnapshotDate and updatedAt. Cache each ranking per (days, sort, filters, pnlMode) and refresh on a schedule that matches how often those timestamps move, instead of on every page view. If you need sustained high request volumes, compare plans on the Data API page or talk to Enterprise.

FAQ

What time windows does the Solana PnL leaderboard API support?

The top-trader board supports rolling 1, 7, 30 and 90 day windows via days, defaulting to 90; other values resolve to the nearest window. KOL period rankings accept 1d, 7d, 14d, 30d, 90d or all, or explicit start and end dates.

Which win rate should I display?

Top-level winRate is the share of closed tokens that were profitable. period.days.winRate is the share of active days that were profitable. Label whichever you show.

Why is a wallet I know missing from the leaderboard?

The top-trader board covers the top 500,000 wallets per period, and default filters require at least 20 trades, 3 active days and $1 invested, and exclude arbitrage wallets. Query the wallet directly with the wallet PnL endpoints.

Does the KOL leaderboard accept pnlMode?

No. The KOL leaderboards return a fixed server-side view. pnlMode applies to the top-trader board, wallet summary, positions, single position and position batch.

Does PnL include SOL and stablecoins?

No. SOL and stablecoins are quote assets and are excluded from PnL positions. Every other SPL token is covered.

Is there a live version of these rankings?

Yes: the PnL leaderboard and KOL leaderboard pages render them in the browser.

References

  • Solana traders leaderboard
  • Get KOL leaderboard
  • Get KOL leaderboard for a period
  • Leaderboards guide
  • How Solana wallet PnL works
  • Track Solana KOL wallets
  • @solana-tracker/data-api on npm

Companion project

The solana-pnl-leaderboard-api example ranks top traders with configurable window, sort, mode and filters, follows the cursor, and prints period and all-time KOL tables.

cp .env.example .env && npm install && npm start
View source on GitHub›Run in StackBlitz›

Runnable Node.js project — clone from GitHub, add keys to .env, then npm start. Use a private local environment for credentials. Check the companion package version before running.

Related Guides

Solana Wallet PnL API: History, Currency and pnlMode
Data API

Solana Wallet PnL API: History, Currency and pnlMode

Read more
Solana Liquidity Events API: LP History and Live Stream
Data API

Solana Liquidity Events API: LP History and Live Stream

Read more
Jupiter DCA API: Track Recurring Orders and Live Fills
Data API

Jupiter DCA API: Track Recurring Orders and Live Fills

Read more

Products

  • Data API
  • Pump.fun API
  • Solana RPC
  • Dedicated Nodes
  • Yellowstone gRPC
  • Raptor Swap API
  • Enterprise

Trading

  • Swap
  • Latest Tokens
  • Trending
  • Top Gainers
  • Memescope
  • Whale Watch
  • KOL Tracker

Tools

  • Wallet Tracker
  • Rugcheck
  • PnL Leaderboard
  • KOLScan
  • Axiom Leaderboard
  • Photon Leaderboard
  • Bloom Leaderboard
  • FOMO Leaderboard
  • GMGN Leaderboard
  • Pump.fun App Leaderboard
  • Terminal Leaderboard
  • Platform Compare
  • My Positions
  • Teams

Resources

  • Developer Guides
  • Blog
  • Documentation
  • API Reference
  • Status
  • Affiliate Program — 25% recurring, uncapped
Solana TrackerSolana Tracker© 2026
Terms of ServicePrivacy PolicyContact