Viem fallback transport: why automatic RPC switching works
A single JSON-RPC endpoint is a single point of failure. When it times out, rate-limits the client, returns malformed data, or loses access to the chain, every read path built on top of it inherits the failure.

The failure is usually misdiagnosed as a frontend problem. It is not. It is a transport problem. The application has no alternate execution path.
Viem’s fallback transport addresses this at the RPC layer. It combines multiple transports behind one client interface and attempts another transport when the current request fails. The mechanism is simple. The edge cases are not. Failover changes where a request is sent. It does not guarantee that every endpoint exposes the same chain state, supports the same methods, or maintains the same view of the latest block.
RPC redundancy is not consensus. A fallback transport can replace a failed endpoint. It cannot make an incorrect endpoint correct.
The architecture of resilient RPC communication
A Viem public client separates application calls from the transport that carries them. The application invokes methods such as readContract, getBlockNumber, simulateContract, or getLogs. The transport converts those operations into JSON-RPC requests and sends them to an endpoint.
With a single HTTP transport, the execution path is deterministic:
1. The client creates a JSON-RPC request.
2. The HTTP transport sends it to one RPC endpoint.
3. The endpoint returns a result or an error.
4. Viem resolves or rejects the client call.
The problem is step four. If the endpoint fails, the request fails. The client does not infer that another provider should be used unless the configured transport explicitly implements that policy.
A fallback transport wraps several underlying transports. Those transports can be HTTP, WebSocket, or other supported protocols. The fallback layer presents one transport to the public client while retaining a list of possible execution paths internally.
A representative client configuration is expressed as createPublicClient({ chain, transport: fallback([http(RPC_URL_A), http(RPC_URL_B)]) }). The exact endpoints are irrelevant to the mechanism. The important property is that the client no longer depends on one endpoint.
The fallback layer has two operating modes:
- Ordered fallback. Viem tries transports in the configured order. If the first request fails, it proceeds to the next transport.
- Ranked fallback. Viem evaluates transports using response stability and latency, then prefers the endpoint with the stronger score.
These modes solve different problems. Ordered fallback gives you predictable priority. Ranked fallback adapts to changing endpoint conditions.
The distinction matters operationally. A primary provider may be the preferred endpoint because it has the correct archive access, generous rate limits, or a contractual service-level agreement. A secondary provider may exist only for outage recovery. If ranking is enabled without understanding its inputs, the client can prefer a faster endpoint that has weaker historical availability or different access to historical state.
The fallback transport does not execute requests against every RPC simultaneously. That is a different policy. Viem exposes loadBalance for load distribution. fallback is primarily a failover mechanism, with optional ranking for endpoint selection.
What belongs in the transport layer
The transport should handle failures that are external to contract logic:
- DNS resolution errors.
- TCP connection failures.
- HTTP failures.
- Request timeouts.
- RPC-level errors returned by the endpoint.
- Temporary endpoint unavailability.
- Provider throttling, depending on how the provider reports it.
The transport should not be expected to resolve application-level failures. A reverted contract call is not automatically evidence that the RPC endpoint is down. A ContractFunctionExecutionError can represent valid contract state. Retrying it against another endpoint may produce the same revert, or may expose an inconsistent state if the endpoints are not synchronized.
That distinction is the first invariant:
A transport failure is not the same as an EVM execution failure.
Confusing the two creates noisy retries and hides the actual attack surface. A frontend that retries every error can multiply requests during an outage and trigger rate limits on every configured provider.
Sequential failover: how Viem routes requests during outages
In unranked mode, the fallback transport consumes an ordered array of transports. The array is a policy declaration.
The first transport is the initial route. The second is the first recovery route. The third is the next recovery route. Viem sequentially attempts the next transport when the current RPC request fails.
The sequence is request-scoped. One failed call does not imply that the entire application permanently switches providers. The fallback transport decides how to route according to the current transport state and configuration. Application code still sees one public client.
A simplified request path looks like this:
1. readContract creates a call against a configured chain.
2. The public client passes the request to the fallback transport.
3. The fallback transport invokes the first underlying transport.
4. The endpoint fails or returns an error that triggers failover.
5. Viem invokes the next transport.
6. The first successful response is returned to the caller.
7. If all transports fail, the client exposes the resulting failure.
The final point is critical. Fallback does not guarantee success. It increases the number of available paths. If all paths fail, the application still needs an explicit error state.
Ordered priority is a reliability policy
The order should not be arbitrary. It determines which provider receives normal traffic when ranking is disabled.
A practical hierarchy may look like this:
| Transport position | Operational role | Typical reason |
|---|---|---|
| First | Primary RPC route | Lowest expected latency or strongest service commitment |
| Second | Regional or provider backup | Independent failure domain |
| Third | Emergency route | Additional availability, often with stricter limits |
| WebSocket route | Subscription or event path | Persistent connection and push-based updates |
Provider independence matters. Three URLs backed by the same infrastructure may represent one failure domain. A hostname change is not redundancy if the underlying service, region, or upstream node cluster is shared.
The same applies to credentials. If all endpoints are subject to one account-level quota, failover may not survive a traffic spike. The client will switch URLs while remaining inside the same rate-limit boundary.
Failover does not erase request semantics
A read request can be retried more safely than a write request, but “read” is not a complete safety classification.
eth_call is generally replayable because it does not mutate chain state. getBalance, getBlockNumber, and getLogs are also read operations. Their results can still differ across endpoints because the nodes may be at different heads or may apply different indexing policies.
A transaction submission is different. sendRawTransaction can be replayed in the sense that the same signed transaction may be submitted to multiple RPC endpoints. But the application must understand the consequences:
- The same transaction hash can be accepted by more than one provider.
- A provider may accept the transaction while the client receives a timeout.
- Retrying can create uncertainty about whether the transaction entered the mempool.
- A replacement transaction can interact with nonce management.
- A wallet or account abstraction flow may have additional submission semantics.
The fallback transport does not provide transaction orchestration. It provides transport selection. A production write path needs its own confirmation and reconciliation strategy.
Error classification is part of the design
A good fallback setup distinguishes endpoint failure from valid protocol output.
Examples of failures that can justify trying another endpoint include connection loss, timeout, and an unavailable RPC method. Examples that usually should not be treated as endpoint failure include:
- Contract revert caused by current state.
- Invalid calldata.
- Insufficient funds.
- Authorization failure.
- Invalid block range supplied by the application.
- A method rejected because the endpoint intentionally does not support it.
The exact behavior depends on the RPC provider and the Viem version. Providers do not always classify errors consistently. One may return an HTTP 429, another a JSON-RPC error object, and another a connection close. The fallback layer can only act on the failure signal it receives.
This is why application telemetry must record the selected route and the original error class. “The request succeeded” is not enough. You need to know whether it succeeded on the primary endpoint or only after two failures.
Dynamic transport ranking: selecting the least damaged endpoint
Static ordering assumes that the configured priority remains valid. Production systems violate that assumption continuously.
Latency changes by region. Providers throttle unevenly. Nodes fall behind. WebSocket connections remain open while their underlying data path becomes stale. A provider that was healthy at deployment may be the worst available route an hour later.
Viem’s ranked fallback mode addresses this by evaluating transports. When ranking is enabled, Viem periodically pings the configured endpoints and uses the results to rank them by stability and latency.
The default ranking parameters from the documented configuration are specific:
- Ping interval:
10_000milliseconds. - Sample history:
10ping samples. - Ping timeout:
1_000milliseconds. - Stability weight:
0.7. - Latency weight:
0.3.
The score is therefore biased toward availability. An endpoint that responds consistently but is somewhat slower can rank above an endpoint that is fast when healthy but frequently fails.
That is a defensible default. A failed request has a higher operational cost than a modest latency increase. A 200-millisecond improvement is irrelevant if the endpoint intermittently returns errors during a transaction simulation or block-sensitive read.
Stability dominates latency
The ranking model can be understood as a weighted decision:
score = stability × 0.7 + latency × 0.3
The implementation details and normalization are handled by Viem. The operational meaning is more important than the formula. Stability carries more than twice the weight of latency.
This prevents a common optimization error. Teams often rank RPC providers by the fastest successful response. That metric ignores failures. The result is an endpoint that wins the benchmark and loses production traffic.
The sample window also affects behavior. With sampleCount: 10 and an interval of 10 seconds, the ranking decision uses a short recent history rather than a long-term reputation. This allows recovery from an outage to be reflected relatively quickly. It also means a brief local network disturbance can influence selection.
That is not automatically good or bad. It is a trade-off between adaptation and stability.
A short window responds quickly but can oscillate. A long window produces a steadier ranking but may retain a degraded provider for too long. The correct choice depends on traffic patterns, endpoint geography, and the cost of switching routes.
Ranking is not load balancing
Ranked fallback does not mean Viem distributes requests evenly across all providers. It selects the preferred transport according to the ranking policy and fails over when necessary.
That difference has direct consequences:
- Do not use
fallbackto estimate aggregate provider capacity. - Do not assume every endpoint receives traffic.
- Do not infer that requests are spread evenly.
- Do not configure multiple providers and expect automatic quota distribution.
- Use
loadBalancewhen concurrent distribution is the actual requirement.
Failover and load balancing have different invariants. Failover protects availability. Load balancing distributes work. Combining them may be appropriate, but they should not be conflated.
Configuring proportional scoring for node selection
The default ranking parameters are a starting point, not a universal production policy.
A latency-sensitive interface may need a stronger latency weight. A settlement or indexing path should usually prioritize stability and deterministic availability. A dashboard that reads the latest block can tolerate occasional endpoint movement differently from a service that reconstructs historical events.
A ranked configuration is conceptually expressed with fallback([...], { rank: { interval: 10_000, sampleCount: 10, timeout: 1_000, weights: { stability: 0.7, latency: 0.3 } } }). The syntax should be checked against the Viem version used by the project, but the parameters describe the policy clearly:
intervalcontrols how often endpoint health is sampled.sampleCountcontrols the rolling history used for ranking.timeoutdefines how long a ping may take before it is treated as a failed or unusable response.weights.stabilitycontrols the contribution of successful responses.weights.latencycontrols the contribution of response speed.
A configuration should be derived from the failure mode you are trying to contain.
If the primary concern is outages
Keep stability dominant. The default 0.7 stability and 0.3 latency weighting reflects this policy. Increasing latency weight because one provider is slightly faster can make the system more sensitive to transient network noise.
The transport should prefer an endpoint that returns valid data repeatedly over one that occasionally disappears.
If the primary concern is user-facing latency
Latency can receive more weight, but only after measuring endpoint behavior from the same regions where the application runs. A server in Frankfurt and a browser in Singapore do not observe the same RPC performance.
Client-side ranking has another complication: each user may rank providers differently. That can be useful, but it makes telemetry more complex. Server-side clients can produce a consistent routing policy. Browser-side clients may produce many local policies.
The correct question is not which provider is fastest in a vendor benchmark. It is which endpoint preserves the application’s latency and correctness invariants under the actual network conditions.
If the endpoints expose different capabilities
Ranking alone does not solve capability mismatch.
One endpoint may support archive queries. Another may prune historical state. One may allow eth_getLogs over a wide block range. Another may enforce a narrow limit. One may expose WebSocket subscriptions. Another may expose only HTTP.
If the application sends all requests through a generic fallback array, a request can fail over from an endpoint that supports the method to one that does not. That is a valid transport failure from the provider’s perspective, but it may be an invalid application route.
Capability compatibility must be established before endpoints enter the same fallback group. Treat the following as part of the transport invariant:
- Same chain ID.
- Same intended network.
- Compatible archive depth.
- Compatible log range limits.
- Compatible tracing or simulation methods.
- Compatible WebSocket behavior for subscription use.
- Compatible authentication and quota expectations.
A fallback endpoint that points to the wrong chain is not a backup. It is a state-corruption vector for reads and a submission hazard for writes.
The chain identity check
Every configured endpoint should be verified with eth_chainId during startup or health validation. Do not rely on the hostname. Do not rely on environment variable naming. Do not rely on provider documentation alone.
A misconfigured endpoint can still return syntactically valid JSON-RPC responses. The client may accept a block number, balance, or contract result from the wrong network. The response is structurally valid and semantically wrong.
That is the most dangerous class of failure because it does not always produce an exception.
For chain-sensitive operations, record the block number used by the response. If one request reads a token balance at block N and a second request reads a related contract state at block N + 20, the application may combine incompatible snapshots. Fallback can increase this probability during endpoint lag or failover.
Where consistency matters, pin reads to an explicit block tag or use a multicall pattern that evaluates related values against one state snapshot. The fallback transport can route the request. It cannot enforce cross-request snapshot consistency.
Integrating fallback with Viem public clients
The basic integration belongs in the public client, not in each individual contract call.
A client can use a fallback transport built from several HTTP transports. Application code then continues to call readContract, getBalance, and other Viem actions without manually selecting providers. This keeps routing policy in one location.
A minimal setup uses createPublicClient with a chain definition and fallback transport. The underlying transports are constructed with separate RPC URLs. The first URL is the primary route when ranking is disabled. Ranking can be enabled through the rank option.
This arrangement is suitable for read-heavy workloads:
- Contract reads.
- Block and transaction queries.
- Log retrieval.
- Gas estimation.
- Transaction simulation.
- Chain metadata requests.
The write path should be considered separately. A WalletClient may use a different transport from the public client. Wallet injection, account signing, and transaction submission are not equivalent to public RPC reads.
The public client and wallet client have different failure models
A public client asks, “What does the chain currently report?”
A wallet client asks, “Can this signed operation be submitted, and what happened after submission?”
The first can usually fail over at the request layer. The second needs transaction identity, receipt tracking, nonce handling, and confirmation logic.
For a signed transaction, the application should preserve the transaction hash once a provider returns it. If the provider times out after accepting the transaction, the absence of a response does not prove that the transaction was rejected. Retrying without checking the transaction status can create duplicate submission attempts and ambiguous state.
The safe sequence is:
1. Sign the transaction once.
2. Submit it through the configured route.
3. Persist or retain the transaction hash as soon as it is available.
4. If the response is uncertain, query transaction presence through a read path.
5. Wait for receipt confirmation.
6. Treat replacement, dropped, and reverted states as distinct outcomes.
Fallback transport is not a substitute for this state machine.
Wagmi fallback transport setup
Wagmi applications generally define transports per chain in the configuration passed to createConfig. The same Viem fallback transport can be used as the chain’s transport, with multiple HTTP transports nested inside it.
The conceptual form is createConfig({ chains: [chain], transports: { [chain.id]: fallback([http(PRIMARY_RPC), http(BACKUP_RPC)]) } }).
The critical detail is chain mapping. The transport must be attached to the correct chain ID. A fallback array for Ethereum mainnet should not be reused for an L2 unless every endpoint in the array serves that L2. A provider URL that looks valid can still expose a different network or a different rollup environment.
For Wagmi applications, keep the following boundaries explicit:
- Connector transport: how the wallet communicates with the user’s wallet provider.
- Public transport: how reads and simulations reach the chain.
- Wallet transport: how signed transactions are submitted.
- Event transport: how logs or block updates are received.
Using fallback for public reads does not automatically create resilient event subscriptions. In particular, fallback transport does not synchronize missing WebSocket block subscriptions through application-level log backfilling. If a WebSocket connection fails, the application needs a subscription recovery strategy.
That strategy normally includes:
- Detecting disconnects.
- Re-establishing the subscription.
- Recording the last processed block.
- Backfilling logs from the last confirmed block.
- Deduplicating events by transaction hash, log index, and block context.
- Handling chain reorganizations where the protocol and application require it.
A transport switch is not an event replay mechanism.
React hooks do not change the underlying failure semantics
Wagmi hooks can expose loading, success, and error states, but they do not remove the distinction between endpoint failure and contract failure.
A hook that reads a contract through a fallback public client may succeed after the first endpoint fails. The UI sees a successful result. Unless telemetry is attached to the client or transport layer, the team may never know that the primary RPC is degraded.
That creates a delayed incident. Users continue receiving responses until the backup is exhausted or throttled.
Production instrumentation should record at least:
- Request method.
- Chain ID.
- Selected transport or provider identifier.
- Whether failover occurred.
- Total request duration.
- Endpoint response class.
- Block number for block-sensitive reads.
- Final error after all routes fail.
Avoid logging secrets or complete authenticated URLs. Provider tokens belong in protected configuration, not application-visible diagnostics.
Failure cases that fallback does not solve
Fallback improves availability. It does not make a distributed system deterministic by itself.
Lagging nodes
Two healthy endpoints can be at different block heights. Both respond successfully. One is simply behind.
A getBlockNumber call may return different values depending on the selected transport. A balance read at the latest block may therefore differ. The difference may be legitimate temporal drift, not a contract bug.
For applications that calculate user-visible values, a small block difference may be acceptable. For settlement logic, liquidation checks, oracle reads, or cross-contract invariant checks, it may not be.
Record the block context. Reject or reconcile responses that exceed the tolerated lag. Do not hide this behind an unconditional retry.
Divergent providers
An RPC provider can return a valid response that is inconsistent with another endpoint because of node lag, indexing differences, or a reorganization.
This is especially relevant to logs. eth_getLogs is sensitive to block ranges, node indexing, and provider-specific limits. A fallback route may return a partial or rejected query if the backup imposes a narrower range.
Split broad historical queries into bounded ranges in the application or indexing layer. Do not assume that every provider accepts the same request size.
Rate limiting
Failover can spread failure or amplify it.
If the application retries aggressively across several providers, one user action can create multiple requests. During a traffic spike, this turns one rate-limit response into a provider cascade.
Use bounded retries. Distinguish transport failover from application retry. A single fallback attempt may be appropriate. Repeating the entire fallback sequence several times without backoff is usually not.
Method support
Provider APIs are not always identical. Some methods are restricted, archived, or implemented with different limits. A fallback array should contain transports that are compatible with the request set.
If the application needs traces, archive state, wide log ranges, or specialized rollup methods, test every endpoint against those methods before deployment. A successful eth_blockNumber check proves almost nothing about broader compatibility.
WebSocket failure
HTTP fallback can recover request-response calls. It does not preserve a live subscription.
A WebSocket transport has connection state. That state includes active subscriptions, last observed events, and the application’s notion of progress. Switching to another transport without replay logic can lose events.
For event-driven applications, the invariant is not “the socket is connected.” The invariant is “every required block range is processed exactly once or is safely deduplicated.” That requires application state.
Writes and nonce coordination
Multiple RPC endpoints do not coordinate local nonce allocation. If several workers submit transactions for one account, fallback does not prevent nonce collisions. It also does not decide whether a timed-out submission was accepted.
Nonce management belongs in an explicit transaction service or wallet policy. The transport can deliver bytes. It cannot serialize intent.
A production implementation should expose its assumptions
The cleanest fallback configuration is not the one with the most URLs. It is the one whose assumptions are observable and testable.
Define the endpoint set around one chain and one capability profile. Give each endpoint an internal identifier. Test chain identity, basic method support, latency, and failure behavior before accepting it into the pool.
Then test the actual failover path.
1. Make the primary endpoint return a timeout.
2. Confirm that the request reaches the secondary endpoint.
3. Verify that the caller receives the secondary result.
4. Confirm that the error from the failed primary is not mistaken for a contract revert.
5. Restore the primary and observe how ranking changes over time.
6. Make the secondary endpoint return a different block height.
7. Verify that the application detects or tolerates the discrepancy according to policy.
8. Break every endpoint and confirm that the final error is actionable.
9. For WebSocket flows, disconnect the socket and verify subscription recovery and log backfill.
10. For transaction flows, simulate an accepted submission followed by a client-side timeout and verify hash reconciliation.
The most common test failure is superficial. Engineers disable the first URL and see that the second URL responds. They do not test lag, method incompatibility, stale subscriptions, or uncertain transaction submission.
That is not a failover test. It is a connectivity test.
A resilient RPC layer must preserve application invariants across route changes. “Another endpoint answered” is only the beginning of the proof.
The rigid checklist for Viem fallback transport
Before shipping viem fallback transport rpc switching, verify the following:
- Every endpoint returns the expected
eth_chainId. - Every endpoint serves the same intended network and environment.
- The fallback order reflects an explicit operational policy.
- Ranking is enabled only when adaptive selection is required.
interval,sampleCount, andtimeoutmatch the expected outage and recovery behavior.- Stability and latency weights reflect the application’s real priority.
- The endpoint set is independent across providers, regions, and quota boundaries where possible.
- Archive, log-range, tracing, and rollup-specific capabilities are compatible.
- Transport failure is distinguished from contract revert and application validation failure.
- Application retries are bounded and use backoff.
- Read consistency requirements define acceptable block drift.
- Block-sensitive results expose their block context to reconciliation logic.
- Write submission preserves transaction hashes across timeouts.
- Nonce management is not delegated to fallback transport.
- WebSocket reconnection includes event backfill and deduplication.
fallbackis not being used as a substitute forloadBalance.- Metrics identify the provider route, failover count, latency, and final error class.
- All endpoints are tested under timeout, throttling, stale-node, and method-unsupported conditions.
Viem’s fallback transport is effective because it solves one problem without pretending to solve all of them. It gives a public client multiple RPC execution paths. In unranked mode, those paths are tried in sequence. With ranking enabled, they are evaluated through recent stability and latency samples. The default ranking policy favors stability, using a 0.7 stability weight and a 0.3 latency weight, with pings every 10 seconds, a ten-sample history, and a one-second timeout.
That is enough to remove the single-endpoint failure mode. It is not enough to guarantee coherent chain reads, reliable event delivery, or safe transaction submission.
The implementation is production-grade only when the surrounding application treats routing as part of correctness. Endpoints must share the same chain identity and capability assumptions. Reads must account for block drift. Writes must reconcile uncertain submission. Subscriptions must backfill after disconnects. Observability must show when the fallback path is carrying production traffic.
The transport can fail over. The invariant must survive.
FAQ
What is the difference between ordered and ranked fallback modes?
Does the fallback transport automatically handle transaction retries?
Can I use the same fallback transport for different blockchain networks?
How does Viem distinguish between a failed RPC and a contract error?
Does ranked fallback distribute requests across all providers?
By Caleb North