Code Playbook

Track Whale Wallet Movements Across 8 Chains in Python

One polling script gets you large wallet transfers and swaps across Ethereum, BSC, Avalanche, Polygon, Arbitrum, Base, Optimism, and Solana — no per-chain node or explorer key required.

~10 min readPython 3.8+Public endpoint, no key required

The Problem: 8 Chains, 8 Explorers

"Track whale wallets" sounds like one task until you try to do it across more than one chain. Ethereum whale tracking usually means an Etherscan-style API. BSC and Avalanche mean running or renting access to a full node, since their public RPC endpoints throttle hard under sustained polling. Polygon, Arbitrum, Base, and Optimism are technically EVM-compatible but each has its own bridge contracts, gas-token quirks, and preferred RPC providers. Solana is a different transaction model entirely — no simple "from/to/value" log, just program instructions you have to decode. And that's before you handle the noise problem: a single large wallet doing a multi-hop DEX route can throw off five or six near-duplicate log entries for what is really one economic action.

Building this yourself means standing up (or paying for) infrastructure per chain, writing a swap-detection heuristic per DEX router, and de-duplicating wash-loop spam before you can even ask "did a whale move $500k in the last hour?" Multiply that by 8 chains and it stops being a side project.

Smart Money API already does the chain-specific plumbing: it watches all 8 chains, applies DEX-router and swap-detection heuristics per chain, collapses near-duplicate rows into a single event with a repeat_count, and normalizes everything into one event schema regardless of which chain it came from. You call one endpoint and get a feed you can filter by chain, size, and event type.

That normalization matters more than it sounds like. A "transfer" on Ethereum and a "swap" on Solana are structurally different events at the RPC level, but from a monitoring standpoint they're the same question: did a large wallet move size, and where did it go? By the time the data reaches this endpoint, every chain's raw logs have already been reduced to the same five or six fields — chain, address, event type, token, USD size, and a transaction reference — so the script below works identically whether you point it at Ethereum, Base, or Solana.

Prerequisites

You'll need:

No account needed to follow along. GET /v1/whales/events and GET /v1/whales/summary are both public. Free tier is 200 calls/day if you do add a key.

The Endpoint

GET https://api.smartmoneyapi.com/v1/whales/events accepts these optional query parameters:

ParamValuesDefault
chainethereum, bsc, avalanche, polygon, arbitrum, base, optimism, solanaall 8
hours1–16824
significancelow, medium, high, criticalmedium
event_typetransfer_in, transfer_out, swapall
limit1–20050
offset0–100000

The response separates total (raw event count in the window) from distinct (post-dedup events actually returned) — the gap between them tells you how much wash-loop/multi-hop noise got collapsed before it reached you. Each event carries a repeat_count so you can see how many near-duplicate rows fed into it.

Python Whale Feed Script

This script polls the endpoint on a loop, filters to a minimum USD size client-side, and prints a clean one-line-per-event feed. It also includes a generator, iter_all_events, that pages through an entire lookback window using offset — useful for a one-shot "get everything in the last N hours" pull instead of the rolling poll.

python
#!/usr/bin/env python3 """ whale_feed.py — polls Smart Money API for large wallet transfers and swaps across 8 chains, filters by USD size, and prints a clean one-line-per-event feed. Public endpoint, no API key required. Set SMARTMONEY_API_KEY if you have one (recommended once you move past prototyping). """ import os import time import requests BASE_URL = "https://api.smartmoneyapi.com" ENDPOINT = "/v1/whales/events" MIN_USD = 250_000 # only show events at or above this size CHAIN = None # e.g. "ethereum", "solana"; None = all 8 chains HOURS = 1 # lookback window per poll POLL_SECONDS = 120 session = requests.Session() api_key = os.environ.get("SMARTMONEY_API_KEY") headers = {"X-API-Key": api_key} if api_key else {} def fetch_events(chain=None, hours=1, limit=50, offset=0, significance=None, event_type=None): params = {"hours": hours, "limit": limit, "offset": offset} if chain: params["chain"] = chain if significance: params["significance"] = significance if event_type: params["event_type"] = event_type resp = session.get(BASE_URL + ENDPOINT, params=params, headers=headers, timeout=10) resp.raise_for_status() return resp.json() def iter_all_events(chain=None, hours=24, page_size=200): """Generator that pages through every event in the window via offset.""" offset = 0 while True: data = fetch_events(chain=chain, hours=hours, limit=page_size, offset=offset) events = data.get("events", []) if not events: return for e in events: yield e offset += len(events) if offset >= data.get("total", 0): return def print_event(e): addr = e["address"] short_addr = f"{addr[:6]}...{addr[-4:]}" if len(addr) > 12 else addr dup = f" (x{e['repeat_count']})" if e.get("repeat_count", 1) > 1 else "" print( f"[{e['chain']:>10}] {e['event_type']:<13} {e['token']:<8} " f"${e['amount_usd']:>14,.0f} {short_addr} tx:{e['tx_hash'][:10]}...{dup}" ) def poll_once(): try: data = fetch_events(chain=CHAIN, hours=HOURS, limit=100) except requests.exceptions.Timeout: print("[warn] request timed out, will retry next cycle") return except requests.exceptions.RequestException as e: print(f"[error] request failed: {e}") return events = data.get("events", []) big_events = [e for e in events if e["amount_usd"] >= MIN_USD] print(f"[{time.strftime('%H:%M:%S')}] {data['distinct']} distinct events " f"({data['total']} raw) in last {data['hours']}h — {len(big_events)} above ${MIN_USD:,}") for e in sorted(big_events, key=lambda x: -x["amount_usd"]): print_event(e) def main(): print(f"Starting whale feed (chain={CHAIN or 'all 8'}, min ${MIN_USD:,}, " f"polling every {POLL_SECONDS}s)") while True: poll_once() time.sleep(POLL_SECONDS) if __name__ == "__main__": main()

JavaScript / Node.js Variant

The same single-poll filter/print logic in Node.js (18+) using the built-in fetch — no npm dependencies.

javascript
// whale_feed.js — Node.js, no external dependencies // (uses the global fetch available in Node 18+) const BASE_URL = "https://api.smartmoneyapi.com"; const ENDPOINT = "/v1/whales/events"; const MIN_USD = 250000; const CHAIN = null; // e.g. "ethereum", "solana"; null = all 8 chains const HOURS = 1; async function fetchEvents({ chain = null, hours = 1, limit = 50, offset = 0 } = {}) { const params = new URLSearchParams({ hours, limit, offset }); if (chain) params.set("chain", chain); const headers = {}; if (process.env.SMARTMONEY_API_KEY) { headers["X-API-Key"] = process.env.SMARTMONEY_API_KEY; } const resp = await fetch(`${BASE_URL}${ENDPOINT}?${params}`, { headers, signal: AbortSignal.timeout(10000), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); return resp.json(); } function printEvent(e) { const short = e.address.length > 12 ? `${e.address.slice(0, 6)}...${e.address.slice(-4)}` : e.address; const dup = e.repeat_count > 1 ? ` (x${e.repeat_count})` : ""; console.log( `[${e.chain.padStart(10)}] ${e.event_type.padEnd(13)} ${e.token.padEnd(8)} ` + `$${e.amount_usd.toLocaleString()} ${short} tx:${e.tx_hash.slice(0, 10)}...${dup}` ); } async function pollOnce() { let data; try { data = await fetchEvents({ chain: CHAIN, hours: HOURS, limit: 100 }); } catch (err) { console.error("[error]", err.message); return; } const big = data.events.filter((e) => e.amount_usd >= MIN_USD); console.log(`${data.distinct} distinct events (${data.total} raw) in last ${data.hours}h — ${big.length} above $${MIN_USD.toLocaleString()}`); big.sort((a, b) => b.amount_usd - a.amount_usd).forEach(printEvent); } pollOnce();
Want to pipe this into Telegram or Discord instead of a terminal?

The response shape is the same either way — swap the print_event() call for a webhook POST and you have an alert bot. A free account is enough to prototype this end-to-end.

Create a free account →

Expected Output

A poll of the public endpoint (no filters, default window) returns JSON shaped like this (one event shown; a real response returns up to limit):

json
{ "events": [ { "chain": "ethereum", "address": "0xabc...", "event_type": "swap", "token": "USDC", "amount": 500000.0, "amount_usd": 500000.0, "tx_hash": "0x...", "block": 19000123, "timestamp": 1753300000, "to_address": "0xdef...", "significance": "high", "repeat_count": 1 } ], "count": 50, "total": 812, "distinct": 50, "offset": 0, "limit": 50, "hours": 24, "chain": "all", "updated": 1753300000 }

The Python script would print that row as something like: [ ethereum] swap USDC $ 500,000 0xabc12...9def tx:0x1234567890... — and would only show it at all if MIN_USD is at or below 500,000.

What to Build Next

Once the feed above is running, a few directions worth taking it:

Full field reference for whale events, the summary endpoint, and per-chain notes are documented at /docs. More playbooks like this one are collected at /resources/playbooks.

Skip the 8 node integrations

One API key gets you deduplicated whale events across 8 chains, plus derivatives, on-chain, and options data on the same key.

See plans and limits
Start free — 200 calls/day, no card

Get live whale flow, funding, open interest and on-chain data across 3 exchanges from one API. Free tier, no credit card, upgrade any time.

Start free →
Try the live API console → (no account needed)