RPC Optimization
The one thing most people get wrong
JSON-RPC batching does not save you money. It merges HTTP round-trips. Every
provider that documents the rule bills each method inside the batch:
- Alchemy:
batch* | CU of method # times called
- Ankr: "every sub-call in a batch is billed and rate-limited individually"
- QuickNode: "batching also counts as CPS because each request is counted individually"
- Infura: bills the batch N+1 — "if the array contains 30 requests… this
would be 31 total requests." Batching on Infura is strictly worse than serial.
- Helius: documents no batch rule at all. Do not claim one either way.
What saves money is aggregation — collapsing N logical reads into 1 billable
method call. Measured, not estimated (benchmarks/, run 2026-08-04):
| 50 EVM balance reads |
Alchemy |
Infura |
Ankr |
QuickNode |
serial (50 × eth_call) |
1,300 CU |
4,000 |
10,000 |
1,000 |
| JSON-RPC batch |
1,300 CU |
4,080 |
10,000 |
1,000 |
Multicall3 (1 × eth_call) |
26 CU |
80 |
200 |
20 |
| 40 Solana account reads |
Alchemy |
Helius |
Ankr |
QuickNode |
Infura |
serial (40 × getAccountInfo) |
400 CU |
40 |
20,000 |
1,200 |
6,400 |
getMultipleAccounts (1 call) |
20 CU |
1 |
500 |
30 |
160 |
50× and 40× respectively. Batching moves neither number.
⚠️ Infura's docs call JSON-RPC batching "multi-call." That is not
Multicall3. On Infura, "multi-call" is the thing that costs N+1 credits;
aggregate3 is the thing that costs 80. Don't let the naming collision
route someone to the expensive option.
Two different budgets — don't conflate them
- Provider budget — credits / CU / rate limits. Fixed by how many billable
methods you invoke. Aggregation, caching, and push-instead-of-poll fix this.
- Agent context budget — tokens burned when a coding agent dumps a huge RPC
response into context. Fixed by
dataSlice, field projection, and writing
results to a file. Measured: 8 Solana accounts as full base64 = 142,073
bytes; the same read with dataSlice(0,32) = 1,729 bytes (99% smaller)
for identical billable cost.
If the user says "save tokens", ask which one they mean. Usually it's #1.
Decision procedure
Stop at the first rule that applies.
Is the data push-able? Polling every 2s = 43,200 calls/day per watcher.
Prefer eth_subscribe (EVM) or accountSubscribe/webhooks (Solana).
Check the provider's subscription pricing first — on Alchemy, WebSockets
and webhooks bill at 0.04 CU/byte, so a chatty subscription can cost more
than the polling it replaced. On Helius a delivered webhook event is 1 credit,
which beats polling only when event rate < poll rate.
Is it cacheable? Immutable data — finalized blocks, confirmed txs, ERC-20
decimals/symbol/name, mint metadata, contract code — fetch once, ever.
Re-fetching ERC-20 decimals() on every render is the most common quota leak
in dApp frontends. Infura's own advice: "Cache Ethereum data locally."
Are there N reads at one block/slot? Aggregate:
- EVM → Multicall3 at
0xcA11bde05977b3631167028862bE2a173976CA11
- Solana accounts →
getMultipleAccounts (100/call) or Anchor fetchMultiple
- Solana tokens/NFTs → DAS
getAssetsByOwner, not N × getAccountInfo
Is it a scan over many accounts? Solana getProgramAccounts is the trap —
10 credits on Helius, and on Alchemy it bills 20 CU but consumes 117
throughput CU, throttling ~6× harder than it bills. Always filter, always
dataSlice, prefer getProgramAccountsV2 on Helius (1 credit vs 10).
On EVM prefer eth_getLogs with tight ranges over per-item eth_call sweeps.
Only now, batch — for latency, never for cost. Skip entirely on Infura.
Dedupe and coalesce. Two components asking for the same balance in one
render should produce one request. A query cache with a sane staleTime
often halves traffic before any protocol work.
When writing new code
- Never call an RPC method inside a
for/map loop. await Promise.all(items.map(x => contract.read.foo(...))) is a multicall waiting to happen.
- Pin an explicit
blockNumber/slot when aggregating — consistent and cacheable.
- Request the narrowest data:
dataSlice, base64 over jsonParsed, specific log topics.
- Put the provider key behind a server route.
NEXT_PUBLIC_* keys get scraped and drained — a real cause of mystery overage that no batching will fix.
Diagnosing an existing project
Run references/audit-patterns.md — grep patterns for the common waste
signatures plus before/after quota estimation.
References
Detect the chain first, then read only that reference. Check for
viem/ethers/wagmi/hardhat/foundry (EVM) vs
@solana/web3.js/@coral-xyz/anchor/Anchor.toml (Solana). A provider name
does not imply a chain — Alchemy, Ankr, QuickNode, and Infura all serve both.
references/evm.md — Multicall3, viem/ethers/wagmi (incl. two silent-failure traps), eth_getLogs caps
references/solana.md — getMultipleAccounts, Anchor, gPA, DAS, subscriptions
references/providers.md — verified per-method costs and limits for all 5 providers
references/audit-patterns.md — grep-based waste audit
references/benchmarking.md — how to measure before/after on your own endpoint
Honest limits
- Multicall3 is EVM-only. No such contract on Solana; the answer there is
getMultipleAccounts. "Multicall for Anchor" is a category error.
- Multicall3 batches reads at one block. It can't span blocks or make an
archive query cheap.
- Aggregation increases response size — good for credits, bad for context.
Slice fields when an LLM is the consumer.
- All costs verified 2026-08-04 against provider docs. Providers reprice
without notice; re-verify before quoting as current.
- Where a provider doesn't document something, this skill says so rather than
guessing. Absent numbers are safer than wrong ones.
1---2name: rpc-optimize3description: Cut JSON-RPC usage and provider costs for EVM and Solana apps — Multicall3, getMultipleAccounts, aggregation vs batching, caching, WebSocket/webhook migration, and per-method cost accounting for Alchemy, Infura, Ankr, QuickNode, and Helius. Use when the user says "too many RPC calls", "hitting rate limits", "hitting my daily limit", "reduce RPC costs", "compute units", "credit usage", "Alchemy limit", "Infura credits", "Helius credits", "429 errors", "multicall", "batch RPC", "getMultipleAccounts", "getProgramAccounts is slow", "optimize my indexer", "my dApp is slow to load", or when reviewing/writing any code that reads onchain state in a loop. Use proactively whenever generating code that calls an RPC provider more than once.4---56# RPC Optimization78## The one thing most people get wrong910**JSON-RPC batching does not save you money.** It merges HTTP round-trips. Every11provider that documents the rule bills each method *inside* the batch:1213- **Alchemy**: `batch* | CU of method # times called`14- **Ankr**: "every sub-call in a batch is billed and rate-limited individually"15- **QuickNode**: "batching also counts as CPS because each request is counted individually"16- **Infura**: bills the batch **N+1** — "if the array contains 30 requests… this17 would be 31 total requests." Batching on Infura is *strictly worse than serial.*18- **Helius**: documents no batch rule at all. Do not claim one either way.1920What saves money is **aggregation** — collapsing N logical reads into 1 billable21method call. Measured, not estimated (`benchmarks/`, run 2026-08-04):2223| 50 EVM balance reads | Alchemy | Infura | Ankr | QuickNode |24|---|---|---|---|---|25| serial (50 × `eth_call`) | 1,300 CU | 4,000 | 10,000 | 1,000 |26| JSON-RPC batch | 1,300 CU | **4,080** | 10,000 | 1,000 |27| **Multicall3 (1 × `eth_call`)** | **26 CU** | **80** | **200** | **20** |2829| 40 Solana account reads | Alchemy | Helius | Ankr | QuickNode | Infura |30|---|---|---|---|---|---|31| serial (40 × `getAccountInfo`) | 400 CU | 40 | 20,000 | 1,200 | 6,400 |32| **`getMultipleAccounts` (1 call)** | **20 CU** | **1** | **500** | **30** | **160** |333450× and 40× respectively. Batching moves neither number.3536> ⚠️ **Infura's docs call JSON-RPC batching "multi-call."** That is *not*37> Multicall3. On Infura, "multi-call" is the thing that costs N+1 credits;38> `aggregate3` is the thing that costs 80. Don't let the naming collision39> route someone to the expensive option.4041## Two different budgets — don't conflate them42431. **Provider budget** — credits / CU / rate limits. Fixed by how many *billable44 methods* you invoke. Aggregation, caching, and push-instead-of-poll fix this.452. **Agent context budget** — tokens burned when a coding agent dumps a huge RPC46 response into context. Fixed by `dataSlice`, field projection, and writing47 results to a file. Measured: 8 Solana accounts as full `base64` = 142,07348 bytes; the same read with `dataSlice(0,32)` = **1,729 bytes (99% smaller)**49 for identical billable cost.5051If the user says "save tokens", ask which one they mean. Usually it's #1.5253## Decision procedure5455Stop at the first rule that applies.56571. **Is the data push-able?** Polling every 2s = 43,200 calls/day per watcher.58 Prefer `eth_subscribe` (EVM) or `accountSubscribe`/webhooks (Solana).59 **Check the provider's subscription pricing first** — on Alchemy, WebSockets60 and webhooks bill at **0.04 CU/byte**, so a chatty subscription can cost more61 than the polling it replaced. On Helius a delivered webhook event is 1 credit,62 which beats polling only when event rate < poll rate.63642. **Is it cacheable?** Immutable data — finalized blocks, confirmed txs, ERC-2065 `decimals`/`symbol`/`name`, mint metadata, contract code — fetch once, ever.66 Re-fetching ERC-20 `decimals()` on every render is the most common quota leak67 in dApp frontends. Infura's own advice: "Cache Ethereum data locally."68693. **Are there N reads at one block/slot?** Aggregate:70 - EVM → Multicall3 at `0xcA11bde05977b3631167028862bE2a173976CA11`71 - Solana accounts → `getMultipleAccounts` (100/call) or Anchor `fetchMultiple`72 - Solana tokens/NFTs → DAS `getAssetsByOwner`, not N × `getAccountInfo`73744. **Is it a scan over many accounts?** Solana `getProgramAccounts` is the trap —75 10 credits on Helius, and on Alchemy it bills 20 CU but consumes **11776 throughput CU**, throttling ~6× harder than it bills. Always filter, always77 `dataSlice`, prefer `getProgramAccountsV2` on Helius (**1 credit vs 10**).78 On EVM prefer `eth_getLogs` with tight ranges over per-item `eth_call` sweeps.79805. **Only now, batch** — for latency, never for cost. Skip entirely on Infura.81826. **Dedupe and coalesce.** Two components asking for the same balance in one83 render should produce one request. A query cache with a sane `staleTime`84 often halves traffic before any protocol work.8586## When writing new code8788- Never call an RPC method inside a `for`/`map` loop. `await Promise.all(items.map(x => contract.read.foo(...)))` is a multicall waiting to happen.89- Pin an explicit `blockNumber`/`slot` when aggregating — consistent and cacheable.90- Request the narrowest data: `dataSlice`, `base64` over `jsonParsed`, specific log `topics`.91- Put the provider key behind a server route. `NEXT_PUBLIC_*` keys get scraped and drained — a real cause of mystery overage that no batching will fix.9293## Diagnosing an existing project9495Run `references/audit-patterns.md` — grep patterns for the common waste96signatures plus before/after quota estimation.9798## References99100**Detect the chain first, then read only that reference.** Check for101`viem`/`ethers`/`wagmi`/`hardhat`/`foundry` (EVM) vs102`@solana/web3.js`/`@coral-xyz/anchor`/`Anchor.toml` (Solana). A provider name103does not imply a chain — Alchemy, Ankr, QuickNode, and Infura all serve both.104105- `references/evm.md` — Multicall3, viem/ethers/wagmi (incl. two silent-failure traps), `eth_getLogs` caps106- `references/solana.md` — `getMultipleAccounts`, Anchor, gPA, DAS, subscriptions107- `references/providers.md` — verified per-method costs and limits for all 5 providers108- `references/audit-patterns.md` — grep-based waste audit109- `references/benchmarking.md` — how to measure before/after on your own endpoint110111## Honest limits112113- Multicall3 is **EVM-only**. No such contract on Solana; the answer there is114 `getMultipleAccounts`. "Multicall for Anchor" is a category error.115- Multicall3 batches reads *at one block*. It can't span blocks or make an116 archive query cheap.117- Aggregation increases response size — good for credits, bad for context.118 Slice fields when an LLM is the consumer.119- All costs verified **2026-08-04** against provider docs. Providers reprice120 without notice; re-verify before quoting as current.121- Where a provider doesn't document something, this skill says so rather than122 guessing. Absent numbers are safer than wrong ones.