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();