Every Solana DEX trade — Pump.fun swaps, Raydium pools, Jupiter routes, Orca whirlpools — happens on-chain and is observable in real-time. But parsing raw Solana transactions to extract trade data is complex, expensive, and error-prone.
MadeOnSol's DEX Trade Stream gives you a clean WebSocket feed of parsed DEX trades, filterable by token, wallet, program, or trade size. No gRPC setup, no transaction parsing, no Yellowstone plugin — just connect and subscribe.
This tutorial shows you how to build a real-time trade monitor from scratch.
What you'll build
A TypeScript application that:
- Connects to MadeOnSol's WebSocket DEX stream
- Subscribes with custom filters (token, wallet, or program)
- Processes trades in real-time
- Handles reconnection and error recovery
Prerequisites
- Node.js 18+
- A MadeOnSol API key — get one free (streaming requires Pro or Ultra)
- Basic TypeScript and WebSocket knowledge
Supported DEX programs
The stream covers 9 Solana DEX programs:
Step 1: Get a streaming token
The WebSocket URL requires a 24-hour token. Generate one via the REST API:
// src/get-token.ts
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({
apiKey: process.env.MADEONSOL_API_KEY!,
});
async function getStreamToken() {
const token = await client.stream.getToken();
console.log("WebSocket URL:", token.ws_url);
console.log("DEX Stream URL:", token.dex_ws_url); // Ultra only
console.log("Expires:", token.expires_at);
return token;
}
The response includes:
ws_url — KOL/deployer event stream (Pro/Ultra)
dex_ws_url — all-DEX trade stream (Ultra only)
token — JWT valid for 24 hours
Step 2: Connect to the DEX stream
// src/stream.ts
import WebSocket from "ws";
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({
apiKey: process.env.MADEONSOL_API_KEY!,
});
async function connectDexStream() {
const { dex_ws_url } = await client.stream.getToken();
if (!dex_ws_url) {
console.error("DEX stream requires Ultra subscription");
return;
}
const ws = new WebSocket(dex_ws_url);
ws.on("open", () => {
console.log("Connected to DEX stream");
// Subscribe with filters (at least one required)
ws.send(JSON.stringify({
type: "subscribe",
filters: {
program: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", // Pump.fun
min_sol: 0.5,
},
}));
});
ws.on("message", (data) => {
const trade = JSON.parse(data.toString());
if (trade.type === "dex:trade") {
console.log(
`[${trade.data.dex}] ${trade.data.action} ${trade.data.token_symbol || trade.data.token_mint.slice(0, 8)} — ` +
`${trade.data.sol_amount.toFixed(2)} SOL`
);
}
});
ws.on("close", () => {
console.log("Disconnected — reconnecting in 5s...");
setTimeout(connectDexStream, 5000);
});
ws.on("error", (err) => {
console.error("WebSocket error:", err.message);
});
}
connectDexStream();
Step 3: Filter options
The subscribe message requires at least one targeting filter:
// Track a specific token
ws.send(JSON.stringify({
type: "subscribe",
filters: {
token_mint: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", // BONK
},
}));
// Track multiple tokens (max 50)
ws.send(JSON.stringify({
type: "subscribe",
filters: {
token_mints: ["mint1...", "mint2...", "mint3..."],
min_sol: 1, // Only trades > 1 SOL
},
}));
// Track a specific wallet across all DEXes
ws.send(JSON.stringify({
type: "subscribe",
filters: {
wallet: "7xKXqmq...",
},
}));
// Track all Pump.fun trades above 2 SOL
ws.send(JSON.stringify({
type: "subscribe",
filters: {
program: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
min_sol: 2,
action: "buy", // Only buys
},
}));
Available filter fields
| Filter | Type | Description |
|---|
token_mint | string | Single token mint address |
token_mints | string[] | Up to 50 token mints |
wallet | string | Single wallet address |
wallets | string[] | Up to 50 wallet addresses |
program | string | DEX program ID |
min_sol | number | Minimum trade size in SOL |
max_sol | number | Maximum trade size in SOL |
action | "buy" | "sell" | Filter by trade direction |
Step 4: Trade data format
Each trade message looks like:
{
"type": "dex:trade",
"data": {
"signature": "5xYz...",
"wallet": "7xKX...",
"token_mint": "DezX...",
"token_symbol": null,
"action": "buy",
"sol_amount": 2.5,
"token_amount": 1500000,
"dex": "pumpfun",
"program_id": "6EF8...",
"slot": 312456789,
"timestamp": "2026-04-05T10:30:15Z"
}
}
Note: token_symbol and token_name are not included in the stream for performance. Resolve token metadata separately using a service like Helius DAS API or your own cache.
Step 5: Build a Pump.fun launch sniping monitor
Here's a practical example — monitor all new Pump.fun trades and flag tokens with high buy volume:
// src/pumpfun-monitor.ts
import WebSocket from "ws";
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: process.env.MADEONSOL_API_KEY! });
interface TokenStats {
buys: number;
sells: number;
totalSol: number;
uniqueBuyers: Set<string>;
firstSeen: number;
}
const tokens = new Map<string, TokenStats>();
async function monitor() {
const { dex_ws_url } = await client.stream.getToken();
if (!dex_ws_url) throw new Error("Ultra subscription required");
const ws = new WebSocket(dex_ws_url);
ws.on("open", () => {
ws.send(JSON.stringify({
type: "subscribe",
filters: {
program: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
min_sol: 0.1,
},
}));
console.log("Monitoring Pump.fun trades...");
});
ws.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.type !== "dex:trade") return;
const { token_mint, action, sol_amount, wallet } = msg.data;
if (!tokens.has(token_mint)) {
tokens.set(token_mint, {
buys: 0, sells: 0, totalSol: 0,
uniqueBuyers: new Set(),
firstSeen: Date.now(),
});
}
const stats = tokens.get(token_mint)!;
if (action === "buy") {
stats.buys++;
stats.uniqueBuyers.add(wallet);
} else {
stats.sells++;
}
stats.totalSol += sol_amount;
// Alert on tokens with 5+ unique buyers in the first 2 minutes
const age = (Date.now() - stats.firstSeen) / 1000;
if (stats.uniqueBuyers.size >= 5 && age < 120 && stats.buys === 5) {
console.log(
`[ALERT] ${token_mint.slice(0, 8)}... — ` +
`${stats.uniqueBuyers.size} buyers, ${stats.totalSol.toFixed(1)} SOL in ${age.toFixed(0)}s`
);
}
});
ws.on("close", () => setTimeout(monitor, 5000));
}
monitor();
Step 6: Combine with KOL data
The real power comes from combining the DEX stream with KOL intelligence. When a KOL buys a token you're monitoring:
// Periodically fetch KOL feed to cross-reference
setInterval(async () => {
const { trades } = await client.kol.feed({ limit: 20, action: "buy" });
for (const trade of trades) {
if (tokens.has(trade.mint)) {
const stats = tokens.get(trade.mint)!;
console.log(
`[KOL BUY] ${trade.kol_name} bought a token you're watching — ` +
`${stats.uniqueBuyers.size} unique buyers, ${stats.totalSol.toFixed(1)} SOL volume`
);
}
}
}, 30_000);
Pricing
| Feature | Pro ($49/mo) | Ultra ($199/mo) |
|---|
| KOL/Deployer WebSocket | 1 connection | 3 connections |
| DEX Trade Stream | No | 2 connections |
| REST API | 10K req/day | 100K req/day |
| Webhooks | 3 | 10 |
The free tier (100 req/day) lets you test the REST API. Streaming and webhooks require Pro or Ultra.
Get started at madeonsol.com/developer.
FAQ
What's the latency of the DEX stream?
Trades appear in the stream within 1-2 seconds of on-chain confirmation. The stream uses gRPC connections to Solana validator nodes for minimal latency.
Can I subscribe to multiple filters at once?
Yes. You can combine filters in a single subscription — for example, track 50 token mints with a minimum SOL filter. You can also send multiple subscribe messages to layer filters.
How does this compare to running my own Yellowstone gRPC?
MadeOnSol handles the infrastructure — gRPC connections to multiple validator nodes, transaction parsing across 9 DEX programs, reconnection logic, and data normalization. Running your own Yellowstone setup requires a dedicated server, validator relationships, and custom parsers for each DEX program.
Does the stream include token names and symbols?
No. For performance, the stream only includes mint addresses. Resolve metadata separately using Helius DAS API, Jupiter token list, or your own cache. This keeps the stream fast and lightweight.