/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:
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/1What 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.
# ── 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: 0Agent 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.txtPrompt 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.txtPrompt 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)):
./erpc.yaml— relative to the working directory./erpc.yml./erpc.ts./erpc.js/erpc.yaml,/erpc.yml,/erpc.ts,/erpc.js/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 jsonvalidate 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 section | Canonical page | Key topics |
|---|---|---|
logLevel, clusterKey, CLI flags, env vars | Config formats (this page, agent panel below) | YAML vs TS loading, discovery order, validate/dump subcommands |
server | Server | HTTP/gRPC listen, TLS, timeouts, graceful shutdown, response headers |
metrics | Monitoring | All 122 Prometheus metrics, histogram bucket config, cardinality controls |
database.evmJsonRpcCache | Cache policies | Finality tiers, TTL semantics, empty behavior, policy ordering |
database.evmJsonRpcCache.connectors | Storage drivers | memory, Redis, PostgreSQL, DynamoDB/ScyllaDB connector fields |
database.sharedState | Shared state | Distributed lock, consensus state, cluster key scoping |
projects[].networks[] | Networks | Per-chain failsafe, integrity checks, block tracking |
projects[].upstreams[] | Upstreams | RPC lifecycle, batching, method filtering, vendor shorthands |
projects[].providers[] | Providers & vendors | Vendor key-based auto-fan-out, onlyNetworks/ignoreNetworks |
projects[].auth | Authentication | secret, JWT, SIWE, network strategies |
projects[].cors | CORS | allowedOrigins, credentials, preflight |
projects[].selectionPolicy | Selection & scoring | evalFunc, score multipliers, exclusion thresholds |
projects[].networks[].failsafe[].timeout | Timeout | Hard deadline fields, interaction with retries |
projects[].networks[].failsafe[].retry | Retry | Backoff, jitter, retryable error classes |
projects[].networks[].failsafe[].hedge | Hedge | Adaptive vs static delay, maxCount, write-method exclusion |
projects[].networks[].failsafe[].circuitBreaker | Circuit breaker | Half-open, thresholds, interaction with selection policy |
projects[].networks[].failsafe[].consensus | Consensus | DVN/quorum, dispute log, low-participant behavior |
projects[].networks[].failsafe[].integrity | Integrity | Block-finality enforcement, re-query on mismatch |
rateLimiters.budgets[] | Rate limiters | Shared budgets, waitTime, per-method rules |
proxyPools[] | HTTP client & proxy pools | Proxy routing, pool sizing |
tracing | Tracing & logging | OTel, sampleRate, endpoint, log formats |
admin | Admin API | Cordon, drain, live config inspection |
healthCheck | Healthcheck | Mode, defaultEval, upstream requirements |
Config loading invariants
- Strict YAML mode (
KnownFields: true) — any unknown key is a fatal error.common/config.go:L103 os.ExpandEnvruns on raw YAML bytes before parse; never applied to TypeScript files.common/config.go:L102SetDefaultsfills every nil/zero field after decode — no field is silently undefined at runtime.common/defaults.go:L49-176Validateruns afterSetDefaultsand returns an error if any invariant is violated;erpc validateexposes the full report.common/validation.go:L15- TypeScript configs are bundled by embedded esbuild (no Node.js required) and evaluated in sobek;
process.envis pre-populated fromos.Environ().common/config.go:L2686 - If
projectsis empty after loading, a syntheticmainproject withrepository+envioproviders 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 md2. 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_KEY4. 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:
failsafe: - matchMethod: "*" matchFinality: realtime timeout: duration: 3s retry: maxAttempts: 2 - matchMethod: "*" timeout: duration: 30s retry: maxAttempts: 5 hedge: delay: 1000ms maxCount: 2Best practices
- Set
server.maxTimeout≥ total retry budget + hedge delay. IfmaxTimeout: 10sbut retries sum to 15s, the server deadline fires first and silently cuts retries short. Useerpc dumpto 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}`— omittingprocess.env.gives the string"alchemy://undefined"and a confusing validation failure. - Use
ttl: 0only forfinality: finalizeddata. Settingttl: 0onrealtimeorunfinalizedpolicies means stale pending-transaction states are cached forever. Short TTLs (2s–10s) are correct for non-final data. - Keep
rateLimitBudgetIDs consistent. Every budget referenced by an upstream or network must exist inrateLimiters.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.yamlwhen migrating toerpc.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. AfterSetDefaultsthey move fromp.Upstreamstop.Providers;erpc validatereports them underprovidersTotal, notupstreamsTotal. This is expected.
Edge cases & gotchas
--set/-sis not implemented. The flag is commented out in the source. Passing-s logLevel=debugproduces"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)validateanddumpsuppress all log output.zerolog.SetGlobalLevel(zerolog.Disabled)is called beforegetConfigruns. No warnings fromSetDefaults, legacy migration, or validation appear on stderr. Pipe stdout throughjq .errorsto 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.yamlanderpc dump ./my-config.yamltreat the first positional arg as a config path withrequireConfig=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.defaultis 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.yamlfails validation as shipped — upstreams referencerateLimitBudget: globalbut noglobalbudget exists. Use it as a shape reference only, not copy-paste directly. Source:erpc.dist.yaml(opens in a new tab)LOG_LEVELenv var overrides config-filelogLevelat two points — first ininit()(applies to the config-loading phase) and second after decode (overridescfg.LogLevelitself). A file setting oflogLevel: errorcan be trumped byLOG_LEVEL=debugwithout editing the file.
Source code entry points
cmd/erpc/main.go:L279-L293(opens in a new tab) — config file discovery loopcommon/config.go:L87-L132(opens in a new tab) —LoadConfig: YAML vs TS dispatch,os.ExpandEnv, strict decode, defaults, validationcommon/defaults.go:L49-L176(opens in a new tab) —Config.SetDefaults: full default cascade including synthetic project injectioncommon/validation.go:L15(opens in a new tab) —Config.Validate: all cross-field validation rulescommon/config.go:L2686-L2766(opens in a new tab) —loadConfigFromTypescript: esbuild bundle, walker, sobek eval, JSON round-tripcmd/erpc/main.go:L109(opens in a new tab) —validatesubcommand: zerolog suppression, structured JSON/MD reporterpc/config_analyzer.go(opens in a new tab) —GenerateValidationReport: static analysis producing the validation report
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:
- esbuild (embedded Go library) bundles the
.ts/.jsfile as a self-contained IIFE, resolving all imports including@erpc-cloud/config.createConfigis a pure identity function — it costs nothing. - A
tsLoaderWalkerJS fragment is appended that depth-first walksexports.default, assigns sequential IDs (fn_0,fn_1, …) to every function-valued object property, and registers them onglobalThis.__erpcFns. - The result is compiled to a
sobek.Programonce and stored ascfg.UserScript. - A throwaway runtime evaluates the program;
exports.defaultis the config object. JSON.stringifywith a replacer converts registered functions to sentinel strings ("__ts_fn__:fn_<n>"); unregistered functions are dropped (never.toString()-serialised).- The JSON is decoded through the same strict YAML decoder used for
.yamlfiles, 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 LegacyTranslateFn → SetDefaults → Validate. 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 path | Type | Default | Behavior / notes |
|---|---|---|---|
logLevel | string | "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 |
clusterKey | string | "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 |
server | object | synthetic empty struct then SetDefaults | HTTP/gRPC server config; see Server. |
metrics | object | enabled=true, port=4001, errorLabelMode="compact" | Prometheus metrics server. |
database | object | nil | EVM JSON-RPC cache and/or shared state. |
projects | array | synthetic main project if empty | Ordered list of project configs. |
rateLimiters | object | nil | Rate limiter budgets. |
healthCheck | object | mode=networks, defaultEval=any:initializedUpstreams | Health check endpoint behaviour. |
admin | object | nil | Admin API; CORS defaults to * origin with no credentials. |
tracing | object | nil | OTel tracing; default protocol=grpc, endpoint=localhost:4317, sampleRate=1.0, serviceName=erpc. |
proxyPools | array | nil | HTTP proxy pool definitions. |
MetricsConfig fields (common/config.go:L2543-2563, common/defaults.go:L749-767)
| YAML path | Type | Default | Behavior |
|---|---|---|---|
metrics.enabled | bool | true (non-test); unset in test builds | If false/nil, metrics server is not started. |
metrics.hostV4 | string | "0.0.0.0" | Prometheus scrape endpoint IPv4 bind host. |
metrics.hostV6 | string | "[::]" | IPv6 bind host. |
metrics.port | int | 4001 | Prometheus scrape port. |
metrics.errorLabelMode | string | "compact" | Controls the error label on error-count metrics. "compact" condenses error codes; "verbose" emits full class names. |
metrics.histogramBuckets | string | "" (built-in defaults) | Comma-separated float64 bucket boundaries for request-duration histograms. Validation rejects non-float values. |
metrics.histogramDropLabels | []string | nil | Label names removed from every histogram to reduce cardinality; counters/gauges unaffected. |
metrics.histogramLabelOverrides | map | nil | Per-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:
repositoryandenvioproviders- 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.maxTimeoutmust be non-zero- Each project needs at least one upstream or provider
project.*.networks.*.aliasmust match[a-zA-Z0-9_-]+and be unique within the project- Referenced
rateLimitBudgetIDs must exist inrateLimiters.budgets onlyNetworksandignoreNetworkson a provider are mutually exclusiveselectionPolicy.evalTimeoutmust be strictly less thanselectionPolicy.evalIntervalselectionPolicy.evalFuncmust compile (or be a TS sentinel)- Connector failsafe may not use
consensusorhedge.quantile(no latency source at connector level) database.sharedState.lockMaxWaitandupdateMaxWaitmust be less thanfallbackTimeout
TypeScript scalar types. (typescript/config/src/types/generic.ts)
| TS type | Accepted forms | Footgun |
|---|---|---|
Duration | `${number}ms`, `${number}s`, `${number}m`, `${number}h`, or bare number | Bare number → milliseconds (not nanoseconds). |
ByteSize | `${number}b`, `${number}kb`, `${number}mb`, or bare number | Only 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:
| Constant | Value | Notes |
|---|---|---|
DataFinalityStateRealtime | 0 | Also accepted as "realtime" or "0" in YAML. |
DataFinalityStateUnfinalized | 1 | Also "unfinalized" or "1". |
DataFinalityStateFinalized | 2 | Also "finalized" or "2". |
DataFinalityStateUnknown | 3 | Also "unknown" or "3". |
CacheEmptyBehaviorIgnore | 0 | — |
CacheEmptyBehaviorAllow | 1 | — |
CacheEmptyBehaviorOnly | 2 | — |
RateLimitPeriodSecond | 0 | Also "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)envglobal — rawos.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)
| Flag | Type | Default | Behavior / footguns |
|---|---|---|---|
--config | string | "" | Path to config file; skips auto-discovery; sets requireConfig=true implicitly — missing file exits 1001. |
| positional arg | string | — | Same as --config; forces requireConfig=true; works with all subcommands (start, validate, dump). |
--require-config | bool | false | Abort 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 --format | string | "json" | "json" or "md". |
dump --format | string | "yaml" | "yaml", "yml", or "json". Any other value exits 1 via an explicit error branch. |
--set / -s | — | not available | Commented out of the source. Passing -s produces "flag provided but not defined: -s" — not a graceful error. |
Environment variables
| Env var | Scope | Behavior | Citation |
|---|---|---|---|
$VAR / ${VAR} in YAML | YAML loader | os.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 TS | TS runtime | Populated 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_LEVEL | bootstrap | Zerolog 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_WRITER | bootstrap | When "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_ID | shared state + consensus | First in the instance-identity priority chain: INSTANCE_ID → POD_NAME → HOSTNAME → os.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_NAME | shared state + consensus | Second in priority chain (Kubernetes pod name). Same two contexts as INSTANCE_ID. | — |
HOSTNAME | shared state + consensus | Third in priority chain (OS hostname). Same two contexts as INSTANCE_ID. | — |
ERPC_NOLOGS | test builds only | When "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_NOMETRICS | test builds only | When "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_PORT | pprof build only | Port 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 values | HTTP server init | Expanded 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 endpoints | provider resolution | After 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, orerpc.Initerror.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 producingalchemy://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 validatein CI before deploying. All log output is suppressed during validation; errors appear only in the JSON/Markdown output. Pipevalidatestdout throughjq .errorsto diagnose failures. - Run
erpc dumpto verify effective defaults. After switching from manual config to system-template, useerpc dumpto confirm the failsafe defaults (retry, timeout, hedge) are what you expect. - Never rely on
--set/-s. The flag is commented out. Use.envorprocess.envfor environment-specific overrides. - Pin
@erpc-cloud/configto 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
- YAML beats TS in auto-discovery.
./erpc.yamland./erpc.ymlare probed before./erpc.tsand./erpc.js. A staleerpc.yamlin the working directory silently shadows yourerpc.ts. - YAML env value with special characters breaks parse. If
$MY_VARexpands to a value containing:or{, the substituted text becomes invalid YAML. Quote all env-interpolated values:value: "${MY_VAR}". - TS
${VAR}withoutprocess.env.is a JS variable reference, not env expansion.${MY_KEY}in a TS template literal references a JS variableMY_KEY. If undeclared, it evaluates to"undefined", producing endpoint strings likealchemy://undefined. This is a silent failure — no parse error, just a wrong URL. - TS default export must be last.
exports.defaultis 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". - 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.stringifyreplacer drops it (undefined), and the decoded field is missing. Source:common/config.go:L2636-L2652(opens in a new tab). - TS module evaluates multiple times. Load, each policy-pool runtime acquire, and
erpc dumpeach re-run the full module. Top-level side effects (logging, HTTP calls,Date.now()) repeat. Keep the config module pure. validateanddumpsuppress all log output.zerolog.SetGlobalLevel(zerolog.Disabled)fires beforegetConfig, so warnings fromSetDefaultsand legacy migration are invisible. Pipevalidatestdout throughjq .errorsto diagnose failures.--set/-sis not implemented. The flag is commented out. Passing it produces a CLI framework error, not a graceful "unsupported" message.dump --format csvexits 1 via an explicit error branch, not a marshal failure. Valid formats areyaml,yml, andjsononly. Source:cmd/erpc/main.go:L190-L194(opens in a new tab).- TS
process.envis 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. - Provider-scheme endpoints are converted to providers, not upstreams.
alchemy://keyin--endpointor inupstreams[].endpointis converted to aProviderConfigand removed fromupstreamsbySetDefaults.erpc validatereports it under providers, soupstreamsTotallooks lower than the endpoint count. erpc.dist.yamlships broken. The canonical YAML example references aglobalrate-limit budget that does not exist inrateLimiters.budgetsand contains a duplicate upstream id.erpc validate --config erpc.dist.yamlexits 1. The TypeScript example (erpc.dist.ts) validates cleanly when env vars are set.erpc dumpmay print__ts_fn__:fn_Nfor a TSevalFuncwhen source resolution fails (best-effort.toString(); any error leaves the sentinel). Source:internal/policy/dump.go:L32-L136(opens in a new tab).- 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. AuthStrategyConfigunion is wrong for non-secret strategies in TS. The hand-writtennetwork/jwt/siwearms declare their payload under the keysecretinstead of the correct key names. Also, thedatabaseauth type is absent from the TS union. Source:typescript/config/src/types/generic.ts:L120-L141(opens in a new tab).grpccache-connector driver is unrepresentable in TS. Go supportsDriverGrpc+ConnectorConfig.grpc, but the hand-writtenTsConnectorConfigunion used forcache.connectorshas nogrpcarm. Same forUpstreamTypemissingevm+blockdaemon. Source:typescript/config/src/types/generic.ts:L63-L83(opens in a new tab).scoreMetricsWindowSizedoc comment discrepancy. The Go source comment claims "Defaults to 10m when zero"; the actual fallback inprojects_registry.gois1 * time.Minute. Theerpc.dist.yamlcomment claims "Defaults to 5s". The code always wins — default is 1m. Source:erpc/projects_registry.go:L50,L133-L134(opens in a new tab).ERPC_PPROF_PORTbinds all interfaces. The pprof server uses0.0.0.0:<port>, not127.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.- TS-required fields that Go would default. Tygo marks non-
omitemptyfields as required, soMemoryConnectorConfigforcesmaxItems+maxTotalSizein TS even though Go defaults them to100000/"1GB". YAML users can omit them. Source:common/defaults.go:L935-L944(opens in a new tab). - Legacy YAML keys work in TS objects too. Both formats share the same
UnmarshalYAMLhooks after the TS JSON round-trip, so e.g.group: 'main'on an upstream becomes atier:maintag in both YAML and TS. - Metrics server is disabled in test builds.
MetricsConfig.SetDefaultssetsEnabledonly when!util.IsTest(). Ingo testruns, metrics are off by default. Tests needing metrics must setmetrics.enabled: trueexplicitly. 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 message | Level | When |
|---|---|---|
"executing command" | INFO | Every CLI action; includes action, version, commit fields |
"looking for config file: <path>" | INFO | Each path probed during auto-discovery |
"resolved configuration file to: <path>" | INFO | Winning path identified |
"initializing eRPC core" | INFO | Before NewERPC construction |
"initializing transports" | INFO | After core init, before server start |
"networks bootstrap completed" | INFO | After all configured networks are initialised |
"shutting down gracefully..." | INFO | On SIGINT/SIGTERM context cancel |
"pprof server started at http://localhost:<port>" | INFO | Only when built with -tags pprof |
"failed to load configuration" | ERROR | Any LoadConfig error before exit 1001 |
"build failed: <messages>" | ERROR | esbuild compilation error in .ts/.js file |
"compile ts config: …" | ERROR | sobek compile error after bundling |
"ts config json-stringify: …" | ERROR | JSON.stringify step failed on TS config object |
"ts config decode: …" | ERROR | Strict YAML decode of the TS JSON output failed (field typo) |
"config default export serialized to null/undefined" | ERROR | TS config did not produce a valid default export |
"evaluate user TS config in policy runtime: …" | ERROR | Policy engine failed to re-run UserScript in a pooled runtime |
"ts selectionPolicy.evalFunc lookup: …" | ERROR | Sentinel __erpcFns id not found or registry not populated |
"no projects found in config; will add a default 'main' project" | WARN | SetDefaults synthetic project injection |
"no providers or upstreams found in project; will use default 'public' endpoints" | WARN | Public fallback injected for a project with no upstreams/providers |
"failed to initialize evm json rpc cache: …" | WARN | Cache init failure is non-fatal; eRPC continues without caching |
"failed to initialize shared state registry: …" | WARN | Shared-state init failure is non-fatal |
"invalid log level '...', defaulting to 'debug'" | WARN | Emitted at init time and at runtime if LOG_LEVEL has an invalid value |
Source code entry points
cmd/erpc/main.go:L279-L322(opens in a new tab) — config file auto-discovery loop,getConfig,--endpointinjection,.envloadingcommon/config.go:L87-L132(opens in a new tab) —LoadConfig: suffix dispatch (YAML vs TS/JS),os.ExpandEnv, strict YAML decode, legacy migration,SetDefaults,Validatecommon/config.go:L2686-L2766(opens in a new tab) —loadConfigFromTypescript: esbuild bundle, walker append, sobek compile + run, JSON stringify with sentinel replacer, strict YAML decode of JSONcommon/compiler.go:L10-L41(opens in a new tab) —CompileTypeScript: esbuild Go API options (IIFE, ES2020, Node platform, inline sourcemap)common/runtime.go:L15-L44(opens in a new tab) —NewRuntime: sobek runtime factory; populatesenvarray andprocess.envmap fromos.Environ()common/defaults.go:L49-L176(opens in a new tab) —Config.SetDefaults: default cascade including syntheticmainproject injectioncommon/validation.go:L15(opens in a new tab) —Config.Validate: exhaustive schema validationinternal/policy/runtime_pool.go:L41-L82(opens in a new tab) — re-runsUserScriptper pooled runtime, preserving closures and module-level helperscommon/config_test.go:L147-L348(opens in a new tab) — TS unified-pipeline + closure-preservation tests (key behavior contracts)
Related pages
- Auth — note: the TypeScript
AuthStrategyConfigunion 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.