Skip to main content

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

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

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);
})();
This will print the connection status and the Geyser client version.

Program watcher

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();
}); 
This will stream the data from the Solana program IDs as provided in DEX_PROGRAM_IDS. For a Python example, see Solana: Listening to pump.fun token mint using Geyser.
8_Bi4fdM_400x400

Ake

Director of Developer Experience @ Chainstack Talk to me all things Web320 years in technology | 8+ years in Web3 full time years experienceTrusted advisor helping developers navigate the complexities of blockchain infrastructure