@nvana-dharma/rpc-pooler
v1.0.0
Published
A resilient RPC connection pooler for Solana that automatically retries failed requests across multiple RPC endpoints.
Readme
@nvana-dharma/rpc-pooler
A resilient RPC connection pooler for Solana that automatically retries failed requests across multiple RPC endpoints.
Why use this? Solana RPC nodes can be unreliable. This package automatically retries your requests across multiple endpoints with configurable retry strategies, so you don't have to handle failover logic yourself.
Installation
pnpm add @nvana-dharma/rpc-poolerQuick Start
The easiest way to use the pooler is with PoolConnectionProvider, which implements common Solana Connection methods with automatic failover:
import { PoolConnectionProvider } from '@nvana-dharma/rpc-pooler'
import { ExponentialBackoffRpcPool } from '@nvana-dharma/rpc-pooler'
import { PublicKey } from '@solana/web3.js'
// Create a pool with your RPC endpoints
const pool = new ExponentialBackoffRpcPool({
urls: [
'https://api.mainnet-beta.solana.com',
'https://solana-api.projectserum.com',
'https://rpc.ankr.com/solana'
],
maxRetries: 3,
requestTimeoutMs: 5000
})
// Wrap it in a connection provider
const connection = new PoolConnectionProvider(pool)
// Use it like a regular Solana Connection
const accountInfo = await connection.getAccountInfo(
new PublicKey('...')
)
const blockHeight = await connection.getBlockHeight('finalized')
const { blockhash } = await connection.getLatestBlockhash()That's it! All your RPC calls now automatically retry across multiple endpoints if one fails.
Supported Methods
PoolConnectionProvider implements these commonly-used Connection methods:
getAccountInfo(publicKey, commitmentOrConfig?)getMultipleAccountsInfo(publicKeys, commitmentOrConfig?)getBlockHeight(commitment?)getLatestBlockhash(commitmentOrConfig?)confirmTransaction(strategy, commitment?)
Need a method that's not listed? You can use the pool directly (see Direct Pool Usage).
Configuration
Basic Options (both pool types)
{
urls: string[] // Required: List of RPC endpoints
defaultCommitment?: Commitment // Default: 'confirmed'
requestTimeoutMs?: number // Default: 30000 (30 seconds)
onError?: (url, error, attempt) => void // Called on each failed attempt
onDebug?: (message) => void // Debug logging callback
}Exponential Backoff Options
{
maxRetries?: number // Default: 3 - How many retry cycles
baseDelayMs?: number // Default: 1000 - Starting delay between retries
maxDelayMs?: number // Default: 30000 - Maximum delay between retries
jitter?: boolean // Default: true - Add randomness to delays
shuffleOnRetry?: boolean // Default: false - Randomize URL order on each retry
}Choosing a Retry Strategy
This package provides two retry strategies:
SimpleRpcPool - Fast Failover
Tries each URL once in order. Fails fast if all endpoints are down.
Best for: Quick failover when you have reliable backup RPCs.
import { SimpleRpcPool } from '@nvana-dharma/rpc-pooler'
const pool = new SimpleRpcPool({
urls: ['primary.rpc.com', 'backup1.rpc.com', 'backup2.rpc.com']
})ExponentialBackoffRpcPool - Resilient Retry
Cycles through all URLs multiple times with increasing delays. Handles temporary outages and rate limits.
Best for: Production environments where reliability is critical.
import { ExponentialBackoffRpcPool } from '@nvana-dharma/rpc-pooler'
const pool = new ExponentialBackoffRpcPool({
urls: ['rpc1.com', 'rpc2.com', 'rpc3.com'],
maxRetries: 3,
baseDelayMs: 1000, // Start with 1 second
maxDelayMs: 30000, // Cap at 30 seconds
jitter: true // Prevent thundering herd
})Complete Example
import {
ExponentialBackoffRpcPool,
PoolConnectionProvider
} from '@nvana-dharma/rpc-pooler'
import { PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'
// Set up the pool
const pool = new ExponentialBackoffRpcPool({
urls: [
'https://api.mainnet-beta.solana.com',
'https://solana-api.projectserum.com',
],
maxRetries: 2,
requestTimeoutMs: 5000,
onError: (url, error, attempt) => {
console.error(`Attempt ${attempt} failed for ${url}:`, error.message)
}
})
const connection = new PoolConnectionProvider(pool)
// Fetch account balance with automatic retry
async function getBalance(pubkey: PublicKey): Promise<number> {
const accountInfo = await connection.getAccountInfo(pubkey)
if (!accountInfo) return 0
return accountInfo.lamports / LAMPORTS_PER_SOL
}
// Fetch multiple accounts with automatic retry
async function getMultipleBalances(pubkeys: PublicKey[]): Promise<number[]> {
const accounts = await connection.getMultipleAccountsInfo(pubkeys)
return accounts.map(acc => acc ? acc.lamports / LAMPORTS_PER_SOL : 0)
}
// Usage
const wallet = new PublicKey('...')
const balance = await getBalance(wallet)
console.log(`Balance: ${balance} SOL`)Advanced Usage
Direct Pool Usage
If you need methods not provided by PoolConnectionProvider, use the pool directly:
import { ExponentialBackoffRpcPool } from '@nvana-dharma/rpc-pooler'
const pool = new ExponentialBackoffRpcPool({ urls: [...] })
// Execute any Connection method
const result = await pool.request((connection) => {
return connection.getSlot()
})
// Complex multi-step operations
const data = await pool.request(async (connection) => {
const slot = await connection.getSlot()
const blockTime = await connection.getBlockTime(slot)
return { slot, blockTime }
})Statistics
Monitor pool performance:
const stats = pool.getStats()
console.log(`Success rate: ${stats.successfulRequests}/${stats.totalRequests}`)
// Per-URL statistics
Object.entries(stats.urlStats).forEach(([url, urlStats]) => {
console.log(`${url}:`)
console.log(` Success rate: ${urlStats.successes}/${urlStats.attempts}`)
console.log(` Avg response time: ${urlStats.averageResponseTime?.toFixed(0)}ms`)
})Dynamic URL Management
Add or remove endpoints at runtime:
const pool = new SimpleRpcPool({ urls: ['https://rpc1.com'] })
pool.addUrl('https://rpc2.com')
pool.removeUrl('https://rpc1.com')
console.log(pool.getUrls()) // ['https://rpc2.com']Error Handling
The pools automatically retry these errors:
- Network timeouts and connection errors
- HTTP 429 (rate limiting), 502, 503, 504 (server errors)
- RPC internal errors
Non-retryable errors (like invalid parameters) fail immediately.
Performance Tips
- Order URLs by reliability - Put your most reliable RPC first
- Set appropriate timeouts - Balance between patience and speed (5-10 seconds is usually good)
- Monitor statistics - Use
getStats()to identify problematic RPCs - Use exponential backoff in production - Handles transient failures better
- Enable jitter - Prevents all clients from retrying simultaneously
Legacy API
The original RpcPooler class is still available for backward compatibility:
import { RpcPooler } from '@nvana-dharma/rpc-pooler'
const pooler = new RpcPooler(['url1', 'url2', 'url3'])
const slot = await pooler.request(
(connection) => connection.getSlot(),
'finalized'
)Note: New code should use SimpleRpcPool or ExponentialBackoffRpcPool with PoolConnectionProvider instead.
License
MIT
