Find pump.fun wallets worth copying
· · Calls, limits and prices as of 2026-10-22.
How do I find pump.fun wallets worth copying?
Take wallets from the coins that ran, then keep only the ones whose whole record says they take profit. The candidates come from the chain: the holders of yesterday's busiest coins, from any Solana RPC. The record comes from pumpdata.fun: POST /wallets labels each wallet from every pump.fun trade since pump.fun's first day, as winner, net_loser, bot, supply_seller, new or none, with its SOL in and out. Keep the winners, rank them by profit, and check each coin they buy before copying it. The script below does the first two steps in about a minute, free.
The shortlist in Python
Standard library only. python3 shortlist.py prints the shortlist: the winners among the holders that hold long enough to follow. The public Solana RPC rate-limits getTokenLargestAccounts hard, so set RPC to your own (a free Helius or QuickNode endpoint is enough).
# Build a copy-trading shortlist: the wallets holding yesterday's busiest pump.fun coins, kept only if their
# whole pump.fun record says they take profit. Standard library only. Free: pumpdata.fun's /free/ calls
# (30 every five minutes, 2,000 wallet lookups a day) and any Solana RPC.
# python3 shortlist.py (RPC=<your RPC URL> python3 shortlist.py with your own)
import json, os, time, urllib.error, urllib.request
PD = "https://pumpdata.fun"
RPC = os.environ.get("RPC", "https://api.mainnet-beta.solana.com") # your own RPC is better: the public one rate-limits
SYSTEM = "11111111111111111111111111111111" # a wallet's account is owned by the System Program
KEY = None # a free key (https://pumpdata.fun/keys): 2,000 wallets a day of its own
def http(url, body=None, headers=None):
req = urllib.request.Request(url, json.dumps(body).encode() if body is not None else None,
{"content-type": "application/json", **(headers or {})})
with urllib.request.urlopen(req, timeout=20) as r:
return json.load(r)
def rpc(method, *params):
for wait in (2, 5, 10, 20, 40): # the public RPC rate-limits; back off and try again
try:
return http(RPC, {"jsonrpc": "2.0", "id": 1, "method": method, "params": list(params)})["result"]
except urllib.error.HTTPError as e:
if e.code != 429: raise
time.sleep(wait)
raise RuntimeError(f"{method}: the RPC kept saying 429; use your own RPC")
def holders(mint):
"""The wallets among a coin's 20 largest holders (pools and the curve are left out)."""
accts = [a["address"] for a in rpc("getTokenLargestAccounts", mint)["value"]]
infos = rpc("getMultipleAccounts", accts, {"encoding": "jsonParsed"})["value"]
owners = list({i["data"]["parsed"]["info"]["owner"] for i in infos if i})
kinds = rpc("getMultipleAccounts", owners, {"encoding": "base64", "dataSlice": {"offset": 0, "length": 0}})["value"]
return [o for o, k in zip(owners, kinds) if k and k["owner"] == SYSTEM]
def classify(wallets):
"""Each wallet's record since pump.fun's first day, 50 at a time."""
out = {}
path, headers = ("/wallets", {"x-api-key": KEY}) if KEY else ("/free/wallets", {})
i = 0
while i < len(wallets):
try:
out.update(http(PD + path, {"addresses": wallets[i:i + 50]}, headers))
i += 50
except urllib.error.HTTPError as e:
if e.code == 429: # the five minutes are used: wait them out, then the same batch
time.sleep(json.load(e).get("retry_after_s", 60)); continue
if e.code == 402: # the day's free wallets are used: free_tier says what next
print(json.load(e).get("free_tier")); break
raise
return out
def shortlist(top=10, min_pnl=50, min_hold=120):
day = http(PD + "/stats.json")
coins = day["coins"]["top_volume"][:top]
wallets = sorted({w for c in coins for w in holders(c["mint"])})
records = classify(wallets)
# winners only, and only ones a copier can follow: a median hold of seconds is gone before a copy lands
keep = [(w, r) for w, r in records.items()
if r["class"] == "winner" and r["pnl_sol"] >= min_pnl
and ((r.get("recent") or {}).get("median_hold_s") or 0) >= min_hold]
keep.sort(key=lambda wr: -wr[1]["pnl_sol"])
return day["day"], len(wallets), keep
if __name__ == "__main__":
day, seen, keep = shortlist()
print(f"{day}: {seen} holder wallets checked, {len(keep)} kept")
for w, r in keep:
rc = r.get("recent") or {}
print(f'{w} {r["pnl_sol"]:>9,.1f} SOL over {r["coins"]:>5} coins, holds {rc.get("median_hold_s") or 0:>5.0f} s, {rc.get("trades_per_day") or 0:>5.0f} trades a day')
What a run looks like
Run on 2026-10-22 for the day before: the 20 largest holders of each of the ten busiest coins, 168 wallets once pools and the bonding curve are left out, classified in four /free/wallets calls. Ten were winners with at least 50 SOL of profit and a median hold of two minutes or more:
2026-10-21: 168 holder wallets checked, 10 kept GgbC…ymsT 6,135.5 SOL over 14547 coins, holds 699 s, 252 trades a day 6vbr…paFa 5,483.3 SOL over 168 coins, holds 36832 s, 5 trades a day 2EYV…ay6a 5,453.0 SOL over 663 coins, holds 1193 s, 36 trades a day G3Vp…X9uk 878.2 SOL over 36 coins, holds 4489 s, 16 trades a day Ew58…eoNP 774.4 SOL over 464 coins, holds 865 s, 69 trades a day 29HF…YGHG 557.0 SOL over 1515 coins, holds 152 s, 24 trades a day 9M2X…YDzL 400.8 SOL over 59 coins, holds 23479 s, 3 trades a day 45xU…P9UB 359.4 SOL over 1472 coins, holds 332 s, 48 trades a day BwPF…NpDd 303.8 SOL over 9825 coins, holds 223 s, 54 trades a day AH39…SKfL 292.2 SOL over 207 coins, holds 5313 s, 13 trades a day
The other 158 were wallets no rule fits, new wallets, bots, supply sellers, or winners that sell within two minutes. That is the point of the filter: holding yesterday's busiest coin is common, and a record of taking profit over dozens of coins is not.
What the labels mean
| Class | The rule | For copying |
|---|---|---|
winner | Took out 10+ SOL and 1% more than it put in, over 20+ coin-weeks, however much it trades | The shortlist, if you can follow it |
net_loser | Put in 50+ SOL more than it took out, over 20+ coin-weeks | Skip, however hot it looks today: it loses over time |
bot | A sniper (20+ coins bought in their create block, half or more of its new coins) or 1,500+ trades a day, over the last 20 days | Skip: it is in the block before you |
supply_seller | Took out 1.5x what it put in, 100 SOL or more | Skip: its profit is what its buyers paid |
new | No record before today | Nothing to go on |
none | No rule fits (most wallets) | Nothing to go on |
Each wallet also comes back with pnl_sol, put_in, took_out, coins, first_seen, and recent: over the last 20 days its trades_per_day, median_hold_s (first buy to first sell), fast_share (trades under 2 s after the last) and snipe_share. The class says whether a wallet makes money; recent says whether you can follow it. Kolscan's top 50 traders on Oct 22: 42 winners, with median holds from 11 seconds to a few minutes.
After the shortlist
Watch the wallets. pumpdata.fun is not a feed. Your RPC's websocket (logsSubscribe with mentions set to a wallet) or a provider's webhooks tell the agent when a shortlisted wallet buys.
Check the coin before copying. A good wallet still buys bad coins, and a copier is always later. Before following a buy, GET /free/launch/{mint} says who launched it, how much the first ten seconds took and how launches like it went: the pre-buy check is that step in code.
Test the copying before the money. Take the shortlist's past buys as picks and replay them: POST /replay fills each one at the pool as it stood after your latency, with every fee and the dead coins counted. Copying a wallet three seconds late is a different trade from the wallet's own.
What it costs
| Lane | Wallet lookups | Price |
|---|---|---|
No key, /free/wallets | 2,000 a day per IP; 30 units every five minutes (a 50-wallet call is 5) | Free |
Free key, x-api-key | 2,000 a day of its own | Free (sign up) |
| Per call, x402 | Any number | $0.01 for up to 50 wallets, USDC on Solana, no account |
Monthly, POST /keys/upgrade | 50,000 a day for 30 days | $10 in USDC, paid once over x402 |
| The whole table | Every wallet's record as files | Bulk data |
Past a free lane's day the call answers 402: the price, and in its body what was used and when it resets. A copy-trading agent already holds a funded wallet, so paying the call is a few lines.
A prompt for agents
Give this to the agent that builds the shortlist.
You are building a copy-trading shortlist of pump.fun wallets with pumpdata.fun.
The docs are at https://pumpdata.fun/llms.txt; read them first.
1. Candidates: take wallets from the chain (the largest holders or the early buyers of coins that ran; the day's
busiest coins are in https://pumpdata.fun/stats.json, coins.top_volume). pumpdata.fun does not list a coin's
holders; your RPC does.
2. POST https://pumpdata.fun/free/wallets with {"addresses": [up to 50]} for each batch: each wallet's class
(winner, bot, supply_seller, net_loser, new, none), pnl_sol, put_in, took_out, coins and first_seen, over every pump.fun
trade since day one. A 50-wallet call counts five of the 30 free units every five minutes; on 429 wait
retry_after_s. Keyless wallet lookups are 2,000 a day; a free key (x-api-key) has 2,000 of its own.
3. Keep class "winner" only. Drop "bot" (a sniper or a 1,500-trades-a-day machine: it is in the block before you),
"supply_seller" (it sells into buyers like you), "net_loser" (it loses over time, however hot today) and "new"
(no record). Then read "recent": drop winners whose
median_hold_s is under two minutes (gone before a copy lands) and rank the rest by pnl_sol.
4. On a 402, the free wallets are used: report free_tier from the body (what was used, when it resets, the price,
the $10 monthly plan) and stop. Never pay without the user's say-so.
5. Before copying any buy, run GET https://pumpdata.fun/free/launch/<mint>: a shortlisted wallet buying a serial
launcher's bundled coin is still a bad copy.
Never call a wallet safe or a sure thing: the labels are rules over its past, not a forecast.
Questions
Where do the candidate wallets come from?
From the chain. pumpdata.fun does not list a coin's holders or buyers; any Solana RPC does (getTokenLargestAccounts gives a coin's 20 largest holders), and a trade stream such as PumpPortal gives the buyers of every new coin as they buy. The script takes the holders of yesterday's ten busiest coins from /stats.json. pumpdata.fun's part is the next step: what each of those wallets has done on pump.fun since its first day.
What makes a wallet a winner?
A rule, not an opinion: it took out at least 10 SOL and 1% more than it put in, over 20 or more coin-weeks of trading on pump.fun, however much it trades. A net_loser put in 50+ SOL more than it took out over as long: often a wallet on today's leaderboard. A bot, over the last 20 days, bought 20+ coins in their create block (half or more of the coins it bought new), or traded 1,500+ times a day. A supply seller took out 1.5 times what it put in, 100 SOL or more. New means no record before today; none means no rule fits, which is most wallets. Of kolscan's top 50 traders on Oct 22, 42 were winners, 7 none and 1 a bot.
Why not just rank wallets by profit?
Because the top of a profit list is bots and supply sellers. A sniper bot that buys every launch shows a profit you cannot copy (it is in the block before you), and a supply seller's profit is what its buyers paid; copying its buys means buying what it is about to sell you. The class removes both before the ranking.
How many wallets is it free for?
2,000 wallet lookups a day with no key (a 50-wallet call counts five of the 30 free units every five minutes), and 2,000 more a day with a free key. The run on this page checked 170 in four calls. Past that, $0.01 for up to 50 wallets, paid per call in USDC over x402, or $10 a month in USDC on the key for 50,000 a day. The whole wallet table is bulk data.
Why filter on the median hold?
Because a winner you cannot follow is not a copy trade. Each wallet comes back with recent: its trades a day, its median seconds from first buy to first sell, and the share of its trades under 2 seconds apart, over the last 20 days. Kolscan's top traders hold for a median of 11 to 150 seconds; a copier a few seconds late buys their exit. The script keeps winners that hold two minutes or more; set min_hold to taste.
How fresh are the labels?
As of the latest tables build, which every answer names in freshness.data_as_of (and the X-Data-As-Of header). A wallet's class changes slowly, so a shortlist built today stays useful for days. Re-run it weekly.
Does pumpdata.fun copy the trades for me?
No. It answers who is worth copying and whether a coin they buy is worth following. Watching the wallets (your RPC's websocket, or a provider's webhooks) and placing the trades are the agent's own; so is the decision.
The labels are rules over a wallet's past on pump.fun, not a forecast and not advice. The shapes, for a model: llms.txt and openapi.json. The MCP server: https://pumpdata.fun/mcp.