back

Rick ·

Which Ethereum RPC provider should I choose for my app?

Dev tooling

Four of the twelve public Ethereum endpoints I tested could not answer a single question.

I sent the same battery of up to 56 JSON-RPC requests — reads, receipts, archive state at four depths, both trace namespaces, filters, and an eth_getLogs window that widened until it broke — to twelve keyless mainnet endpoints, twice, against the same frozen blocks. Then I measured what four ordinary app workloads actually ask for, and priced that method mix against the published rate cards of Alchemy, Infura, QuickNode, dRPC, Ankr and Chainstack.

The results say something more useful than a ranking. The endpoints that work agree almost perfectly about the chain and disagree about nearly everything else: which methods exist, how far back state goes, how many logs you may read at once, and how long you wait. And on the paid side, the thing that moves your bill is not the provider you pick. It is the shape of your call mix.

A matrix of twelve keyless Ethereum mainnet endpoints against seven capabilities. PublicNode, dRPC, Tenderly, MEV Blocker and xrpc.cl answer contract reads, receipts and eth_getLogs. Only dRPC answers debug_* traces. No endpoint answers trace_* methods. PublicNode refuses archive state and trace methods with a message asking for a personal token. Flashbots does not whitelist eth_call at all. LlamaRPC, Cloudflare, Ankr, BlockPI and Merkle failed every data request. The widest eth_getLogs response served was 95,518 logs, by PublicNode, Flashbots and xrpc.cl.

Every cell is one verdict from a real request on September 22, 2026. Both passes used the same frozen anchor blocks and the same transaction hashes; 598 of 647 cells were identical across the two passes, and 594 of 596 once 1RPC — which ran out of free quota between passes — is set aside.

The list of public endpoints is mostly wrong

Start with the endpoints a developer actually finds first: the keyless URLs in chain lists, wallet presets and tutorials. Twelve of them, on September 22, 2026.

Four returned nothing usable, and they failed in four different ways.

eth.llamarpc.com answers with Cloudflare error 525, an SSL handshake failure between the CDN and an origin that is no longer there. Its documentation domain no longer resolves, and llamanodes.com now serves an unrelated spam site. ethereum.blockpi.network answers 521 — web server down. eth.merkle.io returns HTTP 429 with Cloudflare error 1015 on the very first request from a cold IP, which is a rate-limit rule rather than load. And rpc.ankr.com/eth, still printed in plenty of tutorials as a free endpoint, now answers:

{"jsonrpc":"2.0","error":{"code":-32000,"message":"Unauthorized: You must authenticate your request with an API key. Create an account on https://www.ankr.com/rpc/ and generate your personal API key for free."}}

A fifth, cloudflare-eth.com, is the strangest. It answers eth_chainId with 0x1 and identifies itself as cloudflare-ethereum. Then eth_blockNumber returns -32046 Cannot fulfill request, and every method that touches chain data — eth_call, eth_getBalance, eth_getLogs, eth_getBlockByNumber — returns -32603 Internal error. Cloudflare's own documentation still describes which archive methods the gateway serves, and its migration guide says the company is "deprecating legacy hostnames that point to our public gateway endpoints at cloudflare-eth.com". I tried the documented path form, cloudflare-eth.com/v1/mainnet, in case the bare hostname was the problem. Identical results, method for method. The documentation is maintained; the endpoint is not.

So the real keyless set is six: PublicNode, dRPC, Tenderly's gateway, MEV Blocker, Flashbots Protect and xrpc.cl. 1RPC belongs on the list too, with a caveat I will come back to.

They agree about the chain and nothing else

The agreement is worth stating first, because it is the part that makes the disagreements meaningful.

Every endpoint that answered eth_getLogs returned exactly the same number of logs for the same window: 45 logs for one block, 934 for ten, 4,538 for fifty, 9,065 for a hundred, 27,661 for three hundred, 95,518 for a thousand. Identical across five independent endpoints, and identical again in the second pass two hours later — 34 of 34 log counts unchanged.

Head-block freshness was nearly as clean. Asking every endpoint for its head at the same moment, six times per pass over four passes, PublicNode, Tenderly, MEV Blocker and Flashbots reported the highest head every single round, and dRPC was never more than one block behind. The exception was xrpc.cl, which sat 4 to 6 blocks behind through one entire pass and 0 to 1 blocks behind in the others — about a minute of staleness, appearing and disappearing without any error to mark it.

Nobody is serving you a different Ethereum. What differs is which questions you are allowed to ask.

Traces are the dividing line, and almost nobody crosses it

Of the twelve endpoints, exactly one served debug_* traces without a key: dRPC. It answered debug_traceTransaction, debug_traceCall and debug_traceBlockByNumber on recent blocks, and traced a transaction from block 15,000,000 as well.

Not one endpoint served the Parity-style trace_* namespace. The refusals are worth reading side by side, because each one tells you something about the operator:

Endpoint trace_block response
PublicNode -32602 Archive requests require a personal token. Get one at: https://www.allnodes.com/publicnode
dRPC 12 Can't route your request to suitable provider
Tenderly -32005 rate limit exceeded
MEV Blocker HTTP 406 Not Acceptable, from nginx, before the node sees it
Flashbots -32601 rpc method is not whitelisted
xrpc.cl -32601 Method not allowed by XCORP Security Gateway Policy
Cloudflare -32601 Method not found
1RPC -32600 Method trace_block not allowed

Three of those are a policy decision about the method, one is a commercial gate, one is a proxy rejecting the request before the node sees it, one is a routing failure, one is a rate limit and one is a node that simply does not implement it. Only PublicNode's tells you what to do next.

On the keyed side, traces are the clearest tier boundary in the whole market. Alchemy's pricing page marks both the Debug API and the Trace API as unavailable on Free. Infura puts "Debug / Trace API" in the Developer plan's "everything in Core plus" list, which is $50 a month. QuickNode excludes Trace & Debug from its free trial. Ankr's service-plan table marks Trace and Debug with a cross on both Public and Freemium. Chainstack's docs are blunt: "To enable debug and trace APIs on your Ethereum node, you must have a paid plan and deploy the node as a Global Node."

If your app needs a call trace — to decode a failure, to attribute an internal transfer, to build anything resembling an explorer — that requirement alone eliminates every free tier in this comparison except dRPC's public nodes. It is the first question to ask, and it is usually asked last.

Archive access is a range, not a checkbox

"Archive data available" is on most of these homepages. In practice it ranged from sixteen blocks of history to all of it.

I read the same two calls — eth_call for USDC's total supply, and eth_getBalance on a large holder — at nine depths, from 16 blocks back to block 1,000,000.

PublicNode's boundary sits between 16 and 128 blocks: the first depth succeeds, and every deeper one returns Archive requests require a personal token. Flashbots is pruned rather than gated — eth_getBalance works at 10,000 blocks back and returns -32603 state at block #25934096 is pruned at 100,000. dRPC, Tenderly, MEV Blocker and xrpc.cl served every depth I asked for, including block 10,000,000.

One detail is worth keeping, because it looks like a failure and is not. At block 1,000,000, eth_call against USDC returns 0x on all four archive-capable endpoints. That is the correct answer — USDC did not exist yet — and a node without the state for that block would have returned an error instead. An archive probe that only checks "did it answer" will score a working archive node and a broken one the same way; the empty result is the proof.

1RPC deserves its own paragraph here, because its archive answers were inconsistent within a single pass: state served at 1,000 blocks back, refused at 10,000 with historical state ... is not available, served again at 100,000, and once — for a request to 1rpc.io — refused with PublicNode's error message, verbatim, asking for an Allnodes token. That is not a malfunction. 1RPC's own design document says requests "are dispatched randomly to RPC providers", which is a privacy feature: it stops any single upstream from linking your addresses together. The cost is that "the endpoint" has no consistent capability set, because it is not an endpoint. It is a pool.

Then 1RPC stopped answering entirely. After roughly 152 requests from one IP in under an hour, every method began returning -32001 You've reached the usage limit for your current plan. That is the quota, measured: it is not published for the keyless endpoint, and it is small enough that a single page of a moderately chatty dapp could hit it.

The log limit that bites is rarely the one that is documented

eth_getLogs is where a read-heavy app meets the provider's actual policy, and every operator draws the line somewhere different — by block range, by result count, or by response size.

I widened the same USDC Transfer query from 1 block to 10,000 and recorded where each endpoint refused.

Endpoint Widest window served Response How it refused the next step
PublicNode 1,000 blocks, 95,518 logs 57.9 MiB in 10.7s -32701 broker: node error at 5,000 blocks
Flashbots 1,000 blocks, 95,518 logs 57.9 MiB in 44.9s query exceeds max results 200000
xrpc.cl 1,000 blocks, 95,518 logs 57.9 MiB in 17.1s Backend eth node currently syncing and fallbacks busy
Tenderly 100 blocks, 9,065 logs 5.5 MiB in 0.8s -32602 invalid params at 300 blocks
MEV Blocker 100 blocks, 9,065 logs 5.5 MiB in 1.9s query returned more than 10000 results
1RPC 50 blocks, 4,538 logs 2.8 MiB in 22.7s eth_getLogs is limited to 0 - 50 blocks range
dRPC 10 blocks, 934 logs 0.6 MiB in 0.3s Can't route your request to suitable provider
Cloudflare none: every window returned -32603 Internal error — at 1,000 blocks the range rule answered first: 'fromBlock'-'toBlock' range too large. Max range: 800

Three things stand out.

The same 57.9 MiB of logs took 10.7 seconds from PublicNode and 44.9 seconds from Flashbots. Both answers are correct and byte-identical in content. If your backfill is a long chain of sequential requests, that factor of four is your whole afternoon.

Tenderly's refusal does not match its own documentation. The public-endpoint section states that "a single eth_getLogs call returns at most 3,000 results" and that a wider filter "is rejected with JSON-RPC error -32602 and the message Query returned more than 3000 results". I got 9,065 results in one call, twice, and the refusal above that carried the generic invalid params text rather than the documented sentence. The page has no date on it. Trust the measurement over the number.

And dRPC's free tier served the narrowest window of any working endpoint — ten blocks, then a routing failure at fifty. Its rate-limiting documentation explains why the free tier behaves unlike the paid one: free requests go "only over public nodes, which can be less reliable and slower", the log limit "is capped at 10,000 entries", batches are limited to three items, the maximum timeout is two seconds, and "filter methods are disabled by default". Every one of those showed up in my measurements, including the filter rule — dRPC answered eth_newFilter with method is not available on free plan while PublicNode, Tenderly and Flashbots created filters and served eth_getFilterLogs against them.

The first thing to refuse a wide window was my own client

Measuring the same ladder through viem instead of raw fetch, PublicNode's ceiling moved from 1,000 blocks down to 100. The error was not from PublicNode:

ResponseBodyTooLargeError: HTTP response body exceeded the size limit.
Max: 10485760 bytes
Received: 10502144 bytes

viem 2.56.5 caps a JSON-RPC response body at defaultMaxResponseBodySize = 10_485_760 — 10 MiB — and throws ResponseBodyTooLargeError as soon as the stream crosses it. I ran the same two windows three ways against PublicNode to separate the client from the server. Outside viem, the endpoint served 9,577 logs in 6,088,883 bytes for 100 blocks and 29,449 logs in 18,722,860 bytes for 300. Through viem's defaults, the 100-block window succeeded and the 300-block one threw at 10,502,144 bytes read. Raising the cap to 64 MiB, or disabling it, returned all 29,449 logs from the same endpoint.

That is worth knowing before you conclude that a provider is stingy, and before you pay one to fix it. The cap is one option on viem's HTTP transport:

// examples/articles/which-ethereum-rpc-provider-should-i-choose-for-my-app/snippets/raise-body-limit.mjs
// The default cap is 10_485_760 bytes. A larger number raises it; `false`
// removes it. Everything else about the two clients is identical.
const read = async (maxResponseBodySize) => {
  const client = createPublicClient({
    chain: mainnet,
    transport: http(URL_, { timeout: 60_000, maxResponseBodySize }),
  });
  const end = Number(await client.getBlockNumber({ cacheTime: 0 })) - 100;
  return client.getLogs({
    address: USDC,
    event: TRANSFER,
    fromBlock: BigInt(end - 299),
    toBlock: BigInt(end),
  });
};

Running that file prints both outcomes against the same endpoint. It reads the 300 blocks ending 100 behind the current head, so the log count moves with the chain rather than matching the figures above exactly:

cap=default: ResponseBodyTooLargeError max=10485760 read=10502144
cap=67108864: 28792 logs

What the same backfill costs, in requests

Reading thirty days of one popular contract's logs is 216,000 blocks. At the density I measured — 95.5 Transfer logs per block for USDC — the number of requests that takes ranges from 22 to 43,200, depending entirely on whose rule you are under.

A dot plot on a logarithmic scale of how many eth_getLogs requests a 30-day backfill takes under each plan. Chainstack, dRPC and QuickNode paid plans need 22 requests; Alchemy's paid plan 88; Ankr's free tier and the keyless PublicNode, Flashbots and xrpc.cl endpoints 216; dRPC free, Infura free and Infura paid 2,077; Chainstack free, MEV Blocker and Tenderly 2,160; 1RPC 4,320; Alchemy's free tier and dRPC keyless 21,600; and QuickNode's free trial 43,200.

Filled dots come from the plan's published limit; hollow dots are the widest window that keyless endpoint actually served in this experiment. Ankr's paid tier publishes no eth_getLogs limit for the Node API and is left out.

The spread inside a single vendor is larger than the spread between vendors. QuickNode's documentation sets "5 blocks for users on the Free Trial plan and 10,000 blocks for those on paid plans" — a factor of 2,000 across one payment. Alchemy's free tier allows 10 blocks per query against an unlimited range on Pay as You Go, where the binding constraint becomes the documented 150 MB response cap.

Infura is the interesting one: 2,077 requests on the free tier and 2,077 on the paid one, because its limit is a 10,000-result cap that does not move with the plan. Paying Infura more does not make this workload faster. That is a legitimate reason to choose someone else, and it is invisible on a pricing page.

Latency, and the burst nobody advertises

For the six working endpoints I took 60 sequential samples of the same read, 500 ms apart, in two independent passes, then fired 25 concurrent requests at each.

Endpoint p50 p90 Error rate 25 concurrent
PublicNode 18.5 / 18.0ms 24.5 / 23.9ms 0% / 0% 25/25
dRPC 126.9 / 125.2ms 208.8 / 130.8ms 0% / 0% 25/25
Flashbots 169.3 / 171.7ms 211.4 / 177.2ms 0% / 0% 25/25
Tenderly 191.7 / 196.4ms 212.9 / 213.4ms 0% / 0% 25/25
MEV Blocker 275.4 / 273.6ms 356.4 / 376.5ms 1.7% / 0% 25/25
xrpc.cl 513.8 / 474.1ms 1,014 / 900ms 0% / 1.7% 25/25

Both passes are shown because the second one is the check on the first. Every median moved by less than 40 ms, and five of the six moved by less than 6 ms. These are numbers from one machine on one connection, and the ordering is partly a map of where each operator's nearest point of presence sits relative to me, so read the shape rather than the ranking: a free endpoint answering in 18 ms at p50 with a p90 six milliseconds behind it is a well-run service, not a compromise.

Two measurement notes belong with that table. Flashbots reported a 100% error rate in my first attempt, and it was my fault: I was timing eth_call, and Flashbots does not whitelist eth_call at all. The harness now picks a method the endpoint actually serves and records both the method and the reason it fell back — Flashbots' row above is eth_getBalance, everyone else's is eth_call. And the burst is measured with eth_chainId, which is why Cloudflare "passes" it: 25 of 25 concurrent chain-ID requests succeed at an endpoint that cannot return a block number. Merkle, Ankr, LlamaRPC, BlockPI and 1RPC refused all 25.

Flashbots refusing eth_call is not a defect either. Flashbots Protect is a transaction-submission endpoint that happens to answer some reads. Its documented rate limits list eth_call at 200 requests per IP per 5 minutes, which is a limit on a method the service then answers with rpc method is not whitelisted — one of several places in this survey where the docs and the endpoint disagree. Point a wallet at it for sending. Do not point a dapp's read path at it.

Privacy is the axis nobody benchmarks

Every working endpoint in this set returned access-control-allow-origin: *, except MEV Blocker which echoes the requesting origin. None set a cookie. That combination is exactly what makes them convenient — a browser can call them straight from a user's device — and exactly what makes the privacy question concrete: the provider sees your user's IP address, and in the same request, the wallet address the app is asking about.

The operators know this, which is why several of them address it directly. PublicNode's privacy page says "we will not store your IP address or any data for more than 24 hours" and, more specifically, "IP addresses are not connected or correlated to wallet addresses". Flashbots states that Protect RPC "does not track user information (i.e. IP, location, etc.). No user information is stored or logged." 1RPC's relay claims to discard metadata inside a secure enclave, and is candid about why it disassembles multicalls: "A single RPC call containing multiple batched queries can reveal the relationship between an umbrella of different accounts under the same wallet."

dRPC's privacy policy is the one to read carefully. It opens with "dRPC does not store IP addresses, logs, or any personal data", and then lists what it may temporarily log, starting with the IP address, "cleared weekly". Both sentences are on the same page. Tenderly's keyless endpoint is explicitly IP-keyed — the docs say it "tracks them by the caller's IP address" — which is the honest version, since that is how the quota works.

The keyed providers are further along the other end. Alchemy's subprocessor list assigns retention windows to personal data "contained in API requests": 90 days at Cloudflare and Datadog, 60 at Honeycomb, 7 at Imply. Its data-protection addendum names the categories transferred as "IP address, wallet addresses, and information stored on a blockchain". MetaMask's privacy notice, revised September 9, 2026, describes wallet addresses "processed as part of API requests (in URL parameters or bodies)" under default settings, where Infura is the default provider. None of this is hidden. It is simply not on the comparison pages.

Whether the linkage is exploitable is a separate question, and there is published work on it. An INFOCOM 2024 paper reports linking an Ethereum address to a user's IP behind a third-party RPC service with "98.70% and 96.60%" accuracy on testnet and mainnet, and a 2025 follow-up reports "over 95%" across Ethereum, Bitcoin and Solana. Both model a network eavesdropper watching encrypted traffic rather than the provider itself, which is the stronger version of the claim: if the correlation survives encryption from the outside, the operator reading plaintext has nothing left to work out.

So privacy here is a trust decision, not a technical guarantee. The useful move is to stop treating it as binary. Route read traffic that carries a user's address differently from public read traffic that does not, and decide separately who sees the sends.

The bill follows your call mix, not your provider

The last piece is cost, and it is the one where the measurement changed my mind.

I instrumented a viem client so that every JSON-RPC call is counted, then ran four workloads against a live endpoint. A token dashboard written the obvious way — one readContract per field, six tokens, three fields each — makes 20 calls. The same screen assembled through Multicall3 makes 3. A write path costs 11 calls around the send itself: chain ID, nonce, fee estimate, gas estimate, the transaction, and three receipt polls.

Those mixes are the input. The rate cards are the other half, and here is the first surprise: only two of the six providers still weight methods at all. dRPC charges "a flat 20 compute units" for every method, QuickNode charges 20 API credits for all Ethereum methods with a 2× multiplier for Trace & Debug, Ankr charges 200 credits for "all methods" on EVM chains, and Chainstack charges 1 request unit per full-node request and 2 per archive request. Only Alchemy and Infura publish a real per-method table — and Infura's is mostly a flat 80 credits with a handful of exceptions (eth_getLogs 255, eth_estimateGas 300, debug_traceTransaction 1,000).

Priced against those cards, one naive dashboard session costs 498 CU on Alchemy, 1,600 credits on Infura, 400 credits on QuickNode, 400 CU on dRPC, 4,000 credits on Ankr and 20 RU on Chainstack. The units are not comparable. The dollars are.

At 100,000 monthly sessions, one write per five sessions, and an indexer following the head:

Provider Naive dashboard Multicall dashboard
dRPC $0 (free tier) $0 (free tier)
Chainstack $0 (Developer) $0 (Developer)
Alchemy $36.18 (Pay as You Go) $0 (Free)
QuickNode $49 (Build) $49 (Build)
Infura $50 (Developer) $50 (Developer)
Ankr $53.04 (Premium) $0 (Freemium)

Batching one screen cuts the call count by 85% and moves two providers from a paid plan to a free one. That single change is worth more than any choice between the six. The ordering changes too: Ankr is the most expensive option for the naive shape and free for the batched one, because 85% off a flat 200-credit charge is a bigger saving than 85% off a weighted mix.

The free tiers differ in capacity by a factor of twenty-one for exactly the same reason. dRPC's 210M CU a month covers 525,000 naive sessions; Alchemy's 30M CU covers 60,240; QuickNode's 10M-credit trial covers 25,000 — and it is a one-month trial now, not a standing free tier, which is a change from how QuickNode's free plan is usually described.

What I would actually do

Ask the capability questions first, because they eliminate options and the price differences here do not.

Do you need traces? Then you are paying somebody, or you are on dRPC's public nodes, and you should verify that the specific debug_* method you rely on answers before you build on it. Do you need state older than 128 blocks? That removes PublicNode's free endpoint outright, caps you at roughly ten thousand blocks on Flashbots, and on Chainstack it moves you to Growth. Do you read logs in bulk? Compare the binding limit, not the block range — Infura's result cap does not improve when you pay, and QuickNode's five-block trial limit makes a backfill 43,200 requests.

For a read-heavy dapp with no trace requirement, a keyless endpoint is a genuinely reasonable starting point, and PublicNode was the fastest thing I measured. But keep the URL in configuration, because four of the twelve endpoints in this test stopped working without announcing it, and one of them now serves a spam site. That is the strongest argument for a keyed provider: not capability, not price — someone to notice.

For anything with user-specific reads, decide the privacy question deliberately rather than inheriting it from a default. The provider that sees every eth_call your frontend makes also sees the address in it and the IP it came from.

And before you compare providers at all, count your calls. A maxResponseBodySize and a Multicall3 aggregate changed more numbers in this experiment than any provider did.

What this does not tell you

I measured keyless endpoints, because a measurement that requires six signups is not one anyone can reproduce. Everything I say about Alchemy, Infura, QuickNode, Ankr and Chainstack comes from their own published documentation read on September 22, 2026, with the URLs recorded — not from running their keyed tiers. Their real latency, real error rates and real limits under a key may be very different from what their free public behaviour or their docs suggest.

The latency figures come from one machine on one connection over about two hours, so they carry the network path with them. The cost model rests on a declared app shape — 100,000 sessions, one write per five, one indexer — and those assumptions are in the script, not hidden in the prose; change them and the table changes. The log density is USDC's, which is close to a worst case; a quieter contract moves every backfill number down proportionally.

And two of the endpoints changed behaviour during the measurement: 1RPC exhausted its keyless quota, and Flashbots stopped answering eth_getBalance for a stretch after I had used a few hundred requests. Both are recorded rather than smoothed over. It is a reminder of what these numbers are — a careful snapshot of a market that rearranges itself quarterly, taken on one day, with the raw data kept so the next person can take another one.

If you want the money side of this rather than the capability side, I metered a whole application end to end in The real cost of running an Ethereum app. For local work where the provider question mostly goes away, forking mainnet with Anvil is the cheaper habit, and choosing a testnet has the same shape of answer as this one: the documentation rots faster than the infrastructure.