> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dexploit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket: swaps

> Real-time swap stream over WebSocket. Subscribe by pool, get pushes as trades happen.

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](/streaming/grpc). For live OHLCV bars, see [OHLCV candles](/streaming/ohlcv). Pump.fun mint / graduation events stream from this same socket — join the `latest`, `graduating`, and `graduated` discovery rooms.

## When to use WebSocket

| Need                                     | Use                                                                |
| ---------------------------------------- | ------------------------------------------------------------------ |
| Browser app, dashboard                   | WebSocket                                                          |
| Low-to-mid throughput server             | WebSocket                                                          |
| Highest throughput, schema-typed clients | [gRPC](/streaming/grpc)                                            |
| Live OHLCV bars                          | [WebSocket: OHLCV](/streaming/ohlcv)                               |
| Pump.fun mint / migration events         | This socket — join the `latest` / `graduating` / `graduated` rooms |
| One-shot historical data                 | [REST](/quickstart)                                                |

## Endpoint and auth

```
wss://ws.dexploit.dev/ws/swaps
```

Authenticate either via Bearer header (server) or query string (browser):

```
Authorization: Bearer ohlcv_live_sk_<your_key>

# or, for browsers where setting headers is awkward:
wss://ws.dexploit.dev/ws/swaps?api_key=ohlcv_live_sk_<your_key>
```

## Subscribe

After the connection is up, send a `subscribe` message. Filters are optional — empty filters mean "every swap on every pool".

```json theme={null}
{
  "type": "subscribe",
  "filters": {
    "pools": ["<pool_address>"]
  }
}
```

You'll receive:

```json theme={null}
{ "type": "subscribed", "message": "Subscribed: pools" }
```

Then `swap` messages flow as trades happen.

### Supported filters

All filters are optional and combine as AND. Set to `null` or omit to disable.

| Key       | Type                | Notes                                                                                                                                                    |
| --------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pools`   | `string[]`          | Filter by pool address (this is the most common filter — see [Pairs vs tokens](/concepts/pairs-vs-tokens)).                                              |
| `tokens`  | `string[]`          | Filter by token mint.                                                                                                                                    |
| `traders` | `string[]`          | Filter by trader wallet.                                                                                                                                 |
| `dexes`   | `string[]`          | Values: `pumpfun`, `pumpswap`, `raydium_amm`, `raydium_clmm`, `raydium_cpmm`, `orca`, `meteora_damm_v2`, `meteora_dbc`, `meteora_dlmm`, `meteora_pools`. |
| `min_sol` | `number` (lamports) | Minimum SOL amount. 1 SOL = 1,000,000,000 lamports.                                                                                                      |
| `max_sol` | `number` (lamports) | Maximum SOL amount.                                                                                                                                      |
| `is_buy`  | `boolean`           | `true` for buys only, `false` for sells only.                                                                                                            |

## Wire format gotchas

A `swap` frame looks like this (captured live from `meteora_damm_v2`):

```json theme={null}
{
  "type": "swap",
  "signature": "3rjMq3C7Zp4eDDRtKwEBmVFXErFt4twvUVryNerBgkrxqs53XhAgL3LqDvfiZjDLNHEbwkbkQMTwfSjD1BvSboES",
  "slot": 420984167,
  "timestamp": 1779279483,
  "dex": "meteora_damm_v2",
  "swap_type": "sell",
  "trader": "rk4jdi7srE6VKtxPeCKnqpEe7uJdZLLdpv1qAPK8LRd",
  "token_mint": "8ZiqYN6rEm6PapbhCm5wWWzkRjsYyixgqW3K2384D1C3",
  "pool_address": "DT75gs9fygQxi2Aq2ST1jz9C6FTNcbBjpgSpdDpMDdLX",
  "amount_in": 350832255298,
  "amount_out": 729873626,
  "sol_amount": 729873626,
  "token_amount": 350832255298,
  "virtual_sol_reserves": null,
  "virtual_token_reserves": null,
  "real_sol_reserves": 91985332910,
  "real_token_reserves": 44561454812398,
  "lp_fee_bps": 1,
  "protocol_fee_bps": 0,
  "fee": null,
  "creator_fee": null,
  "creator_fee_bps": 0,
  "price_impact_bps": null,
  "base_decimals": 9,
  "quote_decimals": 6,
  "pool_price": 2.0642354092175554e-6,
  "transfer_fee_in": 0,
  "transfer_fee_out": 0,
  "cashback": null,
  "cashback_bps": 0,
  "maker_tags": []
}
```

A few things worth flagging:

* **`swap_type`** is the string `"buy"` or `"sell"` (lowercase). Not `is_buy`. (`is_buy` is only a *filter* key, not a field on the event.) The REST `/swaps*` endpoints use `is_buy` (bool) instead — these two surfaces are intentionally different.
* **`dex` is the string name** here. On the REST `/swaps*` endpoints `dex` is an integer ID. Same enum, different encoding.
* **`timestamp` is epoch seconds.** On REST `/swaps*` it's epoch milliseconds; on REST candles it's ISO 8601.
* **There is no `price` field.** Use `pool_price` (decimals-adjusted mid after the swap) or compute fill price client-side: `price = sol_amount / token_amount`, accounting for `base_decimals` and `quote_decimals`.
* **Amounts are integers in base units.** `sol_amount` is in **lamports** (1 SOL = 1e9). `token_amount` is in token base units; divide by `10 ** quote_decimals` to get the human-readable amount.
* **`pool_address`** is what we elsewhere call `pair_address` — same concept (the on-chain pool/LP account). The swaps stream keys it as `pool_address`; REST and the [OHLCV WebSocket](/streaming/ohlcv) key the *same* account as `pair_address`.
* **`maker_tags`** carries the wallet-intel classification of the trader at swap time (`sniper`, `insider`, `whale`, …). Useful for live alerting on smart-money buys.
* **Nullable fields.** `virtual_*_reserves` are non-null only on pump.fun. `fee`, `creator_fee`, `price_impact_bps`, `cashback` come back null on protocols that don't report them — fall back to the `_bps` siblings.

## 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.

<Note>
  **`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.
</Note>

### Filter-mode frame

In filter-mode (the default — the mode you get after sending a [`subscribe`](#subscribe) 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`.**

```json theme={null}
{
  "type": "pool_event",
  "event_type": "lp_withdraw",
  "timestamp_ms": 1717000000000,
  "slot": 270123456,
  "signature": "5Qx…",
  "user": "9aB…",
  "pool_address": "DT7…",
  "token_mint": "8Zi…",
  "base_amount": 123456789,
  "quote_amount": 4200000000,
  "lp_token_amount": 987654321,
  "drained_pct": 0.97,
  "pool_base_reserve_after": 0,
  "pool_quote_reserve_after": 0,
  "lp_supply_after": 30000,
  "dex": 2
}
```

### `lp_withdraw` / `lp_deposit` (LpEvent) fields

| Field                      | Type          | Notes                                                                                                                                                                                      |
| -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `timestamp_ms`             | `i64`         | Epoch **milliseconds**. ⚠ The `swap` frame on this *same socket* uses `timestamp` in epoch **seconds** — a 1000× difference. Don't mix them.                                               |
| `slot`                     | `u64`         | Solana slot.                                                                                                                                                                               |
| `signature`                | `string`      | Base58 transaction signature.                                                                                                                                                              |
| `user`                     | `string`      | Wallet that added/removed the LP.                                                                                                                                                          |
| `pool_address`             | `string`      | On-chain pool / LP account (elsewhere called `pair_address`).                                                                                                                              |
| `token_mint`               | `string`      | Token mint.                                                                                                                                                                                |
| `base_amount`              | `u64`         | Memecoin side, **atomic** units. Divide by `10**decimals` (\~6 for pump tokens). ⚠ Decimals are **not** carried on the event.                                                              |
| `quote_amount`             | `u64`         | The SOL / WSOL side in **lamports**, despite no `_sol` suffix. ⚠ Divide by `1e9` for SOL (the lamports-named-non-`_sol` trap).                                                             |
| `lp_token_amount`          | `u64`         | LP tokens, atomic. ⚠ Meteora DAMM v2 (`dex=7`) **saturates this to `u64::MAX`** on high-liquidity pools — use `base_amount` + `quote_amount` for value, never `lp_token_amount`.           |
| `drained_pct`              | `f32` (0..1)  | Fraction of pool liquidity removed by this event. Constant-product pools (PumpSwap, RaydiumAmm) use the LP-supply ratio; Meteora CL uses the SOL-reserve ratio. `0.0` when not computable. |
| `pool_base_reserve_after`  | `u64`         | Post-event pool reserve, base side, atomic. `0` when N/A.                                                                                                                                  |
| `pool_quote_reserve_after` | `u64`         | Post-event pool reserve, quote side, **lamports**. `0` when N/A.                                                                                                                           |
| `lp_supply_after`          | `u64`         | Post-event total LP supply. Constant-product only — `0` for Meteora.                                                                                                                       |
| `dex`                      | `int` (Enum8) | `2`=PumpSwap, `3`=RaydiumAmm, `4`=RaydiumClmm, `5`=RaydiumCpmm, `6`=Orca, `7`=MeteoraDammV2, `9`=MeteoraDlmm.                                                                              |

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:

```json theme={null}
{
  "type": "pool_event",
  "event_type": "token_burn",
  "timestamp_ms": 1717000001000,
  "slot": 270123457,
  "signature": "3rj…",
  "user": "9aB…",
  "token_mint": "8Zi…",
  "amount_atomic": 999999999999
}
```

| Field           | Type     | Notes                                     |
| --------------- | -------- | ----------------------------------------- |
| `timestamp_ms`  | `i64`    | Epoch **milliseconds** (same as LpEvent). |
| `slot`          | `u64`    | Solana slot.                              |
| `signature`     | `string` | Base58 transaction signature.             |
| `user`          | `string` | Wallet that burned.                       |
| `token_mint`    | `string` | Token mint.                               |
| `amount_atomic` | `u64`    | Burned amount in **atomic** units.        |

### `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`.

```json theme={null}
{
  "type": "pool_event",
  "event_type": "rug_detected",
  "timestamp_ms": 1717000002000,
  "slot": 270123458,
  "signature": "5Qx…",
  "pool_address": "DT7…",
  "token_mint": "8Zi…",
  "dex": 2,
  "lp_sol_pulled": 4200000000,
  "drained_pct": 0.97,
  "classification": "RUGGED",
  "reason": "lp_withdraw",
  "graduated": true
}
```

| Field            | Type          | Notes                                                                                                                                                                                                                          |
| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `timestamp_ms`   | `i64`         | Epoch **milliseconds**.                                                                                                                                                                                                        |
| `slot`           | `u64`         | Solana slot.                                                                                                                                                                                                                   |
| `signature`      | `string`      | Base58 transaction signature of the triggering withdraw/burn.                                                                                                                                                                  |
| `pool_address`   | `string`      | On-chain pool / LP account.                                                                                                                                                                                                    |
| `token_mint`     | `string`      | Token mint.                                                                                                                                                                                                                    |
| `dex`            | `int` (Enum8) | The venue the drain happened on — any non-`pumpfun` AMM/CLMM (e.g. `2`=PumpSwap, `3`=RaydiumAmm, `4`=RaydiumClmm, `5`=RaydiumCpmm, `6`=Orca, `7`=MeteoraDammV2, `9`=MeteoraDlmm).                                              |
| `lp_sol_pulled`  | `u64`         | SOL liquidity removed, in **lamports**. Divide by `1e9` for SOL.                                                                                                                                                               |
| `drained_pct`    | `f32` (0..1)  | Fraction of pool liquidity removed.                                                                                                                                                                                            |
| `classification` | `string`      | `"RUGGED"` when >95% of liquidity was removed; `"LIQUIDITY_DRAINING"` when 50–95%.                                                                                                                                             |
| `reason`         | `string`      | `"lp_withdraw"` or `"lp_burn"` — which action triggered the classification.                                                                                                                                                    |
| `graduated`      | `bool`        | Whether the mint had already graduated/migrated off the pump.fun bonding curve when the drain landed. The graduation/migration window itself is excluded, so a `rug_detected` is a post-migration (or never-pumped) AMM drain. |

### Filter scope for pool events

In filter-mode, **only `tokens` 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** (a `join` message) instead of filters, the same pool events arrive **wrapped** in a room message. Join a transaction room:

```json theme={null}
{ "type": "join", "room": "transaction:<mint>" }
```

(or `transaction:<mint>:<pool>` to scope to one pool). Events then arrive as:

```json theme={null}
{
  "type": "message",
  "room": "transaction:<mint>",
  "data": { "event_type": "lp_withdraw", "timestamp_ms": 1717000000000, "...": "..." }
}
```

<Warning>
  **Dual-frame trap.** The *same* pool event has **two on-wire shapes** depending on mode. In filter-mode it's top-level (`type: "pool_event"`, `event_type: …`). In room-mode the `data` object carries `event_type` but **not** a top-level `type`. Read `event_type` from `msg` in filter-mode and from `msg.data` in room-mode.
</Warning>

`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](#room-mode-delivers-the-same-events-with-a-different-shape) above).

| Room                | Join string                 | What it carries                                                                                                                                                                                   |
| ------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Latest mints        | `latest`                    | New-mint discovery — one `data` per `mints.observed` event as a token is first seen.                                                                                                              |
| Graduating          | `graduating`                | Tokens crossing the bonding-curve completion threshold (the `mints.completing` lifecycle event).                                                                                                  |
| Graduated           | `graduated`                 | Tokens that migrated to a tradeable AMM pool (the `mints.graduated` lifecycle event).                                                                                                             |
| Rugs (global)       | `rugs`                      | Every `rug_detected` across all venues — the **full `RugRecord`** (`classification`, `drained_pct`, `lp_sol_pulled` in **lamports**, `dex`, plus `reason` + `graduated`). See below.              |
| Token safety        | `safety:{mint}`             | The mint's full actor/safety snapshot (holder/sniper/insider/bundler counts, top-holder %, dev-held %, …), re-pushed on-change (\~3–5 s coalesced).                                               |
| Price bars          | `price:{addr}:{tf}`         | Live OHLCV bar updates for `addr` (a **mint OR pool**) at timeframe `tf`. `tf` ∈ `1s 30s 1m 5m 15m 1h 4h 1d`. A mint-keyed room interleaves the mint's pairs (each frame carries `pair_address`). |
| Wallet trades       | `wallet:{wallet}`           | Every `swap` where `wallet` is the trader (a live per-wallet trade tape).                                                                                                                         |
| Transaction (token) | `transaction:{mint}`        | Interleaved `swap` + `lp_deposit`/`lp_withdraw`/`token_burn` + `rug_detected` for one mint (all pools).                                                                                           |
| Transaction (pool)  | `transaction:{mint}:{pool}` | Same as above, scoped to a single `pool`.                                                                                                                                                         |
| Wallet PnL          | `pnl:{wallet}`              | Per-position PnL frames for `wallet` — one frame per holding, re-pushed as the wallet trades or spot moves. **Pro+.**                                                                             |
| Position PnL        | `pnl:{wallet}:{token}`      | A single `(wallet, token)` position, glass-box (with `realized_breakdown`). **Pro+.** A token literally named `summary` is not addressable.                                                       |
| Wallet PnL summary  | `pnl:{wallet}:summary`      | The wallet-level rollup (`realized_sol`, `unrealized_sol`, `cost_basis_sol`, `open_positions`, `closed_positions`, `positions[]`). **Pro+.**                                                      |

<Note>
  **`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).
</Note>

#### The global `rugs` room

```json theme={null}
{ "type": "join", "room": "rugs" }
```

The `rugs` room fans out the **full `RugRecord`** for every `rug_detected` — the same object the [`rug_detected` field table](#rug-detected-rugrecord-the-classified-signal) 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](/api-reference)) returns — REST **omits `reason` and `graduated`** (they live only on the NATS wire), so use the room when you need them live:

```json theme={null}
{
  "type": "message",
  "room": "rugs",
  "data": {
    "event_type": "rug_detected",
    "token_mint": "8Zi…",
    "pool_address": "DT7…",
    "dex": 7,
    "classification": "RUGGED",
    "drained_pct": 0.97,
    "lp_sol_pulled": 4200000000,
    "reason": "lp_withdraw",
    "graduated": true
  }
}
```

#### A `safety:{mint}` frame

```json theme={null}
{
  "type": "message",
  "room": "safety:8Zi…",
  "data": { "mint": "8Zi…", "holder_count": 412, "top10_pct": 0.38, "dev_held_pct": 0.0, "sniper_count": 9, "insider_count": 3, "bundler_count": 0, "...": "..." }
}
```

#### A `pnl:{wallet}:summary` frame

```json theme={null}
{
  "type": "message",
  "room": "pnl:9aB…:summary",
  "data": {
    "wallet": "9aB…",
    "pnl_mode": "adjusted",
    "realized_sol": 12.34,
    "unrealized_sol": -1.2,
    "cost_basis_sol": 40.0,
    "open_positions": 5,
    "closed_positions": 18,
    "positions": [ { "mint": "8Zi…", "realized_sol": 3.1, "unrealized_sol": 0.4, "...": "..." } ]
  }
}
```

### 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](/concepts/coverage)).

| DEX           | `dex` | LP / rug events |
| ------------- | ----- | --------------- |
| PumpSwap      | `2`   | ✅ Live          |
| RaydiumAmm v4 | `3`   | ✅ Live          |
| RaydiumClmm   | `4`   | ✅ Live          |
| RaydiumCpmm   | `5`   | ✅ Live          |
| Orca          | `6`   | ✅ Live          |
| MeteoraDammV2 | `7`   | ✅ Live          |
| MeteoraDlmm   | `9`   | ✅ Live          |
| MeteoraDbc    | `8`   | ❌ Not emitted   |
| MeteoraPools  | `10`  | ❌ Not emitted   |

<Warning>
  **Absence is not safety on the two uncovered venues.** `meteora_dbc` (`8`) is a
  **bonding curve** — there's no fungible LP, so the curve *is* the liquidity and
  there's nothing to `lp_withdraw`. `meteora_pools` (`10`) routes liquidity through
  **dynamic vaults shared across many pools**, so a per-pool LP add/remove isn't
  derivable from a swap (see [Data coverage](/concepts/coverage)). On those two,
  *not seeing* an LP-removal or `rug_detected` event is **not** proof a pool is
  safe.
</Warning>

### 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>`](/api-reference) — paginated LP/burn history (cursor is epoch **ms**).
* REST: `GET /rugs` (under **Rugs** in the [API reference](/api-reference)) — recent `rug_detected` events across all venues (7-day window, paged; requires an API key). `drained_pct` is a **0..1** fraction and `lp_sol_pulled` is **already in SOL** — but `reason`/`graduated` are **WS-only** (use the [`rugs` room](#the-global-rugs-room) for those).
* REST: `GET /tokens/{mint}/rugs` — rug history for a single mint.
* MCP: the `list_pool_events` tool wraps the same pool-events endpoint.

<Note>
  **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.
</Note>

## Complete TypeScript client

```typescript theme={null}
import WebSocket from 'ws'; // browser: use the global WebSocket

const URL = 'wss://ws.dexploit.dev/ws/swaps';
const API_KEY = 'ohlcv_live_sk_<your_key>';
const POOL = '<pool_address>';

let backoff = 1000;

function connect() {
  const ws = new WebSocket(URL, {
    headers: { Authorization: `Bearer ${API_KEY}` }
  });

  ws.on('open', () => {
    backoff = 1000; // reset on successful connect
    ws.send(JSON.stringify({
      type: 'subscribe',
      filters: { pools: [POOL] }
    }));
  });

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw.toString());
    switch (msg.type) {
      case 'subscribed':
        console.log('subscribed:', msg.message);
        break;
      case 'swap': {
        const sol = msg.sol_amount / 1e9;
        const tok = msg.token_amount / Math.pow(10, msg.quote_decimals ?? 6);
        const price = sol / tok;
        console.log(
          `${msg.swap_type.toUpperCase()} ${tok.toFixed(2)} @ ${price.toExponential(3)} SOL ` +
          `(${msg.dex}, sig ${msg.signature.slice(0, 8)}…)`
        );
        break;
      }
      case 'error':
        console.error('stream error:', msg.message);
        break;
    }
  });

  ws.on('close', () => {
    console.warn(`disconnected, retrying in ${backoff}ms`);
    setTimeout(connect, backoff);
    backoff = Math.min(backoff * 2, 60_000);
  });

  ws.on('error', (err) => {
    console.error('ws error:', err);
    // 'close' fires next; reconnect happens there.
  });
}

connect();
```

For production hardening (resubscribe state, gap-filling via REST, idle pings), see [Reconnect & backpressure](/streaming/reconnect-backpressure).

## Server messages

| `type`         | When                                                   |
| -------------- | ------------------------------------------------------ |
| `subscribed`   | After your `subscribe`.                                |
| `unsubscribed` | After your `unsubscribe`.                              |
| `swap`         | A trade matched your filters.                          |
| `pong`         | Reply to your `ping`.                                  |
| `error`        | Subscribe rejected, auth failed, or server-side error. |

## 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](https://github.com/1tzkaos/docs/blob/main/api-reference/asyncapi.json). All message schemas and live-captured example payloads are included.


## AsyncAPI

````yaml api-reference/asyncapi.json swaps
id: swaps
title: Raw swap stream
description: >-
  Every swap on every indexed Solana DEX, optionally filtered by pool, token,
  trader, dex, SOL amount, or direction. Send a `subscribe` message after
  connecting to set filters — by default, every swap flows through.
servers:
  - id: production
    protocol: wss
    host: ws.dexploit.dev
    bindings: []
    variables: []
address: /ws/swaps
parameters: []
bindings: []
operations:
  - &ref_16
    id: receiveSwapsSwap
    title: Receive swaps swap
    description: Swap event
    type: send
    messages:
      - &ref_30
        id: swap
        payload:
          - name: Swap event
            description: One executed swap on a Solana DEX, normalized across protocols.
            type: object
            properties:
              - name: const
                type: string
                description: swap
                required: false
              - name: signature
                type: string
                required: false
              - name: slot
                type: integer
                required: false
              - name: timestamp
                type: integer
                description: >-
                  Unix epoch **seconds** (REST candles use ISO; REST swaps use
                  ms — yes, all three are different).
                required: false
              - name: dex
                type: string
                description: >-
                  Lowercase snake_case DEX name. Note: on the WebSocket feed
                  `dex` is the string name, but on the REST `/swaps*` endpoints
                  it's an integer ID — these surfaces are intentionally
                  different.
                enumValues:
                  - pumpfun
                  - pumpswap
                  - raydium_amm
                  - raydium_clmm
                  - raydium_cpmm
                  - orca
                  - meteora_damm_v2
                  - meteora_dbc
                  - meteora_dlmm
                  - meteora_pools
                required: false
              - name: swap_type
                type: string
                description: >-
                  **Different from REST**: streaming uses `swap_type` (string),
                  REST uses `is_buy` (bool).
                enumValues:
                  - buy
                  - sell
                required: false
              - name: trader
                type: string
                required: false
              - name: token_mint
                type: string
                required: false
              - name: pool_address
                type: string
                required: false
              - name: amount_in
                type: integer
                description: >-
                  Base-unit amount of whichever side the trader sold. For buys,
                  this is the input SOL.
                required: false
              - name: amount_out
                type: integer
                required: false
              - name: sol_amount
                type: integer
                description: Lamports.
                required: false
              - name: token_amount
                type: integer
                description: Token base units; divide by `10**quote_decimals`.
                required: false
              - name: virtual_sol_reserves
                type: &ref_0
                  - integer
                  - 'null'
                description: Pump.fun bonding curve only — null elsewhere.
                required: false
              - name: virtual_token_reserves
                type: &ref_1
                  - integer
                  - 'null'
                required: false
              - name: real_sol_reserves
                type: integer
                required: false
              - name: real_token_reserves
                type: integer
                required: false
              - name: lp_fee_bps
                type: integer
                description: LP fee on this swap, in basis points.
                required: false
              - name: protocol_fee_bps
                type: integer
                required: false
              - name: fee
                type: &ref_2
                  - integer
                  - 'null'
                description: >-
                  Aggregate lamports paid in fees; null when the protocol breaks
                  it down per fee_bps field instead.
                required: false
              - name: creator_fee
                type: &ref_3
                  - integer
                  - 'null'
                required: false
              - name: creator_fee_bps
                type: integer
                required: false
              - name: price_impact_bps
                type: &ref_4
                  - integer
                  - 'null'
                required: false
              - name: base_decimals
                type: integer
                required: false
              - name: quote_decimals
                type: integer
                required: false
              - name: pool_price
                type: number
                description: Pool mid-price after the swap (decimals-adjusted).
                required: false
              - name: transfer_fee_in
                type: integer
                description: Token-2022 transfer fee paid on the input side.
                required: false
              - name: transfer_fee_out
                type: integer
                required: false
              - name: cashback
                type: &ref_5
                  - integer
                  - 'null'
                required: false
              - name: cashback_bps
                type: integer
                required: false
              - name: maker_tags
                type: array
                description: >-
                  Wallet-intel tags applied to the maker (e.g. `sniper`,
                  `insider`).
                required: false
                properties:
                  - name: item
                    type: string
                    required: false
        headers: []
        jsonPayloadSchema:
          type: object
          description: >-
            Streaming swap shape. Differs from the REST `Swap` schema — see the
            *Wire format gotchas* section in the WebSocket page.
          properties:
            type:
              const: swap
              x-parser-schema-id: <anonymous-schema-5>
            signature:
              type: string
              x-parser-schema-id: <anonymous-schema-6>
            slot:
              type: integer
              format: int64
              x-parser-schema-id: <anonymous-schema-7>
            timestamp:
              type: integer
              format: int64
              description: >-
                Unix epoch **seconds** (REST candles use ISO; REST swaps use ms
                — yes, all three are different).
              x-parser-schema-id: <anonymous-schema-8>
            dex: &ref_10
              type: string
              enum:
                - pumpfun
                - pumpswap
                - raydium_amm
                - raydium_clmm
                - raydium_cpmm
                - orca
                - meteora_damm_v2
                - meteora_dbc
                - meteora_dlmm
                - meteora_pools
              description: >-
                Lowercase snake_case DEX name. Note: on the WebSocket feed `dex`
                is the string name, but on the REST `/swaps*` endpoints it's an
                integer ID — these surfaces are intentionally different.
              x-parser-schema-id: Dex
            swap_type:
              type: string
              enum:
                - buy
                - sell
              description: >-
                **Different from REST**: streaming uses `swap_type` (string),
                REST uses `is_buy` (bool).
              x-parser-schema-id: <anonymous-schema-9>
            trader:
              type: string
              x-parser-schema-id: <anonymous-schema-10>
            token_mint:
              type: string
              x-parser-schema-id: <anonymous-schema-11>
            pool_address:
              type: string
              x-parser-schema-id: <anonymous-schema-12>
            amount_in:
              type: integer
              format: int64
              description: >-
                Base-unit amount of whichever side the trader sold. For buys,
                this is the input SOL.
              x-parser-schema-id: <anonymous-schema-13>
            amount_out:
              type: integer
              format: int64
              x-parser-schema-id: <anonymous-schema-14>
            sol_amount:
              type: integer
              format: int64
              description: Lamports.
              x-parser-schema-id: <anonymous-schema-15>
            token_amount:
              type: integer
              format: int64
              description: Token base units; divide by `10**quote_decimals`.
              x-parser-schema-id: <anonymous-schema-16>
            virtual_sol_reserves:
              type: *ref_0
              format: int64
              description: Pump.fun bonding curve only — null elsewhere.
              x-parser-schema-id: <anonymous-schema-17>
            virtual_token_reserves:
              type: *ref_1
              format: int64
              x-parser-schema-id: <anonymous-schema-18>
            real_sol_reserves:
              type: integer
              format: int64
              x-parser-schema-id: <anonymous-schema-19>
            real_token_reserves:
              type: integer
              format: int64
              x-parser-schema-id: <anonymous-schema-20>
            lp_fee_bps:
              type: integer
              description: LP fee on this swap, in basis points.
              x-parser-schema-id: <anonymous-schema-21>
            protocol_fee_bps:
              type: integer
              x-parser-schema-id: <anonymous-schema-22>
            fee:
              type: *ref_2
              format: int64
              description: >-
                Aggregate lamports paid in fees; null when the protocol breaks
                it down per fee_bps field instead.
              x-parser-schema-id: <anonymous-schema-23>
            creator_fee:
              type: *ref_3
              format: int64
              x-parser-schema-id: <anonymous-schema-24>
            creator_fee_bps:
              type: integer
              x-parser-schema-id: <anonymous-schema-25>
            price_impact_bps:
              type: *ref_4
              x-parser-schema-id: <anonymous-schema-26>
            base_decimals:
              type: integer
              x-parser-schema-id: <anonymous-schema-27>
            quote_decimals:
              type: integer
              x-parser-schema-id: <anonymous-schema-28>
            pool_price:
              type: number
              description: Pool mid-price after the swap (decimals-adjusted).
              x-parser-schema-id: <anonymous-schema-29>
            transfer_fee_in:
              type: integer
              format: int64
              description: Token-2022 transfer fee paid on the input side.
              x-parser-schema-id: <anonymous-schema-30>
            transfer_fee_out:
              type: integer
              format: int64
              x-parser-schema-id: <anonymous-schema-31>
            cashback:
              type: *ref_5
              format: int64
              x-parser-schema-id: <anonymous-schema-32>
            cashback_bps:
              type: integer
              x-parser-schema-id: <anonymous-schema-33>
            maker_tags:
              type: array
              items:
                type: string
                x-parser-schema-id: <anonymous-schema-35>
              description: >-
                Wallet-intel tags applied to the maker (e.g. `sniper`,
                `insider`).
              x-parser-schema-id: <anonymous-schema-34>
          example:
            type: swap
            signature: >-
              3rjMq3C7Zp4eDDRtKwEBmVFXErFt4twvUVryNerBgkrxqs53XhAgL3LqDvfiZjDLNHEbwkbkQMTwfSjD1BvSboES
            slot: 420984167
            timestamp: 1779279483
            dex: meteora_damm_v2
            swap_type: sell
            trader: rk4jdi7srE6VKtxPeCKnqpEe7uJdZLLdpv1qAPK8LRd
            token_mint: 8ZiqYN6rEm6PapbhCm5wWWzkRjsYyixgqW3K2384D1C3
            pool_address: DT75gs9fygQxi2Aq2ST1jz9C6FTNcbBjpgSpdDpMDdLX
            amount_in: 350832255298
            amount_out: 729873626
            sol_amount: 729873626
            token_amount: 350832255298
            virtual_sol_reserves: null
            virtual_token_reserves: null
            real_sol_reserves: 91985332910
            real_token_reserves: 44561454812398
            lp_fee_bps: 1
            protocol_fee_bps: 0
            fee: null
            creator_fee: null
            creator_fee_bps: 0
            price_impact_bps: null
            base_decimals: 9
            quote_decimals: 6
            pool_price: 0.0000020642354092175554
            transfer_fee_in: 0
            transfer_fee_out: 0
            cashback: null
            cashback_bps: 0
            maker_tags: []
          x-parser-schema-id: WsSwapFrame
        title: Swap event
        description: One executed swap on a Solana DEX, normalized across protocols.
        example: |-
          {
            "const": "<string>",
            "signature": "<string>",
            "slot": 123,
            "timestamp": 123,
            "dex": "<string>",
            "swap_type": "<string>",
            "trader": "<string>",
            "token_mint": "<string>",
            "pool_address": "<string>",
            "amount_in": 123,
            "amount_out": 123,
            "sol_amount": 123,
            "token_amount": 123,
            "real_sol_reserves": 123,
            "real_token_reserves": 123,
            "lp_fee_bps": 123,
            "protocol_fee_bps": 123,
            "creator_fee_bps": 123,
            "base_decimals": 123,
            "quote_decimals": 123,
            "pool_price": 123,
            "transfer_fee_in": 123,
            "transfer_fee_out": 123,
            "cashback_bps": 123,
            "maker_tags": {
              "item": "<string>"
            }
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: swap
    bindings: []
    extensions: &ref_8
      - id: x-parser-unique-object-id
        value: swaps
  - &ref_17
    id: receiveSwapsPoolEvent
    title: Receive swaps pool event
    description: Pool event (filter-mode)
    type: send
    messages:
      - &ref_31
        id: poolEvent
        payload:
          - description: >-
              Non-swap pool activity (filter-mode): LP deposit / withdraw, token
              burn, or the server-classified rug signal. All four share `type:
              "pool_event"` — demux on `event_type`. `lp_withdraw` is the
              real-time LP-removal signal; `rug_detected` is the classified
              version.
            oneOf: &ref_7
              - type: object
                description: >-
                  `lp_deposit` / `lp_withdraw` liquidity event. No buy/sell side
                  and no price field.
                properties:
                  type:
                    const: pool_event
                    x-parser-schema-id: <anonymous-schema-36>
                  event_type:
                    type: string
                    enum:
                      - lp_deposit
                      - lp_withdraw
                    x-parser-schema-id: <anonymous-schema-37>
                  timestamp_ms:
                    type: integer
                    format: int64
                    description: >-
                      Epoch **milliseconds** — the `swap` frame uses `timestamp`
                      in epoch **seconds** (1000×).
                    x-parser-schema-id: <anonymous-schema-38>
                  slot:
                    type: integer
                    format: int64
                    x-parser-schema-id: <anonymous-schema-39>
                  signature:
                    type: string
                    description: Base58 transaction signature.
                    x-parser-schema-id: <anonymous-schema-40>
                  user:
                    type: string
                    description: Wallet that added/removed the LP.
                    x-parser-schema-id: <anonymous-schema-41>
                  pool_address:
                    type: string
                    description: >-
                      On-chain pool / LP account (elsewhere called
                      `pair_address`).
                    x-parser-schema-id: <anonymous-schema-42>
                  token_mint:
                    type: string
                    x-parser-schema-id: <anonymous-schema-43>
                  base_amount:
                    type: integer
                    format: int64
                    description: >-
                      Memecoin side, **atomic** units. Divide by `10**decimals`
                      (~6 for pump). Decimals are NOT carried on the event.
                    x-parser-schema-id: <anonymous-schema-44>
                  quote_amount:
                    type: integer
                    format: int64
                    description: >-
                      SOL / WSOL side in **lamports** despite no `_sol` suffix.
                      Divide by `1e9` for SOL.
                    x-parser-schema-id: <anonymous-schema-45>
                  lp_token_amount:
                    type: integer
                    format: int64
                    description: >-
                      LP tokens, atomic. Meteora DAMM v2 (`dex=7`) **saturates
                      this to `u64::MAX`** — use `base_amount`+`quote_amount`
                      for value.
                    x-parser-schema-id: <anonymous-schema-46>
                  drained_pct:
                    type: number
                    format: float
                    description: >-
                      Fraction of pool liquidity removed by this event, 0..1.
                      Constant-product pools use the LP-supply ratio; Meteora CL
                      uses the SOL-reserve ratio. `0.0` when not computable.
                    x-parser-schema-id: <anonymous-schema-47>
                  pool_base_reserve_after:
                    type: integer
                    format: int64
                    description: Post-event pool reserve, base side (atomic). `0` when N/A.
                    x-parser-schema-id: <anonymous-schema-48>
                  pool_quote_reserve_after:
                    type: integer
                    format: int64
                    description: >-
                      Post-event pool reserve, quote side (**lamports**). `0`
                      when N/A.
                    x-parser-schema-id: <anonymous-schema-49>
                  lp_supply_after:
                    type: integer
                    format: int64
                    description: >-
                      Post-event total LP supply. Constant-product only — `0`
                      for Meteora.
                    x-parser-schema-id: <anonymous-schema-50>
                  dex: &ref_6
                    type: integer
                    description: >-
                      Integer (Enum8) DEX id used on **pool-event** frames —
                      distinct from the string `Dex` used on swap frames.
                      `2`=PumpSwap, `3`=RaydiumAmm, `4`=RaydiumClmm,
                      `5`=RaydiumCpmm, `6`=Orca, `7`=MeteoraDammV2,
                      `9`=MeteoraDlmm emit LP/rug pool events. `1`=PumpFun and
                      `8`=MeteoraDbc are bonding curves (no fungible LP);
                      `10`=MeteoraPools LP events are not yet emitted (shared
                      dynamic vaults).
                    enum:
                      - 2
                      - 3
                      - 4
                      - 5
                      - 6
                      - 7
                      - 9
                    x-parser-schema-id: DexId
                example:
                  type: pool_event
                  event_type: lp_withdraw
                  timestamp_ms: 1717000000000
                  slot: 270123456
                  signature: >-
                    5Qx2vG7mB1qf6n7dWbN4d8Zp5gQ3sR9tH2cJ4kL6mN8pVxYzA1bC3dE5fG7hJ9kL2mN4pQ6rS8tU0vW2xY4zA6
                  user: 9aB7cD2eF4gH6jK8mN1pQ3rS5tU7vW9xY1zA3bC5dE7
                  pool_address: DT75gs9fygQxi2Aq2ST1jz9C6FTNcbBjpgSpdDpMDdLX
                  token_mint: 8ZiqYN6rEm6PapbhCm5wWWzkRjsYyixgqW3K2384D1C3
                  base_amount: 123456789
                  quote_amount: 4200000000
                  lp_token_amount: 987654321
                  drained_pct: 0.97
                  pool_base_reserve_after: 0
                  pool_quote_reserve_after: 0
                  lp_supply_after: 30000
                  dex: 2
                x-parser-schema-id: WsLpEventFrame
              - type: object
                description: >-
                  `token_burn` — a DIFFERENT shape: no `pool_address`, no
                  base/quote.
                properties:
                  type:
                    const: pool_event
                    x-parser-schema-id: <anonymous-schema-51>
                  event_type:
                    const: token_burn
                    x-parser-schema-id: <anonymous-schema-52>
                  timestamp_ms:
                    type: integer
                    format: int64
                    description: Epoch **milliseconds**.
                    x-parser-schema-id: <anonymous-schema-53>
                  slot:
                    type: integer
                    format: int64
                    x-parser-schema-id: <anonymous-schema-54>
                  signature:
                    type: string
                    x-parser-schema-id: <anonymous-schema-55>
                  user:
                    type: string
                    description: Wallet that burned.
                    x-parser-schema-id: <anonymous-schema-56>
                  token_mint:
                    type: string
                    x-parser-schema-id: <anonymous-schema-57>
                  amount_atomic:
                    type: integer
                    format: int64
                    description: Burned amount in **atomic** units.
                    x-parser-schema-id: <anonymous-schema-58>
                example:
                  type: pool_event
                  event_type: token_burn
                  timestamp_ms: 1717000001000
                  slot: 270123457
                  signature: >-
                    3rjMq3C7Zp4eDDRtKwEBmVFXErFt4twvUVryNerBgkrxqs53XhAgL3LqDvfiZjDLNHEbwkbkQMTwfSjD1BvSboES
                  user: 9aB7cD2eF4gH6jK8mN1pQ3rS5tU7vW9xY1zA3bC5dE7
                  token_mint: 8ZiqYN6rEm6PapbhCm5wWWzkRjsYyixgqW3K2384D1C3
                  amount_atomic: 999999999999
                x-parser-schema-id: WsBurnRecordFrame
              - type: object
                description: >-
                  `rug_detected` — server-side classified rug / drain signal
                  derived from an `lp_withdraw` or `lp_burn`. Fires on ANY
                  non-`pumpfun` venue when a real LP drain crosses the drain
                  threshold OUTSIDE the graduation/migration window (a genuine
                  AMM/CLMM pool, not the pump.fun bonding curve, which has no
                  fungible LP). Pump graduation-migrations are EXCLUDED.
                properties:
                  type:
                    const: pool_event
                    x-parser-schema-id: <anonymous-schema-59>
                  event_type:
                    const: rug_detected
                    x-parser-schema-id: <anonymous-schema-60>
                  timestamp_ms:
                    type: integer
                    format: int64
                    description: Epoch **milliseconds**.
                    x-parser-schema-id: <anonymous-schema-61>
                  slot:
                    type: integer
                    format: int64
                    x-parser-schema-id: <anonymous-schema-62>
                  signature:
                    type: string
                    description: Base58 signature of the triggering withdraw/burn.
                    x-parser-schema-id: <anonymous-schema-63>
                  pool_address:
                    type: string
                    x-parser-schema-id: <anonymous-schema-64>
                  token_mint:
                    type: string
                    x-parser-schema-id: <anonymous-schema-65>
                  dex: *ref_6
                  lp_sol_pulled:
                    type: integer
                    format: int64
                    description: >-
                      SOL liquidity removed, in **lamports**. Divide by `1e9`
                      for SOL.
                    x-parser-schema-id: <anonymous-schema-66>
                  drained_pct:
                    type: number
                    format: float
                    description: Fraction of pool liquidity removed, 0..1.
                    x-parser-schema-id: <anonymous-schema-67>
                  classification:
                    type: string
                    enum:
                      - RUGGED
                      - LIQUIDITY_DRAINING
                    description: >-
                      `RUGGED` when >95% of liquidity removed;
                      `LIQUIDITY_DRAINING` when 50–95%.
                    x-parser-schema-id: <anonymous-schema-68>
                  reason:
                    type: string
                    enum:
                      - lp_withdraw
                      - lp_burn
                    description: Which action triggered the classification.
                    x-parser-schema-id: <anonymous-schema-69>
                  graduated:
                    type: boolean
                    description: >-
                      Whether the mint had already graduated/migrated off the
                      pump.fun bonding curve when the drain landed. The
                      graduation/migration window itself is excluded, so a
                      `rug_detected` is always a post-migration (or
                      never-pumped) AMM/CLMM drain.
                    x-parser-schema-id: <anonymous-schema-70>
                example:
                  type: pool_event
                  event_type: rug_detected
                  timestamp_ms: 1717000002000
                  slot: 270123458
                  signature: >-
                    5Qx2vG7mB1qf6n7dWbN4d8Zp5gQ3sR9tH2cJ4kL6mN8pVxYzA1bC3dE5fG7hJ9kL2mN4pQ6rS8tU0vW2xY4zA6
                  pool_address: DT75gs9fygQxi2Aq2ST1jz9C6FTNcbBjpgSpdDpMDdLX
                  token_mint: 8ZiqYN6rEm6PapbhCm5wWWzkRjsYyixgqW3K2384D1C3
                  dex: 2
                  lp_sol_pulled: 4200000000
                  drained_pct: 0.97
                  classification: RUGGED
                  reason: lp_withdraw
                  graduated: true
                x-parser-schema-id: WsRugRecordFrame
            x-parser-schema-id: WsPoolEventFrame
            name: Pool event
        headers: []
        jsonPayloadSchema:
          description: >-
            A pool-event frame (filter-mode). One of an LP deposit/withdraw, a
            token burn, or a classified rug. Demux on `event_type`.
          oneOf: *ref_7
          x-parser-schema-id: WsPoolEventFrame
        title: Pool event
        description: >-
          Non-swap pool activity (filter-mode): LP deposit / withdraw, token
          burn, or the server-classified rug signal. All four share `type:
          "pool_event"` — demux on `event_type`. `lp_withdraw` is the real-time
          LP-removal signal; `rug_detected` is the classified version.
        example: '{}'
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: poolEvent
    bindings: []
    extensions: *ref_8
  - &ref_18
    id: receiveSwapsRoomMessage
    title: Receive swaps room message
    description: Room message (room-mode)
    type: send
    messages:
      - &ref_32
        id: roomMessage
        payload:
          - name: Room message
            description: >-
              Room-mode delivery wrapper. The swap or pool event is nested under
              `data`; `data` carries `event_type` but NOT a top-level `type`
              (dual-frame trap).
            type: object
            properties:
              - name: const
                type: string
                description: message
                required: false
              - name: room
                type: string
                description: >-
                  The room this message belongs to. One of the discovery rooms
                  (`latest`/`graduating`/`graduated`/`rugs`), `safety:{mint}`,
                  `price:{addr}:{tf}`, `wallet:{wallet}`,
                  `transaction:{mint}`(`:{pool}`), or a
                  `pnl:{wallet}`(`:{token}`|`:summary`) room. For the global
                  `rugs` room, `data` is the full RugRecord (incl.
                  `reason`+`graduated`, which REST `GET /rugs` omits).
                required: false
              - name: data
                type: object
                description: >-
                  The swap or pool event. For pool events, carries `event_type`
                  (`lp_deposit`/`lp_withdraw`/`token_burn`/`rug_detected`) and
                  the same fields as the filter-mode frame, minus the top-level
                  `type`.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          description: >-
            Room-mode delivery wrapper. The swap or pool event is nested under
            `data`; `data` carries `event_type` but NOT a top-level `type` (the
            dual-frame trap — same event, two shapes by mode).
          properties:
            type:
              const: message
              x-parser-schema-id: <anonymous-schema-71>
            room:
              type: string
              description: >-
                The room this message belongs to. One of the discovery rooms
                (`latest`/`graduating`/`graduated`/`rugs`), `safety:{mint}`,
                `price:{addr}:{tf}`, `wallet:{wallet}`,
                `transaction:{mint}`(`:{pool}`), or a
                `pnl:{wallet}`(`:{token}`|`:summary`) room. For the global
                `rugs` room, `data` is the full RugRecord (incl.
                `reason`+`graduated`, which REST `GET /rugs` omits).
              x-parser-schema-id: <anonymous-schema-72>
            data:
              type: object
              description: >-
                The swap or pool event. For pool events, carries `event_type`
                (`lp_deposit`/`lp_withdraw`/`token_burn`/`rug_detected`) and the
                same fields as the filter-mode frame, minus the top-level
                `type`.
              x-parser-schema-id: <anonymous-schema-73>
          example:
            type: message
            room: transaction:8ZiqYN6rEm6PapbhCm5wWWzkRjsYyixgqW3K2384D1C3
            data:
              event_type: lp_withdraw
              timestamp_ms: 1717000000000
              slot: 270123456
              signature: >-
                5Qx2vG7mB1qf6n7dWbN4d8Zp5gQ3sR9tH2cJ4kL6mN8pVxYzA1bC3dE5fG7hJ9kL2mN4pQ6rS8tU0vW2xY4zA6
              user: 9aB7cD2eF4gH6jK8mN1pQ3rS5tU7vW9xY1zA3bC5dE7
              pool_address: DT75gs9fygQxi2Aq2ST1jz9C6FTNcbBjpgSpdDpMDdLX
              token_mint: 8ZiqYN6rEm6PapbhCm5wWWzkRjsYyixgqW3K2384D1C3
              base_amount: 123456789
              quote_amount: 4200000000
              lp_token_amount: 987654321
              drained_pct: 0.97
              pool_base_reserve_after: 0
              pool_quote_reserve_after: 0
              lp_supply_after: 30000
              dex: 2
          x-parser-schema-id: WsRoomMessageFrame
        title: Room message
        description: >-
          Room-mode delivery wrapper. The swap or pool event is nested under
          `data`; `data` carries `event_type` but NOT a top-level `type`
          (dual-frame trap).
        example: No examples found
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: roomMessage
    bindings: []
    extensions: *ref_8
  - &ref_19
    id: receiveSwapsJoined
    title: Receive swaps joined
    description: Join ack
    type: send
    messages:
      - &ref_33
        id: joined
        payload:
          - name: Join ack
            description: Sent after a successful `join`.
            type: object
            properties:
              - name: type
                type: string
                enumValues:
                  - joined
                  - left
                required: false
              - name: room
                type: string
                required: false
        headers: []
        jsonPayloadSchema: &ref_9
          type: object
          properties:
            type:
              type: string
              enum:
                - joined
                - left
              x-parser-schema-id: <anonymous-schema-74>
            room:
              type: string
              x-parser-schema-id: <anonymous-schema-75>
          x-parser-schema-id: SwapsRoomAck
        title: Join ack
        description: Sent after a successful `join`.
        example: |-
          {
            "type": "<string>",
            "room": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: joined
    bindings: []
    extensions: *ref_8
  - &ref_20
    id: receiveSwapsLeft
    title: Receive swaps left
    description: Leave ack
    type: send
    messages:
      - &ref_34
        id: left
        payload:
          - name: Leave ack
            type: object
            properties:
              - name: type
                type: string
                enumValues:
                  - joined
                  - left
                required: false
              - name: room
                type: string
                required: false
        headers: []
        jsonPayloadSchema: *ref_9
        title: Leave ack
        example: |-
          {
            "type": "<string>",
            "room": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: left
    bindings: []
    extensions: *ref_8
  - &ref_21
    id: receiveSwapsSubscribed
    title: Receive swaps subscribed
    description: Connection / subscribe ack
    type: send
    messages:
      - &ref_35
        id: subscribed
        payload:
          - name: Connection / subscribe ack
            description: >-
              Sent on connect AND after each successful `subscribe`. The
              connect-time frame includes a `client-N` ID for log correlation.
            type: object
            properties:
              - name: const
                type: string
                description: subscribed
                required: false
              - name: message
                type: string
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          properties:
            type:
              const: subscribed
              x-parser-schema-id: <anonymous-schema-1>
            message:
              type: string
              example: >-
                Connected as client-196. Send
                {"type":"subscribe","filters":{...}} to set filters.
              x-parser-schema-id: <anonymous-schema-2>
          x-parser-schema-id: SwapsSubscribedAck
        title: Connection / subscribe ack
        description: >-
          Sent on connect AND after each successful `subscribe`. The
          connect-time frame includes a `client-N` ID for log correlation.
        example: |-
          {
            "const": "<string>",
            "message": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: subscribed
    bindings: []
    extensions: *ref_8
  - &ref_22
    id: receiveSwapsUnsubscribed
    title: Receive swaps unsubscribed
    description: Unsubscribe ack
    type: send
    messages:
      - &ref_36
        id: unsubscribed
        payload:
          - name: Unsubscribe ack
            type: object
            properties:
              - name: type
                type: string
                required: false
              - name: message
                type: string
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          properties:
            type:
              type: string
              x-parser-schema-id: <anonymous-schema-3>
            message:
              type: string
              x-parser-schema-id: <anonymous-schema-4>
          x-parser-schema-id: StreamSimpleAck
        title: Unsubscribe ack
        example: |-
          {
            "type": "<string>",
            "message": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: unsubscribed
    bindings: []
    extensions: *ref_8
  - &ref_23
    id: receiveSwapsPong
    title: Receive swaps pong
    description: Pong
    type: send
    messages:
      - &ref_37
        id: pong
        payload:
          - name: Pong
            description: >-
              Reply to a client `ping`. Send a `ping` every ~25–30s to keep the
              connection from idling out.
            type: object
            properties:
              - name: const
                type: string
                description: pong
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          properties:
            type:
              const: pong
              x-parser-schema-id: <anonymous-schema-77>
          x-parser-schema-id: <anonymous-schema-76>
        title: Pong
        description: >-
          Reply to a client `ping`. Send a `ping` every ~25–30s to keep the
          connection from idling out.
        example: |-
          {
            "const": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: pong
    bindings: []
    extensions: *ref_8
  - &ref_24
    id: receiveSwapsError
    title: Receive swaps error
    description: Stream error
    type: send
    messages:
      - &ref_38
        id: error
        payload:
          - name: Stream error
            description: >-
              Validation failure, auth issue, or server-side error. Connection
              usually stays open after a recoverable error.
            type: object
            properties:
              - name: const
                type: string
                description: error
                required: false
              - name: message
                type: string
                required: false
              - name: error
                type: string
                description: >-
                  Some errors return just `{"error":"…"}` instead of
                  `{"type":"error","message":"…"}` — handle both.
                required: false
              - name: cap
                type: integer
                description: >-
                  Present only on the per-connection subject-cap rejection:
                  `{"error":"subject cap reached for this connection; cap
                  <N>","cap":<N>}`. Non-fatal — the prior subscription stays
                  intact.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          properties:
            type:
              const: error
              x-parser-schema-id: <anonymous-schema-78>
            message:
              type: string
              example: 'invalid subscribe: timeframes empty'
              x-parser-schema-id: <anonymous-schema-79>
            error:
              type: string
              description: >-
                Some errors return just `{"error":"…"}` instead of
                `{"type":"error","message":"…"}` — handle both.
              example: 'invalid subscribe: unknown op: frobnicate'
              x-parser-schema-id: <anonymous-schema-80>
            cap:
              type: integer
              description: >-
                Present only on the per-connection subject-cap rejection:
                `{"error":"subject cap reached for this connection; cap
                <N>","cap":<N>}`. Non-fatal — the prior subscription stays
                intact.
              x-parser-schema-id: <anonymous-schema-81>
          x-parser-schema-id: StreamErrorBody
        title: Stream error
        description: >-
          Validation failure, auth issue, or server-side error. Connection
          usually stays open after a recoverable error.
        example: |-
          {
            "const": "<string>",
            "message": "<string>",
            "error": "<string>",
            "cap": 123
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: error
    bindings: []
    extensions: *ref_8
  - &ref_11
    id: sendSwapsSubscribe
    title: Send swaps subscribe
    description: Set filters
    type: receive
    messages:
      - &ref_25
        id: subscribe
        payload:
          - name: Set filters
            description: >-
              Replaces the active filter set. Empty `filters` means "every swap
              on every pool".
            type: object
            properties:
              - name: const
                type: string
                description: subscribe
                required: false
              - name: filters
                type: object
                description: >-
                  All filters combine as AND. Set to `null` or omit to disable a
                  filter.
                required: false
                properties:
                  - name: pools
                    type: array
                    description: Pool / pair address — usually the most useful filter.
                    required: false
                    properties:
                      - name: item
                        type: string
                        required: false
                  - name: tokens
                    type: array
                    description: Token mints. Matches any pool that trades this token.
                    required: false
                    properties:
                      - name: item
                        type: string
                        required: false
                  - name: traders
                    type: array
                    description: Wallet addresses.
                    required: false
                    properties:
                      - name: item
                        type: string
                        required: false
                  - name: dexes
                    type: array
                    required: false
                    properties:
                      - name: item
                        type: string
                        description: >-
                          Lowercase snake_case DEX name. Note: on the WebSocket
                          feed `dex` is the string name, but on the REST
                          `/swaps*` endpoints it's an integer ID — these
                          surfaces are intentionally different.
                        enumValues:
                          - pumpfun
                          - pumpswap
                          - raydium_amm
                          - raydium_clmm
                          - raydium_cpmm
                          - orca
                          - meteora_damm_v2
                          - meteora_dbc
                          - meteora_dlmm
                          - meteora_pools
                        required: false
                  - name: min_sol
                    type: integer
                    description: Lamports. 1 SOL = 1e9 lamports.
                    required: false
                  - name: max_sol
                    type: integer
                    required: false
                  - name: is_buy
                    type: boolean
                    description: '`true` for buys only, `false` for sells only.'
                    required: false
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
          properties:
            type:
              const: subscribe
              x-parser-schema-id: <anonymous-schema-82>
            filters:
              type: object
              description: >-
                All filters combine as AND. Set to `null` or omit to disable a
                filter.
              properties:
                pools:
                  type: array
                  items:
                    type: string
                    x-parser-schema-id: <anonymous-schema-85>
                  description: Pool / pair address — usually the most useful filter.
                  x-parser-schema-id: <anonymous-schema-84>
                tokens:
                  type: array
                  items:
                    type: string
                    x-parser-schema-id: <anonymous-schema-87>
                  description: Token mints. Matches any pool that trades this token.
                  x-parser-schema-id: <anonymous-schema-86>
                traders:
                  type: array
                  items:
                    type: string
                    x-parser-schema-id: <anonymous-schema-89>
                  description: Wallet addresses.
                  x-parser-schema-id: <anonymous-schema-88>
                dexes:
                  type: array
                  items: *ref_10
                  x-parser-schema-id: <anonymous-schema-90>
                min_sol:
                  type: integer
                  format: int64
                  description: Lamports. 1 SOL = 1e9 lamports.
                  x-parser-schema-id: <anonymous-schema-91>
                max_sol:
                  type: integer
                  format: int64
                  x-parser-schema-id: <anonymous-schema-92>
                is_buy:
                  type: boolean
                  description: '`true` for buys only, `false` for sells only.'
                  x-parser-schema-id: <anonymous-schema-93>
              x-parser-schema-id: <anonymous-schema-83>
          x-parser-schema-id: SwapsSubscribeBody
        title: Set filters
        description: >-
          Replaces the active filter set. Empty `filters` means "every swap on
          every pool".
        example: No examples found
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: subscribe
    bindings: []
    extensions: *ref_8
  - &ref_12
    id: sendSwapsUnsubscribe
    title: Send swaps unsubscribe
    description: Clear filters
    type: receive
    messages:
      - &ref_26
        id: unsubscribe
        payload:
          - name: Clear filters
            type: object
            properties:
              - name: const
                type: string
                description: unsubscribe
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          properties:
            type:
              const: unsubscribe
              x-parser-schema-id: <anonymous-schema-95>
          x-parser-schema-id: <anonymous-schema-94>
        title: Clear filters
        example: |-
          {
            "const": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: unsubscribe
    bindings: []
    extensions: *ref_8
  - &ref_13
    id: sendSwapsPing
    title: Send swaps ping
    description: Heartbeat
    type: receive
    messages:
      - &ref_27
        id: ping
        payload:
          - name: Heartbeat
            description: >-
              Optional keepalive. Server replies with `pong`. Recommended every
              25–30 seconds.
            type: object
            properties:
              - name: const
                type: string
                description: ping
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          properties:
            type:
              const: ping
              x-parser-schema-id: <anonymous-schema-101>
          x-parser-schema-id: <anonymous-schema-100>
        title: Heartbeat
        description: >-
          Optional keepalive. Server replies with `pong`. Recommended every
          25–30 seconds.
        example: |-
          {
            "const": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: ping
    bindings: []
    extensions: *ref_8
  - &ref_14
    id: sendSwapsJoin
    title: Send swaps join
    description: Join a room
    type: receive
    messages:
      - &ref_28
        id: join
        payload:
          - name: Join room
            description: >-
              Subscribe to a room (e.g. `transaction:<mint>` or
              `transaction:<mint>:<pool>`). First `join` locks room-mode.
            type: object
            properties:
              - name: const
                type: string
                description: join
                required: false
              - name: room
                type: string
                description: >-
                  Room to join. Valid rooms: `latest`, `graduating`,
                  `graduated`, `rugs`, `safety:{mint}`, `price:{addr}:{tf}`,
                  `wallet:{wallet}`, `transaction:{mint}`,
                  `transaction:{mint}:{pool}`, `pnl:{wallet}`,
                  `pnl:{wallet}:{token}`, `pnl:{wallet}:summary` (the `pnl:*`
                  rooms require Pro+). The first `join` locks room-mode
                  (mode-exclusive with `subscribe`/filter-mode).
                required: true
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - room
          properties:
            type:
              const: join
              x-parser-schema-id: <anonymous-schema-96>
            room:
              type: string
              description: >-
                Room to join. Valid rooms: `latest`, `graduating`, `graduated`,
                `rugs`, `safety:{mint}`, `price:{addr}:{tf}`, `wallet:{wallet}`,
                `transaction:{mint}`, `transaction:{mint}:{pool}`,
                `pnl:{wallet}`, `pnl:{wallet}:{token}`, `pnl:{wallet}:summary`
                (the `pnl:*` rooms require Pro+). The first `join` locks
                room-mode (mode-exclusive with `subscribe`/filter-mode).
              x-parser-schema-id: <anonymous-schema-97>
          x-parser-schema-id: SwapsJoinBody
        title: Join room
        description: >-
          Subscribe to a room (e.g. `transaction:<mint>` or
          `transaction:<mint>:<pool>`). First `join` locks room-mode.
        example: |-
          {
            "const": "<string>",
            "room": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: join
    bindings: []
    extensions: *ref_8
  - &ref_15
    id: sendSwapsLeave
    title: Send swaps leave
    description: Leave a room
    type: receive
    messages:
      - &ref_29
        id: leave
        payload:
          - name: Leave room
            type: object
            properties:
              - name: const
                type: string
                description: leave
                required: false
              - name: room
                type: string
                required: true
        headers: []
        jsonPayloadSchema:
          type: object
          required:
            - type
            - room
          properties:
            type:
              const: leave
              x-parser-schema-id: <anonymous-schema-98>
            room:
              type: string
              x-parser-schema-id: <anonymous-schema-99>
          x-parser-schema-id: SwapsLeaveBody
        title: Leave room
        example: |-
          {
            "const": "<string>",
            "room": "<string>"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: leave
    bindings: []
    extensions: *ref_8
sendOperations:
  - *ref_11
  - *ref_12
  - *ref_13
  - *ref_14
  - *ref_15
receiveOperations:
  - *ref_16
  - *ref_17
  - *ref_18
  - *ref_19
  - *ref_20
  - *ref_21
  - *ref_22
  - *ref_23
  - *ref_24
sendMessages:
  - *ref_25
  - *ref_26
  - *ref_27
  - *ref_28
  - *ref_29
receiveMessages:
  - *ref_30
  - *ref_31
  - *ref_32
  - *ref_33
  - *ref_34
  - *ref_35
  - *ref_36
  - *ref_37
  - *ref_38
extensions:
  - id: x-parser-unique-object-id
    value: swaps
securitySchemes:
  - id: ApiKeyQuery
    name: api_key
    type: httpApiKey
    description: >-
      `?api_key=ohlcv_live_sk_…` appended to the connection URL.
      Browser-friendly; the only option in environments where you can't set
      headers.
    in: query
    extensions: []

````