Config
erpc.yaml/ts

Complete config example

AIOpen as plain markdown for AI

A single eRPC config file wires together log level, HTTP server, metrics, caching database, projects (with networks, upstreams, and failsafe policies), and shared rate-limit budgets. eRPC auto-detects sane defaults for almost everything — a two-line config is enough to get started, and you add sections only when you need them.

Sections you can configure:

  • logLeveltrace | debug | info | warn | error
  • server — HTTP listen address, timeouts, TLS, gzip, shutdown grace
  • metrics — Prometheus scrape endpoint
  • database — EVM JSON-RPC response cache (memory, Redis, PostgreSQL, DynamoDB/ScyllaDB)
  • projects[] — one entry per traffic profile (frontend, indexer, backend …); each holds networks[], upstreams[], rateLimitBudget, auth, and failsafe
  • rateLimiters — shared per-method budgets consumed by upstreams and projects

Minimal config

Chain IDs are auto-detected; defaults are applied for retries, timeouts, hedges, and everything else.

projectsupstreams
erpc.yaml
logLevel: debugprojects:  - id: main    upstreams:      - endpoint: https://your-node.example.com/      - endpoint: alchemy://${ALCHEMY_API_KEY}      - endpoint: drpc://${DRPC_API_KEY}

Full config — all major sections

logLevel, server, metrics, database, projects, rateLimiters
erpc.yaml
logLevel: warn
server:  httpHostV4: "0.0.0.0"  httpPortV4: 4000  maxTimeout: 30s  enableGzip: true  waitBeforeShutdown: 30s  waitAfterShutdown: 30s  tls:    enabled: false    certFile: "/path/to/cert.pem"    keyFile: "/path/to/key.pem"
metrics:  enabled: true  hostV4: "0.0.0.0"  port: 4001
database:  evmJsonRpcCache:    connectors:      - id: memory-cache        driver: memory        memory:          maxItems: 100000      - id: postgres-cache        driver: postgresql        postgresql:          connectionUri: "postgres://user:pass@db:5432/erpc"          table: rpc_cache    policies:      - network: "*"        method: "*"        finality: finalized        connector: memory-cache        ttl: 0      - network: "*"        method: "*"        finality: unfinalized        connector: memory-cache        ttl: 5s      - network: "*"        method: "eth_getLogs|trace_*"        finality: finalized        connector: postgres-cache        ttl: 0
projects:  - id: main    rateLimitBudget: frontend-budget    networks:      - architecture: evm        evm:          chainId: 1        failsafe:          - matchMethod: "*"            timeout:              duration: 30s            retry:              maxAttempts: 3              delay: 0ms            # Defining a "hedge" is highly-recommended on network-level because if upstream A is being slow for            # a specific request, it can start a new parallel hedged request to upstream B, for whichever responds faster.            hedge:              delay: 500ms              maxCount: 1            # circuitBreaker is upstream-scope only — see upstreams.failsafe below.      - architecture: evm        evm:          chainId: 42161        failsafe:          - matchMethod: "*"            timeout:              duration: 30s            retry:              maxAttempts: 3              delay: 0ms            hedge:              delay: 500ms              maxCount: 1
    # Each upstream supports 1 or more networks (chains)    upstreams:      - id: blastapi-arb        type: evm        endpoint: https://arbitrum-one.blastapi.io/YOUR_KEY        rateLimitBudget: global-blast        evm:          chainId: 42161        ignoreMethods:          - "alchemy_*"        failsafe:          - matchMethod: "*"            timeout:              duration: 15s            retry:              maxAttempts: 2              delay: 500ms              backoffMaxDelay: 3s              backoffFactor: 1.2              jitter: 0ms            # Per-upstream circuit breaker — opens on 80% failure rate over the            # last 200 reqs, probes every 5m, closes on 3 consecutive successes.            circuitBreaker:              failureThresholdCount:    160              failureThresholdCapacity: 200              halfOpenAfter:            5m              successThresholdCount:    3              successThresholdCapacity: 3      - id: alchemy-multi-chain        endpoint: alchemy://${ALCHEMY_API_KEY}        rateLimitBudget: global        jsonRpc:          supportsBatch: true          batchMaxSize: 10          batchMaxWait: 100ms        failsafe:          - matchMethod: "*"            timeout:              duration: 15s            retry:              maxAttempts: 2              delay: 500ms      - id: blastapi-chain-1        type: evm        endpoint: https://eth-mainnet.blastapi.io/xxxxxxx-xxxxxx-xxxxxxx        rateLimitBudget: global-blast        evm:          chainId: 1        failsafe:          - matchMethod: "*"            timeout:              duration: 15s            retry:              maxAttempts: 2              delay: 500ms              backoffMaxDelay: 3s              backoffFactor: 1.2              jitter: 0ms      - id: quiknode-chain-42161        type: evm        endpoint: https://xxxxxx-xxxxxx.arbitrum-mainnet.quiknode.pro/xxxxxxxxxxxxxxxxxxxxxxxx/        rateLimitBudget: global-quicknode        # You can enable auto-ignoring unsupported methods, instead of defining them explicitly.        # NOTE: some providers (e.g. dRPC) are not consistent with "unsupported method" responses,        # so this feature might mark methods as unsupported that are actually supported!        autoIgnoreUnsupportedMethods: true        # To allow auto-batching requests towards the upstream, use these settings.        # Remember if "supportsBatch" is false, you still can send batch requests to eRPC        # but they will be sent to upstream as individual requests.        jsonRpc:          supportsBatch: true          batchMaxSize: 10          batchMaxWait: 100ms        failsafe:          - matchMethod: "*"            timeout:              duration: 15s            retry:              maxAttempts: 2              delay: 500ms
rateLimiters:  budgets:    - id: global      rules:        - method: "*"          maxCount: 10000          period: 1s    - id: global-blast      rules:        - method: "*"          maxCount: 1000          period: 1s    - id: frontend-budget      rules:        - method: "*"          maxCount: 500          period: 1s

Related sections

  • Database — cache connectors and policies in depth
  • Projects — multiple projects, providers, scoring, CORS
  • Networks — per-chain failsafe, integrity checks, static responses
  • Upstreams — RPC endpoints, vendor shorthands, batching, block availability
  • Rate limiters — shared budgets, per-method rules, waitTime
  • Failsafe — timeout, retry, hedge, circuit-breaker in depth
Copy for your AI assistant — exhaustive eRPC config referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

This section covers every top-level field and every common sub-field. Use it as a reference when writing or reviewing an eRPC config.


logLevel

logLevel: warn   # trace | debug | info | warn | error
ValueOutput
traceEvery internal decision, full request/response bodies. Never in production.
debugPer-request logs, rate-limit decisions, upstream selection.
infoHappy-path summaries, one line per request (success/failure). Default on first boot.
warnNon-critical issues — cache DB unreachable, upstream degraded but not fatal.
errorUser-visible failures, misconfigurations. Recommended in production.

Can also be set via the LOG_LEVEL environment variable, which takes precedence at startup if both are defined. Set LOG_WRITER=console for human-readable terminal output instead of structured JSON.


server

server:
  listenV4: true
  httpHostV4: "0.0.0.0"
  httpPortV4: 4000
  listenV6: false
  httpHostV6: "[::]"
  httpPortV6: 5000
  maxTimeout: 30s        # hard deadline for the full request lifecycle
  readTimeout: 10s       # time to read the complete request from the client
  writeTimeout: 20s      # time to write the full response back to the client
  enableGzip: true
  waitBeforeShutdown: 30s   # grace before stop accepting new connections
  waitAfterShutdown: 30s    # grace after stop accepting, to drain in-flight
  tls:
    enabled: false
    certFile: "/path/to/cert.pem"
    keyFile: "/path/to/key.pem"
    caFile: "/path/to/ca.pem"       # optional; enables mTLS client-cert verification
    insecureSkipVerify: false
FieldDefaultNotes
listenV4trueBind an IPv4 listener.
httpHostV4"0.0.0.0"IPv4 bind address.
httpPortV44000IPv4 port.
listenV6falseBind an IPv6 listener.
httpHostV6"[::]"IPv6 bind address.
httpPortV65000IPv6 port.
maxTimeout30sTotal deadline for one request (all retries + hedges included).
readTimeoutHTTP read timeout (Go net/http semantics).
writeTimeoutHTTP write timeout.
enableGzipfalseCompress responses when the client sends Accept-Encoding: gzip.
waitBeforeShutdown0Sleep before closing the listener — lets a load balancer drain traffic.
waitAfterShutdown0Sleep after closing — lets in-flight requests complete.
tls.enabledfalseTerminate TLS on eRPC instead of a sidecar.
tls.certFilePath to the server certificate PEM.
tls.keyFilePath to the server private key PEM.
tls.caFileCA certificate for mTLS client verification. Omit to skip client-cert check.
tls.insecureSkipVerifyfalseSkip verification of client certificates. Dangerous; only for local testing.

metrics

metrics:
  enabled: true
  listenV4: true
  hostV4: "0.0.0.0"
  port: 4001
  listenV6: false
  hostV6: "[::]"

Exposes a Prometheus scrape endpoint at http://<host>:<port>/metrics. When enabled: false (the default), the port is never bound.

FieldDefaultNotes
enabledfalseMust be true to expose the /metrics endpoint.
hostV4"0.0.0.0"IPv4 bind address.
port4001Port for the Prometheus scrape endpoint.
listenV6falseBind on IPv6 as well.
hostV6"[::]"IPv6 bind address.

database

Caching is non-blocking on the critical path — a cache miss or a write failure does not delay the request. All cache operations are best-effort.

database.evmJsonRpcCache

Disable the cache entirely:

database:
  evmJsonRpcCache: ~

Enable with one or more connectors and routing policies:

database:
  evmJsonRpcCache:
    connectors:
      - id: memory-cache
        driver: memory
        memory:
          maxItems: 100000
      - id: redis-cache
        driver: redis
        redis:
          addr: redis://localhost:6379
      - id: postgres-cache
        driver: postgresql
        postgresql:
          connectionUri: "postgres://user:pass@host:5432/db"
          table: rpc_cache
      - id: dynamo-cache
        driver: dynamodb
        dynamodb:
          region: us-east-1
          table: erpc-cache
          # endpoint: http://localhost:8000  # for local DynamoDB / ScyllaDB
    policies:
      - network: "*"
        method: "*"
        finality: realtime
        connector: memory-cache
        ttl: 2s
      - network: "*"
        method: "*"
        finality: unfinalized
        connector: redis-cache
        ttl: 10s
      - network: "*"
        method: "*"
        finality: finalized
        connector: dynamo-cache
        ttl: 0           # 0 = never expire
      - network: "evm:42161|evm:10"
        method: "arbtrace_*"
        finality: finalized
        connector: postgres-cache
        ttl: 86400s
        maxItemSize: 1MB  # skip caching items larger than this

Connector drivers:

DriverWhen to use
memoryFast in-process LRU. Lost on restart. Good for realtime/unfinalized data.
redisShared across replicas. Good for unfinalized / short-TTL data.
postgresqlRelational; good for finalized data with complex query needs.
dynamodbDynamoDB-compatible (also ScyllaDB/Alternator). High-throughput finalized cache.

Policy fields:

FieldNotes
networkMatcher — *, evm:1, `evm:1
methodMatcher — *, eth_call, `eth_getLogs
finalityrealtime / unfinalized / finalized / unknown. Match only requests whose data has this finality.
connectorReferences a connector id defined in connectors[].
ttlDuration string (0 = no expiry, 5s, 1h, 86400s).
maxItemSizeOptional max payload size to store (e.g. 1MB). Larger items skip this policy.
emptyallow (default) — cache empty/null responses; ignore — don't cache them.

Policies are evaluated in order; the first matching policy wins per (network, method, finality) combination.


projects[]

Each project is independently routed by URL: /<projectId>/<architecture>/<chainId>.

projects:
  - id: main                       # required; unique
    rateLimitBudget: frontend-budget
    forwardHeaders: ["X-Request-ID", "traceparent"]
    allowMethods: ["eth_*", "net_*"]
    ignoreMethods: ["debug_*"]
    networks: [...]                 # optional; only needed for overrides
    upstreams: [...]                # required (or use providers[])
    providers: [...]                # optional; vendor key-based auto-fan-out
    auth:
      strategies: [...]            # see Authentication docs
    cors:
      allowedOrigins: ["https://my-dapp.example.com"]

See Projects for the full field reference including scoring knobs (routingStrategy, scoreGranularity, scoreSwitchHysteresis, etc.).


projects[].networks[]

Override failsafe, integrity checks, and selection policies on a per-chain basis. Defining a network entry is optional — omit it to use global defaults.

networks:
  - architecture: evm
    evm:
      chainId: 1
    failsafe:
      - matchMethod: "*"
        timeout:
          duration: 30s
        retry:
          maxAttempts: 3
          delay: 500ms
          backoffMaxDelay: 10s
          backoffFactor: 0.3
          jitter: 500ms
        hedge:
          delay: 1000ms
          maxCount: 2
        circuitBreaker:
          failureThresholdCount: 160   # trips at 80% errors (160/200)
          failureThresholdCapacity: 200
          halfOpenAfter: 5m
          successThresholdCount: 3
          successThresholdCapacity: 3

failsafe[] — per-policy fields:

FieldNotes
matchMethodMatcher for which RPC methods this policy applies to. "*" = all.
timeout.durationHard deadline for the entire request lifecycle (including all retries on this network).
retry.maxAttemptsMaximum number of upstream attempts (including the first).
retry.delayBase delay between retries.
retry.backoffMaxDelayCeiling for exponential backoff.
retry.backoffFactorMultiplier per retry. 1.0 = constant delay; 1.5 = 50% growth per step.
retry.jitterRandom jitter added to each delay.
hedge.delayAfter this delay without a response, fire a parallel request to a second upstream.
hedge.maxCountMaximum simultaneous hedged requests.
circuitBreaker.failureThresholdCountNumber of failures within failureThresholdCapacity samples to trip.
circuitBreaker.failureThresholdCapacityRolling sample window size.
circuitBreaker.halfOpenAfterHow long to wait in open state before attempting a half-open probe.
circuitBreaker.successThresholdCountSuccesses needed in half-open to close the breaker.
circuitBreaker.successThresholdCapacitySample window for the half-open probe.

Hedge is strongly recommended at the network level. It fires a second upstream request after hedge.delay if the first hasn't responded, returning whichever comes back first. This dramatically reduces tail latency without extra retries.


projects[].upstreams[]

upstreams:
  # Direct HTTP endpoint
  - id: my-node
    type: evm
    endpoint: https://your-node.example.com/
    rateLimitBudget: global
    evm:
      chainId: 1
    ignoreMethods:
      - "alchemy_*"
      - "debug_*"
    allowMethods:
      - "eth_*"
      - "net_*"
    autoIgnoreUnsupportedMethods: false
    jsonRpc:
      supportsBatch: true
      batchMaxSize: 10
      batchMaxWait: 100ms
    failsafe:
      - matchMethod: "*"
        timeout:
          duration: 15s
        retry:
          maxAttempts: 2
          delay: 500ms
          backoffMaxDelay: 3s
          backoffFactor: 1.2
          jitter: 0ms
 
  # Vendor shorthand — multi-chain import
  - id: alchemy-all
    endpoint: alchemy://\${ALCHEMY_API_KEY}
    rateLimitBudget: global
 
  # Other supported vendor shorthands:
  # endpoint: drpc://\${DRPC_API_KEY}
  # endpoint: blastapi://\${BLASTAPI_API_KEY}
  # endpoint: infura://\${INFURA_API_KEY}
  # endpoint: envio://rpc.hypersync.xyz
  # endpoint: tenderly://\${TENDERLY_API_KEY}
  # endpoint: chainstack://\${CHAINSTACK_API_KEY}
  # endpoint: onfinality://\${ONFINALITY_API_KEY}
  # endpoint: thirdweb://\${THIRDWEB_API_KEY}
  # endpoint: ankr://\${ANKR_API_KEY}
  # endpoint: quicknode://\${QUICKNODE_API_KEY}

Key upstream fields:

FieldNotes
idRequired; unique label used in logs and metrics.
typeArchitecture type. evm (default for EVM chains).
endpointHTTP(S) URL or vendor shorthand (alchemy://KEY, drpc://KEY, etc.).
rateLimitBudgetID of a budget in rateLimiters.budgets[].
evm.chainIdOptional explicit chain ID. Recommended — skips eth_chainId auto-detection at startup.
ignoreMethodsMethods never sent to this upstream (matcher syntax).
allowMethodsMethod allowlist for this upstream. Implicitly adds ignoreMethods: ["*"].
autoIgnoreUnsupportedMethodsWhen true, eRPC tracks and skips methods this upstream has rejected as unsupported. Caution: some vendors (e.g. dRPC) return inconsistent unsupported-method signals — this can cause false positives.
jsonRpc.supportsBatchWhen true, eRPC may pack multiple pending requests into one JSON-RPC batch.
jsonRpc.batchMaxSizeMaximum requests per batch (default 10).
jsonRpc.batchMaxWaitHow long to wait for the batch to fill before sending (default 100ms).

Vendor shorthand chains are hard-coded in the eRPC binary. When a vendor adds a new chain, eRPC must be updated. For new/uncommon chains use direct HTTP endpoints instead.


rateLimiters

rateLimiters:
  budgets:
    - id: global
      rules:
        - method: "*"
          maxCount: 10000
          period: 1s
          waitTime: 100ms    # allow waiting up to 100ms for capacity; 0 = fail immediately
    - id: global-blast
      rules:
        - method: "*"
          maxCount: 1000
          period: 1s
    - id: per-method-example
      rules:
        - method: "eth_getLogs"
          maxCount: 100
          period: 1s
        - method: "*"
          maxCount: 5000
          period: 1s

Budgets are shared — if upstream A and B both reference global, their combined traffic counts against the limit.

FieldNotes
idUnique identifier referenced by upstreams and projects.
rules[].methodMethod matcher (*, eth_call, `eth_getLogs
rules[].maxCountMaximum requests allowed in period.
rules[].periodWindow length (e.g. 1s, 1m).
rules[].waitTimeWhen capacity is exhausted, wait this long before returning a rate-limit error. 0 (default) = fail immediately.

Config file location and format

By default erpc looks for ./erpc.ts, ./erpc.yaml, or ./erpc.yml in the current directory. Override with:

erpc start --config /path/to/erpc.yaml
erpc start -c /path/to/erpc.ts
erpc validate -c /path/to/erpc.yaml   # validate without starting

YAML supports \${VAR} env-var interpolation. TypeScript uses process.env.VAR directly.


Complete erpc.dist.yaml — annotated

This mirrors the canonical erpc.dist.yaml in the repo root, with every section annotated:

logLevel: warn
 
database:
  evmJsonRpcCache:
    connectors:
      - id: memory-cache
        driver: memory
      - id: postgres-cache
        driver: postgresql
        postgresql:
          connectionUri: postgres://erpc:erpc@localhost:5432/erpc
      - id: redis-cache
        driver: redis
        redis:
          addr: redis://localhost:6379
      - id: scylladb-cache
        driver: dynamodb
        dynamodb:
          region: DC1
          endpoint: http://localhost:8067   # ScyllaDB Alternator endpoint
    policies:
      - network: "*"
        method: "*"
        finality: realtime
        empty: allow
        connector: memory-cache
        ttl: 2s
      - network: "*"
        method: "*"
        finality: unfinalized
        empty: allow
        connector: redis-cache
        ttl: 10s
      - network: "*"
        method: "*"
        finality: finalized
        empty: allow
        connector: scylladb-cache
        ttl: 0
 
server:
  httpHostV4: 0.0.0.0
  httpPortV4: 4000
  maxTimeout: 50s
 
metrics:
  enabled: true
  hostV4: 0.0.0.0
  port: 4001
 
projects:
  - id: main
    networks:
      - architecture: evm
        evm:
          chainId: 1
        failsafe:
          - matchMethod: "*"
            timeout:
              duration: 30s
            retry:
              maxAttempts: 3
              delay: 500ms
              backoffMaxDelay: 10s
              backoffFactor: 0.3
              jitter: 500ms
            hedge:
              delay: 3000ms
              maxCount: 2
      - architecture: evm
        evm:
          chainId: 42161
        failsafe:
          - matchMethod: "*"
            timeout:
              duration: 30s
            retry:
              maxAttempts: 5
              delay: 500ms
              backoffMaxDelay: 10s
              backoffFactor: 0.3
              jitter: 200ms
            hedge:
              delay: 1000ms
              maxCount: 2
    upstreams:
      - id: alchemy-multi-chain-example-1
        endpoint: alchemy://XXXX_YOUR_ALCHEMY_API_KEY_HERE_XXXX
        rateLimitBudget: global
        failsafe:
          - matchMethod: "*"
            timeout:
              duration: 15s
            retry:
              maxAttempts: 2
              delay: 1000ms
              backoffMaxDelay: 10s
              backoffFactor: 0.3
              jitter: 500ms
      - id: tenderly-example-1
        endpoint: tenderly://YOUR_TENDERLY_API_KEY
        rateLimitBudget: global-tenderly
        evm:
          chainId: 1
        failsafe:
          - matchMethod: "*"
            timeout:
              duration: 15s
      - id: blastapi-chain-42161
        type: evm
        endpoint: https://arbitrum-one.blastapi.io/xxxxxxx-xxxxxx-xxxxxxx
        rateLimitBudget: global-blast
        evm:
          chainId: 42161
        failsafe:
          - matchMethod: "*"
            timeout:
              duration: 15s
            retry:
              maxAttempts: 2
              delay: 1000ms
              backoffMaxDelay: 10s
              backoffFactor: 0.3
              jitter: 500ms
 
rateLimiters:
  budgets:
    - id: global
      rules:
        - method: '*'
          maxCount: 10000
          period: 1s
    - 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

Common pitfalls

  • Cache ttl: 0 on realtime / unfinalized data — data never expires; callers get stale pending-tx states. Use a short TTL for non-finalized policies.
  • No hedge on the network level — without it, a slow upstream causes the full timeout.duration to elapse before a second upstream is tried. Add hedge.delay equal to your P90 upstream latency.
  • batchMaxWait too large — adds artificial latency at low traffic; tune to 50-200ms.
  • autoIgnoreUnsupportedMethods: true on inconsistent vendors — some vendors return generic errors for any unsupported call; eRPC might mark valid methods as unsupported and permanently skip them.
  • rateLimitBudget referenced but not defined — startup succeeds but rate limiting is silently skipped. Always define the budget in rateLimiters.budgets[].
  • waitTime: 0 on rate-limit rules — any request that arrives when the budget is exhausted gets an immediate error. Set waitTime: 50ms or higher to allow brief bursts to absorb the wait.
  • maxTimeout shorter than retry total delay — if maxTimeout: 10s but retries add up to 15s, the network-level timeout fires first and cuts retries short. Set maxTimeout to at least the sum of all upstream retry delays plus hedge delay.

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