Solana RPC Rate Limits: A Bigger Tier Rarely Fixes 429s

What rate limits actually measure, why credit-based and request-based tiers behave differently, and how to tell a capacity problem from a routing problem.

BoltTx Team··9 min read
solanarate-limitrpc429infrastructuretrading-bot

You start seeing 429s. The obvious reading is that you need a bigger tier.

Sometimes that is right. Often the 429 is a symptom of something else — a polling loop that grew, an unfiltered subscription, or a retry pattern that multiplies requests under exactly the conditions where you cannot afford them.

Worth diagnosing before upgrading.

What Providers Actually Limit

The word "rate limit" covers at least three different mechanisms, and which one you have determines what a 429 means.

Requests per second. A hard ceiling on call volume. Simple to reason about; you either fit or you do not.

Credits or compute units. Each method costs a different amount from a monthly pool. getSlot is cheap; getProgramAccounts on a large program is very expensive. ★You can hit a credit limit at a low request rate★ if your calls are heavy.

Concurrent connections. A cap on simultaneous open connections rather than on throughput. Usually hit by opening a connection per request instead of reusing.

The confusing case is the second. A bot doing 5 requests per second can burn through credits faster than one doing 50, if those 5 are heavy queries.

Which Calls Are Expensive

Not all RPC methods cost the same, and the difference is large enough to restructure code around.

Call Typical cost
getSlot, getBlockHeight very cheap
getAccountInfo cheap
getSignatureStatuses cheap
getMultipleAccounts ★one call instead of N★
getParsedTransaction moderate
getProgramAccounts ★expensive, scales with program size★

★The single biggest win in most codebases is replacing loops of getAccountInfo with one getMultipleAccounts.★

// Expensive: N requests, N times the limit consumption.
// const accounts = await Promise.all(
//   pubkeys.map((pk) => connection.getAccountInfo(pk)),
// );

// Cheap: one request, up to 100 accounts.
const accounts = await connection.getMultipleAccountsInfo(pubkeys);

For more than 100 accounts, chunk into batches of 100 rather than falling back to individual calls.

If you have already tuned this and the constraint is landing rather than reading, a free BoltTx key is one line to test against.

The Retry Amplification Trap

The most common way to make a rate limit problem worse is to handle it badly.

// Wrong: a 429 triggers an immediate retry, which triggers another 429.
async function fetchWithRetry(fn) {
  for (let i = 0; i < 5; i++) {
    try {
      return await fn();
    } catch {
      // No delay. You just multiplied your request rate by five
      // at the exact moment you were already over the limit.
    }
  }
}

★Retrying a 429 without backoff turns a small overage into a sustained one.★ The correct pattern is exponential backoff with jitter:

async function fetchWithBackoff<T>(fn: () => Promise<T>, max = 5): Promise<T> {
  for (let i = 0; i < max; i++) {
    try {
      return await fn();
    } catch (e) {
      if (i === max - 1) throw e;
      // Exponential, plus jitter so concurrent workers do not
      // all retry on the same tick and re-create the spike.
      const base = 200 * 2 ** i;
      await new Promise((r) => setTimeout(r, base + Math.random() * base));
    }
  }
  throw new Error("unreachable");
}

The jitter matters more than people expect. Without it, every worker that got a 429 retries simultaneously, reproducing the burst that caused the problem.

Reads and Sends Have Different Constraints

This is the distinction that changes what you should do about a limit.

Read limits are a capacity problem. More tier, more capacity. The relationship is direct.

Send behaviour is not a capacity problem. Whether your transaction gets accepted by a validator depends on the stake weight behind the forwarding node, not on how many requests per second your plan allows.

★A bigger tier gives you more room to poll. It does not make your transactions land during congestion.★

Teams hitting 429s on reads sometimes upgrade and then find their landing rate unchanged — because those were never the same constraint.

Diagnosing Before Upgrading

Three checks, in order:

What is actually consuming the quota? Log method names and count them. Most codebases have one loop generating the majority of calls, and it is rarely the one people guess.

const counts = new Map<string, number>();
function track(method: string) {
  counts.set(method, (counts.get(method) ?? 0) + 1);
}
setInterval(() => {
  console.log([...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10));
  counts.clear();
}, 60_000);

Can it be batched or subscribed instead? Polling account state on a timer is often replaceable with onAccountChange, which trades request volume for a persistent connection.

Is the spike from retries? Compare request counts during a 429 window against baseline. If they rise, your retry logic is amplifying rather than absorbing.

Filters Are Not Optional on Subscriptions

An unfiltered onProgramAccountChange on a busy program is one of the fastest ways to hit a limit, because the provider pushes every matching account change to you.

// Filter server-side. The provider then sends only what matches.
connection.onProgramAccountChange(programId, handler, "processed", [
  { dataSize: 165 },
  { memcmp: { offset: 32, bytes: owner.toBase58() } },
]);

★Filters run on the provider's side, so an unfiltered subscription costs both of you.★

What Landing Looks Like

Real transactions through our delivery nodes: median confirmation 336ms — under one slot.

If your reads are comfortably inside your limits and transactions still arrive late during congestion, the constraint is routing rather than capacity — and no tier upgrade addresses that.

Where BoltTx Fits

We handle sending, and we do not sell read capacity. Pair us with whatever read provider your workload needs.

Submissions route through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure, so a transaction is not observable in transit before it lands. You sign locally — we never hold funds, never sign, and never modify transaction contents.

The tip travels inside the transaction, paid on chain from your own wallet, and reverts with the transaction if it fails, because that is how Solana handles atomic transactions. You pay only on transactions that reach the chain.

Get a free API key. No monthly fee:

// Reads stay where they are; only the send endpoint changes.
const sender = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

FAQ

What does a 429 from a Solana RPC mean? You exceeded your tier's limit — requests per second, credits, or concurrent connections, depending on the provider's model. It is a capacity signal about reading, and it says nothing about whether your transactions are landing.

Why do I hit rate limits at a low request rate? Because many providers charge by weighted credits rather than raw request count. A handful of getProgramAccounts calls can consume more quota than hundreds of getSlot calls.

Which Solana RPC calls are most expensive? getProgramAccounts is by far the heaviest, and its cost scales with program size. getParsedTransaction is moderate. getSlot, getAccountInfo, and getSignatureStatuses are comparatively cheap.

How do I reduce Solana RPC usage without losing functionality? Batch with getMultipleAccounts instead of looping getAccountInfo, replace polling with subscriptions where the data is push-friendly, and add server-side filters to any program subscription.

Should I retry immediately after a 429? No. Immediate retries multiply your request rate at exactly the moment you are over the limit. Use exponential backoff with jitter so concurrent workers do not all retry on the same tick.

Why does adding jitter to retries matter? Without it, every worker that received a 429 retries simultaneously and recreates the burst that caused it. Randomising the delay spreads the load out rather than synchronising it.

Will upgrading my RPC tier fix transactions not landing? Usually not. Read capacity and transaction landing are different constraints. Landing depends on the path toward a block producer and the stake weight behind it, neither of which changes with a bigger read quota.

How do I find what is consuming my RPC quota? Instrument your client to count calls by method name and log the top ones periodically. Most codebases have one loop responsible for the majority of requests, and it is rarely the one people expect.

Do WebSocket subscriptions count toward rate limits? Policies differ by provider, but unfiltered high-volume subscriptions are consistently a fast way to hit whatever limit exists, since every matching change is pushed to you.

What is the difference between request limits and credit limits? Request limits cap how many calls you make. Credit limits cap weighted consumption, so heavy methods cost more. Under a credit model you can be well within a request ceiling and still run out.

How many accounts can getMultipleAccounts fetch at once? Up to 100 per call. For more, chunk into batches of 100 rather than falling back to individual getAccountInfo calls, which multiplies your consumption.

Can I use a free tier for a production trading bot? For reads on a small workload, sometimes. For sending during congestion, shared free endpoints are the worst case, because they are busiest at exactly the moments you care about.

Why do I get 429s only during volatile periods? Because your own request volume usually rises then — more price checks, more retries, more activity to parse — while shared capacity is under more pressure from everyone else at the same time.

Should I run separate providers for reads and sends? Many production setups do. The workloads have different constraints: reads need throughput and generous limits, sends need routing quality. Splitting them means one does not throttle the other.

Does connection reuse help with rate limits? It helps with concurrent-connection caps specifically, and it reduces latency. It does not reduce request or credit consumption, since those count calls rather than connections.

How do I know whether to optimise or upgrade? Instrument first. If one loop is generating most of your calls, or your retry logic amplifies during 429 windows, optimisation is cheaper than a tier. If usage is already efficient and genuinely at capacity, upgrade.

Back to all posts