Streaming
WebSocket: swaps
Real-time swap stream over WebSocket. Subscribe by pool, get pushes as trades happen.
WSS
The swaps WebSocket is the easiest way to get real-time DEX trades into a browser, dashboard, or low-volume server. For higher throughput, see gRPC. For live OHLCV bars, see OHLCV candles. Pump.fun mint / graduation events stream from this same socket — join the
Authenticate either via Bearer header (server) or query string (browser):
You’ll receive:
Then
A few things worth flagging:
A burn carries a different field set. There is no
(or
The global
The
A
A
For production hardening (resubscribe state, gap-filling via REST, idle pings), see Reconnect & backpressure.
latest, graduating, and graduated discovery rooms.
When to use WebSocket
Endpoint and auth
Subscribe
After the connection is up, send asubscribe message. Filters are optional — empty filters mean “every swap on every pool”.
swap messages flow as trades happen.
Supported filters
All filters are optional and combine as AND. Set tonull or omit to disable.
Wire format gotchas
Aswap frame looks like this (captured live from meteora_damm_v2):
swap_typeis the string"buy"or"sell"(lowercase). Notis_buy. (is_buyis only a filter key, not a field on the event.) The REST/swaps*endpoints useis_buy(bool) instead — these two surfaces are intentionally different.dexis the string name here. On the REST/swaps*endpointsdexis an integer ID. Same enum, different encoding.timestampis epoch seconds. On REST/swaps*it’s epoch milliseconds; on REST candles it’s ISO 8601.- There is no
pricefield. Usepool_price(decimals-adjusted mid after the swap) or compute fill price client-side:price = sol_amount / token_amount, accounting forbase_decimalsandquote_decimals. - Amounts are integers in base units.
sol_amountis in lamports (1 SOL = 1e9).token_amountis in token base units; divide by10 ** quote_decimalsto get the human-readable amount. pool_addressis what we elsewhere callpair_address— same concept (the on-chain pool/LP account). The swaps stream keys it aspool_address; REST and the OHLCV WebSocket key the same account aspair_address.maker_tagscarries the wallet-intel classification of the trader at swap time (sniper,insider,whale, …). Useful for live alerting on smart-money buys.- Nullable fields.
virtual_*_reservesare non-null only on pump.fun.fee,creator_fee,price_impact_bps,cashbackcome back null on protocols that don’t report them — fall back to the_bpssiblings.
Pool events (LP add/remove + burns)
The same/ws/swaps socket also carries non-swap pool activity — LP deposits, LP withdrawals, token burns, and a server-classified rug signal. An unfiltered connection (or one filtered only by tokens/pools) receives these frames interleaved with swaps. This is the real-time liquidity-removal push that lets a bot react to a rug in the same second it lands, instead of polling.
lp_withdraw is the live rug / LP-removal signal. A large lp_withdraw is liquidity leaving the pool the instant it happens. rug_detected is the classified version — the server applies a drain threshold and a graduation-exclusion gate and tells you RUGGED vs LIQUIDITY_DRAINING so you don’t have to.Filter-mode frame
In filter-mode (the default — the mode you get after sending asubscribe message), pool events arrive with a top-level type: "pool_event" and an event_type discriminator. All four pool-event kinds share type: "pool_event" — demux on event_type.
lp_withdraw / lp_deposit (LpEvent) fields
There is no buy/sell side and no price field on a pool event — it’s a liquidity action, not a trade.
token_burn (BurnRecord) — a different shape
A burn carries a different field set. There is no pool_address and no base/quote amount:
rug_detected (RugRecord) — the classified signal
rug_detected is a server-side classified rug / drain signal, derived from an lp_withdraw or lp_burn. It fires on any non-pumpfun venue when a real LP drain crosses the drain threshold outside the graduation/migration window — i.e. on a genuine AMM/CLMM pool, not the pump.fun bonding curve (which has no fungible LP to pull). Pump graduation-migrations are excluded: they move liquidity but aren’t rugs, so they never produce a rug_detected.
Filter scope for pool events
In filter-mode, onlytokens and pools apply to pool events. The dexes, min_sol, max_sol, traders, is_buy, and wallet_tags filters are swap-only and have no effect on pool-event delivery. A default (empty) subscription delivers the full pool-event firehose immediately on connect — you don’t have to subscribe to start receiving them.
Room-mode delivers the same events with a different shape
If you use rooms (ajoin message) instead of filters, the same pool events arrive wrapped in a room message. Join a transaction room:
transaction:<mint>:<pool> to scope to one pool). Events then arrive as:
transaction:{mint} rooms interleave swaps, pool events (lp_deposit/lp_withdraw/token_burn), and rug_detected — demux on type / event_type. Room-mode is mode-exclusive with filter-mode on a given connection: the first subscribe locks the socket into filter-mode, and the first join locks it into room-mode. Pick one per connection.
Rooms reference
Every valid/ws/swaps room is listed below. Join with { "type": "join", "room": "<room>" }; the server replies { "type": "joined", "room": "<room>" } (and { "type": "left", "room": "<room>" } after a leave). An unknown or malformed room is rejected with an error frame (unknown_room / malformed_room). Joining a room that isn’t the discovery rooms — i.e. any pnl:* room — requires the Pro or Enterprise tier; lower tiers get a tier_required error.
Per-tier join caps apply: Free 5, Developer 25, Pro 100, Enterprise unlimited.
Unless noted, every room delivers the room-mode wrapper — { "type": "message", "room": "<room>", "data": { … } } — and you read the real payload from data (the dual-frame trap above).
pnl:* rooms push an instant snapshot on join. The other rooms only start delivering on the next matching event; the PnL rooms seed the joining connection with its current book immediately (then keep it live).The global rugs room
rugs room fans out the full RugRecord for every rug_detected — the same object the rug_detected field table describes, including reason and graduated (and the dex venue id). This is a superset of what the REST GET /rugs feed (under Rugs in the API reference) returns — REST omits reason and graduated (they live only on the NATS wire), so use the room when you need them live:
A safety:{mint} frame
A pnl:{wallet}:summary frame
Per-DEX coverage matrix
LP and rug events are live for every AMM venue. The only two gaps are the bonding-curve venues, which have no fungible LP to add, remove, or drain (see Data coverage).Historical / replay companions
For backfill and gap-filling, the same events are queryable over REST and MCP:- REST:
GET /pool-events?token=<mint>&limit=N&before=<ts_ms>— paginated LP/burn history (cursor is epoch ms). - REST:
GET /rugs(under Rugs in the API reference) — recentrug_detectedevents across all venues (7-day window, paged; requires an API key).drained_pctis a 0..1 fraction andlp_sol_pulledis already in SOL — butreason/graduatedare WS-only (use therugsroom for those). - REST:
GET /tokens/{mint}/rugs— rug history for a single mint. - MCP: the
list_pool_eventstool wraps the same pool-events endpoint.
Edge:
/ws/swaps is already routed at the OVH edge — both lp_withdraw and rug_detected ride this same socket. No new endpoint or connection is needed.Complete TypeScript client
Server messages
Programmatic spec
If you’re generating clients or feeding the contract into an LLM/codegen pipeline, every WebSocket channel —/ws/swaps and /ws/ohlcv — is described as a single AsyncAPI 3.0 document. All message schemas and live-captured example payloads are included.
