Config
Networks

Networks

A network represents a chain (e.g., evm, solana, etc), and it is a logical grouping of upstreams.

Upstreams are configured separately, and on the first request to a network, the eRPC will automatically find any upstream that support that network.

Config

You can optionally configure each network as follows:

erpc.yaml
projects:
  - id: main
 
    # (OPTIONAL) This array configures network-specific (a.k.a chain-specific) features.
    # For each network "architecture" and corresponding network id (e.g. evm.chainId) is required.
    # You don't need to define networks as they will be automatically detected from configured endpoints (lazy-loaded).
    # Only provide network list if you want to customize features such as failsafe policies, rate limit budget, finality depth, etc.
    networks:
      - architecture: evm
        # (OPTIONAL) When "evm" is used, "chainId" is required, so that rate limit budget or failsafe policies are properly applied.
        evm:
          # (REQUIRED) chainId is required when "evm" architecture is used.
          chainId: 1
          # (OPTIONAL) fallbackFinalityDepth is optional and allows to manually set a finality depth in case upstream does not support eth_getBlockByNumber(finalized).
          # In case this fallback is used, finalized block will be 'LatestBlock - fallbackFinalityDepth'.
          # Defining this fallback helps with increasing cache-hit rate and reducing redundant 'retry' attempts on empty responses, as we know which data is finalized.
          # DEFAULT: auto-detect - via eth_getBlockByNumber(finalized).
          fallbackFinalityDepth: 1024
          # (OPTIONAL) Enable idempotent transaction broadcasting for eth_sendRawTransaction.
          # When true (default), duplicate transaction errors are converted to success responses,
          # allowing safe use of retry/hedge policies with transaction sending.
          idempotentTransactionBroadcast: true
 
        # (OPTIONAL) A friendly alias for this network. This allows you to reference the network using the alias
        # instead of the architecture/chainId format. For example, instead of using /main/evm/1, you can use /main/ethereum.
        # The alias must contain only alphanumeric characters, dash, or underscore.
        alias: ethereum
        # (OPTIONAL) Refer to "Selection Policy" section for more details.
        # Here are default values used for selectionPolicy if not explicitly defined:
        selectionPolicy:
          # Every 1 minute evaluate which upstreams must be included,
          # based on the arbitrary logic (e.g., <90% error rate and <10 block lag):
          evalInterval: 1m
          
          # To isolate selection evaluation and result to each "method" separately change this flag to true
          evalPerMethod: false
          
          # Freeform TypeScript-based logic to select upstreams to be included by returning them:
          evalFunction: |
            (upstreams, method) => {
              
              const defaults = upstreams.filter(u => u.config.group !== 'fallback')
              const fallbacks = upstreams.filter(u => u.config.group === 'fallback')
              
              // Maximum allowed error rate.
              const maxErrorRate = parseFloat(process.env.ROUTING_POLICY_MAX_ERROR_RATE || '0.7')
              
              // Maximum allowed block head lag.
              const maxBlockHeadLag = parseFloat(process.env.ROUTING_POLICY_MAX_BLOCK_HEAD_LAG || '10')
              
              // Minimum number of healthy upstreams that must be included in default group.
              const minHealthyThreshold = parseInt(process.env.ROUTING_POLICY_MIN_HEALTHY_THRESHOLD || '1')
              
              // Filter upstreams that are healthy based on error rate and block head lag.
              const healthyOnes = defaults.filter(
                u => u.metrics.errorRate < maxErrorRate && u.metrics.blockHeadLag < maxBlockHeadLag
              )
              
              // If there are enough healthy upstreams, return them.
              if (healthyOnes.length >= minHealthyThreshold) {
                return healthyOnes
              }
 
 
              // If there are fallbacks defined, try to use them
              if (fallbacks.length > 0) {
                // Apply same health filtering as default rpcs
                let healthyFallbacks = fallbacks.filter(
                  u => u.metrics.errorRate < maxErrorRate && u.metrics.blockHeadLag < maxBlockHeadLag
                )
 
                // If there are healthy fallbacks use them
                if (healthyFallbacks.length > 0) {
                  return healthyFallbacks
                }
              }
 
              // The reason all upstreams are returned is to be less harsh and still consider default nodes (in case they have intermittent issues)
              // Order of upstreams does not matter as that will be decided by the upstream scoring mechanism
              return upstreams
            }
          
          # When an upstream is excluded, you can give it a chance on a regular basis
          # to handle a certain number of sample requests again, so that metrics are refreshed.
          # For example, to see if error rate is improving after 5 minutes, or still too high.
          # This is conceptually similar to how a circuit-breaker works in a "half-open" state.
          # Resampling is not always needed because the "evm state poller" component will still make
          # requests for the "latest" block, which still updates errorRate.
          resampleExcluded: false
          resampleInterval: 5m
          resampleCount: 10
 
        # (OPTIONAL) A network-level rate limit budget applied to all requests despite upstreams own rate-limits.
        # For example even if upstreams can handle 1000 RPS, and network-level is limited to 100 RPS,
        # the request will be rate-limited to 100 RPS.
        rateLimitBudget: my-limiter-budget
 
        # (OPTIONAL) Refer to "Failsafe" section for more details.
        # Here are default values used for networks if not explicitly defined:
        failsafe:
          timeout:
            # On network-level "timeout" is applied for the whole lifecycle of the request (including however many retries happens on upstream)
            duration: 30s
          retry:
            # It is recommended to set a retry policy on network-level to make sure if one upstream is rate-limited,
            # the request will be retried on another upstream. Most often you don't need to set a delay.
            maxAttempts: 3
            delay: 0ms
          # Defining a "hedge" is highly-recommended on network-level because if upstream A is being slow for
          # a specific request, it can start a new parallel hedged request to upstream B, for whichever responds faster.
          hedge:
            delay: 200ms
            maxCount: 3
    
    upstreams:
    # Refer to "Upstreams" section to learn how to configure upstreams.
    # ...
# ...

Defaults and lazy-loading

Networks are lazy-loaded on first request for a network (if not explicitly defined in config). You can configure "networkDefaults" to set default values for all networks (both static or lazy-loaded):

erpc.yaml
projects:
  - id: main
    networkDefaults:
      # (OPTIONAL) A network-level rate limit budget applied to all requests despite upstreams own rate-limits.
      # For example even if upstreams can handle 1000 RPS, and network-level is limited to 100 RPS,
      # the request will be rate-limited to 100 RPS.
      # Defaults to no rate limit budget.
      rateLimitBudget: "my-default-budget"
 
      # (OPTIONAL) Refer to "Failsafe" section for more details.
      # https://docs.erpc.cloud/config/failsafe
      # If a network has its own failsafe defined, it will not take any of policies from networkDefaults.
      # i.e. if network has only "timeout" policy, it will NOT get hedge/retry from networkDefaults (those will be disabled).
      failsafe:
        timeout:
          duration: "30s"
        hedge:
          delay: "200ms"
          maxCount: 3
        retry:
          maxAttempts: 3
          delay: "0ms"
 
      # (OPTIONAL) Refer to "Selection Policy" section for more details about default values.
      # https://docs.erpc.cloud/config/projects/selection-policies#config
      selectionPolicy:
        #...
 
      # (OPTIONAL) Default directives to apply to all requests for this network.
      # These can be overridden by request-specific directives via HTTP headers or query parameters.
      # See https://docs.erpc.cloud/operation/directives for more details about each directive.
      directiveDefaults:
        retryEmpty: true                       # OPTIONAL (default: true)
        retryPending: false                    # OPTIONAL (default: false)
        skipCacheRead: false                   # OPTIONAL (default: false)
        useUpstream: "alchemy-*|localnode-*"   # OPTIONAL (default: *)
    
    # (OPTIONAL) List of customizations per network if needed can be defined as usual:
    # For each static network, first networkDefaults will be applied (deep object merge),
    # then network-specific overrides can be applied.
    networks:
      # ...

If a network has its own failsafe: defined, it will not take any of policies from networkDefaults.
e.g. if a network only has "timeout" policy, it will NOT get hedge/retry from networkDefaults (those will be disabled).

evm Networks

This type of network are generic EVM-based chains that support JSON-RPC protocol.

Integrity Configuration

erpc.yaml
projects:
  - id: main
    networks:
      - architecture: evm
        evm:
          chainId: 1
          integrity:
            # Track highest block across upstreams for "latest" and "finalized" tags
            enforceHighestBlock: true  # default: true
            
            # Validate eth_getLogs block range availability on upstreams
            enforceGetLogsBlockRange: true  # default: true
            
            # Convert null responses to errors for eth_getBlockByNumber tagged blocks ("pending", "latest", etc.)
            # Numeric blocks (0x1234) always error when null regardless of this setting
            # Set to false to allow null responses for eth_getBlockByNumber tagged blocks (e.g. for zkSync)
            enforceNonNullTaggedBlocks: true  # default: true

eth_getLogs

Network-level controls manage validation, proactive splitting, and error-driven splitting for eth_getLogs. Requests may be split into smaller sub-requests and merged transparently.

  • Validation & availability: integrity.enforceGetLogsBlockRange validates range and asserts the upstream has data for fromBlock..toBlock.
  • Hard limits: getLogsMaxAllowedRange, getLogsMaxAllowedAddresses, getLogsMaxAllowedTopics reject oversized requests early.
  • Proactive splitting: If requested range exceeds an effective threshold, the network splits the request into contiguous ranges. The effective threshold is the minimum positive upstream.evm.getLogsAutoSplittingRangeThreshold across selected upstreams, capped by getLogsMaxAllowedRange.
  • Split on error: If an upstream complains about large requests (including provider-specific 413-like errors), the network retries by splitting (first range, then addresses, then topics[0] OR-list) and merges results.
  • Concurrency: getLogsSplitConcurrency limits parallel sub-requests during splitting.

Relationship to upstream config:

  • Upstream-level evm.getLogsAutoSplittingRangeThreshold is a hint used by the network to compute the effective proactive split size. All other getLogs controls are defined at the network level.
erpc.yaml
projects:
  - id: main
    networks:
      - architecture: evm
        evm:
          chainId: 1
 
          # Validate requested block range and ensure upstream has data for both ends.
          # Enabled by default; set to false to skip availability checks.
          integrity:
            enforceGetLogsBlockRange: true
 
          # Hard limits that reject the request up front (413-style errors):
          getLogsMaxAllowedRange: 10000          # Max number of blocks (inclusive)
          getLogsMaxAllowedAddresses: 10000      # Max length when 'address' is an array
          getLogsMaxAllowedTopics: 10000         # Max OR-count when topics[0] is an array
 
          # When providers return "too many results"/large-range errors, split and retry automatically.
          getLogsSplitOnError: true
 
          # Parallelism for split sub-requests (applies to proactive and error-driven splits).
          getLogsSplitConcurrency: 16
 
    # Upstream hint used to compute proactive split size (network takes the min positive across selected upstreams)
    upstreams:
      - id: my-upstream
        endpoint: https://mainnet.example.com
        evm:
          # 0 or negative disables hint for this upstream
          getLogsAutoSplittingRangeThreshold: 5000

Splitting preserves order and merges results server-side. Address count is the length of the address array (if present). Topic count considers only topics[0] when it is an OR-list.

eth_sendRawTransaction

eRPC provides idempotent transaction broadcasting for eth_sendRawTransaction, enabling safe use of retry and hedge policies with transaction sending.

How it works:

  • When an upstream returns "already known" or similar duplicate transaction errors, eRPC converts it to a success response with the transaction hash
  • For "nonce too low" errors, eRPC verifies if the exact transaction exists on-chain before returning success
  • This allows failsafe policies (retry, hedge) to work safely—if a transaction is broadcast to multiple upstreams or retried, duplicate errors are handled gracefully

Enabled by default. To disable:

erpc.yaml
projects:
  - id: main
    networks:
      - architecture: evm
        evm:
          chainId: 1
          # Disable idempotent transaction broadcast (default: true)
          idempotentTransactionBroadcast: false

When enabled, eth_sendRawTransaction can safely use retry and hedge policies. The transaction hash is deterministically computed from the signed transaction, so duplicate detection works across any upstream.

eth_getTransactionCount

When querying nonce values across multiple upstreams (e.g., using consensus), you may want to return the highest nonce rather than the most common one. This prevents issues where stale nonces from lagging nodes cause transaction failures.

Use preferHighestValueFor in the consensus policy to return the highest numeric value:

erpc.yaml
projects:
  - id: main
    networks:
      - architecture: evm
        evm:
          chainId: 1
        failsafe:
          - matchMethod: eth_getTransactionCount
            consensus:
              maxParticipants: 3   # Query 3 upstreams in parallel
              agreementThreshold: 1 # Return highest nonce (typically the most recent)
              preferHighestValueFor:
                eth_getTransactionCount:
                  - result  # Compare the direct result value (hex nonce)

The preferHighestValueFor map supports:

  • Direct result: Use "result" for methods returning a simple value (like eth_getTransactionCount returning "0x5")
  • Nested fields: Use field names for object results (e.g., ["nonce", "blockNumber"] for eth_getTransactionByHash)
  • Tie-breakers: Multiple fields are compared in order—first field is primary, subsequent fields break ties
⚠️

How maxParticipants and agreementThreshold behave:

When preferHighestValueFor is configured for a method:

  • maxParticipants: All configured upstreams are queried in parallel. Set this to the number of upstreams you want to compare (recommended: 2-3).
  • agreementThreshold: Minimum number of upstreams that must agree on a value for it to qualify. Among qualifying values, the highest wins.

Recommendation: Use agreementThreshold: 1. The highest nonce typically represents the most recently mined transaction, which is the correct value to use. Lagging nodes may return stale (lower) nonces, and requiring agreement would incorrectly prefer the stale value.

Only use agreementThreshold: 2 or higher if you have specific concerns about compromised upstreams returning artificially high nonces.

When preferHighestValueFor is configured for a method, it takes precedence over normal hash-based consensus. Error responses are ignored; only valid numeric responses are compared.

Name aliasing

You can define friendly aliases for your networks instead of the /architecture/chainId format. For example, instead of using /main/evm/1, you can use /main/ethereum:

networks:
 - architecture: evm
   evm:
     chainId: 1
   alias: ethereum
 - architecture: evm
   evm:
     chainId: 42161
   alias: arbitrum
 - architecture: evm
   evm:
     chainId: 137
   alias: polygon
POST http://localhost:4000/main/ethereum
POST http://localhost:4000/main/arbitrum
POST http://localhost:4000/main/polygon
  • Aliases are only applicable to statically defined networks in your configuration.

The alias must contain only alphanumeric characters, dash, or underscore.