Config
Example
AI agents: fetch https://docs.erpc.cloud/config/example.llms.txt for the complete machine-readable version of this page (full configuration schema, defaults, worked examples, and source links). Append `.llms.txt` to any docs URL for the same treatment.AIFor agents: /config/example.llms.txt

Example config

A production-ready starting point you can copy today, and a complete annotated reference below it. The recommended config turns on what makes eRPC worth running — multi-provider failover, hedging, permanent caching, observability — with sane values you'd actually deploy; delete what you don't need and defaults fill the gaps.

Recommended starting point

Copy, set two API keys, done. Each block's comment says what power it buys you:

(root)
erpc.yaml
logLevel: info
# Observability on from day one: Prometheus on :4001/metricsmetrics:  enabled: true  port: 4001
# Permanent cache: finalized chain data never changes, so never pay for it twice.# Start in-process; uncomment redis to share the cache across replicas.database:  evmJsonRpcCache:    connectors:      - id: hot        driver: memory      # - id: shared      #   driver: redis      #   redis: { addr: redis:6379 }    policies:      - connector: hot        finality: finalized      # immutable: cached forever      - connector: hot        finality: unfinalized        ttl: 5s                  # fresh data: short-lived cache
# A reusable budget so no single consumer can starve the restrateLimiters:  budgets:    - id: frontend-budget      rules:        - method: "*"          maxCount: 500          period: 1s
projects:  - id: main    rateLimitBudget: frontend-budget
    # Uncomment to require an API key (see /config/auth for JWT, SIWE, IP rules)    # auth:    #   strategies:    #     - type: secret    #       secret: { value: ${MY_API_KEY} }
    # One key per vendor unlocks every chain they support — upstreams appear    # lazily on first request per chain    providers:      - endpoint: alchemy://${ALCHEMY_API_KEY}      - endpoint: drpc://${DRPC_API_KEY}
    upstreams:      # Mix in your own node; selection scoring decides who deserves traffic      - endpoint: https://your-node.example.com/        evm: { chainId: 1 }      # Free public RPC as last resort: the tier:fallback tag keeps it out of      # rotation until paid upstreams are unhealthy      - endpoint: https://ethereum-rpc.publicnode.com        evm: { chainId: 1 }        tags: ["tier:fallback"]
    # Resilience defaults for every chain: bounded latency, automatic    # failover, and hedged slow-tails    networkDefaults:      failsafe:        - matchMethod: "*"          timeout:            duration: 30s        # nothing hangs longer than this          retry:            maxAttempts: 3       # errors fail over across upstreams          hedge:                 # race a backup at the p70 latency mark            delay: { quantile: 0.7, min: 100ms, max: 2s }            maxCount: 1
    # Per-upstream safety net: bench repeat offenders, probe recovery    upstreamDefaults:      failsafe:        - matchMethod: "*"          retry:            maxAttempts: 1          circuitBreaker:            failureThresholdCount: 20            failureThresholdCapacity: 80            halfOpenAfter: 5m            successThresholdCount: 8
    networks:      - architecture: evm        evm: { chainId: 1 }        alias: ethereum          # /main/ethereum instead of /main/evm/1

What this gives you out of the box: every chain your vendors support via one endpoint, permanent caching of immutable data, p99-taming hedges, automatic failover with a circuit-breaker safety net, a fallback tier that only activates when paid upstreams fail, and metrics from request one. The default selection policy needs no configuration — it already excludes unhealthy upstreams and honors the tier:fallback tag.

Complete example — all sections

The annotated example below mirrors erpc.dist.yaml and erpc.dist.ts from the repo root. Read the inline comments for the reasoning behind each value; detailed field references live in the per-section docs linked at the bottom.

logLevel, server, metrics, database, projects, rateLimiters
erpc.yaml
# ── Global ───────────────────────────────────────────────────────────────# trace|debug|info|warn|error. Override at runtime with LOG_LEVEL env var.# Set LOG_WRITER=console for human-readable output instead of JSON.logLevel: warn
# ── Cache database ────────────────────────────────────────────────────────# eRPC supports memory, Redis, PostgreSQL, and DynamoDB/ScyllaDB connectors.# Cache reads/writes are non-blocking — a miss or write failure never delays# the upstream request. Disable entirely with: evmJsonRpcCache: ~database:  evmJsonRpcCache:    connectors:      - id: memory-cache        driver: memory        # memory.maxItems / maxTotalSize default to 100 000 / "1GB".        # Omit sub-block to accept defaults.
      - id: redis-cache        driver: redis        redis:          addr: redis://localhost:6379
      - id: postgres-cache        driver: postgresql        postgresql:          connectionUri: "postgres://erpc:erpc@localhost:5432/erpc"
      - id: scylladb-cache        driver: dynamodb        dynamodb:          region: DC1          # ScyllaDB Alternator endpoint          endpoint: http://localhost:8067
    # Policies are evaluated in order; first match per (network, method,    # finality) triple wins. Use finality tiers to route short-lived data    # to fast/volatile stores and permanently-final data to cheaper storage.    policies:      - network: "*"        method: "*"        # block-tip / pending data — short TTL, fast store        finality: realtime        empty: allow        connector: memory-cache        ttl: 2s
      - network: "*"        method: "*"        # confirmed but not irreversible        finality: unfinalized        empty: allow        connector: redis-cache        ttl: 10s
      - network: "*"        method: "*"        # irreversible; ttl: 0 = never expires        finality: finalized        empty: allow        connector: scylladb-cache        ttl: 0
# ── HTTP server ───────────────────────────────────────────────────────────server:  httpHostV4: "0.0.0.0"  httpPortV4: 4000  # listenV6: false  # httpHostV6: "[::]"  # httpPortV6: 5000  # Hard deadline for the full request lifecycle (all retries + hedges).  # Must be non-zero. Should be >= sum of upstream retry delays + hedge delay.  maxTimeout: 50s  # TLS termination — leave commented unless doing edge termination on eRPC.  # tls:  #   enabled: true  #   certFile: "/path/to/cert.pem"  #   keyFile: "/path/to/key.pem"
# ── Prometheus metrics ────────────────────────────────────────────────────metrics:  enabled: true  hostV4: "0.0.0.0"  port: 4001
# ── Projects ──────────────────────────────────────────────────────────────# Each project maps to a URL prefix: /<projectId>/evm/<chainId># Multiple projects let you apply different auth, rate limits, or failsafe# policies to different caller populations (frontend vs. indexer, etc.).projects:  - id: main
    # Health-tracker rolling window. Per-upstream rolling counters    # (errorRate, p50/p70/p95 latency, throttledRate, misbehaviorRate)    # are kept as a 10-bucket ring spanning this duration; one bucket    # rotates out every windowSize/10.  Defaults to 1m when omitted;    # widen if your aggregate RPS is low enough that 1m gives a noisy score.    scoreMetricsWindowSize: 10s
    upstreamDefaults:      evm:        # State-poller cadence — hits eth_blockNumber + eth_syncing on this        # interval even for excluded upstreams, so health metrics stay fresh.        # Keep this <= scoreMetricsWindowSize.        statePollerInterval: 2s
    # ── Networks ────────────────────────────────────────────────────────    # Network entries are optional. Omit them to use global defaults.    # Define them to override failsafe, integrity, or selection policy    # on a per-chain basis.    networks:      - architecture: evm        evm:          chainId: 1        # failsafe[] is an ordered list of policies matched in order.        # Each policy applies to requests whose method AND finality match.        # The first matching policy wins.        failsafe:          timeout:            duration: 30s          retry:            maxAttempts: 3            delay: 500ms            backoffMaxDelay: 10s            backoffFactor: 0.3            jitter: 500ms          # Hedge fires a parallel request to a second upstream after          # "delay" if the first hasn't responded. Returns whichever wins.          # Strongly recommended — dramatically cuts tail latency.          hedge:            delay: 3000ms            maxCount: 2
      - architecture: evm        evm:          chainId: 42161        failsafe:          timeout:            duration: 30s          retry:            maxAttempts: 5            delay: 500ms            backoffMaxDelay: 10s            backoffFactor: 0.3            jitter: 200ms          hedge:            delay: 1000ms            maxCount: 2
    # ── Upstreams ────────────────────────────────────────────────────────    # Vendor shorthand endpoints (alchemy://, tenderly://, etc.) are    # converted to providers at startup; they cover multiple chains    # automatically using the vendor's chain registry.    # Raw HTTPS endpoints require evm.chainId to skip auto-detection.    upstreams:      - id: alchemy-multi-chain        endpoint: alchemy://${ALCHEMY_API_KEY}        rateLimitBudget: global        failsafe:          timeout:            duration: 15s          retry:            maxAttempts: 2            delay: 1000ms            backoffMaxDelay: 10s            backoffFactor: 0.3            jitter: 500ms
      - id: tenderly-eth        endpoint: tenderly://${TENDERLY_API_KEY}        rateLimitBudget: global-tenderly        evm:          chainId: 1        failsafe:          timeout:            duration: 15s
      - id: blastapi-arb        type: evm        endpoint: https://arbitrum-one.blastapi.io/${BLASTAPI_KEY}        rateLimitBudget: global-blast        evm:          chainId: 42161        # ignoreMethods filters methods never sent to this upstream.        # allowMethods is an allowlist (implicitly adds ignoreMethods: ["*"]).        ignoreMethods:          - "alchemy_*"        # Enable batching when the upstream supports it.        jsonRpc:          supportsBatch: true          batchMaxSize: 10          batchMaxWait: 100ms        failsafe:          timeout:            duration: 15s          retry:            maxAttempts: 2            delay: 1000ms            backoffMaxDelay: 10s            backoffFactor: 0.3            jitter: 500ms
      - id: quicknode-arb        type: evm        endpoint: https://${QUICKNODE_SUBDOMAIN}.arbitrum-mainnet.quiknode.pro/${QUICKNODE_KEY}/        rateLimitBudget: global-quicknode        evm:          chainId: 42161        # autoIgnoreUnsupportedMethods tracks methods this upstream rejects        # and skips them in future requests. Caution: some vendors return        # inconsistent "unsupported" signals — can cause false positives.        autoIgnoreUnsupportedMethods: true        failsafe:          timeout:            duration: 15s          retry:            maxAttempts: 2            delay: 1000ms
# ── Rate limiters ─────────────────────────────────────────────────────────# Budgets are shared — if two upstreams both reference "global", their# combined traffic counts against the limit.# Rules are evaluated in order; first matching method wins per budget.rateLimiters:  budgets:    - id: global-tenderly      rules:        - method: '*'          maxCount: 10000          period: 1s    - id: global-blast      rules:        - method: '*'          maxCount: 1000          period: 1s    - id: global-quicknode      rules:        - method: '*'          maxCount: 300          period: 1s    - id: global      rules:        - method: '*'          maxCount: 10000          period: 1s          # waitTime: 0 = reject immediately when budget exhausted;          # set to e.g. 50ms to absorb brief bursts          waitTime: 0

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: set up eRPC from scratch
I want to set up eRPC for the first time with two providers (Alchemy and a raw HTTPS node),
Redis caching, and basic failsafe policies. Use the full config example as a starting shape and
trim it to what I actually need. My config will live at my eRPC config. Read the reference first:
https://docs.erpc.cloud/config/example.llms.txt
Prompt Example #2: validate and debug my config
Run erpc validate on my eRPC config and explain every error or warning. Then use erpc dump
to show me what defaults got filled in automatically, and flag anything that looks wrong —
especially rate-limit budgets, maxTimeout vs retry sums, and env-var substitution pitfalls.
Reference: https://docs.erpc.cloud/config/example.llms.txt
Prompt Example #3: migrate my config from YAML to TypeScript
Migrate my eRPC config from YAML to TypeScript so I get IDE autocompletion and
type-checking. Preserve every field exactly, replace ${VAR} shell substitutions
with process.env.VAR template literals, keep the default export as the last
statement, and confirm the result passes erpc validate. Reference:
https://docs.erpc.cloud/config/example.llms.txt
Prompt Example #4: debug endpoints resolving to alchemy://undefined
My eRPC TypeScript config is generating endpoint strings like alchemy://undefined
at runtime. Explain why YAML-style ${VAR} expansion does not apply to TypeScript
files and fix my config to use process.env correctly. Reference:
https://docs.erpc.cloud/config/example.llms.txt
Prompt Example #5: wire erpc validate and erpc dump into CI
Add erpc validate --format json and erpc dump steps to my CI pipeline so config
errors fail the build before deploy. Show how to pipe validate output through jq
to surface only errors, and use erpc dump to confirm the effective failsafe
defaults my config produces. Reference:
https://docs.erpc.cloud/config/example.llms.txt
Full config example — agent navigation referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

This page is intentionally a flat annotated example. Every section has a dedicated reference page with complete field tables and behavioral invariants. Use the map below to find the right page for any config question.

How it works

Config file discovery. When no --config flag is given, eRPC probes these paths in order and uses the first it finds (cmd/erpc/main.go:L279-L293 (opens in a new tab)):

  1. ./erpc.yaml — relative to the working directory
  2. ./erpc.yml
  3. ./erpc.ts
  4. ./erpc.js
  5. /erpc.yaml, /erpc.yml, /erpc.ts, /erpc.js
  6. /root/erpc.yaml, /root/erpc.yml, /root/erpc.ts, /root/erpc.js

YAML is probed before TypeScript in the same directory — a stale erpc.yaml silently shadows a newly added erpc.ts. If nothing is found and --require-config is unset, eRPC starts with a synthetic main project using public endpoints.

A .env file in the working directory is loaded automatically before config parsing via github.com/joho/godotenv (cmd/erpc/main.go:L42-L46 (opens in a new tab)). Variables from .env are available as ${VAR} in YAML and as process.env.VAR in TypeScript.

YAML loading. os.ExpandEnv runs on the raw file bytes before YAML parsing (common/config.go:L102 (opens in a new tab)). Every $VAR and ${VAR} pattern is substituted. Unset variables resolve to the empty string. If the substituted value contains YAML-special characters (:, {…) and the field is unquoted, the parse will fail — always quote values that may contain secrets: password: "${REDIS_PASSWORD}".

TypeScript loading. Shell-style expansion is never applied. The TS file is compiled by embedded esbuild and run in a sobek JS runtime where process.env is pre-populated from os.Environ(). Use JS template literals: `alchemy://${process.env.ALCHEMY_API_KEY}`. Writing ${VAR} in a TS string literal without process.env. evaluates the JS variable VAR, which is undefined unless declared — resulting in the string "undefined" and a confusing downstream error.

Validate and dump commands.

# Validate config without starting — exits 1 if errors found
erpc validate --config erpc.yaml
erpc validate --config erpc.ts --format md
 
# Dump the effective config as eRPC will see it (defaults filled in)
erpc dump --config erpc.ts
erpc dump --config erpc.yaml --format json

validate builds the full resource tree (projects → networks → upstreams, rate-limit budgets), checks for orphaned budgets and public endpoints, and prints a structured JSON or Markdown report. Exit 0 if errors is empty. All zerolog output is suppressed during validate — pipe stdout through jq .errors to see only errors (cmd/erpc/main.go:L109 (opens in a new tab)).

dump resolves the effective selection policies (including TypeScript evalFunc sentinels back to their source) and prints the complete config as YAML or JSON.

Defaults cascade. SetDefaults fills every nil/zero field after decode — no field is silently undefined at runtime. If projects is empty after loading, a synthetic main project with repository + envio providers is injected automatically. common/defaults.go:L49-176

Per-section documentation map

Config sectionCanonical pageKey topics
logLevel, clusterKey, CLI flags, env varsConfig formats (this page, agent panel below)YAML vs TS loading, discovery order, validate/dump subcommands
serverServerHTTP/gRPC listen, TLS, timeouts, graceful shutdown, response headers
metricsMonitoringAll 122 Prometheus metrics, histogram bucket config, cardinality controls
database.evmJsonRpcCacheCache policiesFinality tiers, TTL semantics, empty behavior, policy ordering
database.evmJsonRpcCache.connectorsStorage driversmemory, Redis, PostgreSQL, DynamoDB/ScyllaDB connector fields
database.sharedStateShared stateDistributed lock, consensus state, cluster key scoping
projects[].networks[]NetworksPer-chain failsafe, integrity checks, block tracking
projects[].upstreams[]UpstreamsRPC lifecycle, batching, method filtering, vendor shorthands
projects[].providers[]Providers & vendorsVendor key-based auto-fan-out, onlyNetworks/ignoreNetworks
projects[].authAuthenticationsecret, JWT, SIWE, network strategies
projects[].corsCORSallowedOrigins, credentials, preflight
projects[].selectionPolicySelection & scoringevalFunc, score multipliers, exclusion thresholds
projects[].networks[].failsafe[].timeoutTimeoutHard deadline fields, interaction with retries
projects[].networks[].failsafe[].retryRetryBackoff, jitter, retryable error classes
projects[].networks[].failsafe[].hedgeHedgeAdaptive vs static delay, maxCount, write-method exclusion
projects[].networks[].failsafe[].circuitBreakerCircuit breakerHalf-open, thresholds, interaction with selection policy
projects[].networks[].failsafe[].consensusConsensusDVN/quorum, dispute log, low-participant behavior
projects[].networks[].failsafe[].integrityIntegrityBlock-finality enforcement, re-query on mismatch
rateLimiters.budgets[]Rate limitersShared budgets, waitTime, per-method rules
proxyPools[]HTTP client & proxy poolsProxy routing, pool sizing
tracingTracing & loggingOTel, sampleRate, endpoint, log formats
adminAdmin APICordon, drain, live config inspection
healthCheckHealthcheckMode, defaultEval, upstream requirements

Config loading invariants

  • Strict YAML mode (KnownFields: true) — any unknown key is a fatal error. common/config.go:L103
  • os.ExpandEnv runs on raw YAML bytes before parse; never applied to TypeScript files. common/config.go:L102
  • SetDefaults fills every nil/zero field after decode — no field is silently undefined at runtime. common/defaults.go:L49-176
  • Validate runs after SetDefaults and returns an error if any invariant is violated; erpc validate exposes the full report. common/validation.go:L15
  • TypeScript configs are bundled by embedded esbuild (no Node.js required) and evaluated in sobek; process.env is pre-populated from os.Environ(). common/config.go:L2686
  • If projects is empty after loading, a synthetic main project with repository + envio providers is injected automatically. common/defaults.go:L100-167

Worked examples

1. Validate before deploying. Run the validate subcommand against any format to catch typos, orphaned rate-limit budgets, and reference errors before the server starts:

erpc validate --config erpc.yaml
# exits 0 if errors: [] — pipe through jq .errors to see just the problems
erpc validate --config erpc.ts --format md

2. Debug what defaults fill in. Use dump to see the exact effective config the engine will run with — especially useful for TypeScript configs where evalFunc sentinels can be resolved back to source:

erpc dump --config erpc.ts
erpc dump --config erpc.yaml --format json | jq '.projects[0].networks'

3. Start with CLI flags, no config file. Pass one or more --endpoint flags to inject synthetic upstreams into a default main project — useful for quick smoke tests:

erpc --endpoint https://eth.llamarpc.com --endpoint alchemy://$ALCHEMY_KEY

4. Per-finality failsafe in TypeScript. The TS dist example shows how to use matchFinality to give realtime (block-tip) requests a short timeout and retry budget while giving archival calls more headroom:

projects[].networks[].failsafe[]
erpc.yaml
failsafe:  - matchMethod: "*"    matchFinality: realtime    timeout:      duration: 3s    retry:      maxAttempts: 2  - matchMethod: "*"    timeout:      duration: 30s    retry:      maxAttempts: 5    hedge:      delay: 1000ms      maxCount: 2

Best practices

  • Set server.maxTimeout ≥ total retry budget + hedge delay. If maxTimeout: 10s but retries sum to 15s, the server deadline fires first and silently cuts retries short. Use erpc dump to verify the sum.
  • Always quote YAML values that expand env vars containing special characters. connectionUri: "${DATABASE_URL}" — unquoted ${DATABASE_URL} breaks YAML parsing when the URL contains : or {.
  • Never use ${VAR} syntax in TypeScript configs. Use `alchemy://${process.env.ALCHEMY_API_KEY}` — omitting process.env. gives the string "alchemy://undefined" and a confusing validation failure.
  • Use ttl: 0 only for finality: finalized data. Setting ttl: 0 on realtime or unfinalized policies means stale pending-transaction states are cached forever. Short TTLs (2s–10s) are correct for non-final data.
  • Keep rateLimitBudget IDs consistent. Every budget referenced by an upstream or network must exist in rateLimiters.budgets. Validation catches this (erpc validate), but older runtime versions silently skip rate-limiting when a budget is missing.
  • Don't leave a stale erpc.yaml when migrating to erpc.ts. YAML is probed first — the old file silently shadows the new one. Remove or rename it.
  • Vendor shorthand endpoints (alchemy://, tenderly://, etc.) become providers, not upstreams. After SetDefaults they move from p.Upstreams to p.Providers; erpc validate reports them under providersTotal, not upstreamsTotal. This is expected.

Edge cases & gotchas

  • --set / -s is not implemented. The flag is commented out in the source. Passing -s logLevel=debug produces "flag provided but not defined: -s" from the CLI framework. There is no Helm-style dot-path override; use env vars or config file edits. Source: cmd/erpc/main.go:L86-L90 (opens in a new tab)
  • validate and dump suppress all log output. zerolog.SetGlobalLevel(zerolog.Disabled) is called before getConfig runs. No warnings from SetDefaults, legacy migration, or validation appear on stderr. Pipe stdout through jq .errors to see errors. Source: cmd/erpc/main.go:L109 (opens in a new tab)
  • dump --format csv (or any unknown format) exits 1 via a distinct code path, not a marshal error. Valid values are "yaml", "yml", and "json" only.
  • Positional argument works in ALL subcommands. erpc validate ./my-config.yaml and erpc dump ./my-config.yaml treat the first positional arg as a config path with requireConfig=true. Source: cmd/erpc/main.go:L303-L305 (opens in a new tab)
  • TypeScript default export must be the last statement. The sobek runtime's exports.default is inspected after the script runs; a later expression can overwrite it. Error: "config object must be default exported from TypeScript code AND must be the last statement in the file".
  • TS module executes multiple times — once at load and once per policy-pool runtime acquire. Top-level side effects (logging, expensive computation) are repeated; keep the config pure.
  • erpc.dist.yaml fails validation as shipped — upstreams reference rateLimitBudget: global but no global budget exists. Use it as a shape reference only, not copy-paste directly. Source: erpc.dist.yaml (opens in a new tab)
  • LOG_LEVEL env var overrides config-file logLevel at two points — first in init() (applies to the config-loading phase) and second after decode (overrides cfg.LogLevel itself). A file setting of logLevel: error can be trumped by LOG_LEVEL=debug without editing the file.

Source code entry points

Config formats — YAML vs TypeScript

eRPC accepts the same schema as erpc.yaml/erpc.yml (strict YAML) or erpc.ts/erpc.js (TypeScript compiled in-process). Everything below — discovery order, the TS loading pipeline, env-var semantics, validate/dump tooling — applies to whichever format you pick.

How it works

File discovery. When no --config flag or positional argument is supplied, eRPC probes a hardcoded list in order: ./erpc.yaml, ./erpc.yml, ./erpc.ts, ./erpc.js, then the same four filenames under / and /root/. The first path where fs.Stat succeeds wins. YAML takes priority over TypeScript within the same directory — a leftover erpc.yaml silently shadows a newly added erpc.ts. Passing an explicit path forces that file and treats a missing file as a fatal error (exit 1001). A .env file in the working directory is loaded at process init() time via godotenv before any config parsing.

YAML loading. common.LoadConfig reads the file, runs os.ExpandEnv on the raw bytes (substituting every $VAR and ${VAR} pattern from the OS environment), then decodes with gopkg.in/yaml.v3 in strict mode (KnownFields(true) — unknown keys are errors). After decode, it applies LegacyTranslateFn (legacy key migration), then SetDefaults, then Validate.

TypeScript loading pipeline. loadConfigFromTypescript (common/config.go:L2686) runs these steps:

  1. esbuild (embedded Go library) bundles the .ts/.js file as a self-contained IIFE, resolving all imports including @erpc-cloud/config. createConfig is a pure identity function — it costs nothing.
  2. A tsLoaderWalker JS fragment is appended that depth-first walks exports.default, assigns sequential IDs (fn_0, fn_1, …) to every function-valued object property, and registers them on globalThis.__erpcFns.
  3. The result is compiled to a sobek.Program once and stored as cfg.UserScript.
  4. A throwaway runtime evaluates the program; exports.default is the config object.
  5. JSON.stringify with a replacer converts registered functions to sentinel strings ("__ts_fn__:fn_<n>"); unregistered functions are dropped (never .toString()-serialised).
  6. The JSON is decoded through the same strict YAML decoder used for .yaml files, so schema validation is identical — typos fail with the same unknown-field error.

os.ExpandEnv is YAML-only. TypeScript/JS files are never subjected to shell-variable expansion. Use process.env.MY_KEY (a JS expression) instead of ${MY_KEY}.

Function preservation at runtime. cfg.UserScript is re-evaluated once per policy-engine runtime-pool acquire, rebuilding __erpcFns natively in that runtime so closures and module-level helpers remain live. At tick time, a __ts_fn__:fn_<n> sentinel is resolved by id lookup in __erpcFns and invoked directly.

Shared post-load pipeline. Both formats share LegacyTranslateFnSetDefaultsValidate. Validation errors cause exit 1001. erpc validate loads config, runs static analysis (orphan rate-limit budgets, missing upstreams, etc.), and prints a JSON or Markdown report. erpc dump loads config and renders the fully defaulted effective config as YAML or JSON — useful for seeing what TS sentinel functions resolve to.

Config schema

Top-level Config fields (common/config.go:L38-68)

YAML pathTypeDefaultBehavior / notes
logLevelstring"INFO"Parsed by zerolog. Valid values: trace, debug, info, warn, error, fatal, panic, disabled. Invalid value defaults to debug with a warning. Overridable at runtime by LOG_LEVEL env var. common/defaults.go:L50-52
clusterKeystring"erpc-default"Identifies the logical replica group for shared-state scoping. Propagated to database.sharedState.clusterKey when that field is unset — an explicit database.sharedState.clusterKey always wins. common/defaults.go:L53-55
serverobjectsynthetic empty struct then SetDefaultsHTTP/gRPC server config; see Server.
metricsobjectenabled=true, port=4001, errorLabelMode="compact"Prometheus metrics server.
databaseobjectnilEVM JSON-RPC cache and/or shared state.
projectsarraysynthetic main project if emptyOrdered list of project configs.
rateLimitersobjectnilRate limiter budgets.
healthCheckobjectmode=networks, defaultEval=any:initializedUpstreamsHealth check endpoint behaviour.
adminobjectnilAdmin API; CORS defaults to * origin with no credentials.
tracingobjectnilOTel tracing; default protocol=grpc, endpoint=localhost:4317, sampleRate=1.0, serviceName=erpc.
proxyPoolsarraynilHTTP proxy pool definitions.

MetricsConfig fields (common/config.go:L2543-2563, common/defaults.go:L749-767)

YAML pathTypeDefaultBehavior
metrics.enabledbooltrue (non-test); unset in test buildsIf false/nil, metrics server is not started.
metrics.hostV4string"0.0.0.0"Prometheus scrape endpoint IPv4 bind host.
metrics.hostV6string"[::]"IPv6 bind host.
metrics.portint4001Prometheus scrape port.
metrics.errorLabelModestring"compact"Controls the error label on error-count metrics. "compact" condenses error codes; "verbose" emits full class names.
metrics.histogramBucketsstring"" (built-in defaults)Comma-separated float64 bucket boundaries for request-duration histograms. Validation rejects non-float values.
metrics.histogramDropLabels[]stringnilLabel names removed from every histogram to reduce cardinality; counters/gauges unaffected.
metrics.histogramLabelOverridesmapnilPer-metric label keep-list; key is metric name without erpc_ prefix (e.g. "network_request_duration_seconds").

Default project synthesis. When projects is empty, SetDefaults injects a synthetic main project (common/defaults.go:L100-167) with:

  • repository and envio providers
  • An aliasing rule so /evm/<chainId> resolves without a project prefix
  • Network defaults: evm.getLogsMaxAllowedRange=30_000, getLogsSplitOnError=true, integrity enforcement enabled
  • Network-level failsafe: retry(maxAttempts=5), timeout(120s), hedge(p70 quantile, maxCount=2)
  • Upstream-level failsafe: retry(maxAttempts=1, delay=500ms), timeout(60s)
  • Upstream defaults: evm.getLogsAutoSplittingRangeThreshold=5000

Validation rules (common/validation.go:L15). Key rules beyond basic non-null checks:

  • server.maxTimeout must be non-zero
  • Each project needs at least one upstream or provider
  • project.*.networks.*.alias must match [a-zA-Z0-9_-]+ and be unique within the project
  • Referenced rateLimitBudget IDs must exist in rateLimiters.budgets
  • onlyNetworks and ignoreNetworks on a provider are mutually exclusive
  • selectionPolicy.evalTimeout must be strictly less than selectionPolicy.evalInterval
  • selectionPolicy.evalFunc must compile (or be a TS sentinel)
  • Connector failsafe may not use consensus or hedge.quantile (no latency source at connector level)
  • database.sharedState.lockMaxWait and updateMaxWait must be less than fallbackTimeout

TypeScript scalar types. (typescript/config/src/types/generic.ts)

TS typeAccepted formsFootgun
Duration`${number}ms`, `${number}s`, `${number}m`, `${number}h`, or bare numberBare number → milliseconds (not nanoseconds).
ByteSize`${number}b`, `${number}kb`, `${number}mb`, or bare numberOnly B/KB/MB parsed — no gb form for cache-policy minItemSize/maxItemSize. Memory connector maxTotalSize uses a different parser that does accept GB.
LogLevel"trace" | "debug" | "info" | "warn" | "error" | "disabled" | undefined

TS enum values. TS constants serialize as JSON numbers; the Go YAML decoder accepts both numbers and names:

ConstantValueNotes
DataFinalityStateRealtime0Also accepted as "realtime" or "0" in YAML.
DataFinalityStateUnfinalized1Also "unfinalized" or "1".
DataFinalityStateFinalized2Also "finalized" or "2".
DataFinalityStateUnknown3Also "unknown" or "3".
CacheEmptyBehaviorIgnore0
CacheEmptyBehaviorAllow1
CacheEmptyBehaviorOnly2
RateLimitPeriodSecond0Also "1s"; Go also accepts duration aliases like "24h", "7d".

TS sobek runtime environment. Inside a TS config (or evalFunc):

  • process.env.X — full OS environment as a key/value map (snapshot at runtime creation)
  • env global — raw os.Environ() string array ("KEY=VALUE" pairs)
  • console.log/info/warn/debug/trace — forwarded to zerolog at the matching level; suppressed below global log level
  • Node stdlib (fs, http, …) — NOT available in sobek; only env/process/console are installed

Config formats own no additional YAML runtime fields. The table below documents the loader's environment contract and CLI flags.

CLI flags (cmd/erpc/main.go:L76-94)

FlagTypeDefaultBehavior / footguns
--configstring""Path to config file; skips auto-discovery; sets requireConfig=true implicitly — missing file exits 1001.
positional argstringSame as --config; forces requireConfig=true; works with all subcommands (start, validate, dump).
--require-configboolfalseAbort if no config found in search path; no effect when --config is given (already implied).
--endpoint / -e[]string[]Inject upstream endpoint URLs when no providers/upstreams exist in config. Provider-scheme URLs (e.g. alchemy://key) are converted to providers at SetDefaults time. Invalid URLs abort with exit 1001.
validate --formatstring"json""json" or "md".
dump --formatstring"yaml""yaml", "yml", or "json". Any other value exits 1 via an explicit error branch.
--set / -snot availableCommented out of the source. Passing -s produces "flag provided but not defined: -s" — not a graceful error.

Environment variables

Env varScopeBehaviorCitation
$VAR / ${VAR} in YAMLYAML loaderos.ExpandEnv on raw bytes before parse; unset vars → empty string. Unquoted values containing : or { will break YAML parse.common/config.go:L102
process.env.X in TSTS runtimePopulated from os.Environ() when the sobek runtime is created; .env file loaded first. Snapshot — not updated if env changes after startup.common/runtime.go:L23-36
LOG_LEVELbootstrapZerolog global level; overrides logLevel in config. Applied twice: at init() and again after decode. Valid values: trace, debug, info, warn, error, fatal, panic, disabled. Invalid value falls back to debug with a warning.cmd/erpc/main.go:L59-L66 (opens in a new tab)
LOG_WRITERbootstrapWhen "console", switches zerolog to human-readable output (04:05.000ms time format). Set at init() — cannot be changed at runtime.cmd/erpc/main.go:L53-L57 (opens in a new tab)
INSTANCE_IDshared state + consensusFirst in the instance-identity priority chain: INSTANCE_IDPOD_NAMEHOSTNAMEos.Hostname()"unknown" (shared-state) or SHA-256 hash of (unixNano+pid) (consensus). Used as UpdatedBy field in shared-state writes and as the {instanceId} token in consensus dispute-log filenames. In Kubernetes, setting POD_NAME via the downward API is recommended.data/shared_state_registry.go:L82-L98 (opens in a new tab)
POD_NAMEshared state + consensusSecond in priority chain (Kubernetes pod name). Same two contexts as INSTANCE_ID.
HOSTNAMEshared state + consensusThird in priority chain (OS hostname). Same two contexts as INSTANCE_ID.
ERPC_NOLOGStest builds onlyWhen "1", sets zerolog global level to Disabled and replaces the logger with io.Discard. Suppresses all log output. Only effective in non-production builds (build tag !test guard on initflags.go).cmd/erpc/initflags.go (opens in a new tab)
ERPC_NOMETRICStest builds onlyWhen "1", swaps prometheus.DefaultRegisterer/DefaultGatherer with a no-op registry. Metric registrations succeed but accumulate no memory. Irreversible within the process.cmd/erpc/initflags.go (opens in a new tab)
ERPC_PPROF_PORTpprof build onlyPort for the pprof HTTP server (default 6060). Only active when binary built with -tags pprof. Security: binds 0.0.0.0:<port> (all interfaces, not localhost-only) — must be firewall-restricted in production.cmd/erpc/pprof.go (opens in a new tab)
$VAR in server.responseHeaders valuesHTTP server initExpanded once at startup via os.ExpandEnv during HTTP server construction (after full config decode, distinct from YAML-level expansion). Headers that expand to empty string are silently omitted.erpc/http_server.go:L135-L148 (opens in a new tab)
$VAR in provider-generated endpointsprovider resolutionAfter the vendor's GenerateConfigs returns each UpstreamConfig, os.ExpandEnv is called on each Endpoint string — distinct from (and after) YAML-level expansion. Double-expansion possible if GenerateConfigs builds an endpoint that itself contains $VAR literals.thirdparty/provider.go:L67-L71 (opens in a new tab)

validate command JSON output schema (erpc/config_analyzer.go:L76-L123)

{
  "errors":   [],
  "warnings": [],
  "notices":  [],
  "resources": {
    "totals": { "projectsTotal", "networksTotal", "upstreamsTotal", "rateLimitBudgetsTotal" },
    "tree": { "projects": [...], "rateLimiters": { "budgets": [...] } }
  }
}

Exits 0 if errors is empty, exits 1 otherwise.

Worked examples

All patterns below are distilled from real production fleets; comments explain the non-obvious choices.

1. YAML with .env secrets — simplest production shape. Env vars are substituted before parse; quote every value that might contain : or { to avoid YAML parse errors after expansion:

# erpc.yaml (with .env file providing ALCHEMY_API_KEY, INFURA_API_KEY)
logLevel: warn
projects:
  - id: main
    networks:
      - architecture: evm
        evm: { chainId: 1 }
    upstreams:
      # Always quote env-interpolated values — a bare ${VAR} containing ":"
      # produces invalid YAML after os.ExpandEnv substitution.
      - endpoint: "alchemy://${ALCHEMY_API_KEY}"
      - endpoint: "infura://${INFURA_API_KEY}"

2. TypeScript config split into shared modules — the production multi-chain shape. A large fleet uses one erpc.ts that imports typed fragments from shared/ files. The module-level networkDefaults and upstreamDefaults objects are evaluated once at load and reused across chains — closures in evalFunc bodies capture them without serialisation:

// erpc.ts — assembly point; env vars only here
import { createConfig } from "@erpc-cloud/config";
import { cacheConfig } from "./shared/cache";
import { networkDefaults } from "./shared/networks";
import { upstreamDefaults } from "./shared/upstreams";
 
export default createConfig({
  logLevel: "warn",
  database: {
    evmJsonRpcCache: cacheConfig(),
  },
  projects: [{
    id: "main",
    // Defaults applied to every network / upstream in this project
    networkDefaults,
    upstreamDefaults,
    networks: [{ architecture: "evm", evm: { chainId: 1 } }],
    upstreams: [{
      // process.env — NOT ${VAR} — JS template literals need process.env.
      // ${ALCHEMY_API_KEY} without process.env resolves to "undefined" silently.
      endpoint: `alchemy://${process.env.ALCHEMY_API_KEY}`,
    }],
  }],
});

3. TypeScript with a real closure in evalFunc — upstream weight map. The TS path preserves closures; the module-level weights constant is available inside the function body at policy-tick time, with no serialisation. This is the canonical reason to prefer TypeScript over YAML for complex selection policies:

// erpc.ts
import { createConfig } from "@erpc-cloud/config";
 
// Module-level constant — captured by reference in evalFunc and available
// on every policy-pool runtime acquire without stringifying the function.
const weights: Record<string, number> = { "alchemy": 3, "infura": 1 };
 
export default createConfig({
  logLevel: "info",
  projects: [{
    id: "main",
    networks: [{ architecture: "evm", evm: { chainId: 1 } }],
    upstreams: [
      { endpoint: `alchemy://${process.env.ALCHEMY_API_KEY}` },
      { endpoint: `infura://${process.env.INFURA_API_KEY}` },
    ],
    selectionPolicy: {
      evalFunc: (upstreams) =>
        upstreams.sort((a, b) =>
          (weights[b.upstream.id] ?? 0) - (weights[a.upstream.id] ?? 0)
        ),
    },
  }],
});

4. Per-method failsafe tiers in upstreamDefaults — production upstream-level policies. Production fleets use upstreamDefaults.failsafe to apply tight adaptive timeouts per method group at the upstream boundary, so the network-level retry budget isn't burned on upstreams that are merely slow. Method-specific entries must come before the catch-all (hedge: null suppresses upstream-level hedging — hedging belongs at network scope):

// shared/upstreams.ts
import type { FailsafeConfig, UpstreamConfig } from "@erpc-cloud/config";
 
const upstreamFailsafe: FailsafeConfig[] = [
  {
    matchMethod: "eth_getLogs|eth_getBlockReceipts",
    // Heavy range queries routinely take seconds — floor of 2s prevents premature
    // upstream-level timeouts that burn network-level retry budget on subgraph backfills.
    timeout: { duration: "15s", quantile: 0.9, minDuration: "2s", maxDuration: "15s" } as any,
    // Hedge and retry are null at upstream scope — handled at network scope instead.
    hedge: null,
    retry: null,
  },
  {
    matchMethod: "eth_call",
    // eth_call latency is bimodal (cache hit ~5ms, heavy simulation ~10s).
    // Adaptive timeout tracks actual p80 so fast calls aren't capped at the ceiling.
    timeout: { duration: "20s", quantile: 0.8, minDuration: "500ms", maxDuration: "20s" } as any,
    hedge: null,
    retry: null,
  },
  {
    matchMethod: "*",
    timeout: { duration: "60s", quantile: 0.8, minDuration: "500ms", maxDuration: "60s" } as any,
    hedge: null,
    retry: null,
  },
];
 
export const upstreamDefaults: UpstreamConfig = {
  // Disable filter methods — providers rarely support them reliably.
  ignoreMethods: ["eth_newFilter", "eth_newBlockFilter", "eth_newPendingTransactionFilter"],
  failsafe: upstreamFailsafe,
};

5. Using erpc validate and erpc dump in CI. Validate exits 1 on errors and suppresses all log output; pipe through jq to surface only errors. Use erpc dump after switching formats to confirm effective failsafe defaults:

# CI pre-deploy check — fails build if config has errors
erpc validate --format json ./erpc.ts | jq .errors
 
# Markdown report for humans (e.g. in a PR comment)
erpc validate --format md ./erpc.yaml
 
# Inspect effective defaults after migrating YAML → TS
erpc dump --format yaml ./erpc.ts
 
# Check a specific section (e.g. first network's failsafe after adding defaults)
erpc dump --format json ./erpc.ts | jq '.projects[0].networks[0].failsafe'

Request/response behavior

Config loading is a startup-phase operation, not a per-request path. No HTTP headers or JSON-RPC bodies are involved. The loader reports its outcome through log messages and exit codes only.

Exit codes (util/exit.go:L7-10):

  • 1001 (ExitCodeERPCStartFailed) — CLI/config load failure, validation error, or erpc.Init error.
  • 1002 (ExitCodeHttpServerFailed) — HTTP or gRPC server fatal error.

Best practices

  • Quote all YAML env-interpolated values. password: "${REDIS_PASSWORD}" is safe; password: ${REDIS_PASSWORD} breaks if the value contains : or {. Source: common/config.go:L102
  • Use process.env.VAR, not ${VAR}, in TypeScript configs. ${MY_KEY} in a TS template literal is a JS variable reference — if undeclared, it evaluates to "undefined", silently producing alchemy://undefined. Source: common/config.go:L95-109
  • Keep the TS config module pure. The module is evaluated multiple times (load, each policy-pool runtime acquire, erpc dump). Top-level side effects like logging or HTTP calls repeat on every acquire.
  • Run erpc validate in CI before deploying. All log output is suppressed during validation; errors appear only in the JSON/Markdown output. Pipe validate stdout through jq .errors to diagnose failures.
  • Run erpc dump to verify effective defaults. After switching from manual config to system-template, use erpc dump to confirm the failsafe defaults (retry, timeout, hedge) are what you expect.
  • Never rely on --set / -s. The flag is commented out. Use .env or process.env for environment-specific overrides.
  • Pin @erpc-cloud/config to the matching binary version. The npm package is always released in lockstep with the Go binary; a version mismatch means the TS types may not match the runtime schema.

Edge cases & gotchas

  1. YAML beats TS in auto-discovery. ./erpc.yaml and ./erpc.yml are probed before ./erpc.ts and ./erpc.js. A stale erpc.yaml in the working directory silently shadows your erpc.ts.
  2. YAML env value with special characters breaks parse. If $MY_VAR expands to a value containing : or {, the substituted text becomes invalid YAML. Quote all env-interpolated values: value: "${MY_VAR}".
  3. TS ${VAR} without process.env. is a JS variable reference, not env expansion. ${MY_KEY} in a TS template literal references a JS variable MY_KEY. If undeclared, it evaluates to "undefined", producing endpoint strings like alchemy://undefined. This is a silent failure — no parse error, just a wrong URL.
  4. TS default export must be last. exports.default is read after the program runs. A reassignment by any later expression overwrites the config. Error: "config object must be default exported from TypeScript code AND must be the last statement in the file".
  5. Functions outside walkable positions are silently dropped. The walker registers only function-valued object properties. A function placed directly as an array element is not walked; the JSON.stringify replacer drops it (undefined), and the decoded field is missing. Source: common/config.go:L2636-L2652 (opens in a new tab).
  6. TS module evaluates multiple times. Load, each policy-pool runtime acquire, and erpc dump each re-run the full module. Top-level side effects (logging, HTTP calls, Date.now()) repeat. Keep the config module pure.
  7. validate and dump suppress all log output. zerolog.SetGlobalLevel(zerolog.Disabled) fires before getConfig, so warnings from SetDefaults and legacy migration are invisible. Pipe validate stdout through jq .errors to diagnose failures.
  8. --set / -s is not implemented. The flag is commented out. Passing it produces a CLI framework error, not a graceful "unsupported" message.
  9. dump --format csv exits 1 via an explicit error branch, not a marshal failure. Valid formats are yaml, yml, and json only. Source: cmd/erpc/main.go:L190-L194 (opens in a new tab).
  10. TS process.env is a snapshot. The env map is built once when the sobek runtime is created. Changes to OS env vars after startup are not reflected; a restart is required.
  11. Provider-scheme endpoints are converted to providers, not upstreams. alchemy://key in --endpoint or in upstreams[].endpoint is converted to a ProviderConfig and removed from upstreams by SetDefaults. erpc validate reports it under providers, so upstreamsTotal looks lower than the endpoint count.
  12. erpc.dist.yaml ships broken. The canonical YAML example references a global rate-limit budget that does not exist in rateLimiters.budgets and contains a duplicate upstream id. erpc validate --config erpc.dist.yaml exits 1. The TypeScript example (erpc.dist.ts) validates cleanly when env vars are set.
  13. erpc dump may print __ts_fn__:fn_N for a TS evalFunc when source resolution fails (best-effort .toString(); any error leaves the sentinel). Source: internal/policy/dump.go:L32-L136 (opens in a new tab).
  14. Strict decode applies to TS configs too. decoder.KnownFields(true) on the JSON intermediary means a property typo still fails at load with the same unknown-field error YAML gives.
  15. AuthStrategyConfig union is wrong for non-secret strategies in TS. The hand-written network/jwt/siwe arms declare their payload under the key secret instead of the correct key names. Also, the database auth type is absent from the TS union. Source: typescript/config/src/types/generic.ts:L120-L141 (opens in a new tab).
  16. grpc cache-connector driver is unrepresentable in TS. Go supports DriverGrpc + ConnectorConfig.grpc, but the hand-written TsConnectorConfig union used for cache.connectors has no grpc arm. Same for UpstreamType missing evm+blockdaemon. Source: typescript/config/src/types/generic.ts:L63-L83 (opens in a new tab).
  17. scoreMetricsWindowSize doc comment discrepancy. The Go source comment claims "Defaults to 10m when zero"; the actual fallback in projects_registry.go is 1 * time.Minute. The erpc.dist.yaml comment claims "Defaults to 5s". The code always wins — default is 1m. Source: erpc/projects_registry.go:L50,L133-L134 (opens in a new tab).
  18. ERPC_PPROF_PORT binds all interfaces. The pprof server uses 0.0.0.0:<port>, not 127.0.0.1. In production the port must be firewall-restricted or the binary must be built without -tags pprof; any host that can reach the port gets full Go runtime profiling access.
  19. TS-required fields that Go would default. Tygo marks non-omitempty fields as required, so MemoryConnectorConfig forces maxItems + maxTotalSize in TS even though Go defaults them to 100000 / "1GB". YAML users can omit them. Source: common/defaults.go:L935-L944 (opens in a new tab).
  20. Legacy YAML keys work in TS objects too. Both formats share the same UnmarshalYAML hooks after the TS JSON round-trip, so e.g. group: 'main' on an upstream becomes a tier:main tag in both YAML and TS.
  21. Metrics server is disabled in test builds. MetricsConfig.SetDefaults sets Enabled only when !util.IsTest(). In go test runs, metrics are off by default. Tests needing metrics must set metrics.enabled: true explicitly. Source: common/defaults.go:L750-L752 (opens in a new tab).

Observability

The config-loading and format subsystems emit no Prometheus metrics. Operational signals are log-only:

Log messageLevelWhen
"executing command"INFOEvery CLI action; includes action, version, commit fields
"looking for config file: <path>"INFOEach path probed during auto-discovery
"resolved configuration file to: <path>"INFOWinning path identified
"initializing eRPC core"INFOBefore NewERPC construction
"initializing transports"INFOAfter core init, before server start
"networks bootstrap completed"INFOAfter all configured networks are initialised
"shutting down gracefully..."INFOOn SIGINT/SIGTERM context cancel
"pprof server started at http://localhost:<port>"INFOOnly when built with -tags pprof
"failed to load configuration"ERRORAny LoadConfig error before exit 1001
"build failed: <messages>"ERROResbuild compilation error in .ts/.js file
"compile ts config: …"ERRORsobek compile error after bundling
"ts config json-stringify: …"ERRORJSON.stringify step failed on TS config object
"ts config decode: …"ERRORStrict YAML decode of the TS JSON output failed (field typo)
"config default export serialized to null/undefined"ERRORTS config did not produce a valid default export
"evaluate user TS config in policy runtime: …"ERRORPolicy engine failed to re-run UserScript in a pooled runtime
"ts selectionPolicy.evalFunc lookup: …"ERRORSentinel __erpcFns id not found or registry not populated
"no projects found in config; will add a default 'main' project"WARNSetDefaults synthetic project injection
"no providers or upstreams found in project; will use default 'public' endpoints"WARNPublic fallback injected for a project with no upstreams/providers
"failed to initialize evm json rpc cache: …"WARNCache init failure is non-fatal; eRPC continues without caching
"failed to initialize shared state registry: …"WARNShared-state init failure is non-fatal
"invalid log level '...', defaulting to 'debug'"WARNEmitted at init time and at runtime if LOG_LEVEL has an invalid value

Source code entry points

Related pages

  • Auth — note: the TypeScript AuthStrategyConfig union has known bugs for non-secret strategies; prefer YAML when using jwt/siwe/network auth.
  • Upstreams — every upstream field, vendor shorthands, batching, method filtering.
  • Networks — per-chain failsafe, finality, integrity checks.
  • Cache policies — finality tiers, TTL semantics, connector routing.
  • Rate limiters — shared budgets, waitTime, per-method rules.
  • Failsafe overview — retry, hedge, circuit breaker, consensus.