in-solana by Supanode

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:

DimensionTypical free tierTypical paid tier
Requests per second (RPS)10–40100–1000+
Requests per day100K–500KUnlimited or 10M+
Compute units per secondVariesVaries
WebSocket subscriptions5–2050–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:

DataCache duration
Epoch info~2 days (epoch length)
Token metadataHours to days
Program accounts (static)Minutes
Account balance1–5 seconds
Recent blockhashUse 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 dataSlice to fetch only the bytes you need.
  • Use memcmp filters 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-After header 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.

Updated: