Optimizing RPC usage can significantly improve performance, reduce costs, and enhance user experience. This guide covers proven techniques for efficient Solana RPC interactions.
Quick Start
Transaction Optimization
Compute Unit Management
1. Simulate to determine actual usage:
const testTransaction = new VersionedTransaction ( /* your transaction */ );
const simulation = await connection . simulateTransaction ( testTransaction , {
replaceRecentBlockhash: true ,
sigVerify: false
});
const unitsConsumed = simulation . value . unitsConsumed ;
2. Set appropriate limits with margin:
const computeUnitLimit = Math . ceil ( unitsConsumed * 1.1 );
const computeUnitIx = ComputeBudgetProgram . setComputeUnitLimit ({
units: computeUnitLimit
});
instructions . unshift ( computeUnitIx ); // Add at beginning
Priority Fee Optimization
1. Get dynamic fee estimates:
const response = await fetch ( `https://mainnet.helius-rpc.com/?api-key= ${ API_KEY } ` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
method: 'getPriorityFeeEstimate' ,
params: [{
accountKeys: [ '11111111111111111111111111111112' ], // System Program
options: { recommended: true }
}]
})
});
const { priorityFeeEstimate } = await response . json (). result ;
2. Apply the priority fee:
const priorityFeeIx = ComputeBudgetProgram . setComputeUnitPrice ({
microLamports: priorityFeeEstimate
});
instructions . unshift ( priorityFeeIx );
Transaction Sending Best Practices
Standard Approach With Confirmation // Serialize and encode
const serializedTx = transaction . serialize ();
const signature = await connection . sendRawTransaction ( serializedTx , {
skipPreflight: true , // Saves ~100ms
maxRetries: 0 // Handle retries manually
});
// Serialize and encode
const serializedTx = transaction . serialize ();
const signature = await connection . sendRawTransaction ( serializedTx , {
skipPreflight: true , // Saves ~100ms
maxRetries: 0 // Handle retries manually
});
// Send and confirm with custom logic
const signature = await connection . sendRawTransaction ( serializedTx );
// Monitor confirmation
const confirmation = await connection . confirmTransaction ({
signature ,
blockhash: latestBlockhash . blockhash ,
lastValidBlockHeight: latestBlockhash . lastValidBlockHeight
});
Data Retrieval Optimization
Efficient Account Queries
Single Account Multiple Accounts Program Accounts // Use dataSlice to reduce payload size
const accountInfo = await connection . getAccountInfo ( pubkey , {
encoding: 'base64' ,
dataSlice: { offset: 0 , length: 100 }, // Only get needed data
commitment: 'confirmed'
});
// Use dataSlice to reduce payload size
const accountInfo = await connection . getAccountInfo ( pubkey , {
encoding: 'base64' ,
dataSlice: { offset: 0 , length: 100 }, // Only get needed data
commitment: 'confirmed'
});
// Batch multiple account queries
const accounts = await connection . getMultipleAccountsInfo ([
pubkey1 , pubkey2 , pubkey3
], {
encoding: 'base64' ,
commitment: 'confirmed'
});
// Use filters to reduce data transfer
const accounts = await connection . getProgramAccounts ( programId , {
filters: [
{ dataSize: 165 }, // Token account size
{ memcmp: { offset: 0 , bytes: mintAddress }}
],
encoding: 'jsonParsed'
});
Token Balance Lookups
// Don't do this - requires N+1 RPC calls
const tokenAccounts = await connection . getTokenAccountsByOwner ( owner , {
programId: TOKEN_PROGRAM_ID
});
const balances = await Promise . all (
tokenAccounts . value . map ( acc =>
connection . getTokenAccountBalance ( acc . pubkey )
)
);
// ~500ms + (100ms * N accounts)
Transaction History
// Avoid sequential transaction fetching
const signatures = await connection . getSignaturesForAddress ( address , { limit: 100 });
const transactions = await Promise . all (
signatures . map ( sig => connection . getTransaction ( sig . signature ))
);
// ~1s + (200ms * 100 txs) = ~21s
Real-time Monitoring
Account Subscriptions
// Avoid polling - wastes resources
setInterval ( async () => {
const accountInfo = await connection . getAccountInfo ( pubkey );
// Process updates...
}, 1000 );
Program Account Monitoring
// Monitor specific program accounts with filters
connection . onProgramAccountChange (
programId ,
( accountInfo , context ) => {
// Handle program account changes
},
'confirmed' ,
{
filters: [
{ dataSize: 1024 },
{ memcmp: { offset: 0 , bytes: ACCOUNT_DISCRIMINATOR }}
],
encoding: 'base64'
}
);
Transaction Monitoring
// Subscribe to transaction logs for real-time monitoring
const ws = new WebSocket ( `wss://mainnet.helius-rpc.com/?api-key= ${ API_KEY } ` );
ws . on ( 'open' , () => {
ws . send ( JSON . stringify ({
jsonrpc: '2.0' ,
id: 1 ,
method: 'logsSubscribe' ,
params: [
{ mentions: [ programId ] },
{ commitment: 'confirmed' }
]
}));
});
ws . on ( 'message' , ( data ) => {
const message = JSON . parse ( data );
if ( message . params ) {
const signature = message . params . result . value . signature ;
// Process transaction signature
}
});
Advanced Patterns
Smart Retry Logic
class RetryManager {
private backoff = new ExponentialBackoff ({
min: 100 ,
max: 5000 ,
factor: 2 ,
jitter: 0.2
});
async executeWithRetry < T >( operation : () => Promise < T >) : Promise < T > {
while ( true ) {
try {
return await operation ();
} catch ( error ) {
if ( error . message . includes ( '429' )) {
// Rate limit - wait and retry
await this . backoff . delay ();
continue ;
}
throw error ;
}
}
}
}
Memory-Efficient Processing
// Process large datasets in chunks
function chunk < T >( array : T [], size : number ) : T [][] {
return Array . from ({ length: Math . ceil ( array . length / size ) }, ( _ , i ) =>
array . slice ( i * size , i * size + size )
);
}
// Process program accounts in batches
const allAccounts = await connection . getProgramAccounts ( programId , {
dataSlice: { offset: 0 , length: 32 }
});
const chunks = chunk ( allAccounts , 100 );
for ( const batch of chunks ) {
const detailedAccounts = await connection . getMultipleAccountsInfo (
batch . map ( acc => acc . pubkey )
);
// Process batch...
}
Connection Pooling
class ConnectionPool {
private connections : Connection [] = [];
private currentIndex = 0 ;
constructor ( rpcUrls : string []) {
this . connections = rpcUrls . map ( url => new Connection ( url ));
}
getConnection () : Connection {
const connection = this . connections [ this . currentIndex ];
this . currentIndex = ( this . currentIndex + 1 ) % this . connections . length ;
return connection ;
}
}
const pool = new ConnectionPool ([
'https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY' ,
'https://mainnet-backup.helius-rpc.com/?api-key=YOUR_API_KEY'
]);
Track RPC Usage
class RPCMonitor {
private metrics = {
calls: 0 ,
errors: 0 ,
totalLatency: 0
};
async monitoredCall < T >( operation : () => Promise < T >) : Promise < T > {
const start = Date . now ();
this . metrics . calls ++ ;
try {
const result = await operation ();
this . metrics . totalLatency += Date . now () - start ;
return result ;
} catch ( error ) {
this . metrics . errors ++ ;
throw error ;
}
}
getStats () {
return {
... this . metrics ,
averageLatency: this . metrics . totalLatency / this . metrics . calls ,
errorRate: this . metrics . errors / this . metrics . calls
};
}
}
Best Practices
Commitment Levels
processed confirmed finalized
Use for : WebSocket subscriptions, real-time updates
Latency : ~400ms
Reliability : Good for most applications
Use for : WebSocket subscriptions, real-time updates
Latency : ~400ms
Reliability : Good for most applications
Use for : General queries, account info
Latency : ~1s
Reliability : Recommended for most use cases
Use for : Final settlement, irreversible operations
Latency : ~32s
Reliability : Maximum certainty
Resource Management
Error Handling
// Implement robust error handling
async function robustRPCCall < T >( operation : () => Promise < T >) : Promise < T > {
try {
return await operation ();
} catch ( error ) {
if ( error . code === - 32602 ) {
// Invalid params - fix request
throw new Error ( 'Invalid RPC parameters' );
} else if ( error . code === - 32005 ) {
// Node behind - retry with different node
throw new Error ( 'Node synchronization issue' );
} else if ( error . message . includes ( '429' )) {
// Rate limit - implement backoff
throw new Error ( 'Rate limited' );
}
throw error ;
}
}
Common Pitfalls to Avoid
Avoid these common mistakes:
Polling instead of using WebSocket subscriptions
Fetching full account data when only partial data is needed
Not using batch operations for multiple queries
Ignoring rate limits and not implementing proper retry logic
Using finalized
commitment when confirmed
is sufficient
Not closing subscriptions, leading to memory leaks
Summary
By implementing these optimization techniques, you can achieve:
60-90% reduction in API call volume
Significantly lower latency for real-time operations
Reduced bandwidth usage through targeted queries
Better error resilience with smart retry logic
Lower operational costs through efficient resource usage