Solana RPC rate limits are based on request units (CU) rather than simple HTTP request counts. Each method has a different cost, and exceeding your budget returns HTTP 429. This article explains the mechanics, shows how to observe rate limits with curl, and provides practical fixes including Web3.js retry logic, caching, and moving to a dedicated endpoint.
Direct Answer: What Causes Solana RPC 429 Errors?
Solana RPC 429 errors occur when you exceed the rate limit set by the RPC provider. Unlike simple HTTP request counting, Solana uses a request unit (RU) budget system: each JSON-RPC method has a different cost, and your total consumption is measured over a time window. When you exceed the budget, the server responds with HTTP 429 Too Many Requests. The fix is to reduce your RU consumption, implement retries with backoff, cache responses, or move to a dedicated endpoint with higher limits.
This article explains the mechanics of Solana's rate limiting, shows you how to observe it with a simple curl command, and provides code examples for handling 429s in Solana Web3.js. We'll also discuss when it's time to upgrade from a public endpoint to a dedicated one.
How Solana RPC Rate Limiting Works: Request Units vs. HTTP Counts
Solana's JSON-RPC API is documented in the Solana JSON-RPC documentation. The key concept is that each method has a cost in request units (CU). For example, getBalance is cheap (1 CU), while getProgramAccounts can cost up to 10,000 CU depending on the data size. Providers like OnFinality enforce a per-second or per-minute CU budget. When you exceed it, you get a 429.
This design prevents a single client from monopolizing resources with expensive calls. It also means that a few heavy calls can exhaust your budget faster than many light ones. For instance, calling getBlock with high transaction details is much more expensive than getBalance.
The exact CU costs are not always published by Solana Labs, but they are documented in community resources and provider documentation. The table below lists approximate costs based on Solana's RPC documentation and community analysis. These are approximate and may vary by provider; always check your provider's documentation.
- getBalance – 1 CU
- getLatestBlockhash – 1 CU
- getBlock (with transaction details) – 100-200 CU
- getSignaturesForAddress – 100-200 CU per page
- getProgramAccounts – up to 10,000 CU depending on data size
Default Public Endpoint Limits and Cluster Load-Shedding
Public Solana RPC endpoints (like api.mainnet-beta.solana.com) are heavily rate-limited. They are intended for light testing, not production. OnFinality's public endpoint also has limits, but they are more generous. However, even with a public endpoint, you may encounter 429s during network congestion.
Solana clusters also implement load-shedding: when the node is overloaded, it may drop requests or return errors even if you haven't hit your rate limit. This is separate from your provider's rate limit. Load-shedding is more likely during high network activity or when you send expensive requests.
To verify your current rate limit, you can check the response headers from your provider. Many providers include x-ratelimit-remaining or similar headers. OnFinality's API service provides detailed usage metrics.
Observing Rate Limits with curl: A Reproducible Test
You can observe rate limiting behavior with a simple curl command against a public Solana RPC. The following command sends a lightweight getBalance request. Run it in a loop to see when you start getting 429 responses.
Note: The exact rate limit depends on the provider and current load. This test is for observation, not a benchmark. Run it a few times to see the pattern.
curl -s -X POST https://api.mainnet-beta.solana.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["So11111111111111111111111111111111111111112"]}'
# Loop to trigger rate limit (use with caution)
for i in {1..100}; do curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.mainnet-beta.solana.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["So11111111111111111111111111111111111111112"]}'; doneHandling 429s in Solana Web3.js: Retry and Retry-After
When using Solana Web3.js, you can implement retry logic with exponential backoff. The library's Connection class has a fetch option that allows you to customize the HTTP client. You can also use a custom httpAgent or intercept responses.
Here's a practical example that retries on 429, respecting the Retry-After header if present. This is a common pattern for production applications.
const web3 = require('@solana/web3.js');
const fetch = require('node-fetch');
const endpoint = 'https://api.mainnet-beta.solana.com';
async function customFetch(url, options) {
let response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('retry-after');
const delay = retryAfter ? parseInt(retryAfter) * 1000 : 1000;
await new Promise(resolve => setTimeout(resolve, delay));
response = await fetch(url, options);
}
return response;
}
const connection = new web3.Connection(endpoint, 'confirmed', {
fetch: customFetch
});
async function main() {
const balance = await connection.getBalance('So11111111111111111111111111111111111111112');
console.log('Balance:', balance);
}
main().catch(console.error);Caching to Reduce Request Unit Consumption
Caching is one of the most effective ways to avoid 429s. Many RPC responses are static or change infrequently. For example, account balances, transaction signatures, and even block data can be cached for a short period.
Implement a simple in-memory cache with a TTL (time-to-live). For production, consider Redis or a similar distributed cache. The key is to cache responses that are expensive to fetch, like getProgramAccounts or getSignaturesForAddress.
Here's a minimal caching wrapper for Web3.js methods.
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 10 }); // 10 seconds TTL
async function cachedGetBalance(connection, address) {
const key = `balance:${address}`;
const cached = cache.get(key);
if (cached) return cached;
const balance = await connection.getBalance(address);
cache.set(key, balance);
return balance;
}
// Usage
const balance = await cachedGetBalance(connection, 'So11111111111111111111111111111111111111112');Common Failures and Fixes
Failure 1: Hitting 429 on public endpoints. Fix: Implement retries with backoff, reduce request frequency, or switch to a dedicated endpoint.
Failure 2: Expensive methods like getProgramAccounts. Fix: Use filters to reduce data size, or cache results. Consider using getMultipleAccounts instead of looping getAccountInfo.
Failure 3: Not respecting Retry-After. Fix: Always parse the Retry-After header and wait accordingly.
Failure 4: Ignoring load-shedding. Fix: Monitor network health and adjust your request patterns. If the cluster is overloaded, even a dedicated endpoint may return errors.
Tradeoffs and Limitations
Retrying on 429 is essential, but it can increase latency. Caching reduces load but may serve stale data. Dedicated endpoints cost money but provide higher limits and reliability.
There is no one-size-fits-all solution. You need to balance cost, latency, and data freshness. For production, a dedicated endpoint is often worth the investment.
Also note that Solana's rate limits are not standardized across providers. Always check your provider's documentation for specific limits and costs.
Next Steps: Move to a Dedicated Endpoint
If you're consistently hitting 429s, it's time to move to a dedicated endpoint. OnFinality offers dedicated Solana RPC endpoints with higher rate limits and no shared throttling. You can also use the RPC Assistant to compare providers.
For more advanced needs, explore the API service which provides additional features like WebSocket support and analytics. Check our pricing for plans that fit your usage.
If you're new to RPC troubleshooting, read our how to fix generic RPC 429 errors guide. And for more Solana-specific tips, browse the OnFinality Learn hub.
Solana Request Unit Cost Table: Expensive vs. Cheap Methods
Understanding the relative cost of each RPC method is crucial for staying within your request unit budget. The table below provides approximate CU costs for common methods, based on community analysis and provider documentation. These are estimates; always verify with your provider.
Cheap methods like getBalance, getLatestBlockhash, and getSlot cost only 1 CU each. They are safe to call frequently. Medium-cost methods like getBlock (without transaction details) and getSignaturesForAddress range from 100 to 200 CU per call. Expensive methods like getProgramAccounts can cost up to 10,000 CU, especially when fetching large accounts without filters.
To minimize costs, prefer cheap methods when possible. For example, use getMultipleAccounts instead of multiple getAccountInfo calls, and use getSignaturesForAddress with pagination to limit data size.
- 1 CU:
getBalance,getLatestBlockhash,getSlot,getBlockHeight - 100-200 CU:
getBlock(without transaction details),getSignaturesForAddress(per page),getTransaction(with details) - Up to 10,000 CU:
getProgramAccounts(depending on data size and filters)
getProgramAccounts: The Heaviest Caller and How to Index Instead
getProgramAccounts is notorious for consuming massive amounts of request units. It can return large amounts of data, and without proper filters, it can easily exhaust your budget. Many developers use it to fetch all accounts owned by a program, but this is often unnecessary and inefficient.
Instead of repeatedly calling getProgramAccounts, consider indexing the data off-chain. You can use a service like Helius or QuickNode to index program data, or run your own indexer using WebSockets to listen for account changes. This way, you only fetch the data you need, when you need it, reducing RPC load.
If you must use getProgramAccounts, always apply filters to narrow the results. For example, use dataSize or memcmp filters to reduce the response size. Also, cache the results and refresh them periodically rather than on every request.
Scheduling High-CU Traffic Off-Peak and Using getSignaturesForAddress Pagination
If your application performs heavy RPC operations, such as syncing historical data, consider scheduling them during off-peak hours. Network congestion and provider load are typically lower during these times, reducing the chance of hitting rate limits.
For fetching transaction history, use getSignaturesForAddress with pagination. This method returns a list of signatures for a given address, and you can paginate through them using the before parameter. This approach is more efficient than fetching all signatures at once, and it allows you to control the data volume.
Here's an example of paginating through signatures using Web3.js. The loop fetches signatures in batches of 100, processing each batch before moving to the next. This reduces the load on the RPC and helps you stay within your budget.
const web3 = require('@solana/web3.js');
const connection = new web3.Connection('https://api.mainnet-beta.solana.com');
const address = 'So11111111111111111111111111111111111111112';
async function getSignaturesPaginated(address, limit = 100) {
let signatures = [];
let before = undefined;
while (true) {
const batch = await connection.getSignaturesForAddress(address, { limit, before });
if (batch.length === 0) break;
signatures = signatures.concat(batch);
before = batch[batch.length - 1].signature;
// Process batch here
}
return signatures;
}
getSignaturesPaginated(address).then(sigs => console.log(`Total signatures: ${sigs.length}`));Assumptions and Limits
This article assumes you are using a standard Solana RPC provider and that the request unit costs are as documented by Solana Labs and community sources. However, provider quotas vary significantly. Some providers may have lower or higher limits, and they may enforce different rate-limiting algorithms.
Before deploying to production, always check your provider's dashboard or documentation to understand your specific limits. OnFinality, for example, provides detailed usage metrics in its dashboard. Also, note that the CU costs listed here are approximate and may change as Solana evolves.
Finally, the code examples are simplified for illustration. In production, you should add proper error handling, logging, and monitoring to ensure your application behaves correctly under rate limits.