> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Solana: Geyser and Yellowstone gRPC in Node.js

> Stream real-time Solana transactions over gRPC using the Yellowstone client in Node.js with a Chainstack Geyser-enabled node for low-latency program data.

## 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

<Info>
  Chainstack Solana nodes have Jito ShredStream enabled by default, providing faster and more reliable real-time data streaming. See [Yellowstone gRPC Geyser plugin](/docs/yellowstone-grpc-geyser-plugin) for details.
</Info>

## Prerequisites

* Get a [reliable Solana RPC endpoint](https://chainstack.com/build-better-with-solana/) with the [Yellowstone gRPC Geyser plugin](/docs/yellowstone-grpc-geyser-plugin).
* Install [Yellowstone Node.js gRPC client](https://www.npmjs.com/package/@triton-one/yellowstone-grpc).

## 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

```js theme={"system"}
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

Note the use of ping every 10 seconds to keep the connection alive as specified in the docs: [Sending pings to keep the stream alive](https://docs.triton.one/project-yellowstone/dragons-mouth-grpc-subscriptions#sending-pings-to-keep-the-stream-alive).

```js theme={"system"}
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
const PING_INTERVAL_MILLISECONDS = 10000; // Send ping every 10 seconds

// DEX Program IDs to watch
const DEX_PROGRAM_IDS = [
    "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
    "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
    "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
    "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
    "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C",
    "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
    "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB",
    "MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG",
    "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X",
    "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"
];

async function main() {
    const client = new Client(ENDPOINT, TOKEN);

    try {
        console.log('Connecting to Solana...');
        const stream = await client.subscribe();

        // Prepare ping request
        const pingRequest = {
            ping: { id: 1 },
            accounts: {},
            accountsDataSlice: [],
            transactions: {},
            transactionsStatus: {},
            blocks: {},
            blocksMeta: {},
            entry: {},
            slots: {},
        };

        // Set up periodic ping to keep the stream alive
        const pingInterval = setInterval(async () => {
            try {
                await new Promise((resolve, reject) => {
                    stream.write(pingRequest, (err) => {
                        if (err === null || err === undefined) {
                            resolve();
                        } else {
                            reject(err);
                        }
                    });
                });
                console.log("Ping sent to keep stream alive");
            } catch (error) {
                console.error("Error sending ping:", error);
            }
        }, PING_INTERVAL_MILLISECONDS);

        const request = {
            accounts: {},
            slots: {},
            transactions: {
                dex: {
                    vote: false,
                    failed: false,
                    accountExclude: [],
                    accountRequired: [],
                    accountInclude: DEX_PROGRAM_IDS
                }
            },
            transactionsStatus: {},
            entry: {},
            blocks: {},
            blocksMeta: {},
            commitment: 'confirmed',
            accountsDataSlice: [],
        };

        stream.write(request);
        console.log('Connection established - watching DEX transactions\n');
        console.log('Monitoring programs:', DEX_PROGRAM_IDS);

        stream.on('data', (data) => {
            // Handle pong responses
            if (data.pong) {
                console.log("Received Pong response");
                return;
            }

            if (data.transaction && data.transaction.transaction) {
                const tx = data.transaction;
                try {
                    // Convert signature to string
                    const signature = tx.transaction.signature.toString('hex');
                    
                    // Find which DEX 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);
                }
            }
        });

        stream.on("error", (error) => {
            console.error('Stream error:', error);
            clearInterval(pingInterval); // Clean up the ping interval on error
        });

        // Clean up on stream close
        stream.on("close", () => {
            console.log("Stream closed");
            clearInterval(pingInterval);
        });

    } catch (error) {
        console.error('Error in subscription process:', error);
    }
}

// Run the main function
main().catch((err) => {
    console.error('Unhandled error in main:', err);
});

// Handle shutdown
process.on('SIGINT', () => {
    console.log('Shutting down...');
    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](/docs/solana-listening-to-pumpfun-token-mint-using-geyser).

<CardGroup>
  <Card title="Ake">
    <img src="https://mintcdn.com/chainstack/UN3rP7zhB69idvnC/images/docs/profile_images/1719912994363326464/8_Bi4fdM_400x400.jpg?fit=max&auto=format&n=UN3rP7zhB69idvnC&q=85&s=792a24ab1b4682406fa589c0ecd88e5d" alt="Ake" style={{width: '80px', height: '80px', borderRadius: '50%', objectFit: 'cover', display: 'block', margin: '0 auto'}} noZoom width="400" height="400" data-path="images/docs/profile_images/1719912994363326464/8_Bi4fdM_400x400.jpg" />

    <Icon icon="code" iconType="solid" /> Director of Developer Experience @ Chainstack
    <br /><Icon icon="screwdriver-wrench" iconType="solid" /> Talk to me all things Web3
    <br />20 years in technology | 8+ years in Web3 full time years experience

    <div style={{display: "flex", justifyContent: "center", gap: "12px"}}>
      <a href="https://github.com/akegaviar/" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="github" iconType="brands" />
      </a>

      <a href="https://twitter.com/akegaviar" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="twitter" iconType="brands" />
      </a>

      <a href="https://www.linkedin.com/in/ake/" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="linkedin" iconType="brands" />
      </a>
    </div>
  </Card>
</CardGroup>
