> ## 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: Multiple RPC endpoints for dApp performance

> Configure multiple Solana RPC endpoints with load balancing and failover for improved dApp reliability and performance using Chainstack node access.

**TLDR:**

* Use multiple Solana RPC endpoints to reduce latency and avoid single points of failure.
* A distributed setup ensures higher availability and faster data updates, especially for real-time balance monitoring.
* This sample Node.js code with a connection pool approach demonstrates how to subscribe to account balance changes across different geographic locations simultaneously.
* Environment variables keep your endpoints secure while allowing easy setup and configuration.

## Main article

This guide will explore how a distributed approach to querying the Solana blockchain can reduce latency and provide a more resilient infrastructure for your DApps. Whether you're a blockchain enthusiast or a seasoned developer looking to optimize your Solana-based projects, this tutorial is crafted to offer valuable insights and practical steps to achieve a seamless and efficient blockchain interaction experience.

### A brief overview of Solana's architecture

Solana stands out in the blockchain world for its unprecedented throughput and minimal transaction costs stemming from its innovative architecture. The Proof of History (PoH) consensus mechanism is at the heart of Solana's efficiency. This unique solution enables greater scalability by creating a historical record proving that an event has occurred at a specific time. This, coupled with the blockchain's underlying Proof of Stake (PoS) consensus, allows Solana to process thousands of transactions per second without compromising decentralization or security.

## A simple project

At the heart of this tutorial lies a practical, hands-on example that will guide us through the intricacies of interacting with Solana's network using multiple RPC endpoints.

We will develop a robust piece of JavaScript code to manage a pool of Solana connections. This code establishes connections to several RPC endpoints across different geographic locations and intelligently distributes operations amongst these endpoints to achieve optimal performance and reliability. Through this example, we aim to listen to the balance changes of a specific Solana account from multiple nodes in parallel. Using multiple nodes in different regions simultaneously can improve reliability and performance, and the first node to get the updated data will win the race.

### The importance of multiple endpoints

The traditional approach of interacting with the blockchain through a single RPC endpoint can be a bottleneck for your application. This is where the strategy of using multiple endpoints across various geographic locations comes into play, offering several key advantages:

* **Redundancy**: By having your application connected to multiple nodes, you mitigate the risk of a single point of failure. If one node goes down or becomes unreachable, your application can seamlessly switch to another, ensuring uninterrupted service. This is because each node is physically running on a different machine.
* **Reduced Latency**: Latency can vary significantly based on the geographic distance between your user and the RPC endpoint. Generally, the endpoint closer to the user will respond faster, but sometimes, another node may pick up new data sooner.
* **Higher Availability**: Different nodes may have varying loads or maintenance schedules. Running multiple endpoints ensures that your application always has access to at least one up-to-date and responsive node, thereby improving the reliability of your service.

Throughout this tutorial, we'll guide you through setting up a Solana connection pool to interact with multiple RPC endpoints efficiently. We aim to empower your applications to leverage Solana's high-performance blockchain most effectively, ensuring users enjoy a fast, reliable, and seamless experience.

## Prerequisites

Before we get into the code, ensuring you have the right tools and setup is crucial. Let’s walk through everything you’ll need:

### Environment setup

To interact with the Solana blockchain and execute the scripts you'll write, certain environmental setups are necessary:

* **Node.js**: Install Node.js (version 18 or above) on your machine. Node.js is fundamental for running our scripts and managing dependencies. You can download it from [the official Node.js website](https://nodejs.org/).
* **npm (Node Package Manager)**: npm is the default package manager for Node.js and is indispensable for installing the libraries our project requires. It comes bundled with Node.js, so there's no separate installation process.

### Deploy a Chainstack Solana node

Deploy multiple [reliable Solana RPC endpoints](https://chainstack.com/build-better-with-solana/). Note that you need to be on a [paid plan](https://chainstack.com/pricing/) to deploy multiple nodes.

1. [Sign up with Chainstack](https://console.chainstack.com/user/account/create).
2. [Deploy a node](/docs/manage-your-networks).
3. [View node access and credentials](/docs/manage-your-node#view-node-access-and-credentials).

### Installing required packages

Our project relies on a Solana JavaScript library plus `dotenv` (which loads environment variables from a `.env` file into `process.env`, keeping RPC URLs out of your code). This guide shows both Solana JS libraries side by side — both are actively maintained, so use whichever fits your project:

* **`@solana/kit`** — the newer, tree-shakable, functional SDK. Account subscriptions are exposed through a websocket client (`createSolanaRpcSubscriptions`).
* **`@solana/web3.js`** — the classic object-oriented API, where a `Connection` object carries both HTTP and websocket calls (`onAccountChange`).

To install, navigate to your project directory and run:

<Tabs>
  <Tab title="@solana/kit">
    ```bash Bash theme={"system"}
    npm install @solana/kit dotenv
    ```
  </Tab>

  <Tab title="@solana/web3.js (classic)">
    ```bash Bash theme={"system"}
    npm install @solana/web3.js dotenv
    ```
  </Tab>
</Tabs>

Then add this line to your `package.json` file:

<CodeGroup>
  ```jsx jsx theme={"system"}
    "type": "module",
  ```
</CodeGroup>

### Setting up environment variables

We'll use environment variables to manage our RPC endpoints and other sensitive information. This method keeps our credentials secure and makes our application easily configurable without hardcoding sensitive data:

1. Create a `.env` file in the root of your project directory.
2. Add your Solana RPC URLs and sensitive information to the `.env` file. For instance:

```
# HTTPS endpoints
SOLANA_RPC_NODE_1="YOUR_CHAINSTACK_RPC"
SOLANA_RPC_NODE_3="YOUR_CHAINSTACK_RPC"
SOLANA_RPC_NODE_2="YOUR_CHAINSTACK_RPC"

# WSS endpoints
SOLANA_WSS_NODE_1="YOUR_CHAINSTACK_WSS"
SOLANA_WSS_NODE_3="YOUR_CHAINSTACK_WSS"
SOLANA_WSS_NODE_2="YOUR_CHAINSTACK_WSS"
```

### Additional considerations

* **Security**: Always keep your private keys and sensitive information secure. Do not commit your `.env` file or any files containing sensitive data to version control. Make sure to include the `.env` file in your `.gitignore`.

With these prerequisites out of the way, you're now set to dive into the code.

## The code

Now that we are set, create a new file in your project named `index.js` and paste the following code into it.

<Tabs>
  <Tab title="@solana/kit">
    ```javascript Javascript theme={"system"}
    import { createSolanaRpcSubscriptions, address } from "@solana/kit";
    import "dotenv/config";

    // Account subscriptions are websocket-only, so each pool entry needs a WSS URL.
    class SubscriptionPool {
        constructor(endpoints) {
            this.subscribers = endpoints.map(endpoint => ({
                wssUrl: endpoint.wss,
                rpcSubscriptions: createSolanaRpcSubscriptions(endpoint.wss),
            }));
        }
    }

    let latestLoggedBalance = null; // Shared state to track the latest logged balance

    async function listenForBalanceChanges(publicKey, subscriptionPool, abortSignal) {
        const account = address(publicKey);

        // Run one subscription per endpoint concurrently; the first node to see the
        // new data logs it first.
        await Promise.all(subscriptionPool.subscribers.map(async ({ rpcSubscriptions, wssUrl }) => {
            console.log(`Setting up balance change listener for node: ${wssUrl.slice(0,33)}`);

            // subscribe() resolves to an async iterable of notifications
            const notifications = await rpcSubscriptions
                .accountNotifications(account, { commitment: "confirmed" })
                .subscribe({ abortSignal });

            for await (const notification of notifications) {
                const newBalance = notification.value.lamports; // bigint in @solana/kit

                // Check if this balance change has already been logged
                if (latestLoggedBalance !== newBalance) {
                    const dateTimeString = new Date().toISOString(); // ISO 8601 format

                    console.log(`[${dateTimeString}] Balance change detected on node: ${wssUrl.slice(0,33)}`);
                    console.log(`[${dateTimeString}] New balance: ${newBalance} lamports`);
                    console.log("======================================================================");

                    // Update the shared state with the new balance
                    latestLoggedBalance = newBalance;
                }
            }
        }));
    }

    (async () => {
        const nodeEndpoints = [
            { wss: process.env.SOLANA_WSS_NODE_1 },
            { wss: process.env.SOLANA_WSS_NODE_2 },
            { wss: process.env.SOLANA_WSS_NODE_3 },
        ];

        const subscriptionPool = new SubscriptionPool(nodeEndpoints);
        const publicKey = "G98hD3T33SiJa8WcWgJ9coT5fz1F3ciwJjKnecxxd3Bi"; // Replace with the address you're interested in

        // Listen for balance changes on all nodes; call abortController.abort() to stop
        const abortController = new AbortController();
        await listenForBalanceChanges(publicKey, subscriptionPool, abortController.signal);
    })();
    ```
  </Tab>

  <Tab title="@solana/web3.js (classic)">
    ```javascript Javascript theme={"system"}
    import { Connection, PublicKey } from '@solana/web3.js';
    import 'dotenv/config';

    class ConnectionPool {
        constructor(endpoints) {
            this.connections = endpoints.map(endpoint => ({
                rpcUrl: endpoint.rpc,
                connection: new Connection(endpoint.rpc, { wsEndpoint: endpoint.wss }),
            }));
        }
    }

    let latestLoggedBalance = null; // Shared state to track the latest logged balance

    function listenForBalanceChanges(publicKey, connectionPool) {
        connectionPool.connections.forEach(({ connection, rpcUrl }) => {
            console.log(`Setting up balance change listener for node: ${rpcUrl.slice(0,33)}`);
            connection.onAccountChange(new PublicKey(publicKey), (accountInfo) => {
                const newBalance = accountInfo.lamports;

                // Check if this balance change has already been logged
                if (latestLoggedBalance !== newBalance) {
                    const now = new Date();
                    const dateTimeString = now.toISOString(); // Converts current time to ISO 8601 format

                    console.log(`[${dateTimeString}] Balance change detected on node: ${rpcUrl.slice(0,33)}`);
                    console.log(`[${dateTimeString}] New balance: ${newBalance} lamports`);
                    console.log("======================================================================")

                    // Update the shared state with the new balance
                    latestLoggedBalance = newBalance;
                }
            }, 'confirmed');
        });
    }

    (async () => {
        const nodeEndpoints = [
            { rpc: process.env.SOLANA_RPC_NODE_1, wss: process.env.SOLANA_WSS_NODE_1 },
            { rpc: process.env.SOLANA_RPC_NODE_2, wss: process.env.SOLANA_WSS_NODE_2 },
            { rpc: process.env.SOLANA_RPC_NODE_3, wss: process.env.SOLANA_WSS_NODE_3 },
        ];

        const connectionPool = new ConnectionPool(nodeEndpoints);
        const publicKey = "G98hD3T33SiJa8WcWgJ9coT5fz1F3ciwJjKnecxxd3Bi"; // Replace with the address you're interested in

        // Listen for balance changes on all nodes
        listenForBalanceChanges(publicKey, connectionPool);
    })();
    ```
  </Tab>
</Tabs>

## Code breakdown

Both versions do the same thing — open one client per endpoint, subscribe to a single account across all of them, and log the first node to report each new balance. They differ only in how the subscription is opened and consumed. Here are the key components:

### Imports and environment variables

The `@solana/kit` version imports `createSolanaRpcSubscriptions` and `address`; the `@solana/web3.js` version imports `Connection` and `PublicKey`. Both load `dotenv/config` to read the endpoints from `.env` and keep them out of the code.

### The connection/subscription pool

Each pool entry holds one client per endpoint:

* `@solana/web3.js` builds a `Connection` per node, passing the HTTP RPC URL plus `{ wsEndpoint }` for its websocket.
* `@solana/kit` builds a `createSolanaRpcSubscriptions` client per node. Account subscriptions are websocket-only, which is why the `@solana/kit` pool takes just the `wss` URL for each endpoint.

### Shared state for balance tracking

Both keep a single `latestLoggedBalance` so the same balance isn't logged twice when multiple nodes report the same change. In `@solana/kit` this value is a `bigint`; in `@solana/web3.js` it's a `number`.

### Listening for balance changes

This is the core API difference between the two libraries:

* `@solana/web3.js` registers a **callback**: `connection.onAccountChange(publicKey, (accountInfo) => { ... }, 'confirmed')`, where `accountInfo.lamports` is a `number`.
* `@solana/kit` opens an **async iterable**: `await rpcSubscriptions.accountNotifications(account, { commitment: 'confirmed' }).subscribe({ abortSignal })`, then consumes it with `for await (const notification of notifications)`. Each `notification.value.lamports` is a `bigint`. Running one subscription per endpoint under `Promise.all` keeps them all listening concurrently, and the shared `abortSignal` tears them all down cleanly.

### Main execution flow

Both define the endpoints from environment variables, build the pool, choose a public key to watch, and start listening. The `@solana/kit` version additionally creates an `AbortController` and awaits the listeners; call `abortController.abort()` to stop every subscription at once.

## Run the script

To run the script, make sure to place your HTTPS and WSS endpoints in the `.env` file, then run the start command in the console:

<CodeGroup>
  ```jsx jsx theme={"system"}
  node index
  ```
</CodeGroup>

This will start the program and log any new balance change in real-time, like the following:

<CodeGroup>
  ```jsx jsx theme={"system"}
  Setting up balance change listener for node: https://solana-mainnet.core.chain
  Setting up balance change listener for node: https://nd-094-012-520.p2pify.com
  Setting up balance change listener for node: https://nd-445-788-065.p2pify.com
  [2024-04-08T21:00:49.290Z] Balance change detected on node: https://nd-445-788-065.p2pify.com
  [2024-04-08T21:00:49.290Z] New balance: 1104861848 lamports
  ======================================================================
  [2024-04-08T21:00:49.927Z] Balance change detected on node: https://nd-445-788-065.p2pify.com
  [2024-04-08T21:00:49.927Z] New balance: 1104856848 lamports
  ======================================================================
  [2024-04-08T21:00:51.788Z] Balance change detected on node: https://nd-445-788-065.p2pify.com
  [2024-04-08T21:00:51.788Z] New balance: 1104851848 lamports
  ======================================================================
  [2024-04-08T21:00:52.841Z] Balance change detected on node: https://nd-094-012-520.p2pify.com
  [2024-04-08T21:00:52.841Z] New balance: 1104846848 lamports
  ======================================================================
  [2024-04-08T21:00:54.139Z] Balance change detected on node: https://nd-094-012-520.p2pify.com
  [2024-04-08T21:00:54.139Z] New balance: 1104841848 lamports
  ======================================================================
  ```
</CodeGroup>

## Conclusion

To wrap up this tutorial, we've explored the world of leveraging multiple RPC endpoints across different geographical locations to enhance the reliability and performance of Solana-based applications. We've demonstrated a pragmatic approach to optimizing blockchain interactions for a seamless user experience by implementing a real-time connection pool to listen for account balance changes. This method minimizes latency, mitigates the risk of single points of failure, and ensures your application remains resilient and responsive under varying network conditions.

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