Dealing With RPC Rate Limits
TL;DR
- - Rate limits vary by provider — typically 10–100 RPS on free tiers, 100–1000+ on paid
- - Batch JSON-RPC requests to reduce call count without losing data
- - Cache read-heavy responses (epoch info, token metadata) to avoid redundant calls
Every Solana RPC provider enforces rate limits to protect infrastructure from overload. When you exceed the limit, the provider returns HTTP 429 (Too Many Requests) and your request is dropped. Understanding these limits and designing around them is essential for reliable applications.
How rate limits work
RPC rate limits are enforced per API key, per IP address, or both, depending on the provider. Common dimensions:
| Dimension | Typical free tier | Typical paid tier |
|---|---|---|
| Requests per second (RPS) | 10–40 | 100–1000+ |
| Requests per day | 100K–500K | Unlimited or 10M+ |
| Compute units per second | Varies | Varies |
| WebSocket subscriptions | 5–20 | 50–500 |
Some providers use a compute unit model where different RPC methods cost different amounts. A getBalance call may cost 1 CU, while getProgramAccounts costs 100 CU. This means heavy methods deplete your quota faster.
Detecting rate limits
A 429 response includes headers indicating your limit status:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1729500000
Not all providers include all headers. At minimum, check for HTTP status code 429 and implement backoff.
Strategies to reduce RPC calls
1. Batch requests
JSON-RPC supports batch requests — send multiple method calls in a single HTTP request:
const batch = [
{ jsonrpc: "2.0", id: 1, method: "getBalance", params: ["pubkey1"] },
{ jsonrpc: "2.0", id: 2, method: "getBalance", params: ["pubkey2"] },
{ jsonrpc: "2.0", id: 3, method: "getBalance", params: ["pubkey3"] },
];
const response = await fetch(rpcUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(batch),
});
A batch of 10 calls counts as 1 HTTP request against most providers’ RPS limits (though compute-unit-based providers may count each method separately).
2. Cache read-heavy data
Data that changes infrequently should be cached:
| Data | Cache duration |
|---|---|
| Epoch info | ~2 days (epoch length) |
| Token metadata | Hours to days |
| Program accounts (static) | Minutes |
| Account balance | 1–5 seconds |
| Recent blockhash | Use until expired (~60–90s) |
3. Use WebSocket subscriptions
Instead of polling getAccountInfo every second, subscribe to account changes:
connection.onAccountChange(publicKey, (accountInfo) => {
// React to changes instead of polling
});
One WebSocket subscription replaces hundreds of polling requests per minute.
4. Use getMultipleAccounts instead of multiple getAccountInfo
// Instead of 10 separate calls:
const accounts = await connection.getMultipleAccountsInfo([
pubkey1, pubkey2, pubkey3, /* ... up to 100 */
]);
One call retrieves up to 100 accounts.
5. Reduce getProgramAccounts usage
getProgramAccounts is the most expensive RPC call. Strategies to reduce it:
- Use
dataSliceto fetch only the bytes you need. - Use
memcmpfilters to narrow results server-side. - Consider Geyser gRPC streams for real-time program account monitoring.
- Cache and maintain your own index of program accounts.
Handling 429 responses
Implement exponential backoff with jitter:
async function rpcWithRetry(fn: () => Promise<any>, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err: any) {
if (err?.status === 429 && i < maxRetries - 1) {
const delay = Math.min(1000 * 2 ** i, 30000);
const jitter = Math.random() * delay * 0.1;
await new Promise((r) => setTimeout(r, delay + jitter));
continue;
}
throw err;
}
}
}
Key points:
- Exponential backoff prevents hammering the endpoint.
- Jitter prevents thundering herd when multiple clients back off simultaneously.
- Max retries prevents infinite loops.
- Respect
Retry-Afterheader when present.
Multi-provider failover
Distribute load across multiple RPC providers:
const providers = [
{ url: PRIMARY_RPC, weight: 0.7 },
{ url: SECONDARY_RPC, weight: 0.3 },
];
When one provider rate-limits you, route requests to another. This also provides redundancy against provider outages.
FAQ
Are there RPC providers without rate limits? No provider has truly zero rate limits. Dedicated nodes have very high limits (tens of thousands of RPS) but still enforce hardware-level constraints. “Unlimited” plans typically mean the limit is high enough that normal usage does not hit it.
Do WebSocket subscriptions count against rate limits? The initial subscribe request counts. Subsequent push notifications from the server do not count because they are server-initiated.
Does sendTransaction have separate limits?
Some providers rate-limit write operations (sendTransaction) separately from read operations. Check your provider’s documentation.
How do I know my current usage? Most paid providers offer a dashboard showing current RPS, daily usage, and remaining quota. Some providers include usage headers in responses.
Sources
Updated: