Build a pump.fun copy trading bot with WebSocket streams

· 9 min read

copy tradingwebsocketwallet groupstrust scorestreams

A copy trading bot has two halves. The execution half — keys, signing, submission, landing — is yours and is not covered here. The data half — knowing that a wallet worth copying just bought, in sub-second, with enough context to decide whether to follow — is what this guide builds, end to end, on the Raiden API. Raiden supplies the eyes of a pump.fun copy trading bot; you bring the hands. Getting the eyes right is where most solana copy trading bot projects quietly fail: they detect too slowly, mirror blindly, and never handle the exit.

The architecture in one screen

#LayerWhat it doesAPI surface
1Leader listThe wallets worth mirroring, saved server-sidePOST /wallet-groups + …/wallets
2StreamA subscription that pushes only those wallets' fillsPOST /ws-subscriptions
3ListenerThe WebSocket connection consuming type: swap eventswss://terminal.raiden.wtf/ws
4Risk gatePre-trade checks on every signal before mirroring/tokens/{mint}/trust, /tokens/{mint}/swaps
5Exit mirrorThe leader's sell is a swap event too — act on itsame stream

Step 1 — Curate the leaders into a wallet group

A copy bot is only as good as the wallets it copies. How to find and vet them — realized PnL net of all fees, win rate, hold style, and the traps in each metric — is its own guide: the best Solana wallets to copy trade. Vetting runs on a 6-month rolling window of history (complete record since May 1, 2026), so a leader's track record is checkable before you wire money to its signals. Once you have the shortlist, save it as a wallet group — streams filter by group id, never by inline addresses:

curl -X POST -H "X-API-Key: $RAIDEN_KEY" -H "Content-Type: application/json" \
  -d '{"name":"Copy leaders"}' "https://terminal.raiden.wtf/api/wallet-groups"
{ "id": "grp_5f3a…", "name": "Copy leaders", "count": 0, "members": [] }

curl -X POST -H "X-API-Key: $RAIDEN_KEY" -H "Content-Type: application/json" \
  -d '{"wallets":["8psN…VRtf","Cb3f…g9rE"]}' \
  "https://terminal.raiden.wtf/api/wallet-groups/grp_5f3a…/wallets"
{ "ok": true, "added": 2 }

Adds are idempotent and a group holds up to 1,000 wallets. The stream resolves the group into a wallet snapshot when it is created or updated — so after editing the roster later (drop a decaying leader, add a new one), re-issue a PUT on the stream to re-resolve the list. The sub token stays the same, so the bot itself never changes.

Step 2 — Create the stream

One call creates a push subscription for the swaps kind, filtered to your group. The response contains the opaque token you connect with:

curl -X POST -H "X-API-Key: $RAIDEN_KEY" -H "Content-Type: application/json" \
  -d '{"name":"copy-leaders","kinds":["swaps"],"wallet_list_id":"grp_5f3a…"}' \
  "https://terminal.raiden.wtf/api/ws-subscriptions"
{
  "id": 3, "token": "sub_a1b2c3…", "name": "copy-leaders", "active": true,
  "kinds": ["swaps"], "wallet_list_id": "grp_5f3a…",
  "wallets": ["8psN…VRtf", "Cb3f…g9rE"]
}

Accounts get 3 streams by default — enough to split leaders by strategy (one stream of scalpers, one of holders) and size the mirror differently per stream. The full stream system — all five kinds, filters, reconnection — is covered in the pump.fun WebSocket guide.

Step 3 — Connect and read swap events

Connect with your API key and the sub token; only events matching the stream's kinds and filters are pushed. Every fill by a tracked wallet arrives as a type: swap event:

wscat -c "wss://terminal.raiden.wtf/ws?key=$RAIDEN_KEY&sub=sub_a1b2c3…"
{
  "topic": "sub:3",
  "type": "swap",
  "time": "2026-07-13T09:01:22.400Z",
  "mint": "…pump",
  "trader": "8psN…VRtf",
  "is_buy": true,
  "sol_amount": 4200000000,
  "token_amount": 191833,
  "price": "0.0000041",
  "sig": "…",
  "slot": 351902144,
  "tip": 100000
}
Units: SOL amounts are lamports (1 SOL = 1e9); token_amount is in raw token base units. REST endpoints return large numbers as strings; the WebSocket swap event pushes them as numbers, with price as a string. A sol_amount of 4200000000 is a 4.2 SOL buy.

This event is the entire signal: who (trader), what (mint), which direction (is_buy), how big (sol_amount), at what price, in which slot. To copy trade pump.fun wallets you now need the part most bots get wrong — deciding whether to mirror.

Step 4 — The mirror decision layer

A raw mirror ("leader bought → I buy") is a machine for inheriting other people's exit liquidity. Three checks — each a single API call — separate a bot from a donation:

  • Trust gate. GET /tokens/{mint}/trust returns a live 0–100 score with a verdict: clean (≥65), caution (40–64), likely_rug (<40). Never mirror a buy into likely_rug — the leader may be an insider entering a position you cannot exit, or exit-scalping a dump you will hold. The score blends dev reputation, known rug-dumpers currently in the token, bundled supply, dev exit and wash trading; the full methodology is in the pump.fun rug check guide.
  • Position sizing. Size relative to the leader, never fixed. A leader's 0.2 SOL probe and its 15 SOL conviction entry are different signals; a flat mirror size treats them identically. A fraction of sol_amount with a hard cap keeps your exposure proportional to theirs.
  • Slippage guard. Between the leader's fill and your decision, the price moves. GET /tokens/{mint}/swaps?order=desc&limit=1 returns the latest indexed trade — compare its price to the event's. If the token already ran well past the leader's entry, you are not copying a trade, you are chasing one.
curl -H "X-API-Key: $RAIDEN_KEY" "https://terminal.raiden.wtf/api/tokens/…pump/trust"
{
  "mint": "…pump",
  "score": 45,
  "verdict": "caution",
  "capped": true,
  "cap_reason": "bundled",
  "signals": {
    "dev_score":       { "value": 0,    "comp": 8.4,  "known": true },
    "dumpers_holding": { "value": 3,    "comp": 15.0, "known": true },
    "bundled_pct":     { "value": 52.0, "comp": 0.0,  "known": true }, …
  }
}

Step 5 — Exits: the half most bots skip

The leader's sell is also a swap event — same stream, is_buy: false. A copy bot that mirrors entries but not exits is long everything its leaders have already left; mirroring the exit is three lines in the listener below. One caveat decides whether a leader is copyable at all: the fast flip. A sniper that lands in the same slot as the token creation has entered — and often exited — inside a window no external signal can beat; with human or API latency you would buy its exit. Before wiring a leader in, measure it: GET /versus scores 2–5 wallets on their shared tokens with per-fill slot and block-index timing, and the wallet-landing guide shows how to read the result. Leaders whose edge is selection, not landing — entries several slots after creation, holds measured in minutes — are the ones mirror trading solana style actually works on.

The full listener in Python

import asyncio, json, requests, websockets

BASE = "https://terminal.raiden.wtf/api"
KEY  = "YOUR_KEY"
H    = {"X-API-Key": KEY}
WS   = f"wss://terminal.raiden.wtf/ws?key={KEY}&sub=sub_a1b2c3…"

MIRROR_RATIO = 0.10        # copy 10% of the leader's size
MAX_LAMPORTS = 500_000_000 # hard cap per entry (0.5 SOL)
MAX_CHASE    = 0.15        # skip if price already ran >15% past the fill

positions = {}             # mint -> lamports we hold

def risk_gate(ev):
    """Return a sized buy in lamports, or None to skip."""
    mint = ev["mint"]
    # 1 · trust verdict — never mirror into a likely rug
    t = requests.get(f"{BASE}/tokens/{mint}/trust", headers=H).json()
    if t["verdict"] == "likely_rug":
        return None
    # 2 · slippage guard — latest indexed price vs the leader's fill
    last = requests.get(f"{BASE}/tokens/{mint}/swaps",
                        params={"order": "desc", "limit": 1},
                        headers=H).json()["data"]
    if last and float(last[0]["price"]) > float(ev["price"]) * (1 + MAX_CHASE):
        return None   # already ran away — chasing, not copying
    # 3 · size relative to the leader, capped
    return min(int(ev["sol_amount"] * MIRROR_RATIO), MAX_LAMPORTS)

def execute_buy(mint, lamports):
    # === YOUR STACK: build, sign, submit, confirm ===
    raise NotImplementedError

def execute_sell(mint):
    # === YOUR STACK ===
    raise NotImplementedError

async def listen():
    async with websockets.connect(WS) as ws:
        async for raw in ws:
            ev = json.loads(raw)
            if ev.get("type") != "swap":
                continue
            mint = ev["mint"]
            if ev["is_buy"]:
                if mint in positions:
                    continue            # already in — don't stack entries
                size = risk_gate(ev)
                if size:
                    execute_buy(mint, size)
                    positions[mint] = size
            elif mint in positions:      # the leader's SELL — mirror the exit
                execute_sell(mint)
                del positions[mint]

asyncio.run(listen())
Production notes: the gate uses blocking requests calls for clarity — move them to an async client so one slow lookup never delays the next event. Reconnect with backoff, and treat a restart as a state problem: your positions dict must survive it, or exits will be missed.

Why most copy bots lose

  • Latency vs the leader. You always enter after them and exit after them — both deltas cost money on every round trip. The bot is only viable on leaders whose profit per trade is much larger than your two deltas; that is a property you select for, not one you engineer around.
  • Entries without exits. Mirroring buys and "managing" sells manually means holding every position through the leader's exit dump. The sell signal is already in the stream — not using it is a choice.
  • Mirroring insiders. A wallet with a spectacular win rate on tokens that later rug is not skilled — it is early because it is connected. The trust gate catches the token side; vetting the leader's history catches the wallet side.
  • Copying the uncopyable. Same-slot snipers look best on every leaderboard and are precisely the wallets whose edge cannot be inherited. Measure first, follow second.

Everything above is public API surface — full request and response shapes are in the API reference, and the free invite-only beta keys described in the pump.fun API overview are enough to build and test the whole data half before a single SOL is at risk.

FAQ

Frequently asked questions

Does the Raiden API execute my copy trades?

No. Raiden is the data side only: it detects the leader's fill and pushes it to you in sub-second over WebSocket, and gives you the risk data to decide. Signing, submission and execution are your own stack — there is no trading API.

How do I avoid copying a wallet into a rug?

Gate every buy signal through the live trust score: GET /tokens/{mint}/trust returns a 0-100 score and a verdict — clean, caution or likely_rug. Never mirror a buy into a likely_rug token, whatever the leader does; insider entries are often positions you cannot exit.

Can I copy trade a same-slot sniper on pump.fun?

Not profitably. A sniper that lands in the same slot as the token creation has entered — and often exited — before any external signal can reach your bot. Measure a candidate leader's timing with /versus first: if its edge is same-slot landing, pick a slower leader.

Are the leader's sells streamed too?

Yes — every swap event carries is_buy. A leader's sell arrives on the same stream as its buys, so the listener that mirrors entries can mirror exits with three extra lines. Most losing copy bots copy only the entry.

How many wallets can one stream follow?

The swaps kind is filtered by a wallet group, and a group holds up to 1,000 wallets. Accounts get 3 streams by default, so a single account can follow thousands of leaders split across streams.

What does a swap event contain?

The trader, the mint, is_buy, sol_amount in lamports, token_amount in raw token units, the price, the slot, the transaction signature and the tip — enough to size and gate a mirror decision, with the mint ready for a trust lookup before you act.

Build on the same data

Private beta — free invite-only keys, full REST + WebSocket access, and the same firehose that powers the Terminal.