Subscriptions
Live launch, migration, and trade events.
APIs
| API | Scope | addTokens() |
|---|---|---|
subscribeLaunches() | Global | Not needed |
subscribeMigrations() | Global | Updates the local interest set |
subscribeTrades() | Per token | Adds logical Solana log subscriptions |
One Solana Connection can carry many logical subscriptions. Watching more trade mints increases subscription count even when the transport stays the same.
Launch → migration → trades
#!/usr/bin/env bun
import { Connection } from "@solana/web3.js";
import { configure, createMeasure } from "measure-fn";
import {
subscribeLaunches,
subscribeMigrations,
subscribeTrades,
type TradeEvent,
} from "@solard/sdk";
configure({ silent: false });
const m = createMeasure("slrd:subscriptions-example", {
maxResultLength: 2400,
});
function rpcUrl(): string {
const value =
process.env.RPC_ENDPOINT?.trim() ??
process.env.SOLANA_RPC_URL?.trim() ??
process.env.HELIUS_RPC_URL?.trim();
if (!value) {
throw new Error(
"Set RPC_ENDPOINT, SOLANA_RPC_URL, or HELIUS_RPC_URL before running this example.",
);
}
return value;
}
function printableTrade(event: TradeEvent) {
return {
...event,
baseRaw: event.baseRaw?.toString() ?? null,
quoteRaw: event.quoteRaw?.toString() ?? null,
virtualBaseRaw: event.virtualBaseRaw?.toString() ?? null,
virtualQuoteRaw: event.virtualQuoteRaw?.toString() ?? null,
};
}
const wsEndpoint =
process.env.SOLANA_WS_URL?.trim() ?? process.env.HELIUS_WS_URL?.trim();
const connection = new Connection(
rpcUrl(),
wsEndpoint ? { commitment: "confirmed", wsEndpoint } : "confirmed",
);
const controller = new AbortController();
const trades = await subscribeTrades({
connection,
tokens: [],
metadata: false,
signal: controller.signal,
onTrade(event) {
const value = printableTrade(event);
m.sync(
{ start: () => "trade", end: (result: typeof value) => result },
() => value,
);
},
});
const migrations = await subscribeMigrations({
connection,
tokens: [],
metadata: "chain",
signal: controller.signal,
async onMigration(event) {
m.sync(
{ start: () => "migration", end: (result: typeof event) => result },
() => event,
);
await trades.addTokens(event.mint);
},
});
const launches = await subscribeLaunches({
connection,
metadata: "chain",
signal: controller.signal,
async onLaunch(event) {
if (event.isMayhemMode === true) return;
m.sync(
{ start: () => "launch", end: (result: typeof event) => result },
() => event,
);
await migrations.addTokens(event.mint);
},
});
m.sync(
{
start: () => "listening",
end: (value: Record<string, unknown>) => value,
},
() => ({
flow: "launch -> migration -> trades",
mayhem: "ignored",
launchMetadata: "chain",
migrationMetadata: "chain",
tradeMetadata: false,
}),
);
const stop = () => controller.abort();
process.once("SIGINT", stop);
process.once("SIGTERM", stop);
try {
await Promise.all([launches.closed, migrations.closed, trades.closed]);
} finally {
process.removeListener("SIGINT", stop);
process.removeListener("SIGTERM", stop);
await Promise.allSettled([
launches.close(),
migrations.close(),
trades.close(),
]);
}Flow
launch
└─ accepted → migrations.addTokens(mint)
migration
└─ observed → trades.addTokens(mint)
trade
└─ onTrade(event)The handles are created in the opposite order—trades, migrations, launches—because each upstream callback needs the downstream handle.
Lifecycle
| Code | Purpose |
|---|---|
Connection | Application-owned RPC and WebSocket transport. |
AbortController | One cancellation signal for all streams. |
closed | Promise that resolves when a stream ends. |
close() | Stops a stream and releases its subscriptions. |
Promise.allSettled() | Attempts every cleanup even if one close fails. |
Metadata
| Value | Result |
|---|---|
false | Decoded event only. |
"chain" | Add on-chain metadata. |
"full" | Add external metadata when available. |
The example keeps trades unenriched because they are high-frequency, while launch and migration events use chain metadata.
Policy
isMayhemMode is event data. The line if (event.isMayhemMode === true) return is application policy. printableTrade() is presentation code: it converts bigint values only when logging them.
Servers
HTTP routes, WebSocket client protocols, auth, fan-out, persistence, backpressure, and health checks belong above these subscriptions.