Live streams
Subscribe to live trades, live PnL and live wallet balances over a WebSocket with your API key.
Connect to wss://evm.vortexdeployer.com/evm/ws and send JSON messages. Put your key in token on each subscribe.
const ws = new WebSocket('wss://evm.vortexdeployer.com/evm/ws');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'subscribe', topic: 'txs', token: 'vd_…' }));
setInterval(() => ws.send(JSON.stringify({ type: 'ping' })), 25_000);
};
ws.onmessage = (m) => console.log(JSON.parse(m.data));Never open a keyed stream from a web page. Anyone can read the key from the browser.
Topics#
| Topic | Scope | Sends |
|---|---|---|
txs | stream | Every trade the platform sees, as it happens — across every venue. No per-token subscribe needed |
txs:<mint> | stream | Trades on one token, starting with a snapshot of its recent trades |
pnl | stream | Your per-token PnL on Solana, refreshed as it changes |
pnl:<mint> | stream | One token's PnL for your account |
wallet:<address> | stream | Live balance of one of your wallets: a snapshot, then a frame on every change |
tasks | read | Your running tasks, then every change |
After a subscribe you get { "type": "subscribed", "topic": … }, or { "type": "error", "topic": …, "message": … } saying why not. Send { "type": "unsubscribe", "topic": … } to stop a topic.
The bare txs topic is a firehose: subscribe once and every trade streams in, no mint list to keep. It answers with { "type": "txs_snapshot", "topic": "txs", "note": "firehose: deltas only" }, then live frames. txs:<mint> instead opens with a trade_snapshot of that token's recent trades.
Trade frames#
Every trade frame carries both sides of the pair, the pool, and the pool's reserves before and after the swap.
Stream frames are the platform's raw chain feed and keep snake_case names (pool_address, base_amount, block_index). Only REST answers are camelCase.
{
"type": "trade",
"chain": "solana",
"mint": "NnLz6wXfB3c41KwPWPvk9Rz75zR5enTGtYmsELrpump",
"quote_mint": "So11111111111111111111111111111111111111112",
"pool_address": "B8GeyGLChfj3Mn9HCyVJYS4nMRSMVDCCutRTwGasoH7V",
"dex": "pump_swap",
"side": "buy",
"trader": "A2uqWi4Zm3yVYqvuRb4GF6BSb56pocvw1J2V2nQeU3QE",
"base_amount": "44917",
"quote_amount": "179054",
"signature": "42RU7t2Drzyr…faT22",
"block": 447482805,
"block_index": 805,
"timestamp": 1789549114,
"pre_base_reserves": "2105102154648",
"pre_quote_reserves": "8391614579937",
"post_base_reserves": "2105102109731",
"post_quote_reserves": "8391614758991"
}| Field | |
|---|---|
mint | The token traded (the base token) |
quote_mint | The token it traded against (the quote token — e.g. wrapped SOL) |
pool_address | The pool the trade went through |
dex | The venue (pump_fun, pump_swap, raydium_cpmm, meteora_bonding, pons, …) |
side | buy or sell |
trader | The wallet that traded |
base_amount, quote_amount | Amounts traded, in each token's base units |
signature, block, block_index, timestamp | Where the trade landed on chain |
pre_base_reserves, pre_quote_reserves | The pool's two reserves just before the swap |
post_base_reserves, post_quote_reserves | The pool's two reserves just after |
Reserves are null for a venue that does not report them. From the reserves you can read the pool's price without another call.
PnL frames#
{ "type": "pnl_update", "mint": "7xKX…pump", "at": 1757923203112, "pnl": { } }atonly ever goes up. Ignore a frame whoseatis not newer than the last one.- On
pnl(no mint), the frame carrieschainandtokensinstead ofpnl. pnl: nullcomes with areason.{ "type": "resync" }means the server may have missed updates: re-read what you need over REST.
Balance frames#
wallet:<address> streams the live coin and token balance of one wallet. The address must be one of your wallets — any other address is refused. Subscribing answers with a wallet_balance_snapshot, then a wallet_balance_update on every change:
{
"type": "wallet_balance_snapshot",
"topic": "wallet:7tXi4mbvppPBgYgTVj2NpYq9K1KYYQvHv6tfCpJv9kMT",
"chain": "solana",
"address": "7tXi4mbvppPBgYgTVj2NpYq9K1KYYQvHv6tfCpJv9kMT",
"sol": "419434195",
"tokens": [ { "mint": "7xKX…pump", "amount": "3512000000" } ],
"block": 447482145,
"block_index": null
}{
"type": "wallet_balance_update",
"chain": "solana",
"address": "7tXi4mbvppPBgYgTVj2NpYq9K1KYYQvHv6tfCpJv9kMT",
"sol": "420100000",
"tokens": [ { "mint": "7xKX…pump", "amount": "3500000000" } ],
"block": 447482806,
"block_index": 806
}| Field | |
|---|---|
chain, address | The wallet |
sol | Coin balance in the smallest unit as a string — lamports on Solana, wei on EVM |
tokens | Token balances being tracked for this wallet: mint and amount in base units |
block, block_index | Where the balance was last observed on chain (block_index can be null) |
To seed a token into the snapshot, send mint on the subscribe: { "type": "subscribe", "topic": "wallet:<address>", "mint": "7xKX…pump", "token": "vd_…" }. The stream then keeps that token's amount live alongside the coin balance.
A key limited to an allowed wallet list can only open wallets on that list.
Rules#
- One identity per socket. Once a socket subscribes with a key, a different key or account on the same socket is refused.
- Up to 5 sockets per key, and 50
txs/pnlsubscriptions (and 50walletsubscriptions) per socket. - Too many bad keys closes the socket (code
4008). - Revoking or editing the key closes its sockets (code
4001). Reconnect with a valid key. - Keys can't use the socket's read requests. Use REST for reads.
- The key's IP allowlist applies to sockets too.
Reconnect with a backoff after a drop, then subscribe again. Subscriptions don't survive a reconnect.