Why Real-Time Matters
Token prices move in milliseconds. New tokens launch every minute on platforms like Pump.fun. If your dApp shows stale data, users leave. Real-time streaming is not optional — it is the baseline expectation for any trading, portfolio, or analytics interface on Solana.
This guide shows how to build a real-time token tracking pipeline: Solana RPC subscriptions feed a NestJS WebSocket gateway, which pushes updates to a Next.js frontend. This is the same architecture behind the Cilantro Terminal we built at Cyber Vision.
Architecture
Solana RPC (WebSocket)
│ onAccountChange / onLogs
▼
NestJS WebSocket Gateway
│ Socket.io events
▼
Next.js Client
│ useEffect + socket.io-client
▼
Live UI (price feeds, token launches, balances)
Prerequisites
- Node.js 18+
- A Solana RPC with WebSocket support (all major providers include it)
- Basic Next.js and NestJS knowledge
1. Solana WebSocket Subscriptions
Solana's JSON-RPC exposes several subscription methods over WebSocket:
| Method | Use Case |
|---|---|
onAccountChange | Watch a specific account (balance, token account) |
onLogs | Watch program logs (new swaps, mints, transfers) |
onProgramAccountChange | Watch all accounts owned by a program |
onSlotChange | Track new slots (block production) |
Subscribing to Account Changes
import { Connection, PublicKey } from "@solana/web3.js";
const connection = new Connection("wss://api.devnet.solana.com", "confirmed");
const tokenAccount = new PublicKey("TOKEN_ACCOUNT_ADDRESS");
const subId = connection.onAccountChange(tokenAccount, (accountInfo) => {
console.log("Account updated:", {
lamports: accountInfo.lamports,
dataLength: accountInfo.data.length,
owner: accountInfo.
Watching Program Logs
const programId = new PublicKey("PROGRAM_ID");
connection.onLogs(programId, (logs) => {
console.log("Transaction:", logs.signature);
console.log("Logs:", logs.logs);
if (logs.logs.some(log => log.includes("Transfer"))) {
console.log("Token transfer detected!");
}
});Most RPC providers limit WebSocket subscriptions to 100-200 per connection. For high-throughput monitoring (watching thousands of token accounts), use Helius webhooks or a Geyser plugin instead of raw WebSocket subscriptions.
2. NestJS WebSocket Gateway
The NestJS gateway acts as a bridge: it subscribes to Solana events and pushes them to connected frontend clients via Socket.io.
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io// src/gateway/token.gateway.ts
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
MessageBody,
ConnectedSocket,
} from "@nestjs/websockets";
import { Server, Socket } from "socket.io";
import { Logger } from "@nestjs/common";
import { Connection, PublicKey } from "@solana/web3.js";
@WebSocketGateway({
cors: { origin: "*" },
namespace: "/tokens",
})
export class TokenGateway
Use Socket.io rooms to group clients watching the same account. This way, one Solana subscription serves all clients interested in that account, keeping the RPC subscription count low.
3. Token Price Tracking Service
For token prices, parse SPL token account data to extract balances and compute prices from liquidity pool reserves:
// src/services/price.service.ts
import { Injectable, Logger } from "@nestjs/common";
import { Connection, PublicKey } from "@solana/web3.js";
import { AccountLayout } from "@solana/spl-token";
@Injectable()
export class PriceService {
private readonly logger = new Logger(PriceService.name);
constructor(private readonly connection: Connection) {}
async getPoolReserves(poolAddress: string) {
const accountInfo
4. Next.js Frontend Client
Install the Socket.io client:
npm install socket.io-clientCustom Hook for Real-Time Data
// hooks/useSolanaSocket.ts
"use client";
import { useEffect, useState, useCallback, useRef } from "react";
import { io, Socket } from "socket.io-client";
interface AccountUpdate {
address: string;
lamports: number;
data: string;
timestamp: number;
}
export function useSolanaSocket(address: string | null) {
const [data, setData] = useState
Live Balance Component
"use client";
import { useSolanaSocket } from "@/hooks/useSolanaSocket";
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
export function LiveBalance({ address }: { address: string }) {
const { data, connected } = useSolanaSocket(address);
const balance = data ? data.lamports / LAMPORTS_PER_SOL : null;
return (
<div className="flex items-center gap-2">
<span className=
5. Handling Reconnection and Backpressure
WebSocket connections drop. RPC nodes restart. Your app must handle this gracefully.
// Reconnection with exponential backoff
socket.on("disconnect", (reason) => {
if (reason === "io server disconnect") {
socket.connect(); // Reconnect immediately
}
// Otherwise, socket.io auto-reconnects with backoff
});
// Re-subscribe after reconnection
socket.on("connect", () => {
for (const address of watchedAddresses) {
socket.emit("subscribe:account", { address });
}
});For backpressure on the server side, buffer events and throttle:
import { throttle } from "lodash";
const emitThrottled = throttle((address: string, data: any) => {
this.server.to(address).emit("account:update", data);
}, 500); // Max 2 updates per second per accountPerformance Tips
| Concern | Solution |
|---|---|
| Too many subscriptions | Use rooms to share one RPC subscription across clients |
| High-frequency updates | Throttle emits to 2-5 per second |
| Stale data on reconnect | Fetch latest state via REST on reconnect, then switch to WebSocket |
| Memory leaks | Clean up subscriptions when rooms empty |
| RPC limits | Use dedicated WebSocket endpoints from Helius or QuickNode |
Wrapping Up
Real-time data transforms a static dashboard into a living trading terminal. The pattern — Solana WebSocket subscriptions, NestJS gateway as a bridge, Socket.io to the Next.js frontend — handles everything from balance monitors to full token launch aggregators.
For the NestJS backend architecture, see our NestJS + Solana guide. For the React wallet integration, check Solana React Basics.