Config
Networks

Networks

AIOpen as plain markdown for AI

A network is a logical grouping of upstreams that serve one chain. eRPC discovers networks lazily — on first request to a network, every configured upstream that supports that chain is enrolled automatically. You only need to enumerate networks[] when you want to customize behavior (failsafe, selection policy, rate-limit budget, finality semantics, integrity checks, static responses, alias).

You can configure:

  • Chain identityarchitecture: evm + evm.chainId; optionally a friendly alias (e.g. ethereum instead of evm/1)
  • Finality semanticsfallbackFinalityDepth, dynamic block-time multipliers, fallbackStatePollerDebounce
  • Failsafetimeout, retry, hedge, consensus, with matchMethod / matchFinality scoping
  • Selection policy — JS eval function to decide which upstreams handle which method
  • Rate limits — bind a rateLimitBudget enforced before any upstream is contacted
  • Request directives defaults — turn on retryEmpty, set a default useUpstream, etc.
  • eth_getLogs and trace_filter — proactive splitting + split-on-error + hard limits
  • eth_sendRawTransaction — idempotent broadcasting for safe retry/hedge
  • Static responses — canned answers for (method, params) pairs that no real upstream can serve
  • Method classification overrides — extend or replace the default cacheable-method table per network

Minimum useful config

Networks are lazy-loaded. The smallest explicit network is just architecture + evm.chainId plus whatever feature you want to override.

projectsnetworks[]
erpc.yaml
projects:  - id: main    networks:      - architecture: evm        evm:          chainId: 1        alias: ethereum         # friendly URL: /main/ethereum instead of /main/evm/1

The single-object failsafe: form (one object instead of an array) is accepted as shorthand for an array with one entry. The array form with matchMethod: "*" is what you want as soon as you need per-method scoping.

Production config — network-level failsafe + selection + rate limit

A realistic mainnet entry with a network-wide failsafe (covers retries across upstreams when any one is rate-limited), a hedge to short-circuit slow tails, and a network-level rate-limit budget.

projectsnetworks[]
erpc.yaml
projects:  - id: main    networks:      - architecture: evm        evm:          chainId: 1        rateLimitBudget: mainnet-network   # enforced before any upstream is contacted        failsafe:          - matchMethod: "*"            timeout:              # Network timeout covers the full request lifecycle (every retry/hedge across upstreams)              duration: 30s            retry:              maxAttempts: 3              delay: 0ms              # 0 = no wait; immediately fail over to the next upstream            hedge:              # If the primary takes longer than 'delay', race a second copy on another upstream.              delay: 200ms              maxCount: 3        directiveDefaults:          retryEmpty: true            # retry empty responses unless the method is in retry.emptyResultAccept          useUpstream: "alchemy-*|localnode-*"   # default upstream filter; can be overridden per-request

Lazy-load defaults

Networks not listed under networks[] still work — they're discovered on first request and inherit from networkDefaults. Use this to apply baseline failsafe and directives across every chain in the project.

projectsnetworkDefaults
erpc.yaml
projects:  - id: main    networkDefaults:      # Applies to every network in this project — both statically listed and lazy-loaded.      rateLimitBudget: my-default-budget      failsafe:        - matchMethod: "*"          timeout: { duration: 30s }          retry: { maxAttempts: 3, delay: 0ms }          hedge: { delay: 200ms, maxCount: 3 }      directiveDefaults:        retryEmpty: true        retryPending: false        skipCacheRead: false              # false | true | wildcard pattern (e.g. "memory*")    networks:      # ...network-specific overrides go here; deep-merged on top of networkDefaults
⚠️

If a network has its own failsafe: defined, none of networkDefaults.failsafe is merged in — defaults are wholesale replaced, not deep-merged for that field. Same for selectionPolicy. Other top-level fields (rateLimitBudget, directiveDefaults) are deep-merged.

multiplexing is also settable under networkDefaults and behaves like any other scalar field: a per-network multiplexing value wins; if absent, the default applies to every network in the project.

Name aliasing

Use a friendly alias instead of the architecture/chainId URL segment. Aliases are only available for statically-defined networks (lazy-loaded networks don't have one).

projects:
  - id: main
    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 must contain only alphanumeric characters, dashes, and underscores.

Static responses

For chains that deviate from common client assumptions in ways no real upstream can serve. Example: a chain whose genesis block is at height 1 instead of 0 has no valid answer for eth_getBlockByNumber("0x0", false). staticResponses[] returns a canned response for matching (method, params) pairs — no upstream is contacted.

projects:
  - id: main
    networks:
      - architecture: evm
        evm:
          chainId: 999
        staticResponses:
          # Synthetic genesis block
          - method: eth_getBlockByNumber
            params: ["0x0", false]
            response:
              result:
                number: "0x0"
                hash: "0x0000000000000000000000000000000000000000000000000000000000000000"
                parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000"
                # ...remaining block fields
 
          # Or return a JSON-RPC error instead of a result
          - method: some_unsupported_method
            params: []
            response:
              error:
                code: -32601
                message: "Method not found"
                data:                       # optional, attached as response.error.data
                  hint: "use eth_call instead"
  • method must match exactly.
  • params is matched by deep equality. Keys may be in any order; numbers compare by value (1 matches 1.0); hex strings compare literally ("0x0""0x00").
  • First-match-wins, in declaration order.
  • Exactly one of response.result / response.error must be set.
  • Matched requests skip cache, multiplexer, and upstream selection entirely. Hits are exported as erpc_network_static_response_served_total.
  • Internal state pollers (latest/finalized block lookups) still go to upstreams — they're not intercepted.
Copy for your AI assistant — full networks referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

Every field on networks[]

FieldTypeNotes
architecturestring (required)evm (only supported value today).
evmobjectEVM-specific config — see "evm.* fields".
aliasstringFriendly URL segment for this network (ethereum, arbitrum). Only applies to static networks. Allowed characters: alphanumeric, -, _.
failsafearrayPer-network failsafe policies. Wraps the full request lifecycle (including any upstream-level retries). Accepts matchMethod and matchFinality per entry.
selectionPolicyobjectevalFunc (JS, run on evalInterval) returning the ordered list of upstreams to use. Order IS the routing decision — position 0 = primary, missing = excluded. See "selectionPolicy fields".
directiveDefaultsobjectDefault request directives applied if the request doesn't override them via header/query.
rateLimitBudgetstringBind to a budget from rateLimiters.budgets[]. Enforced before any upstream is contacted.
methodsobjectPer-network override of cacheable-method classification — see "Per-network method overrides".
multiplexingboolPer-network override for the in-flight request deduplication. Default true (inherits global). When false, identical concurrent requests each hit upstreams independently.
staticResponsesarrayCanned (method, params) → response mappings — see "Static responses" above.

evm.* fields

FieldDefaultNotes
chainIdrequired when architecture: evmThe chain's EIP-155 chain ID.
fallbackFinalityDepthauto-detected via eth_getBlockByNumber("finalized")Used when an upstream doesn't expose the finalized tag. Finalized block = latest - fallbackFinalityDepth. Higher values are safer (more reorg-resistant) at the cost of cache hit rate.
fallbackStatePollerDebounce5sStatic debounce used for block polling until enough blocks have been observed to compute a dynamic block time.
dynamicBlockTimeDebounceMultiplier0.7Multiplier on the observed block time to derive the dynamic polling debounce. 0.7 means polling at 70% of the block time (more frequent than chain tick) so the latest-block pointer stays fresh. Lower = more aggressive polling (fresher data, more upstream load); higher = gentler (less load, slightly more latency on tip-following). Fast chains like Arbitrum or BNB benefit from lower values (e.g. 0.5); slow chains like Ethereum L1 can use higher (e.g. 0.9).
blockUnavailableDelayMultiplier0.8Multiplier on the observed block time used as the retry delay when ALL upstreams returned "block not available." Falls back to the static retry.blockUnavailableDelay until block time is known. Lower = shorter wait between retries (faster recovery, more polling pressure); higher = longer wait (less pressure, more latency). Fast chains benefit from lower values; slow chains can tolerate higher.
maxRetryableBlockDistance128Cap on how far ahead of any upstream's known head a request can target before retries stop being attempted. When a request asks for a block that is beyond every upstream's latest tip, eRPC would otherwise retry indefinitely while upstreams catch up. Setting maxRetryableBlockDistance limits how far ahead is still considered "catching up" — once the requested block exceeds the nearest upstream's tip by more than this value, eRPC fails fast with a missing-data error instead of looping. Increase for very slow-syncing chains; decrease to surface indexing gaps faster.
idempotentTransactionBroadcasttrueWhen true, duplicate-transaction errors on eth_sendRawTransaction are converted to successful responses by re-computing the tx hash. Lets retry/hedge be safe for transaction broadcast.
markEmptyAsErrorMethodsnoneList of methods where an empty response should be treated as an error (and thus retried/rotated). For example, ["eth_getTransactionReceipt"] on a chain where empty receipts indicate the tx is missing rather than pending.
getLogsMaxAllowedRangenoneHard limit on eth_getLogs block range. Requests beyond this are rejected with a 413-style error before being sent.
getLogsMaxAllowedAddressesnoneHard limit on the length of the address array in eth_getLogs.
getLogsMaxAllowedTopicsnoneHard limit on topics[0] OR-list length.
getLogsSplitOnErrortrueWhen true and an upstream returns "too many results" / 413-style errors, retry by splitting the range, then the addresses, then topics[0]. Results merged server-side.
getLogsSplitConcurrency16Parallelism cap for split sub-requests.
traceFilterSplitOnErrorfalseSame idea as getLogsSplitOnError for trace_filter / arbtrace_filter.
traceFilterSplitConcurrency10Parallelism cap for trace-filter splits.
enforceBlockAvailabilitynil (= true)Network-level toggle for block-availability enforcement. nil or true means upstreams are skipped when the requested block falls outside their evm.blockAvailability bounds. Set false to globally disable the filter for this network — upstreams will be tried regardless of their declared availability bounds. For per-method control, override enforceBlockAvailability inside methods.definitions.<methodName>.

Per-network method overrides — methods.*

By default, eRPC uses a built-in cacheable-method table (see evmJsonRpcCache → default methods). You can extend or replace it per network:

networks:
  - architecture: evm
    evm: { chainId: 999 }
    methods:
      # When true (default), per-network entries augment the global defaults.
      # Set false to ENTIRELY REPLACE the defaults with the definitions below.
      preserveDefaultMethods: true
      definitions:
        # Add a chain-specific RPC method that's not in the default table
        custom_specialQuery:
          finalized: true
        # Override an existing default: don't translate `latest` tag to a number for this method
        eth_blockNumber:
          translateLatestTag: false

Each entry in definitions is a CacheMethodConfig with the following fields:

FieldTypeDefaultNotes
finalizedboolfalseWhen true, responses are treated as finalized data — cached indefinitely under the finalized finality state. Use for immutable point-lookups (eth_getBlockByHash, eth_getTransactionByHash for a confirmed tx).
realtimeboolfalseWhen true, this method observes live mempool state. Realtime responses are not cached.
statefulboolfalseWhen true, the response depends on caller-specific state. Disables multiplexing — each caller gets its own upstream call. Use for custom RPC methods that tie results to connection-level session context.
reqRefs[][]stringJSON-path segments pointing to block-reference fields in the request params. Used for finality classification, cache key construction, and block-availability filtering. Example: [[1]] means second param is the block number.
respRefs[][]stringJSON-path segments pointing to block-reference fields in the response result. Used to extract the canonical block number from the response object (e.g. [["blockNumber"]] for a transaction).
translateLatestTagbooltrueWhen true, the latest tag in this method's request is rewritten to a concrete hex block number before caching. Set false when latest should be preserved as-is in the cache key.
translateFinalizedTagbooltrueSame as translateLatestTag but for the finalized tag.
enforceBlockAvailabilitybooltruePer-method override of the network-level evm.enforceBlockAvailability. Set false to skip block-availability filtering for this method only — useful for methods without a meaningful block parameter that would otherwise be filtered.

When to use stateful: true. Mark a method stateful when its result depends on caller-supplied session state — for example, a custom RPC method tenant_query that takes a session token as a parameter and returns data scoped to that caller. Without stateful: true, eRPC can multiplex different callers' requests through the same upstream connection, which can yield cross-tenant cache hits or wrong results if the upstream ties its response to the connection's session context. Setting stateful: true disables request multiplexing for that method.

networks:
  - architecture: evm
    evm: { chainId: 999 }
    methods:
      definitions:
        tenant_query:
          stateful: true    # each caller gets a dedicated upstream connection

selectionPolicy fields

selectionPolicy:
  evalInterval: 15s           # how often to re-evaluate eligibility (default 15s)
  evalTimeout: 100ms          # per-tick eval deadline (must be < evalInterval; prior cache retained on timeout)
  evalScope: network          # 'network' (default) | 'network-method' | 'network-finality' | 'network-method-finality'
  evalFunc: |
    (upstreams, ctx) =>
      upstreams
        .removeCordoned()
        .excludeIf(all(samplesAbove(10), errorRateAbove(0.7)))
        .whenEmpty(() => upstreams)
        .preferTag('!tier:fallback', { minHealthy: 1, fallback: 'tier:fallback' })
        .sortByScore(PREFER_FASTEST)
        .stickyPrimary({ hysteresis: 0.30, minSwitchInterval: '30s' })
        .probeExcluded({ sampleRate: 0.1, minSamples: 10, maxConcurrent: 4, timeout: '10s' })

evalFunc returns an ordered Upstream[]: position 0 is the primary, the rest are retry order, anything missing is excluded. Use the chainable stdlib (removeCordoned, excludeIf, whenEmpty, preferTag, sortByScore, stickyPrimary, probeExcluded, …) — see Selection policy for the full DSL + every method.

Default policy: omitting selectionPolicy (or evalFunc) applies a production-hardened chain-agnostic default — removeCordoned + excludeIf filters (error / throttle rates each gated on ≥ 10 samples, p70 latency deviation gated on ≥ 20 samples, block-head lag) + tier-based fallback via preferTag('!tier:fallback') + sortByScore(PREFER_FASTEST) + sticky primary + probeExcluded shadow-mirroring (gives excluded upstreams a chance to heal via background traffic). See the full default-policy chain for thresholds and rationale. Investigations are exposed via OTLP tracing and the erpc_selection_* Prometheus metric family.

directiveDefaults — request directives at network level

These apply to every request on this network unless the client explicitly overrides them via HTTP header (X-ERPC-…) or query param.

DirectiveDefaultNotes
retryEmptytrueRetry empty responses (unless method is in retry.emptyResultAccept).
retryPendingfalseTreat pending-block responses as retryable.
skipCacheReadfalsefalse = read from every cache; true = skip ALL caches; string = wildcard pattern matching connector IDs to skip (e.g. "memory*" skips all in-memory cache connectors while still reading from Redis/Postgres).
useUpstreamnoneMatcher: only consider upstreams whose id matches (alchemy-*|localnode-*).
skipInterpolationfalseSkip block-tag → concrete-number rewriting in request params and cache keys entirely. Use only when your client intentionally sends symbolic tags and you want them preserved verbatim through the cache layer. Rarely needed.
enforceHighestBlocktrueTrack highest block seen across upstreams; serve latest/finalized consistently.
enforceGetLogsBlockRangetrueValidate eth_getLogs block range against upstream availability.
enforceNonNullTaggedBlockstrueConvert null responses to errors for eth_getBlockByNumber("latest"|"pending"|...). Numeric block requests are always errors when null regardless. Set false for chains like zkSync that legitimately return null for some tags.
validateTransactionsRoot, validateTransactionFields, validateTransactionBlockInfo, validateHeaderFieldLengthsfalseCross-validate transaction-root, per-tx fields, block-info, and header field lengths. Expensive but catches malformed responses.
validateLogFields, validateLogsBloomEmptiness, validateLogsBloomMatchfalseLog-level validations. validateLogsBloomMatch recomputes the bloom filter from logs — most expensive of the three.
enforceLogIndexStrictIncrements, validateTxHashUniqueness, validateTransactionIndexfalseReceipt-level validations.
validateReceiptTransactionMatch, validateContractCreationfalseCross-validate receipt vs transaction. Requires ground-truth transactions in library mode.
receiptsCountExactnoneExact expected receipt count for a block response. When set, eRPC rejects responses whose receipt list length doesn't match exactly. Use in conjunction with a known block to catch missing-receipt bugs on specific upstreams.
receiptsCountAtLeastnoneMinimum expected receipt count. Less strict than receiptsCountExact — rejects only if the upstream returns fewer receipts than this threshold.
validationExpectedBlockHashnoneExpected block hash (hex string) for the response block. Rejects any response whose hash field doesn't match. Useful in library mode when the caller knows the canonical hash and wants to catch equivocating upstreams.
validationExpectedBlockNumbernoneExpected block number (integer). Rejects responses whose number field doesn't match. Use alongside validationExpectedBlockHash for full block-identity validation.

eth_getLogs — splitting and limits

MechanismSettingPurpose
ValidationdirectiveDefaults.enforceGetLogsBlockRange (default true)Reject if range exceeds the chosen upstream's known availability.
Hard limitsevm.getLogsMaxAllowedRange / getLogsMaxAllowedAddresses / getLogsMaxAllowedTopicsReject oversized requests upfront with a 413-style error.
Proactive splittingupstream.evm.getLogsAutoSplittingRangeThreshold (per upstream)Network takes the min positive across selected upstreams and splits into contiguous ranges of at most that size.
Split on errorevm.getLogsSplitOnError (default true)Retry by bisecting on range, then addresses, then topics[0] if an upstream returns "too many results".
Concurrencyevm.getLogsSplitConcurrency (default 16)Parallelism for split sub-requests.

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

trace_filter and arbtrace_filter — splitting

Same pattern as eth_getLogs, but opt-in (disabled by default):

  • Proactive splitting: set upstream.evm.traceFilterAutoSplittingRangeThreshold to a positive value. Pick something below the upstream's per-response result cap for typical trace density.
  • Split on error: set network.evm.traceFilterSplitOnError: true. Bisects block range, then fromAddress, then toAddress.
  • Concurrency: network.evm.traceFilterSplitConcurrency (default 10).

Sub-ranges and address-list halves are disjoint by construction — no server-side deduplication. Order is preserved by sub-request index.

eth_sendRawTransaction — idempotent broadcasting

Enabled by default via evm.idempotentTransactionBroadcast: true. When set:

  • "Already known" / duplicate-transaction errors are converted to success responses (the tx hash is recomputed from the signed payload).
  • "Nonce too low" errors are verified against on-chain state — if the tx exists, return success.

This makes retry and hedge policies safe for transaction broadcast — duplicate broadcasts to multiple upstreams don't surface as errors to the client.

To disable (e.g. on chains where you want to see the duplicate-broadcast errors):

networks:
  - architecture: evm
    evm:
      chainId: 1
      idempotentTransactionBroadcast: false

eth_getTransactionCount — return highest nonce, not most-common

When fanning nonce queries across multiple upstreams, you usually want the highest value, not the most-agreed value (lagging upstreams will report stale lower nonces).

networks:
  - architecture: evm
    evm: { chainId: 1 }
    failsafe:
      - matchMethod: eth_getTransactionCount
        consensus:
          maxParticipants: 3        # query 3 upstreams in parallel
          agreementThreshold: 1     # only need 1 valid response; pick the highest
          preferHighestValueFor:
            eth_getTransactionCount:
              - result              # the result IS the nonce (hex string)

The preferHighestValueFor map keys are method names; values are arrays of JSON field paths to compare:

  • "result" — compare the response's direct result value (works for eth_getTransactionCount which returns "0x5" directly).
  • Field name(s) — for object results (e.g. ["nonce", "blockNumber"] for eth_getTransactionByHash where you want highest nonce, breaking ties by highest blockNumber).
  • Multiple fields are compared in declaration order; first decides, later ones break ties.

When preferHighestValueFor is set:

  • maxParticipants — set to the number of upstreams you want to compare (2-3 is typical).
  • agreementThreshold — recommended 1. The highest nonce typically reflects the most recently mined tx; requiring agreement would prefer the stale value. Use ≥2 only when you fear compromised upstreams returning artificially-high nonces.

preferHighestValueFor takes precedence over normal hash-based consensus for the matched method. Error responses are ignored; only valid numeric responses are compared.

markEmptyAsErrorMethods

Treat empty responses on specific methods as errors (so they're retried and the upstream is scored down):

networks:
  - architecture: evm
    evm:
      chainId: 1
      markEmptyAsErrorMethods:
        - eth_getTransactionReceipt   # empty here means tx missing, not pending

By default, empty responses on most methods are valid data (caching them is safe). Use this only when an empty value indicates the upstream lacks the data.

Static-response error variant

The staticResponses example in the human section shows result-style. For error-style:

staticResponses:
  - method: some_unsupported_method
    params: []
    response:
      error:
        code: -32601                  # JSON-RPC standard error codes; -32601 = method not found
        message: "Method not found"
        data:                          # optional, embedded as response.error.data
          hint: "use eth_call instead"

The same (method, params) matching rules apply. Use this for methods you want clients to immediately know are unsupported on this chain without round-tripping to an upstream.

Multiplexing override

By default, identical concurrent requests on the same network are deduplicated — a single upstream call is made, and the result is shared with all callers. To disable for one network (e.g. to force every probe through to upstream during latency testing):

networks:
  - architecture: evm
    evm: { chainId: 1 }
    multiplexing: false

true is the default and almost always what you want — disabling triples upstream RPS during traffic spikes for no benefit in production.

Common pitfalls

  • failsafe replaces, doesn't deep-merge networkDefaults.failsafe — if networks[].failsafe is set, the network entirely overrides defaults for that field. Same for selectionPolicy. Other fields like rateLimitBudget and directiveDefaults ARE deep-merged.
  • matchFinality: ["latest"] — there is no latest finality state. Valid values are finalized, unfinalized, realtime, unknown. Invalid values silently never match.
  • Network timeout vs upstream timeout — the network timeout.duration covers the full request lifecycle including every upstream retry. The upstream's own timeout only bounds one attempt. Set the network timeout generously (≥ upstream timeout × maxAttempts).
  • fallbackFinalityDepth: 1024 blocks finality detection — if you set this and the upstream actually supports eth_getBlockByNumber("finalized"), eRPC still uses the dynamic value. The fallback only kicks in if the upstream doesn't.
  • Static responses bypass everything — including auth's rate-limit budgets. Don't put privileged data in a static response.
  • alias: eth/1 is invalid — only alphanumeric, dash, and underscore allowed.

Append .llms.txt to this URL (or use the AI link above) to fetch the entire expanded reference as plain markdown for an AI assistant.