You have a dApp that reads from Ethereum. Your users keep asking when you'll support Base / Arbitrum / Polygon. You've been putting it off because the last time you tried, you ended up with five if (chain === ...) branches and a half-finished provider abstraction.
This post is a practical recipe for going from "one chain" to "five chains" in an afternoon. The trick is that you don't need to change your data layer — only how you target the RPC endpoint.
The starting point
A typical Ethereum-only setup with viem:
Three lines of glue: chain, endpoint, key. Same pattern with ethers, web3.js, or whatever your stack uses.
The trick
Every TheRPC EVM endpoint has the same shape: https://<chain>.therpc.io/<YOUR_KEY>. The only thing that changes between Ethereum and Base is the <chain> subdomain. So if your code can pick a chain at call time, you can multiplex.
Replace the single-client pattern with a per-chain factory:
Done. Now clients.ethereum, clients.base, clients.arbitrum, etc. all read from the right chain. Same auth, same key, same rate-limit pool.
Reads work the same everywhere
ERC-20 balance lookups, ENS resolution (where supported), event log filtering — the JSON-RPC API is identical across EVM chains. The only chain-specific differences are:
- Block times. Ethereum ~12 s, Base ~2 s, Arbitrum ~250 ms, Polygon ~2 s, Avalanche ~2 s. Adjust polling intervals.
- Finality. Ethereum has the
finalizedtag (epoch finalization, ~12 min). Most L2s don't yet — use a confirmation-count heuristic instead. - Native token. Always wei in the response, but the symbol differs (ETH / BASE-ETH / ARB-ETH / MATIC / AVAX). Show the right currency to the user.
The aggregator pattern
For most cross-chain UIs, you want "total balance across all chains" or "logs from any of these chains where event X happened." Parallelise:
Five concurrent calls hit five chain endpoints through TheRPC. All bill against the same key. The rate-limit pool is shared across chains, so you don't need a separate quota for each.
When not to use this pattern
The factory-of-clients pattern works for read workloads. For write workloads (signing + submitting transactions) there are chain-specific quirks:
- Different gas markets. Each chain has its own fee-suggestion endpoint.
eth_maxPriorityFeePerGasworks on Ethereum but isn't supported on Arbitrum (where the priority fee is non-meaningful). - Different chain IDs. Signing for chain ID 1 (mainnet) and broadcasting to chain ID 8453 (Base) won't work — EIP-155 makes the signature chain-specific. Always sign with the right chain ID.
- Different rollup-specific tooling. Optimism / Arbitrum / Base each have their own L1 / L2 message bridges, deposits, withdrawals. Those are L2-specific APIs, not standard JSON-RPC.
For writes, you still use one client per chain, but your wallet code has to know which chain it's signing for. The pattern of "transparent fan-out" doesn't apply to writes.
What you ship
By the end of the afternoon you have:
- A
clientsmap covering 5+ EVM chains. - An aggregator function that hits all of them in parallel.
- UI that lets the user pick or auto-detects which chains to show.
- One environment variable (
THERPC_KEY) instead of one per provider per chain. - One billing dashboard.
The work that used to take a week of provider integration is now a one-file refactor.
Going further
- Non-EVM chains. Solana and Bitcoin live at
https://solana.therpc.io/<YOUR_KEY>andhttps://bitcoin.therpc.io/<YOUR_KEY>respectively. Their JSON-RPC APIs differ (Solana isgetSlot,getBalance,getAccountInfo; Bitcoin isgetblockcount,getrawtransaction, etc.) — but the auth + endpoint shape is identical. - Health-aware fallback. If you want client-side resilience on top of our server-side failover, set a 5-second timeout per call and fall back to a stale cache. Use
@tanstack/react-query'splaceholderDatafor this. - Block-tag harmonisation. On Ethereum, use
finalized. On L2s without it, uselatestminus N (where N is the chain's safe confirmation count). Wrap the difference in a helper so your business logic doesn't care.
Questions, hit us at @therpc on X.