Overview
This quick guide shows you how to:- Check the Geyser connection using the @triton-one/yellowstone-grpc client in Node.js
- Stream the programs using the @triton-one/yellowstone-grpc client in Node.js
Prerequisites
- Get the Yellowstone gRPC Geyser plugin.
- Install Yellowstone Node.js gRPC client.
Implementation
Once you have the Chainstack Yellowstone gRPC Geyser endpoint and the authentication token, use the following examples to check the connection and to monitor programs.Connection checker
Copy
const { default: Client } = require("@triton-one/yellowstone-grpc");
const ENDPOINT = "CHAINSTACK_GEYSER_URL"; // Replace with your actual endpoint
const TOKEN = "CHAINSTACK_GEYSER_TOKEN"; // Replace with your actual token
(async () => {
const client = new Client(ENDPOINT, TOKEN);
const version = await client.getVersion();
console.log(version);
})();
Program watcher
Copy
const { default: Client } = require("@triton-one/yellowstone-grpc");
const Base58 = require('bs58');
const ENDPOINT = "CHAINSTACK_GEYSER_URL"; // Replace with your actual endpoint
const TOKEN = "CHAINSTACK_GEYSER_TOKEN"; // Replace with your actual token
// DEX Program IDs to watch
const DEX_PROGRAM_IDS = [
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
"CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
"CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C",
"LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
"Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB",
"MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG",
"FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X",
"whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"
];
// Configuration for reconnection
const MAX_RECONNECT_ATTEMPTS = 10;
const INITIAL_BACKOFF_MS = 1000;
const MAX_BACKOFF_MS = 30000;
// Client options with keepalive and stream settings
const CLIENT_OPTIONS = {
"grpc.keepalive_time_ms": 60000,
"grpc.http2.min_time_between_pings_ms": 60000,
"grpc.keepalive_timeout_ms": 30000,
"grpc.http2.max_pings_without_data": 0,
"grpc.keepalive_permit_without_calls": 1,
"grpc.max_receive_message_length": 100 * 1024 * 1024, // 100MB
"grpc.max_send_message_length": 100 * 1024 * 1024, // 100MB
"grpc.service_config": JSON.stringify({
"methodConfig": [{
"name": [{ "service": "geyser.Geyser" }],
"retryPolicy": {
"maxAttempts": 5,
"initialBackoff": "1s",
"maxBackoff": "10s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE", "INTERNAL"]
}
}]
})
};
// Track connection state
let isConnected = false;
let reconnectAttempts = 0;
let reconnectTimeout = null;
// The subscription request
const createSubscriptionRequest = () => ({
accounts: {},
slots: {},
transactions: {
dex: {
vote: false,
failed: false,
accountExclude: [],
accountRequired: [],
accountInclude: DEX_PROGRAM_IDS
}
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
commitment: 'confirmed',
accountsDataSlice: [],
ping: undefined,
});
async function connectWithRetry() {
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
console.error(`Exceeded maximum reconnection attempts (${MAX_RECONNECT_ATTEMPTS}). Exiting.`);
process.exit(1);
}
try {
// Clear any existing reconnect timeout
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
console.log(`Connection attempt ${reconnectAttempts + 1}/${MAX_RECONNECT_ATTEMPTS}`);
await subscribe();
// Reset reconnect attempts on successful connection
reconnectAttempts = 0;
isConnected = true;
} catch (error) {
isConnected = false;
reconnectAttempts++;
// Calculate backoff time with exponential backoff
const backoff = Math.min(
INITIAL_BACKOFF_MS * Math.pow(2, reconnectAttempts - 1),
MAX_BACKOFF_MS
);
console.error(`Connection failed. Retrying in ${backoff/1000} seconds...`);
console.error(`Error details: ${error.message}`);
// Schedule reconnection
reconnectTimeout = setTimeout(connectWithRetry, backoff);
}
}
async function subscribe() {
const client = new Client(
ENDPOINT,
TOKEN,
CLIENT_OPTIONS
);
console.log('Connecting to Solana...');
const stream = await client.subscribe();
console.log('Connection established - watching DEX transactions\n');
console.log('Monitoring programs:', DEX_PROGRAM_IDS);
// Send the subscription request
stream.write(createSubscriptionRequest());
// Handle incoming data
stream.on('data', (data) => {
if (data.transaction && data.transaction.transaction) {
const tx = data.transaction;
try {
// Convert signature to string
const signature = tx.transaction.signature.toString('hex');
// Find which program was involved in this transaction
let involvedPrograms = [];
if (tx.transaction.transaction.message.accountKeys) {
involvedPrograms = DEX_PROGRAM_IDS.filter(progId =>
tx.transaction.transaction.message.accountKeys.includes(progId));
}
console.log('New DEX Transaction:', {
signature: signature,
slot: tx.slot,
success: tx.transaction.meta?.err === null,
accounts: tx.transaction.transaction.message.accountKeys.length,
instructions: tx.transaction.transaction.message.instructions.length,
lamportFee: tx.transaction.meta?.fee || 0,
computeUnits: tx.transaction.meta?.computeUnitsConsumed || 0,
involvedDEX: involvedPrograms
});
// Log transaction details
if (tx.transaction.meta?.logMessages) {
console.log('Transaction logs:');
tx.transaction.meta.logMessages.forEach(log => console.log(log));
}
console.log('----------------------------------------');
} catch (err) {
console.error('Error processing transaction:', err);
console.error('Raw signature:', tx.transaction.signature);
}
}
});
// Handle errors
stream.on("error", (error) => {
console.error(`Stream error: ${error.message}`);
console.error(`Error code: ${error.code}, details: ${error.details}`);
isConnected = false;
// Attempt reconnection for specific error types
if (error.code === 13 || // INTERNAL
error.code === 14 || // UNAVAILABLE
error.details?.includes('lagged') ||
error.details?.includes('disconnect')) {
console.log('Attempting to reconnect...');
connectWithRetry();
}
});
// Handle end of stream
stream.on('end', () => {
console.log('Stream ended');
isConnected = false;
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
console.log('Stream ended unexpectedly. Reconnecting...');
connectWithRetry();
}
});
return stream;
}
// Start the connection process
connectWithRetry().catch((err) => {
console.error('Unhandled error in main process:', err);
process.exit(1);
});
// Health check interval
const healthCheckInterval = setInterval(() => {
if (isConnected) {
console.log('Heartbeat - watching DEX transactions...');
} else {
console.log('Connection appears to be down. Reconnection logic should handle this.');
}
}, 30000);
// Handle shutdown
process.on('SIGINT', () => {
console.log('Shutting down...');
clearInterval(healthCheckInterval);
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
}
process.exit();
});
DEX_PROGRAM_IDS.
For a Python example, see Solana: Listening to pump.fun token mint using Geyser.
