Skip to content

fallback transport doesn't retry on execution errors (e.g. NonceTooLowError) and can mix RPCs within a single operation #4509

Description

@mjadach-iv

Check existing issues

Viem Version

2.47.18

Current Behavior

The fallback transport only falls through to the next RPC on transport-level errors (network failures, HTTP 5xx). It does not retry on execution-level errors like NonceTooLowError, IntrinsicGasTooLowError, or other JSON-RPC errors returned with HTTP 200.

This means if an RPC returns a valid JSON-RPC error response (e.g. {"error":{"code":-32000,"message":"nonce too low"}}), viem treats the transport as "working" and never tries the next RPC.

Additional issue: cross-RPC state mismatch
Even if the fallback did retry on execution errors, there's a deeper problem. A single sendTransaction call internally makes multiple RPC requests:

  • eth_getTransactionCount (nonce)
  • eth_estimateGas
  • eth_sendRawTransaction

Since the fallback transport handles each request() independently, if the first RPC fails mid-operation (e.g. succeeds on eth_getTransactionCount but fails on eth_sendRawTransaction), the next request may go to a different RPC. This RPC might have a different view of the chain state, causing the nonce (already baked into the signed transaction) to be invalid.

There is no way to ensure a single RPC is used for the entire lifecycle of a multi-step client operation like sendTransaction or writeContract.

This issue is very visible when there was a hard fork and not all RPC providers updated their nodes, or simpy one is stuck and provides old nonce of the address.

Expected Behavior

The fallback mechanism should have another flag that if triggered - would treat not getting a hash of a TX back as an error, so the retry mechanism on another RPC would kick in.

Steps To Reproduce

You can run 2 client versions, for example on gnosis: https://github.com/NethermindEth/nethermind/releases/tag/1.36.2 and v1.36.1.

Anything else?

We had to abandon the fallback transport and implement our own retry logic at the client level — creating separate walletClient/publicClient instances per RPC and retrying the entire operation on the next client.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions