@geeklad/solana-multi-rpc
v1.0.0
Published
A drop-in replacement for the @solana/web3.js Connection that uses multiple RPCs
Readme
@geeklad/solana-multi-rpc
A drop-in replacement for the @solana/web3.js Connection class that transparently spreads requests across multiple RPC endpoints with per-connection rate limiting and automatic retry logic.
Features
- Drop-in replacement — implements the full
Connectioninterface via ESProxy; swap it in without changing any call sites - Smart throttle-aware routing — tracks each endpoint's sliding-window request budget and routes to the next available connection
- Automatic retries — retries historical/archival methods (
getBlock,getSignaturesForAddress, etc.) on a different RPC when one fails or returns empty results - Piling prevention — pending request counts are updated synchronously before any
await, so concurrent callers always see accurate queue depth - Subscription passthrough — subscription methods (
onAccountChange,onSlotChange, …) are forwarded to the first connection
Installation
npm install @geeklad/solana-multi-rpcQuick Start
import MultiRpcConnection from "@geeklad/solana-multi-rpc";
import type { MultiRpcConnectionConfig } from "@geeklad/solana-multi-rpc";
const configs: MultiRpcConnectionConfig[] = [
{
endpoint: "https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY",
throttleOptions: { maxRequests: 10, timeWindowMs: 1000 },
commitment: "confirmed",
},
{
endpoint: "https://example.quicknode.pro/YOUR_ENDPOINT_TOKEN/",
throttleOptions: { maxRequests: 5, timeWindowMs: 1000 },
commitment: "confirmed",
},
{
endpoint: "https://api.mainnet-beta.solana.com",
throttleOptions: { maxRequests: 2, timeWindowMs: 1000 },
commitment: "confirmed",
},
];
const connection = new MultiRpcConnection(configs, {
maxRetries: 3,
retryDelayMs: 500,
});
// Use exactly like a normal Connection
const slot = await connection.getSlot();
const balance = await connection.getBalance(myPublicKey);See examples/usage.ts for a full working example.
API
MultiRpcConnectionConfig
Extends @solana/web3.js ConnectionConfig with two required fields:
| Field | Type | Description |
|---|---|---|
| endpoint | string | RPC HTTP URL |
| throttleOptions | object | Per-connection rate-limit budget (see sub-fields below) |
throttleOptions sub-fields:
| Field | Type | Description |
|---|---|---|
| maxRequests | number | Max requests allowed per time window |
| timeWindowMs | number | Rolling window duration in milliseconds |
All standard ConnectionConfig fields (commitment, wsEndpoint, httpHeaders, etc.) are also supported and forwarded to the underlying Connection.
new MultiRpcConnection(configs, options?)
| Parameter | Type | Default | Description |
|---|---|---|---|
| configs | MultiRpcConnectionConfig[] | — | One or more RPC configurations (at least one required) |
| options.maxRetries | number | 3 | Max retry attempts for retryable methods |
| options.retryDelayMs | number | 1000 | Delay between retry attempts in milliseconds |
| options.logLevel | LogLevel | "warn" | Built-in log filter level (see Logging) |
| options.logger | Logger | — | Custom logger instance; overrides logLevel (see Logging) |
Instance properties
| Property | Type | Description |
|---|---|---|
| endpoints | string[] | All configured RPC endpoint URLs |
| connectionCount | number | Number of configured connections |
| rpcEndpoint | string | Human-readable summary, e.g. MultiRPC[url1, url2] |
Routing Behavior
On each call the router:
- Finds the first immediately-available connection (estimated wait = 0 ms), starting from the current round-robin position.
- If all connections are throttled, picks the one with the shortest estimated wait and logs the wait time.
The estimated wait accounts for both active requests within the current window and requests already queued ahead in the pending backlog.
Retry Behavior
The following methods are automatically retried across different RPC endpoints on failure or empty results:
getBlockgetBlocksgetSignaturesForAddressgetTransactiongetParsedTransactiongetParsedTransactions
Errors that trigger a retry include network errors, timeouts, rate-limit responses, server errors, and connection resets. Non-retryable errors (e.g. invalid parameters) are thrown immediately.
getSignaturesForAddress additionally retries when a response is empty — useful when a provider has insufficient transaction history.
Logging
MultiRpcConnection includes a built-in logger with four levels:
| Level | What is emitted |
|---|---|
| "debug" | Per-request RPC selection, retry progress, throttle waits |
| "warn" | RPC failures, empty-result retries (default) |
| "error" | Non-retryable errors, all-RPCs-exhausted failures |
| "silent" | Nothing |
The default level is "warn" — noisy debug messages are suppressed out of the box. Disabled levels are pure no-ops (zero overhead; no string formatting or allocations).
Changing the log level
const connection = new MultiRpcConnection(configs, {
logLevel: "debug", // surface per-request routing and retry details
});Using a custom logger (pino, winston, etc.)
Pass any object that implements debug(), warn(), and error() methods. When a custom logger is provided, logLevel is ignored — configure filtering on your logger directly.
import pino from "pino";
const logger = pino({ level: "warn" });
const connection = new MultiRpcConnection(configs, { logger });import winston from "winston";
const logger = winston.createLogger({
level: "warn",
transports: [new winston.transports.Console()],
});
const connection = new MultiRpcConnection(configs, { logger });Silencing all output
const connection = new MultiRpcConnection(configs, {
logLevel: "silent",
});Subscriptions
Subscription methods (onAccountChange, onProgramAccountChange, onSlotChange, and the corresponding remove* methods) are always routed to the first configured connection. A debug-level message is logged when this happens.
Building
npm run build # compiles TypeScript to ./distLicense
Apache-2.0
