getReserves polling loop.
🛰️ Production Example: LiquidityRadar
The full open-source version of what you’ll build here. A zero-dependency engine, a clone-and-run CLI, and a deployable Cloudflare Worker with a public status page, webhook alerts, and flood protection. Fork it and point it at your own pools. View source →
How detection works
Every block in which a subscribed pool’s reserves change, the stream emits apool_reserves event with USD values already computed, including total_delta_usd, the signed dollar change of the whole pool in that block.
That one field is the entire trick:
| What happened on-chain | total_delta_usd | Why |
|---|---|---|
| Swap (any size) | ≈ $0 | One token’s reserve goes up, the other goes down. The dollar values cancel. |
| Liquidity added | Strongly positive | Both tokens’ reserves rise together. |
| Liquidity removed / drained | Strongly negative | Both tokens’ reserves fall together. Nothing cancels it. |
, the other -$45,930, net total_delta_usd: 84.37`. The same pool’s biggest one-block liquidity moves run six figures with nothing on the other side. A drain cannot disguise itself as trading volume.
Step 1: Watch a pool’s reserves live
No signup, no key. This streams real reserve changes for the largest USDC/WETH pool on Ethereum, one event per block in which its reserves changed:Live capture from production
total_reserve_usd: the pool’s current total liquidity in USDtotal_delta_usd: the signed USD change this block (the swap above nets to84.37, which is noise)block: where it happened, for receipts
Step 2: The detector
Watch up to 25 pools on a single connection by POSTing a JSON array. Events route back viarequest_id, the index of the subscription in your array. Mixing chains in one connection works.
The detection rule, two thresholds that scale together:
|total_delta_usd| ≥ MIN_USD, which ignores dust|total_delta_usd| / reserve_before ≥ MIN_PCT, which ignores moves the pool barely feels
node drain-detector.mjs / python drain_detector.py). At the default thresholds, alerts are rare by design. A 20% single-block move on a real pool is an event. Output from a live run with thresholds lowered for demonstration:
Live run (thresholds lowered to show output)
Step 3: Tune the thresholds
Why 20%? Because liquidity bots churn below it
Why 20%? Because liquidity bots churn below it
Major pools host JIT (just-in-time) liquidity bots that add and remove six-figure positions around individual swaps. We’ve measured them moving ±4% of an $8M pool every block, which is exactly the oscillation in the sample output above. Liquidity genuinely moves, so a detector genuinely fires; it’s just market plumbing, not an exit. A 20% bar clears that churn with a 5x margin. If you watch pools with heavy JIT activity and want a lower bar, net the deltas over a few consecutive blocks: JIT cycles cancel out, drains don’t.
Watching fresh, small pools (where rugs actually happen)
Watching fresh, small pools (where rugs actually happen)
For day-old pools in the 500k range, the defaults already work: a rug pull on a 70k negative delta at ~100% of the pool. If you want earlier partial-exit signals, drop
MIN_USD to ~$5k and keep MIN_PCT at 20%. On small pools the percentage gate does the real work.Finding pools worth watching
Finding pools worth watching
The REST filter endpoint finds rug-candidates programmatically: pools created in the last week with real activity:Feed the resulting pool IDs straight into
POOLS. One connection covers 25 of them.Step 4: Route alerts anywhere
Replaceconsole.log with a webhook call and the detector becomes a bot. Discord, for example:
pool_id + block, hold a per-pool cooldown, and cap sends per hour, because a multi-block drain fires once per block otherwise. LiquidityRadar implements that gate, plus reconnection with backoff, a persistent status page, and one-command Cloudflare deployment, if you’d rather fork than build.
Limits and errors worth knowing
- 25 subscriptions per POST connection, 10 concurrent streams per IP. Chunk bigger watchlists across connections.
- One invalid entry rejects the whole multiplexed connection. The stream sends an
errorevent naming the bad asset ("pool not found: …"), then closes. Validate addresses before subscribing. errorevents are not all equal: subscription problems (asset not found,unsupported chain) are permanent: fix the input, don’t retry. Capacity problems (ip stream limit exceeded) clear on their own, so retry those with backoff.- A
pingarrives every ~15s; if nothing arrives for 30s+, reconnect. See error handling for the full catalog.
FAQ
Can a rug pull hide from this detector?
Can a rug pull hide from this detector?
Not by swapping: swaps net to ~zero in
total_delta_usd regardless of size. An exit could in principle drip liquidity out in many small blocks below your thresholds, which is why percentage-of-pool matters more than absolute USD: 100 blocks of 1% exits still cross a 20% net threshold if you accumulate deltas over a window.Why SSE instead of polling getReserves or listening to Sync events?
Why SSE instead of polling getReserves or listening to Sync events?
Polling misses everything between polls. A pool can drain in one block, and a 30-second loop finds out after the money is gone. Decoding
Sync/Mint/Burn logs over a WebSocket node connection works but costs a node provider subscription and per-DEX decoding logic. The reserve stream pushes per-block deltas with USD values pre-computed, for every major DEX shape (Uniswap v2/v3/v4, Curve, Aerodrome, Raydium, Orca…), with no key.Is this free? What's the catch?
Is this free? What's the catch?
Free, no API key, no credit card. The published limits are the catch: 25 subscriptions per connection, 10 streams per IP.
Does it work on Solana / Base / my chain?
Does it work on Solana / Base / my chain?
The same code in this guide watches Ethereum, Base, and Solana on one connection. Any chain DexPaprika indexes works, see networks.
Next steps
Reserve Streaming Overview
The full feature guide: both subscription methods, payload schemas, limits.
Streaming Quick Start
Prices and reserves from zero in 10 minutes.
Multi-Pool API Reference
The POST /sse/reserves endpoint in full detail.
LiquidityRadar on GitHub
The production version: deployable worker, status page, alert gate.
Get support
Join Discord
Connect with our community and get real-time support.
Give Feedback
Share your experience and help us improve.