# Networks > Source: https://docs.erpc.cloud/config/projects/networks > One entry per chain — eRPC routes every request to the right upstreams, caches results, and retries failures, all without touching your code. > Format: machine-readable markdown export of the docs page above. > All collapsible AI sections are inlined and fully expanded. # Networks A **network** is one chain inside a project. Declare it once and eRPC owns the whole pipeline: smarter caching, automatic failover, and in-flight deduplication — on every chain you touch. Three things make it ergonomic: **zero-config chains** bootstrap automatically from your providers on first request, **`networkDefaults`** lets one block set shared policy for every chain, and a human-readable **`alias`** replaces `evm/42161` in every URL. **What you get:** - Any EVM chain, any provider — routes automatically on first request. - One `networkDefaults` block replaces repetitive per-chain config. - `alias: arbitrum` turns every URL into `/main/arbitrum` instead of `/main/evm/42161`. - Static canned replies for chains with non-standard genesis blocks. - In-flight deduplication so 100 identical concurrent requests cost you one upstream call. ## Quick taste Illustrative, not a tuned production config — declare Ethereum with a human-readable alias: **Config path:** `projects[].networks[]` **YAML — `erpc.yaml`:** ```yaml projects: - id: main networks: - architecture: evm evm: chainId: 1 # human-readable alias replaces evm/1 in every URL and metric label alias: ethereum ``` **TypeScript — `erpc.ts`:** ```typescript projects: [{ id: "main", networks: [{ architecture: "evm", evm: { chainId: 1 }, // human-readable alias replaces evm/1 in every URL and metric label alias: "ethereum", }], }] ``` ## Agent reference Copy one of these prompts into your AI agent session (Claude Code, Cursor, …) — each one points the agent at this page's machine-readable reference so it can do the work correctly: **Prompt Example #1: add multiple chains with shared policy** ```text I'm running eRPC for several EVM chains and want to declare Ethereum mainnet, Arbitrum, and Base in a single project. Set human-readable aliases (e.g. "ethereum", "arbitrum", "base"), share a common rate-limit budget and failsafe via networkDefaults, and keep per-chain blocks minimal. Work with my existing eRPC config. Read the full reference first: https://docs.erpc.cloud/config/projects/networks.llms.txt ``` **Prompt Example #2: tune getLogs limits and splitting** ```text My eRPC instance is hitting "block range too large" errors from upstreams on eth_getLogs. Adjust getLogsMaxAllowedRange for my Ethereum mainnet network, enable automatic range splitting on errors, and set a safe concurrency cap so sub-requests don't flood the provider. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/config/projects/networks.llms.txt ``` **Prompt Example #3: fix backward tip jumps in multi-pod deployment** ```text My clients occasionally see older block numbers between requests, which I think means different eRPC pods are returning different "latest" tips. Enable cluster-min served tip for the "latest" axis on my production chains so all pods agree on the served tip. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/config/projects/networks.llms.txt ``` **Prompt Example #4: disable validation for a non-standard chain** ```text I'm adding zkSync Era to eRPC and eth_getBlockByNumber responses from zkSync don't have a valid transactionsRoot field — eRPC is rejecting them. Override directiveDefaults for the zkSync network only to disable validateTransactionsRoot without touching the global policy. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/config/projects/networks.llms.txt ``` **Prompt Example #5: debug 503 on a newly added chain** ```text I added a new chain to eRPC's networks list but requests are returning HTTP 503 with ErrNetworkInitializing. Walk me through what the bootstrap task does, what conditions produce 503 vs 404, and which config keys to check first (providers, onlyNetworks/ignoreNetworks, rateLimitBudget). Work with my existing eRPC config. Reference: https://docs.erpc.cloud/config/projects/networks.llms.txt ``` --- ### Networks — full agent reference ### How it works **Architecture concept.** `NetworkArchitecture` is a string enum; only `"evm"` is valid today (`common/network.go:L35-37`). The canonical id is `evm:` from `util.EvmNetworkId` (`util/ids.go:L11-13`). The `network` Prometheus label equals the alias when set, otherwise the raw id. **Lazy creation.** `NetworksRegistry.GetNetwork` fast-paths a `sync.Map` lookup; on miss it validates the id format and schedules a bootstrap task. The task: (1) resolves or synthesizes a `NetworkConfig` and runs `SetDefaults` so it inherits `networkDefaults` exactly like static networks; (2) calls `PrepareUpstreamsForNetwork` — fans out to every configured provider, polls 200 ms for up to 30 s until ≥1 upstream is ready; (3) wires the cache DAL; (4) registers with the selection-policy engine. Statically-declared networks bootstrap in background at startup through the same machinery; a failed eager bootstrap is retried when the first request arrives. **Forward pipeline order** (exact sequence, `erpc/networks.go:L931-1603`): 1. Apply `directiveDefaults` + start OTel span. 2. **Static response** match — returns immediately if hit (before multiplexer, cache, upstreams). 3. **Multiplexer** — leader/follower on `CacheHash`; followers copy the leader's response. 4. **Cache read** — non-null hit closes the mux and returns. 5. **Policy-engine upstream ordering** — falls back to registration order if empty; `ErrNoUpstreamsFound` (404) if still empty. 6. EVM pre-forward hook (future-block short-circuit; never cached). 7. Stateful-method guard, network rate-limit, request normalization. 8. **Failsafe executor selection** — first config-order entry matching method + finality. 9. **Upstream sweep** — per-upstream block-availability gate, hedge/retry orchestration. 10. Post-sweep: timeout translation, last-valid-response fallback, async cache set, misbehavior accounting. **networkDefaults inheritance** (`common/defaults.go:L1781-1973`). "Network wins; defaults fill gaps." Scalars inherit when zero-valued; pointers when nil. `selectionPolicy` and `directiveDefaults` are whole-struct copies (no per-field merge). `evm` is a whole-struct copy when absent from the network, otherwise per-field fill for 14 named fields. Failsafe: if the network has entries, each is matched against the first compatible default (wildcard method + finality) and missing sub-fields are inherited; if the network has no entries the defaults list is deep-copied wholesale. **Finality classification** (`erpc/networks.go:L1645-1743`). Explicit `finalized`/`realtime` flags in `methods.definitions` win. Otherwise the block ref/number is extracted from the request, then from the response body (for hash-keyed cache hits). Non-numeric tags → `realtime`; numeric blocks are checked via `EvmIsBlockFinalized` on the serving upstream, then the last upstream tried, then the network-wide lowest-finalized heuristic. **Served tip.** Default ("max mode"): `EvmHighestLatestBlockNumber` / `EvmHighestFinalizedBlockNumber` return the MAX across policy-eligible, non-syncing upstreams. Setting `evm.servedTip.enabledFor` switches that axis to a cluster-min, cross-pod monotonic counter. Rollback tolerance is 1024 blocks (`DefaultToleratedBlockHeadRollback`, [`architecture/evm/evm_state_poller.go:L27`](https://github.com/erpc/erpc/blob/main/architecture/evm/evm_state_poller.go#L27)). A `use-upstream` selector produces a per-group scoped tip keyed by the SHA-256 (first 8 bytes, hex-encoded) of the sorted matched upstream-id set; unmatched selectors compute a stateless min and emit no gauges. Group partitions are capped at 16 (`maxServedTipPartitions`); a "simple" group selector must be ≤128 chars, no whitespace, at most one leading `!`, and the matched set must be ≥2 upstreams and less than all upstreams. Syncing upstreams are excluded from every head computation (max mode, cluster mode, lowest-finalized, and guaranteed-method floors). `EvmLeaderUpstream` returns the upstream with the highest raw `LatestBlock()`; `EvmLowestFinalizedBlockNumber` returns the min positive effective finalized across non-syncing upstreams. ### Config schema #### `projects[].networks[]` — NetworkConfig | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `architecture` | `string` (`evm`) | Inferred `"evm"` when `evm` block present ([`common/defaults.go:L1908-1912`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1908-L1912)); forced `evm` in constructor when empty ([`erpc/networks_registry.go:L169-171`](https://github.com/erpc/erpc/blob/main/erpc/networks_registry.go#L169-L171)) | Only `evm` is valid. Required by validation. | | `evm` | `EvmNetworkConfig` | Auto-created empty struct ([`common/defaults.go:L1914-1916`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1914-L1916)) | See EvmNetworkConfig table below. | | `alias` | `string` | `""` | Charset `[a-zA-Z0-9_-]+` ([`common/validation.go:L1264-1269`](https://github.com/erpc/erpc/blob/main/common/validation.go#L1264-L1269)); unique per project ([`common/validation.go:L622-627`](https://github.com/erpc/erpc/blob/main/common/validation.go#L622-L627)). Used as the `network` metric label via `Network.Label()` ([`erpc/networks.go:L278-286`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L278-L286)). Aliases work in URL path only — not in body `networkId`. | | `rateLimitBudget` | `string` | `""` (none); inherits `networkDefaults.rateLimitBudget` when empty ([`common/defaults.go:L1783-1785`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1783-L1785)) | Must reference an existing budget. Enforced per-request before upstream contact → `ErrNetworkRateLimitRuleExceeded`. | | `failsafe[]` | `[]FailsafeConfig` | `nil`; inherited/merged from `networkDefaults.failsafe` | One `networkExecutor` per entry + a no-op catch-all ([`erpc/networks_registry.go:L115-138`](https://github.com/erpc/erpc/blob/main/erpc/networks_registry.go#L115-L138)). Selection: first config-order match on `matchMethod` wildcard + `matchFinality` ([`erpc/networks.go:L907-929`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L907-L929)). Old single-object YAML auto-converted to a one-element list with `matchMethod: "*"`. | | `selectionPolicy` | `SelectionPolicyConfig` | `nil`; inherited wholesale from `networkDefaults.selectionPolicy` when nil ([`common/defaults.go:L1833-1836`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1833-L1836)) | Auto-attached when any upstream has tag `tier:fallback` ([`common/defaults.go:L1932-1940`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1932-L1940)). See [Selection & scoring](/config/projects/selection-policies.llms.txt). | | `directiveDefaults` | `DirectiveDefaultsConfig` | Always materialized ([`common/defaults.go:L1947-1950`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1947-L1950)); inherited wholesale from `networkDefaults.directiveDefaults` when nil ([`common/defaults.go:L1837-1840`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1837-L1840)) | **Footgun**: a network that sets ANY `directiveDefaults` field ignores `networkDefaults.directiveDefaults` entirely — no per-field merge. Applied at request start ([`erpc/networks.go:L937`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L937)). | | `multiplexing` | `*bool` | `nil` = enabled ([`common/config.go:L2034-2039`](https://github.com/erpc/erpc/blob/main/common/config.go#L2034-L2039)); inherits `networkDefaults.multiplexing` when nil ([`common/defaults.go:L1841-1844`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1841-L1844)) | Gates in-flight identical-request dedup. **Footgun**: legacy single-object `networkDefaults.failsafe` YAML drops this field silently (old struct has no `Multiplexing` field, [`common/config.go:L626-655`](https://github.com/erpc/erpc/blob/main/common/config.go#L626-L655)). | | `staticResponses[]` | `[]StaticResponseConfig` | `nil` | Checked before multiplexer, cache, upstreams ([`erpc/networks.go:L976-981`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L976-L981)). See [Static responses](/config/projects/static-responses.llms.txt). | | `staticResponses[].method` | `string` | required | Exact JSON-RPC method name; case-sensitive string equality ([`common/validation.go:L1282-1284`](https://github.com/erpc/erpc/blob/main/common/validation.go#L1282-L1284)). | | `staticResponses[].params` | `[]any` | `nil` | `nil` and `[]` are interchangeable in matching (both have `len==0`). YAML `params: []` deserializes to non-nil empty slice; omitted `params:` deserializes to nil — both match requests with zero params. Hex strings (`"0x0"` vs `"0x00"`) are NOT normalized — exact string match only. Declaration order matters: first match wins. | | `staticResponses[].response.result` | `any` | — | Exactly one of `result` or `error` must be set; both or neither → startup error. | | `staticResponses[].response.error.code` | `int` | `0` | No minimum/maximum enforced. **Footgun**: code `0` passes validation but is omitted from the wire JSON (`json:"code,omitempty"` on `int`, [`common/errors.go:L2226`](https://github.com/erpc/erpc/blob/main/common/errors.go#L2226)). Always set a non-zero code in production. | | `staticResponses[].response.error.message` | `string` | required when error set | Non-empty string required; `Validate()` returns `"response.error.message is required"` otherwise. | | `staticResponses[].response.error.data` | `any` | `nil` | Optional arbitrary extra data; omitted from JSON when nil. | | `methods.preserveDefaultMethods` | `bool` | `false` | **Critical footgun**: `false` + any `definitions` entries → ALL built-in methods (hundreds, including `eth_call`, `eth_getLogs`) are silently replaced by only your custom entries plus stateful markers. `false` + no `definitions` → all built-ins kept (same as omitting the block). `true` + any `definitions` → built-ins copied first, then user entries merged on top ([`common/defaults.go:L493-576`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L493-L576)). | | `methods.definitions` | `map[string]CacheMethodConfig` | Full built-in table ([`common/defaults.go:L493-528`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L493-L528)) | Per-method fields: `finalized`, `realtime`, `stateful`, `reqRefs`, `respRefs`, `translateLatestTag`, `translateFinalizedTag`, `enforceBlockAvailability`. | | `methods.definitions..finalized` | `bool` | `false` (true for static-cache methods) | **Footgun**: setting `true` on a non-finalized method (e.g. `eth_getBalance`) permanently caches ALL responses from that method with long/permanent TTL — a severe data-correctness bug ([`erpc/networks.go:L1656-1660`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1656-L1660)). | | `methods.definitions..realtime` | `bool` | `false` (true for `eth_blockNumber` and similar) | Forces `GetFinality` → `Realtime`; responses use the realtime cache policy (short TTL). | | `methods.definitions..stateful` | `bool` | `true` for `DefaultStatefulMethodNames`, else `false` | **Footgun**: `stateful: false` on any of the 6 built-in stateful methods (`eth_newFilter`, `eth_newBlockFilter`, `eth_newPendingTransactionFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_uninstallFilter`) is silently overridden to `true` with no warning ([`common/defaults.go:L512-519`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L512-L519)). | | `methods.definitions..translateLatestTag` | `*bool` | `nil` = enabled | `nil`/`true` → `"latest"` tag replaced with current block hex before forwarding and cache keying. `false` → tag passed through unchanged. `eth_getBlockByNumber` ships with `false` so it can discover new blocks. **Footgun**: `false` on a changing method means all callers share one `"latest"` cache key; stale data returned until TTL expires ([`common/config.go:L307-309`](https://github.com/erpc/erpc/blob/main/common/config.go#L307-L309)). | | `methods.definitions..translateFinalizedTag` | `*bool` | `nil` = enabled | Same as `translateLatestTag` for the `"finalized"` tag ([`common/config.go:L310-312`](https://github.com/erpc/erpc/blob/main/common/config.go#L310-L312)). | | `methods.definitions..enforceBlockAvailability` | `*bool` | `nil` (inherit) | Per-method override; highest priority in the 5-step enforcement chain ([`erpc/networks.go:L1806-1813`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1806-L1813)). | #### `projects[].networks[].evm` — EvmNetworkConfig | Field | Type | Default | Notes | |---|---|---|---| | `chainId` | `int64` | — (no default) | Drives `networkId = "evm:"`. Not validated > 0 for networks; `chainId: 0` yields `evm:0`. | | `fallbackFinalityDepth` | `int64` | `1024` ([`common/defaults.go:L1975`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1975)) | Used when an upstream doesn't expose the `finalized` tag. Must be > 0 after defaults. | | `fallbackStatePollerDebounce` | `Duration` | `5s` ([`common/defaults.go:L1976`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1976)) | Static debounce for block polling until dynamic block time is learned. Must be > 0. | | `dynamicBlockTimeDebounceMultiplier` | `*float64` | `0.7` ([`common/defaults.go:L1977`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1977)) | Polling debounce = EMA block time × multiplier. Lower → more aggressive polling (fresher data, more upstream load). | | `blockUnavailableDelayMultiplier` | `*float64` | `1.0` ([`common/defaults.go:L1978`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1978)) | Retry delay for block-unavailable = EMA block time × multiplier. | | `enforceBlockAvailability` | `*bool` | `nil` (defer; fallback = enabled) | Network-level priority 2 in 5-step chain. `nil` defers to upstream bounds and system defaults. `false` disables enforcement even when an upstream has explicit bounds configured. | | `maxRetryableBlockDistance` | `*int64` | `nil` → `128` at use site ([`erpc/networks.go:L1973-1979`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1973-L1979)) | Block-unavailable errors within this distance of upstream head are retryable; beyond → non-retryable skip. | | `getLogsMaxAllowedRange` | `int64` | `30_000` ([`common/defaults.go:L2092-2094`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2092-L2094)) | Hard limit on `eth_getLogs` block range. Must be > 0; `0` inherits defaults value. | | `getLogsMaxAllowedAddresses` | `int64` | `0` = unlimited | Enforced only when > 0 ([`architecture/evm/eth_getLogs.go:L212`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getLogs.go#L212)). | | `getLogsMaxAllowedTopics` | `int64` | `0` = unlimited | Enforced only when > 0. | | `getLogsSplitOnError` | `*bool` | `true` ([`common/defaults.go:L2095-2097`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2095-L2097)) | Retry by bisecting range on "too many results" errors. | | `getLogsSplitConcurrency` | `int` | `10` ([`common/defaults.go:L2098-2100`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2098-L2100)) | Parallelism cap for split sub-requests. | | `traceFilterSplitOnError` | `*bool` | `nil` = **off** (deliberate opt-in, [`common/defaults.go:L2102-2104`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2102-L2104)) | Opt-in bisecting for `trace_filter`/`arbtrace_filter`. | | `traceFilterSplitConcurrency` | `int` | `10` ([`common/defaults.go:L2105-2107`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2105-L2107)) | Parallelism cap for trace-filter splits. | | `idempotentTransactionBroadcast` | `*bool` | `nil` = **enabled** ([`architecture/evm/eth_sendRawTransaction.go:L27-36`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_sendRawTransaction.go#L27-L36)) | "Already known"/"nonce too low"-verified errors become success-with-tx-hash, making retry/hedge safe for `eth_sendRawTransaction`. | | `markEmptyAsErrorMethods` | `[]string` | `eth_blockNumber`, `eth_getBlockByNumber`, `eth_getTransactionByHash`, and 8 others ([`common/defaults.go:L2044-2057`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2044-L2057)) | Methods where empty response = error (retried, upstream scored down). `eth_getTransactionReceipt` deliberately excluded. | | `emptyResultConfidence` | `blockHead` \| `finalizedBlock` | `blockHead` ([`common/defaults.go:L2075-2079`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2075-L2079)) | How confirmed a block must be for empty point-lookups to be retried as missing data. | | `evm.integrity.enforceHighestBlock` | `*bool` | `true` ([`common/defaults.go:L2117-2120`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2117-L2120)) | **Deprecated** — migrated into `directiveDefaults.enforceHighestBlock` at `SetDefaults` time when the directive is unset ([`common/defaults.go:L1952-1966`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1952-L1966)). Prefer `directiveDefaults.enforceHighestBlock`. | | `evm.integrity.enforceGetLogsBlockRange` | `*bool` | `true` ([`common/defaults.go:L2121-2123`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2121-L2123)) | **Deprecated** — same migration path as `enforceHighestBlock`. | | `evm.integrity.enforceNonNullTaggedBlocks` | `*bool` | `true` ([`common/defaults.go:L2124-2126`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2124-L2126)) | **Deprecated** — same migration path. | | `maxFutureBlockRetryDistance` | `*int64` | `nil` | **Deprecated** — warned about and set to nil in `SetDefaults` ([`common/defaults.go:L2080-2083`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2080-L2083)). Has no effect; remove from configs. | | `servedTip` | `EvmServedTipConfig` | `nil` = max mode | Copied wholesale from `networkDefaults.evm.servedTip` when nil. | | `servedTip.enabledFor` | `[]string` | `[]` (max mode) | Valid: `latest`, `finalized`, `safe`. Listing a tag switches that axis from max-across-upstreams to cluster-min + monotonic clamp via shared state. | | `servedTip.clusterDelta` | `int64` | `0` = auto-derive from EMA block time, clamped `[2, 10]` | Must be ≥ 0. | | `servedTip.guaranteedMethods` | `[]string` (glob) | `[]` | Global tip clamped to per-method supporting-set floor ([`erpc/networks.go:L643-649`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L643-L649)). | #### `projects[].networks[].directiveDefaults` — DirectiveDefaultsConfig | Field | Type | Default | |---|---|---| | `retryEmpty` | `*bool` | `nil` | | `retryPending` | `*bool` | `nil` | | `skipCacheRead` | `bool\|string` | `nil` | | `useUpstream` | `*string` | `nil` | | `skipInterpolation` | `*bool` | `nil` | | `skipConsensus` | `*bool` | `nil` | | `enforceHighestBlock` | `*bool` | **`true`** ([`common/defaults.go:L1458-1460`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1458-L1460)) | | `enforceGetLogsBlockRange` | `*bool` | **`true`** ([`common/defaults.go:L1461-1463`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1461-L1463)) | | `enforceNonNullTaggedBlocks` | `*bool` | **`true`** ([`common/defaults.go:L1464-1466`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1464-L1466)) | | `validateTransactionsRoot` | `*bool` | **`true`** ([`common/defaults.go:L1467-1469`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1467-L1469)) | | `validateHeaderFieldLengths` | `*bool` | `nil` | | `validateTransactionFields` | `*bool` | `nil` | | `validateTransactionBlockInfo` | `*bool` | `nil` | | `enforceLogIndexStrictIncrements` | `*bool` | `nil` | | `validateTxHashUniqueness` | `*bool` | `nil` | | `validateTransactionIndex` | `*bool` | `nil` | | `validateLogFields` | `*bool` | `nil` | | `validateLogsBloomEmptiness` | `*bool` | `nil` | | `validateLogsBloomMatch` | `*bool` | `nil` | | `validateReceiptTransactionMatch` | `*bool` | `nil` | | `validateContractCreation` | `*bool` | `nil` | | `receiptsCountExact` | `*int64` | `nil` | | `receiptsCountAtLeast` | `*int64` | `nil` | | `validationExpectedBlockHash` | `*string` | `nil` | | `validationExpectedBlockNumber` | `*int64` | `nil` | #### `projects[].networkDefaults` — NetworkDefaults | Field | Type | Default | Inheritance rule | |---|---|---|---| | `rateLimitBudget` | `string` | `""` | Copied when network's value is empty string ([`common/defaults.go:L1783-1785`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1783-L1785)). Validated after inheritance per network — an invalid name surfaces as a per-network error (e.g. `"network.*.rateLimitBudget 'x' does not exist"`), not a top-level `networkDefaults` error. Networks that explicitly set their own non-empty `rateLimitBudget` are not affected. | | `failsafe[]` | `[]FailsafeConfig` | `nil` | Network has none → deep-copied wholesale. Network has some → per-entry merge from the FIRST compatible default (wildcard method + finality match); break on first match ([`common/defaults.go:L1793-1832`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1793-L1832)). | | `selectionPolicy` | `SelectionPolicyConfig` | `nil` | Shallow-copied when network's is nil ([`common/defaults.go:L1833-1836`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1833-L1836)). | | `directiveDefaults` | `DirectiveDefaultsConfig` | `nil` | Shallow-copied when network's is nil — **no per-field merge** ([`common/defaults.go:L1837-1840`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1837-L1840)). | | `evm` | `EvmNetworkConfig` | `nil` | Struct-copied wholesale when network has no `evm` block. Otherwise per-field fill for: `integrity`, `fallbackStatePollerDebounce`, `dynamicBlockTimeDebounceMultiplier`, `blockUnavailableDelayMultiplier`, `fallbackFinalityDepth`, `getLogsMaxAllowed*`, `getLogs*`, `traceFilter*`, `servedTip`, `emptyResultConfidence`. NOT inherited individually: `chainId`, `enforceBlockAvailability`, `maxRetryableBlockDistance`, `markEmptyAsErrorMethods`, `idempotentTransactionBroadcast` ([`common/defaults.go:L1845-1889`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1845-L1889)). | | `multiplexing` | `*bool` | `nil` | Value-copied when network's `multiplexing` is nil ([`common/defaults.go:L1841-1844`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1841-L1844)). No deep merge — a network must set its own `multiplexing` to deviate from the project-wide default. | Note: `networkDefaults` has **no** `alias`, `methods`, `staticResponses`, or `architecture` fields — those are per-network only. #### `server.aliasing.rules[]` — domain-based aliasing | Field | Type | Default | Behavior | |---|---|---|---| | `rules[]` | list | `nil`; auto-created `{matchDomain: "*", serveProject: "main"}` when config has zero projects ([`common/defaults.go:L100-110`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L100-L110)) | First rule whose `matchDomain` wildcard matches the request Host wins ([`erpc/http_server.go:L232-257`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L232-L257)). | | `rules[].matchDomain` | `string` (wildcard) | — | Matched against Host header (port stripped). | | `rules[].serveProject` | `string` | — | Pre-selects projectId. | | `rules[].serveArchitecture` | `string` | — | Pre-selects architecture. | | `rules[].serveChain` | `string` | — | Pre-selects chainId. Pre-selecting project+chain WITHOUT architecture is rejected at parse time. | ### Worked examples All patterns below are distilled from real production fleets; comments explain the non-obvious choices. **1. The production baseline: `networkDefaults` with finality-split failsafe policies.** Block-availability races on realtime/unfinalized requests need a non-zero retry delay; finalized/unknown don't — this is why production splits them: **Config path:** `projects[].networkDefaults` **YAML — `erpc.yaml`:** ```yaml projects: - id: main networkDefaults: # All chains inherit these unless they set their own failsafe list directiveDefaults: retryEmpty: true retryPending: false # multiplexing: false in production to avoid follower-timeout amplification # on chains where every block is a write (e.g. high-throughput L2s) multiplexing: false evm: getLogsMaxAllowedRange: 30000 getLogsSplitOnError: true # cluster-min tip on "latest" prevents backward tip jumps across pods servedTip: enabledFor: [latest] failsafe: # NOTE: delay MUST NOT be 0 for realtime/unfinalized — a short non-zero # delay gives shared-state propagation time to catch up after a block # arrives on one upstream but not yet on another - matchMethod: "eth_call|eth_getLogs" matchFinality: [realtime, unfinalized] hedge: quantile: 0.95 maxCount: 1 minDelay: 500ms maxDelay: 10s retry: maxAttempts: 4 delay: 50ms - matchMethod: "eth_call|eth_getLogs" matchFinality: [finalized, unknown] hedge: quantile: 0.95 maxCount: 1 minDelay: 500ms maxDelay: 10s retry: maxAttempts: 4 delay: 0 - matchMethod: "*" retry: maxAttempts: 6 delay: 50ms networks: - architecture: evm evm: { chainId: 1 } alias: ethereum - architecture: evm evm: { chainId: 42161 } alias: arbitrum ``` **TypeScript — `erpc.ts`:** ```typescript projects: [{ id: "main", networkDefaults: { directiveDefaults: { retryEmpty: true, retryPending: false }, // multiplexing: false in production to avoid follower-timeout amplification multiplexing: false, evm: { getLogsMaxAllowedRange: 30000, getLogsSplitOnError: true, // cluster-min tip on "latest" prevents backward tip jumps across pods servedTip: { enabledFor: ["latest"] }, }, failsafe: [ // NOTE: delay MUST NOT be 0 for realtime/unfinalized — non-zero delay // gives shared-state propagation time after a block arrives on one upstream { matchMethod: "eth_call|eth_getLogs", matchFinality: ["realtime", "unfinalized"], hedge: { quantile: 0.95, maxCount: 1, minDelay: "500ms", maxDelay: "10s" }, retry: { maxAttempts: 4, delay: "50ms" }, }, { matchMethod: "eth_call|eth_getLogs", matchFinality: ["finalized", "unknown"], hedge: { quantile: 0.95, maxCount: 1, minDelay: "500ms", maxDelay: "10s" }, retry: { maxAttempts: 4, delay: "0" }, }, { matchMethod: "*", retry: { maxAttempts: 6, delay: "50ms" } }, ], }, networks: [ { evm: { chainId: 1 }, alias: "ethereum" }, { evm: { chainId: 42161 }, alias: "arbitrum" }, ], }] ``` **2. Per-chain bespoke policy appended before the shared catch-all.** Ethereum mainnet adds consensus on eth_getLogs and state-read integrity checks; Arbitrum bumps the getLogs hedge floor for its faster blocks. Both spread `sharedNetworkFailsafe` at the end so the catch-all policy is never per-chain: **Config path:** `projects[].networks[]` **YAML — `erpc.yaml`:** ```yaml networks: - evm: { chainId: 1 } alias: ethereum failsafe: # Ethereum-specific: 2-of-3 consensus on getLogs for indexing integrity - matchMethod: "eth_getLogs|eth_getBlockReceipts" matchFinality: [unfinalized, unknown] timeout: { duration: 15s } hedge: quantile: 0.95 maxCount: 1 minDelay: 500ms maxDelay: 10s retry: maxAttempts: 6 delay: 0 emptyResultDelay: 1000ms # consensus config would follow here # ... sharedNetworkFailsafe spread after (catch-all always last) - evm: { chainId: 42161 } alias: arbitrum-one failsafe: # Arbitrum-specific: getLogs minDelay raised to 1000ms — Arbitrum delivers # blocks at ~250ms and block-availability races persist slightly longer - matchMethod: "eth_getLogs|eth_getBlockReceipts" matchFinality: [unfinalized] hedge: quantile: 0.95 maxCount: 1 minDelay: 1000ms maxDelay: 10s retry: maxAttempts: 6 delay: 0 # ... sharedNetworkFailsafe spread after ``` **TypeScript — `erpc.ts`:** ```typescript networks: [ { evm: { chainId: 1 }, alias: "ethereum", failsafe: [ { matchMethod: "eth_getLogs|eth_getBlockReceipts", matchFinality: ["unfinalized", "unknown"], timeout: { duration: "15s" }, hedge: { quantile: 0.95, maxCount: 1, minDelay: "500ms", maxDelay: "10s" }, retry: { maxAttempts: 6, delay: "0", emptyResultDelay: "1000ms" }, // consensus config would follow }, // ...sharedNetworkFailsafe spread after ], }, { evm: { chainId: 42161 }, alias: "arbitrum-one", failsafe: [ { matchMethod: "eth_getLogs|eth_getBlockReceipts", matchFinality: ["unfinalized"], // Arbitrum delivers ~250ms blocks; minDelay raised to avoid // block-availability races more common on fast L2s hedge: { quantile: 0.95, maxCount: 1, minDelay: "1000ms", maxDelay: "10s" }, retry: { maxAttempts: 6, delay: "0" }, }, // ...sharedNetworkFailsafe spread after ], }, ] ``` **3. Static genesis-block response for a chain that starts at block 1.** Serve a canned reply before the multiplexer and cache so clients don't hit a dead upstream on block 0. The `staticResponses` array is checked before everything else in the forward pipeline: **Config path:** `projects[].networks[].staticResponses[]` **YAML — `erpc.yaml`:** ```yaml networks: - evm: { chainId: 1328 } alias: sei-testnet staticResponses: - method: eth_getBlockByNumber params: ["0x0", false] response: result: # Fill the fields your clients read; unrecognised fields are fine number: "0x0" hash: "0x0000000000000000000000000000000000000000000000000000000000000000" parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000" transactions: [] ``` **TypeScript — `erpc.ts`:** ```typescript networks: [{ evm: { chainId: 1328 }, alias: "sei-testnet", staticResponses: [{ method: "eth_getBlockByNumber", params: ["0x0", false], response: { result: { number: "0x0", hash: "0x0000000000000000000000000000000000000000000000000000000000000000", parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", transactions: [], }, }, }], }] ``` **4. Disable `validateTransactionsRoot` for non-standard chains.** ZkSync Era and other ZK-rollups omit or zero-fill `transactionsRoot` — eRPC's default validation rejects the response. Set `directiveDefaults` at the network level (not project-wide) so only that chain skips the check: **Config path:** `projects[].networks[].directiveDefaults` **YAML — `erpc.yaml`:** ```yaml networks: - evm: { chainId: 324 } alias: zksync-mainnet # directiveDefaults is ALL-OR-NOTHING: setting any field here means # networkDefaults.directiveDefaults is completely ignored for this network directiveDefaults: validateTransactionsRoot: false # Re-state any networkDefaults.directiveDefaults fields you still want retryEmpty: true retryPending: false ``` **TypeScript — `erpc.ts`:** ```typescript networks: [{ evm: { chainId: 324 }, alias: "zksync-mainnet", // directiveDefaults is ALL-OR-NOTHING — re-state shared fields you want kept directiveDefaults: { validateTransactionsRoot: false, retryEmpty: true, retryPending: false, }, }] ``` **5. Domain-based aliasing for fully path-free routing.** Route an entire subdomain straight to a chain — no project or network segment in the URL path. The alias and domain rule work together so clients call a clean HTTPS endpoint: ```yaml server: aliasing: rules: - matchDomain: "eth.rpc.example.com" serveProject: main serveArchitecture: evm serveChain: "1" # Wildcard: any unknown subdomain falls through to the main project - matchDomain: "*.rpc.example.com" serveProject: main ``` ``` POST https://eth.rpc.example.com/ → main/evm:1 ``` ### Request/response behavior - The `network` label on every Prometheus metric equals the alias when set, otherwise the raw `evm:N` id. This affects all `erpc_network_*` metrics. [`erpc/networks.go:L278-286`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L278-L286) - Static-response hits short-circuit before the multiplexer; `static_response.hit=true` is set on the `Network.Forward` OTel span. They never read or write the cache. - Multiplexer followers receive a copy of the leader's response; if the follower's context deadline fires before the leader completes, the error is `ErrNetworkRequestTimeout` (HTTP 504 status code, JSON-RPC error code −32603). This is distinct from `ErrFailsafeTimeoutExceeded` (failsafe policy timeout) and `ErrEndpointRequestTimeout` (individual upstream HTTP timeout). - `ErrNetworkInitializing` (HTTP 503) is returned while the bootstrap task is still running. The client should retry. Failed (non-fatal) bootstrap tasks are auto-retried by the background loop with factor-1.5 backoff (3 s to 130 s, task timeout 120 s). - `ErrNetworkNotSupported` (HTTP 404) is returned when all providers report the chain is not supported and zero upstreams were registered. It is never retried by the bootstrap loop. - `ErrNetworkNotFound` (HTTP 404) is returned after a successful bootstrap task that stored nothing (defensive path). The caller must retry. - `ErrInvalidRequest` (HTTP 400) is returned when the networkId in the URL is not a valid `evm:` format — the error message suggests using an alias or `evm/42161` form. [`erpc/networks_registry.go:L220-222`](https://github.com/erpc/erpc/blob/main/erpc/networks_registry.go#L220-L222) - `ErrNoUpstreamsFound` (HTTP 404) fires when the policy engine returns an empty ordered list and the raw registration list is also empty — all upstreams have been filtered out. - `ErrUpstreamsExhausted` fires when every upstream in the ordered list was tried and all failed; it carries per-upstream errors. [`erpc/networks.go:L1392-1406`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1392-L1406) - `ErrNotImplemented` (HTTP 501) is returned for `eth_accounts` and `eth_sign` (always); stateful methods with more than one candidate upstream also produce this error unless scoped by a `use-upstream` directive. - `ErrInvalidEvmChainId` (HTTP 400) is returned when the chain id cannot be parsed. - `ErrNetworkRequestTimeout` wire behavior: JSON-RPC error code −32603; HTTP transport status is 200 (JSON-RPC over HTTP); `ErrorStatusCode()` returns 504 but this is only seen in non-JSON-RPC error paths. The error is retryable toward both network and upstream (no explicit non-retryable flag). Message format: `"network-level request towards one or more upstreams timed out after ms"` where N is the elapsed time since the follower registered with the multiplexer. - An alias in the request body `networkId` field is silently ignored — the body fallback splits the literal value on `:` with no alias lookup. Only URL path segments resolve aliases. ### Best practices - Always set `networkDefaults.failsafe` before adding per-chain `failsafe` entries — once a network has any failsafe list, the defaults list is superseded entirely for that network. - Use `alias` on every statically-declared chain you control. It makes URLs readable, shows up in every Prometheus label, and costs nothing. - Set `preserveDefaultMethods: true` before adding any custom `methods.definitions` entry. The default `false` silently drops all built-in methods (including `eth_call`, `eth_getLogs`) the moment you add one definition. - Never set `finalized: true` on a method that returns block-specific data (`eth_getBalance`, `eth_getCode`, etc.) — every response will be permanently cached at the first value ever returned. - Enable `servedTip.enabledFor: [latest, finalized]` in production multi-pod deployments to prevent backward tip jumps across pods that could surface stale reads to clients. - Keep `getLogsMaxAllowedRange` at its default 30 000 unless your upstreams document a higher limit — range splits happen automatically on oversized requests but `getLogsSplitConcurrency` caps parallelism at 10 to avoid flooding providers. - For `eth_sendRawTransaction` in high-retry configs, leave `idempotentTransactionBroadcast` enabled (default). Disabling it turns idempotent "already known" errors back into real failures, making retries unsafe. ### Edge cases & gotchas 1. **Aliases work in URLs only, not body `networkId`** — body fallback splits on `:` with no alias lookup; only `parseUrlPath` resolves aliases. [`erpc/http_server.go:L613-634`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L613-L634) 2. **Unknown alias segment falls through silently** — an unresolvable single path segment is treated as architecture, yielding "architecture is not valid (must be 'evm')" instead of an alias-not-found error. 3. **`directiveDefaults` is all-or-nothing** — a network-level `directiveDefaults` block completely replaces `networkDefaults.directiveDefaults`; individual fields are not merged. [`common/defaults.go:L1837-1840`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1837-L1840) 4. **`failsafe` replaces, not merges** — any `networks[].failsafe` list supersedes `networkDefaults.failsafe` entirely for that network. 5. **`preserveDefaultMethods: false` + one custom `definitions` entry drops ALL built-ins** — including `eth_call`, `eth_getLogs`, etc. Only the custom entries plus the 6 stateful markers survive. [`common/defaults.go:L561-573`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L561-L573) 6. **`finalized: true` on a non-finalized method permanently caches all responses** — every response is placed in the `finalized` cache bucket (long/permanent TTL). Setting this on `eth_getBalance` would permanently cache every balance at the first value returned. [`erpc/networks.go:L1656-1660`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1656-L1660) 7. **`networkDefaults.multiplexing` has no deep merge** — a network must set its own `multiplexing` field; there is no per-network override via defaults alone. 8. **Legacy single-object `networkDefaults.failsafe` silently drops `multiplexing`** — the old-format fallback struct has no `Multiplexing` field. [`common/config.go:L626-655`](https://github.com/erpc/erpc/blob/main/common/config.go#L626-L655) 9. **Lazily-exposed networks never get a metrics alias resolver entry** — `erpc/init.go` builds the map only from static `cfg.Projects[].Networks[]`; gRPC-cache metrics for lazy networks show the raw networkId. [`erpc/init.go:L62-77`](https://github.com/erpc/erpc/blob/main/erpc/init.go#L62-L77) 10. **`evm.enforceBlockAvailability: false` overrides upstream bounds** — step 3 of the enforcement chain (upstream-has-bounds) is only reached when steps 1–2 yield no decision; an explicit `false` at the network level (step 2) short-circuits it. [`erpc/networks.go:L1814-1817`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1814-L1817) 11. **Static response error code `0` is omitted from the wire response** — `Code` is tagged `json:"code,omitempty"` on int, so `0` disappears from the JSON error object. Always use a non-zero code in production. [`common/errors.go:L2226`](https://github.com/erpc/erpc/blob/main/common/errors.go#L2226) 12. **Failsafe merge picks the FIRST matching default** — ordering of `networkDefaults.failsafe` entries matters when multiple wildcard entries could match. 13. **`stateful: false` on a built-in stateful method is silently overridden** — `MethodsConfig.SetDefaults` force-sets `Stateful = true` for all 6 stateful methods in all three code paths, overwriting any user-supplied `false` without any warning. [`common/defaults.go:L512-573`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L512-L573) 14. **`EnforceGetLogsBlockRange` and `EnforceHighestBlock` directives cannot be overridden per-request via HTTP headers for `eth_getLogs`, `trace_filter`, `arbtrace_filter`, and `eth_blockNumber`** — these hooks read from the config baked in at startup, not live per-request directives. Set `enforceGetLogsBlockRange: false` in `directiveDefaults`, not as an HTTP header. [`architecture/evm/eth_getLogs.go:L282-285`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getLogs.go#L282-L285) 15. **`ErrNetworkRequestTimeout` is produced only in the multiplexer follower path** — a request that is itself the leader experiences `ErrFailsafeTimeoutExceeded` or upstream sweep errors, never `ErrNetworkRequestTimeout`. [`erpc/networks.go:L2079-2084`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L2079-L2084) 16. **`evm.chainId: 0` is not rejected** — `chainId: 0` produces `networkId = evm:0`, which passes `util.IsValidNetworkId` but is excluded by provider `onlyNetworks`/`ignoreNetworks` lists that use the stricter `chainId > 0` check. 17. **`translateLatestTag: false` on a non-historical method collapses all callers to one cache key** — all requests for that method share the literal `"latest"` key; stale data is returned until the cache TTL expires. 18. **`preserveDefaultMethods: false` with NO `definitions` block keeps ALL built-ins** — the drop-all behavior only triggers when `definitions` is non-empty AND `preserveDefaultMethods` is false. A config with `methods: { preserveDefaultMethods: false }` and no `definitions` key is identical to omitting the `methods` block entirely. [`common/defaults.go:L494`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L494) 19. **Lazy network alias registration happens after first bootstrap** — statically-declared networks register aliases eagerly in the `NetworksRegistry` constructor; lazily-created networks only register their alias inside `prepareNetwork`, which runs during bootstrap. Until bootstrap completes, alias-based URLs for lazy networks return an alias-not-found error. [`erpc/networks_registry.go:L322-328`](https://github.com/erpc/erpc/blob/main/erpc/networks_registry.go#L322-L328) 20. **Failed lazy bootstrap is user-retryable** — non-fatal bootstrap tasks are retried by the background auto-retry loop and by the next request's `ExecuteTasks`. Only `resolveNetworkConfig` format errors are `TaskFatal` and never retried. [`erpc/networks_registry.go:L263-266`](https://github.com/erpc/erpc/blob/main/erpc/networks_registry.go#L263-L266) 21. **Provider `onlyNetworks` bypasses vendor support checks entirely** — a networkId listed in `onlyNetworks` returns `SupportsNetwork = true` without consulting the vendor's dynamic check; `ignoreNetworks` is evaluated first and short-circuits to false. [`thirdparty/provider.go:L34-49`](https://github.com/erpc/erpc/blob/main/thirdparty/provider.go#L34-L49) 22. **Synthesized lazy network configs become permanently visible** — `ExposeNetworkConfig` appends to `project.Config.Networks` (admin API surface) and never overwrites an existing entry, so lazy networks persist in the admin response after first use. [`erpc/projects.go:L58-77`](https://github.com/erpc/erpc/blob/main/erpc/projects.go#L58-L77) 23. **Syncing upstreams are excluded from every head computation** — max mode, cluster mode, lowest-finalized, and guaranteed-method floor calculations all skip upstreams in `EvmSyncingStateSyncing`. [`erpc/networks.go:L327-330`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L327-L330) 24. **Future-block short-circuit compares against the MAX eligible head, not the cluster-min served tip** — it never nulls a block that the most-ahead upstream reports having. The synthetic null response is never cached, and the short-circuit only fires for concrete block numbers (not tags or hashes). [`erpc/networks.go:L570-610`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L570-L610) 25. **Wrong-empty misbehavior is suppressed for out-of-bounds blocks** — if the block number in a missing-data response falls outside an upstream's configured `blockAvailability` bounds, the empty response is expected and no misbehavior is recorded against that upstream. [`erpc/networks.go:L1566-1587`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1566-L1587) 26. **`getLogsMaxAllowedRange: 0` is rejected by validation** — the field must be > 0 after defaults (`common/validation.go:L1309-1311`). Unlike `getLogsMaxAllowedAddresses` and `getLogsMaxAllowedTopics` (which default to 0 = unlimited), `getLogsMaxAllowedRange` defaults to 30 000 and cannot be explicitly zeroed out; setting `0` in a network inheriting from defaults is treated as "unset" and inherits the parent value. [`common/defaults.go:L2092-2094`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2092-L2094) 27. **Zero projects → implicit `main` project + catch-all aliasing rule** — when the config has no projects, eRPC auto-creates `{matchDomain: "*", serveProject: "main"}`, so `/evm/123` works without a project path segment. [`common/defaults.go:L100-110`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L100-L110) 28. **Selector-scoped tips never pollute network gauges** — stateless scoped picks (unmatched or non-simple selectors) use a sentinel lane and emit no Prometheus gauge; equivalent selectors dedup into one partition keyed by matched-set hash; the cap of 16 partitions is enforced globally per network. [`erpc/networks.go:L98-105`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L98-L105) 29. **Static response params matching has no wildcard** — there is no glob or `*` support; every params slot must match exactly. To catch a method regardless of params, add an entry with an empty `params` array. To match block 0 (`"0x0"`), be exact — `"0x00"` will not match. [`common/static_response.go:L14-99`](https://github.com/erpc/erpc/blob/main/common/static_response.go#L14-L99) ### Observability | Metric | Type | Labels | When it fires | |---|---|---|---| | `erpc_network_request_received_total` | counter | `project`, `network`, `category`, `finality`, `user`, `agent_name` | On project forward entry | | `erpc_network_successful_request_total` | counter | `project`, `network`, `vendor`, `upstream`, `category`, `attempt`, `finality`, `emptyish`, `user`, `agent_name` | Successful response (vendor/upstream = `` for cache hits) | | `erpc_network_failed_request_total` | counter | `project`, `network`, `category`, `attempt`, `error`, `severity`, `finality`, `user`, `agent_name` | Failed response | | `erpc_network_request_duration_seconds` | histogram | `project`, `network`, `vendor`, `upstream`, `category`, `finality`, `user` | Request completed (vendor/upstream = `` on failure) | | `erpc_network_multiplexed_request_total` | counter | `project`, `network`, `category`, `finality`, `user`, `agent_name` | Follower registered with the in-flight deduplicator | | `erpc_network_static_response_served_total` | counter | `project`, `network`, `category` | Static response matched and served | | `erpc_network_timeout_fired_total` | counter | `project`, `network`, `category`, `finality`, `scope` | Failsafe timeout fired; `scope=network` at network level | | `erpc_network_served_tip_block_number` | gauge | `project`, `network`, `lane`, `axis` | Served-tip value updated; absent in max mode | | `erpc_network_served_tip_lag_blocks` | gauge | `project`, `network`, `lane`, `axis` | `MaxEligible - served`, clamped ≥ 0; absent in max mode | | `erpc_network_served_tip_upstream_excluded_total` | counter | `project`, `network`, `upstream`, `axis`, `reason` | Upstream excluded from tip computation (`velocity`\|`outlier`) | | `erpc_network_hedged_request_total` | counter | `project`, `network`, `upstream`, `category`, `attempt`, `finality`, `user`, `agent_name` | Hedge attempt dispatched for this upstream | | `erpc_network_hedge_discards_total` | counter | `project`, `network`, `upstream`, `category`, `attempt`, `hedge`, `finality`, `user`, `agent_name` | Hedge response discarded (a faster response already won) | | `erpc_upstream_wrong_empty_response_total` | counter | `project`, `vendor`, `network`, `upstream`, `category`, `finality`, `user`, `agent_name` | Upstream returned missing-data while another upstream served real data; suppressed when the block is outside the upstream's `blockAvailability` bounds | | `erpc_unexpected_panic_total` | counter | (standard labels) | Panic recovered in the async cache-set goroutine | **Notable logs** (zerolog, at `erpc/networks_registry.go` and `erpc/networks.go` unless noted): - `"registered network alias"` / `"skipping duplicate alias registration with different target"` — alias map events. - `"network initialization ended with zero upstreams"` — bootstrap completed but no upstreams were registered. - `"networks bootstrap completed"` / `"failed to bootstrap networks in background"` — startup background bootstrap outcome. - `"response served from cache"` — cache hit in the forward pipeline. - `"found identical request initiating multiplexer"` — multiplexer follower registered. - `"block availability check failed; failing open to allow request"` — block-availability extraction or poller error; request proceeds. - `"served static response (no upstream contacted)"` (debug) — static response matched and served (`erpc/networks_static_responses.go`). - `"skipping static response: cannot inspect request"` (debug) — `JsonRpcRequest()` failed; static check skipped. - `"failed to build static response"` (error) — `NewJsonRpcResponse` failed on a matched static entry. - Provider task: `"registering N upstream(s) from provider"` (`upstream/registry.go`). ### Source code entry points - [`erpc/networks_registry.go:L83-376`](https://github.com/erpc/erpc/blob/main/erpc/networks_registry.go#L83-L376) — `NetworksRegistry`: lazy `GetNetwork`, bootstrap tasks, `resolveNetworkConfig` synthesis, alias map register/resolve, `NewNetwork` constructor. - [`erpc/networks.go:L931-1603`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L931-L1603) — `Network.Forward`: full request pipeline, failsafe-executor matching, static responses, multiplexing, cache, upstream ordering, block-availability gating, finality classification. - [`common/defaults.go:L1781-1973`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1781-L1973) — `NetworkConfig.SetDefaults`: networkDefaults inheritance, per-field fill rules, EvmNetworkConfig/MethodsConfig/DirectiveDefaults sub-passes. - [`erpc/http_server.go:L810-1006`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L810-L1006) — `parseUrlPath`: URL/alias parsing decision table, domain-aliasing rules, body-networkId fallback. - [`common/config.go:L1995-2256`](https://github.com/erpc/erpc/blob/main/common/config.go#L1995-L2256) — `NetworkConfig`, `EvmNetworkConfig` struct definitions, legacy single-failsafe decode, `NetworkId()`. - [`upstream/registry.go:L155-293`](https://github.com/erpc/erpc/blob/main/upstream/registry.go#L155-L293) — `PrepareUpstreamsForNetwork`: provider fan-out, ready-wait, 503/404 error states. - [`erpc/networks_static_responses.go`](https://github.com/erpc/erpc/blob/main/erpc/networks_static_responses.go) — `tryServeStaticResponse`: canned-response serving, metric emit. - [`common/static_response.go:L14-99`](https://github.com/erpc/erpc/blob/main/common/static_response.go#L14-L99) — `FindStaticResponseMatch`, `paramsEqual`, `valueEqual`: pure matching logic (YAML int vs JSON float64 tolerance, map key order independence). ### Related pages - [Static responses](/config/projects/static-responses.llms.txt) — full matching semantics and edge cases for canned replies. - [Selection & scoring](/config/projects/selection-policies.llms.txt) — upstream ordering within a network. - [Retry](/config/failsafe/retry.llms.txt) — per-network failsafe retry policy. - [Hedge](/config/failsafe/hedge.llms.txt) — parallel backup requests on slow upstreams. - [Timeout](/config/failsafe/timeout.llms.txt) — bounds the upstream sweep from outside. - [Rate limiters](/config/rate-limiters.llms.txt) — budgets referenced by `rateLimitBudget`. - [Survive provider outages](/use-cases/survive-provider-outages.llms.txt) — the outcome networks + failsafe deliver together. --- ## Navigation (machine-readable surface) - Up: [Projects](https://docs.erpc.cloud/config/projects.llms.txt) - Root index of every page: [llms.txt](https://docs.erpc.cloud/llms.txt) · everything in one file: [llms-full.txt](https://docs.erpc.cloud/llms-full.txt) ### Sibling pages - [CORS](https://docs.erpc.cloud/config/projects/cors.llms.txt) — Let your frontend talk to eRPC safely — configure which browser origins are allowed, in seconds, without blocking a single server-to-server call. - [Providers & vendors](https://docs.erpc.cloud/config/projects/providers.llms.txt) — One API key, every chain — declare a single provider entry and eRPC auto-generates upstreams for each network on first request, with 23 built-in vendor integrations. - [Selection & scoring](https://docs.erpc.cloud/config/projects/selection-policies.llms.txt) — eRPC ranks your upstreams every 15 seconds using live health data — bad actors drop out automatically, the fastest healthy provider goes first, and re-admission is metric-driven, not timer-driven. - [Shadow upstreams](https://docs.erpc.cloud/config/projects/shadow-upstreams.llms.txt) — Dark-launch a new RPC provider by mirroring live traffic to it in the background — zero latency impact, automatic response comparison, and Prometheus counters to prove it's ready. - [Static responses](https://docs.erpc.cloud/config/projects/static-responses.llms.txt) — Return hardcoded JSON-RPC replies instantly for specific method+params pairs — no upstream contact, zero quota consumed, microsecond latency. - [Upstreams](https://docs.erpc.cloud/config/projects/upstreams.llms.txt) — Add any RPC endpoint — Alchemy, a self-hosted node, a gRPC feed — and eRPC figures out what it can serve, heals it when it breaks, and routes around it when it can't.