Solana Tracker

WebSocket - Data Stream

The WebSocket API is only available for Premium, Business, and Enterprise plans.

💡 Using our SDK? The official JavaScript/TypeScript SDK includes a built-in Datastream class with an improved API for WebSocket connections. Check out the SDK documentation for easier integration.

With the WebSocket API, you can stream:

  • Parsed transactions (per pair or for a wallet)
  • New pools/tokens
  • Price updates
  • Pool updates
  • Token updates
  • Pumpfun Graduating / Graduated
  • Pumpfun Bonding Curve Percentage
  • Curve percentage
  • NEW: Top 10 holders live tracking
  • NEW: Developer token balance / percentage live tracking
  • NEW: Live statistics (volume, wallets, buys / sells etc) for tokens and pools across multiple timeframes
  • NEW: Snipers and insiders tracking
  • NEW: Wallet balance tracking
  • Other market-related data in real time.

WebSocketService Class

Below is the WebSocketService class for managing WebSocket connections and room subscriptions.

import EventEmitter from "eventemitter3";
 
class WebSocketService {
    constructor(wsUrl) {
        this.wsUrl = wsUrl;
        this.socket = null;
        this.transactionSocket = null;
        this.reconnectAttempts = 0;
        this.reconnectDelay = 2500;
        this.reconnectDelayMax = 4500;
        this.randomizationFactor = 0.5;
        this.emitter = new EventEmitter();
        this.subscribedRooms = new Set();
        this.transactions = new Set();
        this.connect();
 
        if (typeof window !== "undefined") {
            window.addEventListener("beforeunload", this.disconnect.bind(this));
        }
 
    }
 
    async connect() {
        if (this.socket && this.transactionSocket) {
            return;
        }
 
        try {
            this.socket = new WebSocket(this.wsUrl);
            this.transactionSocket = new WebSocket(this.wsUrl);
            this.setupSocketListeners(this.socket, "main");
            this.setupSocketListeners(this.transactionSocket, "transaction");
        } catch (e) {
            console.error("Error connecting to WebSocket:", e);
            this.reconnect();
        }
 
    }
 
    setupSocketListeners(socket, type) {
        socket.onopen = () => {
            console.log(`Connected to ${type} WebSocket server`);
            this.reconnectAttempts = 0;
            this.resubscribeToRooms();
        };
 
        socket.onclose = () => {
            console.log(`Disconnected from ${type} WebSocket server`);
            if (type === "main") this.socket = null;
            if (type === "transaction") this.transactionSocket = null;
            this.reconnect();
        };
 
        socket.onmessage = (event) => {
            try {
                const message = JSON.parse(event.data);
                if (message.type === "message") {
                    if (message.data?.tx && this.transactions.has(message.data.tx)) {
                        return;
                    } else if (message.data?.tx) {
                        this.transactions.add(message.data.tx);
                    }
 
                    // Emit on the actual room
                    this.emitter.emit(message.room, message.data);
 
                    // If this is a pool message, also emit on token:primary if subscribed
                    if (message.room.startsWith('pool:') && message.data?.tokenAddress) {
                        const primaryRoom = `token:${message.data.tokenAddress}:primary`;
                        if (this.subscribedRooms.has(primaryRoom)) {
                            this.emitter.emit(primaryRoom, message.data);
                        }
                    }
 
                    // Special handling for price events
                    if (message.room.includes('price:')) {
                        this.emitter.emit(`price-by-token:${message.data.token}`, message.data);
                    }
                }
            } catch (error) {
                console.error("Error processing message:", error);
            }
        };
 
    }
 
    disconnect() {
        if (this.socket) {
            this.socket.close();
            this.socket = null;
        }
        if (this.transactionSocket) {
            this.transactionSocket.close();
            this.transactionSocket = null;
        }
        this.subscribedRooms.clear();
        this.transactions.clear();
    }
 
    reconnect() {
        console.log("Reconnecting to WebSocket server");
        const delay = Math.min(
            this.reconnectDelay _ Math.pow(2, this.reconnectAttempts),
            this.reconnectDelayMax
        );
        const jitter = delay _ this.randomizationFactor;
        const reconnectDelay = delay + Math.random()\ * jitter;
 
        setTimeout(() => {
            this.reconnectAttempts++;
            this.connect();
        }, reconnectDelay);
 
    }
 
    joinRoom(room) {
        this.subscribedRooms.add(room);
        const socket = room.includes("transaction") ?
            this.transactionSocket :
            this.socket;
        if (socket && socket.readyState === WebSocket.OPEN) {
            socket.send(JSON.stringify({
                type: "join",
                room
            }));
        }
    }
 
    leaveRoom(room) {
        this.subscribedRooms.delete(room);
        const socket = room.includes("transaction") ?
            this.transactionSocket :
            this.socket;
        if (socket && socket.readyState === WebSocket.OPEN) {
            socket.send(JSON.stringify({
                type: "leave",
                room
            }));
        }
    }
 
    on(room, listener) {
        this.emitter.on(room, listener);
    }
 
    off(room, listener) {
        this.emitter.off(room, listener);
    }
 
    getSocket() {
        return this.socket;
    }
 
    resubscribeToRooms() {
        if (
            this.socket &&
            this.socket.readyState === WebSocket.OPEN &&
            this.transactionSocket &&
            this.transactionSocket.readyState === WebSocket.OPEN
        ) {
            for (const room of this.subscribedRooms) {
                const socket = room.includes("transaction") ?
                    this.transactionSocket :
                    this.socket;
                socket.send(JSON.stringify({
                    type: "join",
                    room
                }));
            }
        }
    }
}
 
export default WebSocketService;

Room Types and Usage

Latest Tokens/Pools

Room Name: latest
Description: Receive updates about the latest tokens and pools.

{
  "token": {
    "name": "Token Name",
    "symbol": "DANCE",
    "mint": "AmJaZvdNptvofC4qe3tvuBNgqLm65p1of5pk6JFHpump",
    "uri": "https://cf-ipfs.com/ipfs/QmVrh4ER81fns3S4QU48WiBuhiusc1KsCxsM8mSs1bEGPv",
    "decimals": 6,
    "hasFileMetaData": true,
    "createdOn": "https://pump.fun"
  },
  "pools": [
    {
      "liquidity": {
        "quote": 62,
        "usd": 8907.761583907999
      },
      "price": {
        "quote": 2.9853991922957425e-8,
        "usd": 0.000004289229715768062
      },
      "tokenSupply": 1000000000000000,
      "lpBurn": 100,
      "tokenAddress": "AmJaZvdNptvofC4qe3tvuBNgqLm65p1of5pk6JFHpump",
      "marketCap": {
        "quote": 29.853991922957423,
        "usd": 4289.229715768061
      },
      "decimals": 6,
      "security": {
        "freezeAuthority": null,
        "mintAuthority": null
      },
      "quoteToken": "So11111111111111111111111111111111111111112",
      "market": "pumpfun",
      "deployer": "4Rz5xqikxtZ2s7wE9uQ6n2oLXQi6K65XGoYpKxf24Hqo",
      "openTime": 0,
      "poolId": "GmJaZvdNptvofC4qe3tvuBNgqLm65p1of5pk6JFHpump"
    }
  ],
  "events": {
    "30m": {
      "priceChangePercentage": 0
    },
    "1h": {
      "priceChangePercentage": 0
    },
    "4h": {
      "priceChangePercentage": 0
    },
    "24h": {
      "priceChangePercentage": 0
    }
  },
  "risk": {
    "snipers": {
      "count": 0,
      "totalBalance": 0,
      "totalPercentage": 0,
      "wallets": []
    },
    "insiders": {
      "count": 0,
      "totalBalance": 0,
      "totalPercentage": 0,
      "wallets": []
    },
    "top10": 0,
    "rugged": false,
    "risks": [
      {
        "name": "No social media",
        "description": "This token has no social media links",
        "level": "warning",
        "score": 2000
      },
      {
        "name": "Pump.fun contracts can be changed at any time",
        "description": "Pump.fun contracts can be changed by Pump.fun at any time",
        "level": "warning",
        "score": 10
      },
      {
        "name": "Bonding curve not complete",
        "description": "No raydium liquidity pool, bonding curve not complete",
        "level": "warning",
        "score": 4000
      }
    ],
    "score": 5
  }
}

Usage Examples

Here are examples of how to use each room type:

const wsService = new WebSocketService("wss://websocket-url-here.com");
 
// 1. Latest Tokens/Pools
wsService.joinRoom("latest");
wsService.on("latest", (data) => {
  console.log("Latest token/pool update:", data);
});
 
// 2. Pool Changes
wsService.joinRoom(`pool:${poolId}`);
wsService.on(`pool:${poolId}`, (data) => {
  console.log(`Pool ${poolId} update:`, data);
});
 
// 3. Pair Transactions
wsService.joinRoom(`transaction:${tokenAddress}:${poolId}`);
wsService.on(`transaction:${tokenAddress}:${poolId}`, (data) => {
  console.log(`New transaction for ${tokenAddress} in pool ${poolId}:`, data);
});
 
// 4. Transactions
wsService.joinRoom(`transaction:${tokenAddress}`);
wsService.on(`transaction:${tokenAddress}`, (data) => {
  console.log(`New transaction for ${tokenAddress}:`, data);
});
 
// 5. Pair and Wallet Transactions
wsService.joinRoom(`transaction:${tokenAddress}:${poolId}:${walletAddress}`);
wsService.on(
  `transaction:${tokenAddress}:${poolId}:${walletAddress}`,
  (data) => {
    console.log(
      `New transaction for ${tokenAddress} in pool ${poolId} for wallet ${walletAddress}:`,
      data
    );
  }
);
 
// 6. Price Updates
wsService.joinRoom(`price:${poolId}`);
wsService.on(`price:${poolId}`, (data) => {
  console.log(`Price update for pool ${poolId}:`, data);
});
 
wsService.joinRoom(`price-by-token:${tokenId}`);
wsService.on(`price-by-token:${tokenId}`, (data) => {
  console.log(`Price update for token ${tokenId}:`, data);
});
 
// 7. Wallet Transactions
wsService.joinRoom(`wallet:${walletAddress}`);
wsService.on(`wallet:${walletAddress}`, (data) => {
  console.log(`New transaction for wallet ${walletAddress}:`, data);
});
 
// 8. NEW - Wallet Balance Updates
// Monitor all token balance changes for a wallet
wsService.joinRoom(`wallet:${walletAddress}:balance`);
wsService.on(`wallet:${walletAddress}:balance`, (data) => {
  console.log(`Balance update for wallet ${walletAddress}:`);
  console.log(`Token: ${data.token}`);
  console.log(`New balance: ${data.amount}`);
});
 
// Monitor specific token balance for a wallet
wsService.joinRoom(`wallet:${walletAddress}:${tokenAddress}:balance`);
wsService.on(`wallet:${walletAddress}:${tokenAddress}:balance`, (data) => {
  console.log(`Token balance update for wallet ${walletAddress}:`);
  console.log(`New ${data.token} balance: ${data.amount}`);
});
 
// 9. Graduating tokens
wsService.joinRoom("graduating");
wsService.on("graduating", (data) => {
  console.log("Latest graduating token", data);
});
 
// Graduating with custom market cap
wsService.joinRoom("graduating:sol:175");
wsService.on("graduating:sol:175", (data) => {
  console.log("Latest graduating token", data);
});
 
// 10. Graduated tokens
wsService.joinRoom("graduated");
wsService.on("graduated", (data) => {
  console.log("Latest graduated token", data);
});
 
// 11. Metadata
wsService.joinRoom(`metadata:${tokenAddress}`);
wsService.on(`metadata:${tokenAddress}`, (data) => {
  console.log("Metadata updated", data);
});
 
// 12. Holders update
wsService.joinRoom(`holders:${tokenAddress}`);
wsService.on(`holders:${tokenAddress}`, (data) => {
  console.log("Total holders count for token has been updated", data);
});
 
// 13. Token Changes
wsService.joinRoom(`token:${tokenAddress}`);
wsService.on(`token:${tokenAddress}`, (data) => {
  console.log(`Token ${tokenAddress} update:`, data);
});
 
// 14. Curve Percentage Updates
// Monitor tokens reaching 30% on Pump.fun
wsService.joinRoom("pumpfun:curve:30");
wsService.on("pumpfun:curve:30", (data) => {
  console.log(`Token ${data.token.symbol} reached 30% curve on Pump.fun`);
  console.log(`Current market cap: $${data.pools[0].marketCap.usd}`);
});
 
// Monitor different percentages and markets
wsService.joinRoom("meteora-curve:curve:75");
wsService.on("meteora-curve:curve:75", (data) => {
  console.log(`Token ${data.token.symbol} reached 75% on Meteora`);
});
 
wsService.joinRoom("boop:curve:50");
wsService.on("boop:curve:50", (data) => {
  console.log(`Token ${data.token.symbol} is at 50% on Boop`);
});
 
// 15. NEW - Snipers Updates
wsService.joinRoom(`sniper:${tokenAddress}`);
wsService.on(`sniper:${tokenAddress}`, (data) => {
  console.log(`Sniper update for ${tokenAddress}:`);
  console.log(`Wallet: ${data.wallet}`);
  console.log(
    `Holdings: ${data.percentage.toFixed(
      2
    )}% (was ${data.previousPercentage.toFixed(2)}%)`
  );
  console.log(`Total snipers hold: ${data.totalSniperPercentage.toFixed(2)}%`);
});
 
// 16. NEW - Insiders Updates
wsService.joinRoom(`insider:${tokenAddress}`);
wsService.on(`insider:${tokenAddress}`, (data) => {
  console.log(`Insider update for ${tokenAddress}:`);
  console.log(`Wallet: ${data.wallet}`);
  console.log(
    `Holdings: ${data.percentage.toFixed(
      2
    )}% (was ${data.previousPercentage.toFixed(2)}%)`
  );
  console.log(
    `Total insiders hold: ${data.totalInsiderPercentage.toFixed(2)}%`
  );
});
 
// 17. NEW - Token Stats Updates
wsService.joinRoom(`stats:token:${tokenAddress}`);
wsService.on(`stats:token:${tokenAddress}`, (data) => {
  console.log(`Stats update for token ${tokenAddress}:`);
 
  // Access specific timeframe
  if (data['24h']) {
    console.log('24h Stats:');
    console.log(`  Volume: $${data['24h'].volume.total.toLocaleString()}`);
    console.log(`  Transactions: ${data['24h'].transactions}`);
    console.log(`  Unique wallets: ${data['24h'].wallets}`);
    console.log(`  Price change: ${data['24h'].priceChangePercentage.toFixed(2)}%`);
  }
 
  // Iterate through all available timeframes
  Object.entries(data).forEach(([timeframe, stats]) => {
    console.log(`${timeframe}: ${stats.transactions} txns, ${stats.wallets} wallets`);
  });
});
 
// 18. NEW - Pool Stats Updates
wsService.joinRoom(`stats:pool:${poolId}`);
wsService.on(`stats:pool:${poolId}`, (data) => {
  console.log(`Stats update for pool ${poolId}:`);
 
  if (data['1h']) {
    console.log('Last hour:');
    console.log(`  Buyers: ${data['1h'].buyers}, Sellers: ${data['1h'].sellers}`);
    console.log(`  Buy volume: $${data['1h'].volume.buys.toLocaleString()}`);
    console.log(`  Sell volume: $${data['1h'].volume.sells.toLocaleString()}`);
  }
});
 
// 19. NEW - Developer Holdings Updates
wsService.joinRoom(`dev_holding:${tokenAddress}`);
wsService.on(`dev_holding:${tokenAddress}`, (data) => {
  console.log(`Developer holdings update for ${data.token}:`);
  console.log(`Creator wallet: ${data.creator}`);
  console.log(`Current holdings: ${data.percentage.toFixed(4)}%`);
  console.log(`Previous holdings: ${data.previousPercentage.toFixed(4)}%`);
  console.log(`Timestamp: ${new Date(data.timestamp).toISOString()}`);
  
  // Calculate change
  const change = data.percentage - data.previousPercentage;
  
  // Alert based on developer action
  if (change < -5) {
    console.log(`🚨 MAJOR DEV SELL: Developer sold ${Math.abs(change).toFixed(4)}% of supply`);
  } else if (change < 0) {
    console.log(`⚠️ Dev sold ${Math.abs(change).toFixed(4)}% of supply`);
  } else if (change > 0) {
    console.log(`✅ Dev bought ${change.toFixed(4)}% of supply`);
  }
});
 
// 20. NEW - Top 10 Holders Updates
wsService.joinRoom(`top10:${tokenAddress}`);
wsService.on(`top10:${tokenAddress}`, (data) => {
  console.log(`Top 10 holders update for ${data.token}:`);
  console.log(`Combined holdings: ${data.totalPercentage.toFixed(2)}%`);
  console.log(`Timestamp: ${new Date(data.timestamp).toISOString()}`);
  
  // Show change if available
  if (data.previousPercentage !== null) {
    const change = data.totalPercentage - data.previousPercentage;
    console.log(`Change from previous: ${change > 0 ? '+' : ''}${change.toFixed(2)}%`);
    
    if (Math.abs(change) > 5) {
      console.log(`🔄 Significant redistribution detected`);
    }
  }

Using the Official SDK

The official JavaScript/TypeScript SDK provides a more intuitive API for WebSocket connections:

import { Datastream } from "@solana-tracker/data-api";
 
// Initialize the Datastream
const datastream = new Datastream({
  wsUrl: "wss://datastream.solanatracker.io/your-datastream-url-here",
});
 
// Connect to the WebSocket server
await datastream.connect();
 
// Subscribe with chained syntax
datastream.subscribe.latest().on((data) => {
  console.log("New token:", data.token.name);
});
 
// Price updates
datastream.subscribe.price.token("tokenAddress").on((price) => {
  console.log(`Price: $${price.price}`);
});
 
// Curve percentage updates
datastream.subscribe.curvePercentage("pumpfun", 30).on((data) => {
  console.log(`Token ${data.token.symbol} reached 30% on Pump.fun`);
});
 
// Transactions
datastream.subscribe.tx.token("tokenAddress").on((tx) => {
  console.log(`${tx.type}: ${tx.amount} tokens`);
});
 
 
// NEW - Live stats tracking
// Subscribe to token statistics across all timeframes
datastream.subscribe.stats.token("tokenAddress").on((stats) => {
  console.log('Token stats update:');
 
  // Access 24h stats
  if (stats['24h']) {
    console.log(`24h volume: $${stats['24h'].volume.total.toLocaleString()}`);
    console.log(`24h transactions: ${stats['24h'].transactions}`);
    console.log(`24h price change: ${stats['24h'].priceChangePercentage.toFixed(2)}%`);
  }
 
  // Access 1h stats
  if (stats['1h']) {
    console.log(`1h buyers: ${stats['1h'].buyers}, sellers: ${stats['1h'].sellers}`);
  }
});
 
// Subscribe to pool statistics
datastream.subscribe.stats.pool("poolId").on((stats) => {
  console.log('Pool stats update:');
 
  // Access 5m stats
  if (stats['5m']) {
    console.log(`5m volume: $${stats['5m'].volume.total.toLocaleString()}`);
    console.log(`5m wallets: ${stats['5m'].wallets}`);
  }
});
 
// NEW - Wallet balance tracking
// Note: Direct .on() usage is deprecated
datastream.subscribe.tx
  .wallet("walletAddress")
  .transactions()
  .on((tx) => {
    console.log(`Transaction: ${tx.type} ${tx.amount} tokens`);
  });
 
// Monitor all token balances for a wallet
datastream.subscribe.tx
  .wallet("walletAddress")
  .balance()
  .on((update) => {
    console.log(`Balance changed for token ${update.token}: ${update.amount}`);
  });
 
// Monitor specific token balance
datastream.subscribe.tx
  .wallet("walletAddress")
  .tokenBalance("tokenMint")
  .on((update) => {
    console.log(`Token balance is now: ${update.amount}`);
  });
 
// NEW - Snipers tracking
datastream.subscribe.snipers("tokenAddress").on((update) => {
  console.log(
    `Sniper ${update.wallet.slice(
      0,
      8
    )}... now holds ${update.percentage.toFixed(2)}%`
  );
});
 
// NEW - Insiders tracking
datastream.subscribe.insiders("tokenAddress").on((update) => {
  console.log(
    `Insider ${update.wallet.slice(
      0,
      8
    )}... now holds ${update.percentage.toFixed(2)}%`
  );
});
 
// NEW - Developer Holdings tracking
datastream.subscribe.token("tokenAddress").dev.holding().on((update) => {
  console.log('Developer Holdings Update:');
  console.log(`  Wallet: ${update.creator.slice(0, 8)}...`);
  console.log(`  Current: ${update.percentage.toFixed(4)}%`);
  console.log(`  Previous: ${update.previousPercentage.toFixed(4)}%`);
  
  const change = update.percentage - update.previousPercentage;
  
  // Trigger alerts based on developer behavior
  if (change < -10) {
    // Major sell - potential rug pull
    sendAlert('CRITICAL', `Dev dumped ${Math.abs(change).toFixed(2)}% of supply`);
  } else if (change < -5) {
    // Moderate sell
    sendAlert('WARNING', `Dev sold ${Math.abs(change).toFixed(2)}% of supply`);
  } else if (change < 0) {
    // Minor sell
    console.log(`Dev reduced position by ${Math.abs(change).toFixed(2)}%`);
  } else if (change > 5) {
    // Developer buying - bullish signal
    console.log(`✅ Dev increased position by ${change.toFixed(2)}%`);
  }
});
 
// NEW - Top 10 Holders tracking
datastream.subscribe.token("tokenAddress").top10().on((update) => {
  console.log(`Top 10 Holders Analysis:`);
  console.log(`  Total control: ${update.totalPercentage.toFixed(2)}%`);
  
  // Show top 3 whales
  console.log('\n  Top 3 Whales:');
  update.holders.slice(0, 3).forEach((holder, i) => {
    console.log(`    #${i + 1}: ${holder.percentage.toFixed(2)}%`);
  });
  
  // Concentration risk scoring
  let riskScore = 0;
  let riskLevel = 'LOW';
  
  if (update.totalPercentage > 50) {
    riskScore = 9;
    riskLevel = 'CRITICAL';
  } else if (update.totalPercentage > 40) {
    riskScore = 7;
    riskLevel = 'HIGH';
  } else if (update.totalPercentage > 30) {
    riskScore = 5;
    riskLevel = 'MODERATE';
  } else if (update.totalPercentage > 20) {
    riskScore = 3;
    riskLevel = 'LOW';
  } else {
    riskScore = 1;
    riskLevel = 'MINIMAL';
  }
  
  console.log(`\n  Concentration Risk: ${riskLevel} (Score: ${riskScore}/10)`);
  
  // Check for changes
  if (update.previousPercentage !== null) {
    const change = update.totalPercentage - update.previousPercentage;
    if (Math.abs(change) > 10) {
      console.log(`  ⚠️ Major redistribution: ${change > 0 ? '+' : ''}${change.toFixed(2)}%`);
    }
  }
});
 
// Clean disconnection
datastream.disconnect();

View full SDK documentation →