/operation/cli.llms.txt
CLI & env vars
Three commands cover the full config lifecycle: start runs the proxy, validate
catches mistakes before they reach production, and dump shows the exact effective
config — defaults filled, TypeScript resolved — that the engine would actually load.
Point any of them at a YAML, TypeScript, or JavaScript file, or skip the file entirely
and spin up a quick test node with --endpoint.
Quick taste
Illustrative, not a tuned production config — validate a config file in CI:
# shellerpc validate --config ./erpc.yaml --format json | jq .errorsAgent 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 config with CI validation and safe env var injection
I want to validate my eRPC config in CI before every deploy, and inject secrets like API keys and Redis passwords safely via environment variables. My config is my eRPC config. Read the full reference first: https://docs.erpc.cloud/operation/cli.llms.txt
Prompt Example #2: tune metrics cardinality and histogram buckets
Audit the metrics section of my eRPC config: adjust histogram bucket boundaries to match our typical latency distribution (10ms–10s), drop the user and composite labels to reduce cardinality, and make sure errorLabelMode is right for Grafana. Reference: https://docs.erpc.cloud/operation/cli.llms.txt
Prompt Example #3: debug a TypeScript config that silently drops a function
My eRPC TypeScript config's selectionPolicy evalFunc seems to be ignored at runtime — the dump output shows a sentinel but requests aren't being filtered. Walk me through why TS functions can be silently dropped and how to verify they were registered correctly. Config: ./eRPC config. Reference: https://docs.erpc.cloud/operation/cli.llms.txt
Prompt Example #4: configure graceful shutdown for Kubernetes rolling deploys
My Kubernetes deployment sees connection errors during rolling updates. Configure the waitBeforeShutdown and waitAfterShutdown values in my eRPC config, and set up INSTANCE_ID via the downward API so shared-state UpdatedBy does not collide across pods. Reference: https://docs.erpc.cloud/operation/cli.llms.txt
Prompt Example #5: explain which logLevel and LOG_LEVEL to use in each environment
I have three environments (dev, staging, prod) and I'm unsure which log level to set in the config file vs which to override at runtime with LOG_LEVEL. Also explain why validate and dump always suppress logs. My config lives at my eRPC config. Reference: https://docs.erpc.cloud/operation/cli.llms.txt
CLI & env vars — full agent referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.
How it works
Startup sequence. main() registers a signal.NotifyContext on SIGINT/SIGTERM
(cmd/erpc/main.go:L72), builds a CLI app via github.com/urfave/cli/v3, and calls
cmd.Run. For the start/default action, baseCliAction calls getConfig then
erpc.Init. erpc.Init (erpc/init.go:L19) runs in order:
- Set zerolog global level from
cfg.LogLevel. - Log the final config as JSON at INFO (may contain redacted secrets).
- Install histogram buckets and label filter.
- Register a global network-alias resolver for metrics label consistency.
- Init EVM JSON-RPC cache if configured (failures are warnings, not fatal).
- Init shared-state registry if configured (same warn-on-fail pattern).
- Build
ERPCcore: tracing, rate limiters, proxy pool, shared-state fallback, vendors registry, projects registry. - Call
erpcInstance.Bootstrap(appCtx)— eager network bootstrap in background goroutines. - Start HTTP server goroutine.
- Optionally start a standalone gRPC server if gRPC is enabled and does not share the HTTP v4 port.
- Start Prometheus metrics server.
- Block on
<-appCtx.Done(), then sleepserver.waitAfterShutdownbefore returning.
Config file search order. When --config and a positional argument are both absent
and --require-config is not set, getConfig probes (cmd/erpc/main.go:L279-L293):
./erpc.yaml./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
First path where fs.Stat succeeds wins. If none match and no --endpoint flag was
supplied, eRPC starts with a synthetic main project using public RPC providers.
YAML loading. LoadConfig reads the file, runs os.ExpandEnv on raw bytes
(common/config.go:L102), then decodes with gopkg.in/yaml.v3 in strict mode
(KnownFields(true) — unknown keys are fatal). After decode: legacy migration hook
(LegacyTranslateFn, wired to legacy.TranslateFromConfig at cmd/erpc/main.go:L36)
fires first, potentially emitting zerolog Warn messages for deprecated fields, then
SetDefaults, then Validate. Tests that exercise legacy YAML must set LegacyTranslateFn manually.
TypeScript/JS loading. loadConfigFromTypescript (common/config.go:L2686):
- esbuild compiles the file as an IIFE bundle (
Bundle:true,Format:IIFE,Target:ES2020,Platform:Node,GlobalName:"exports"). - The
tsLoaderWalkerJS fragment is appended — it depth-first walksexports.default, assigns IDs (fn_0,fn_1, …) to every function-typed leaf, registers them inglobalThis.__erpcFns[id], and stampsfn.__erpcFnId = id. - Combined source is compiled to a
sobek.Program(userScript) and run in a temporary runtime. JSON.stringifywith a replacer converts functions to"__ts_fn__:fn_<n>"sentinels; functions not reached by the walker are dropped silently.- The resulting JSON is decoded through
yaml.DecoderwithKnownFields(true). cfg.UserScriptis set; the policy-engine pool re-runs this program in each acquired sobek runtime to reconstruct live__erpcFnsreferences — no.toString()round-trip, closures preserved.
validate subcommand. Loads config, calls erpc.GenerateValidationReport which
builds a resource tree and checks for orphan rate-limit budgets, public endpoints, and
static-analysis issues. Output is JSON (default) or Markdown. Exits 0 when errors
is empty, 1 otherwise. All zerolog output is silenced via
zerolog.SetGlobalLevel(zerolog.Disabled) before getConfig — no warnings from
SetDefaults or Validate appear on stderr.
dump subcommand. Loads config, calls policy.ResolveEffectiveSelectionPolicies
to fill in the effective selectionPolicy per network as the engine would derive it,
then marshals to YAML (default) or JSON. Useful for verifying what a TypeScript config
actually produces. Log output is also suppressed.
Graceful shutdown. On SIGINT/SIGTERM: signal.NotifyContext cancels appCtx.
The HTTP server's goroutine wakes, sleeps server.waitBeforeShutdown (default 10 s —
gives Kubernetes time to mark the pod NotReady), then calls srv.Shutdown(30 s budget).
The metrics server shuts down with a 5 s budget. The OTel tracer provider shuts down in
a goroutine with a 5 s budget. erpc.Init blocks on <-appCtx.Done() then sleeps
server.waitAfterShutdown (default 10 s) before returning, allowing in-flight requests
to drain.
pprof. A //go:build pprof file registers standard net/http/pprof routes and
starts a listener at 0.0.0.0:<port> (default 6060; override with ERPC_PPROF_PORT).
Mutex and block profiling are enabled at rate 1. The standard binary does not include
pprof — build with -tags pprof to activate.
Config schema
CLI subcommands — cmd/erpc/main.go:L200-244
| Subcommand | Flags | Behavior |
|---|---|---|
erpc [config] (root / no subcommand) | --config, --endpoint/-e, --require-config | Same as start: load config, run erpc.Init. |
erpc start | --endpoint/-e (repeatable), --require-config; root --config also honored via flag lookup | Start the proxy service. |
erpc validate | --format json|md (default json) | Validate config; exit 1 on any errors; logs suppressed. |
erpc dump | --format yaml|json (default yaml) | Dump effective config with resolved selection policies; exit 1 on load/marshal error or unsupported format. |
CLI flags — cmd/erpc/main.go:L76-94
| Flag | Type | Default | Behavior / footguns |
|---|---|---|---|
--config | string | "" | Path to config file. When non-empty, sets requireConfig=true; no fallback to auto-discovery. If the path does not exist, exits 1001. |
--endpoint / -e | []string | [] | Zero or more upstream endpoint URLs. Validated with url.ParseRequestURI; invalid URLs abort with exit 1001. Injected as synthetic upstreams into the first project when no providers/upstreams exist. |
--require-config | bool | false | If true and no config file found, aborts. Skips auto-discovery and --endpoint-only mode. |
validate --format | string | "json" | Output format: json or md. |
dump --format | string | "yaml" | Output format: yaml, yml, or json. Any other value triggers "unsupported format" and exits 1. |
--set / -s is not available. Commented out at cmd/erpc/main.go:L86-90. Passing it produces a framework-level flag error before any application code runs.
Top-level Config fields — common/config.go:L38-68
| YAML path | Type | Default | Behavior / footguns |
|---|---|---|---|
logLevel | string | "INFO" | Zerolog level. Invalid value defaults to debug with a warning. Overridable at runtime by LOG_LEVEL env var. Valid: trace, debug, info, warn, error, fatal, panic, disabled. |
clusterKey | string | "erpc-default" | Logical replica group ID for shared-state scoping. Propagated to database.sharedState.clusterKey only if that field is empty. Precedence: explicit database.sharedState.clusterKey > clusterKey > hard-coded default. |
server | *ServerConfig | empty struct then SetDefaults | HTTP/gRPC server config (bind addresses, timeouts, graceful-shutdown windows). See HTTP server. |
healthCheck | *HealthCheckConfig | mode=networks, defaultEval=any:initializedUpstreams | Health check endpoint behaviour. |
admin | *AdminConfig | nil | Admin JSON-RPC API; CORS defaults to * origin with no credentials when section is present. |
database | *DatabaseConfig | nil | EVM JSON-RPC cache and/or shared-state connector. |
projects | []*ProjectConfig | synthetic main project with public + envio providers when empty | Ordered list of project configs. When empty, SetDefaults injects a main project. |
rateLimiters | *RateLimiterConfig | nil | Rate limiter budgets. Budget IDs referenced by projects/upstreams/networks must exist here. |
proxyPools | []*ProxyPoolConfig | nil | HTTP proxy pool definitions. |
tracing | *TracingConfig | nil | OTel tracing; defaults when set: protocol=grpc, endpoint=localhost:4317, sampleRate=1.0, serviceName=erpc. |
metrics.enabled | *bool | true (non-test builds) | If false or nil, metrics server is not started. In test builds the default is nil. |
metrics.port | *int | 4001 | Prometheus scrape port. |
metrics.hostV4 | *string | "0.0.0.0" | IPv4 bind address for the metrics server. |
metrics.hostV6 | *string | "[::]" | IPv6 bind address. |
metrics.errorLabelMode | LabelMode | "compact" | Controls the error label on request-error counters. "compact" condenses codes; "verbose" emits full class names. |
metrics.histogramBuckets | string | "" (built-in defaults) | Comma-separated float64 bucket boundaries. Non-float values are rejected by validation. |
metrics.histogramDropLabels | []string | nil | Label names removed from every histogram to reduce cardinality. |
metrics.histogramLabelOverrides | map[string][]string | nil | Per-metric label keep-list; key is metric name without erpc_ prefix. |
metrics.counterDropLabels | []string | nil | Label names removed from every counter carrying caller-controlled dimensions. Sums are preserved; the dimension is not. |
metrics.counterLabelOverrides | map[string][]string | nil | Per-metric label keep-list for counters; key is metric name without erpc_ prefix. |
Environment variables
| Env var | Where applied | Effect |
|---|---|---|
LOG_LEVEL | init() and after config decode | Zerolog global level. Applied twice: first during config-loading so those logs respect it; second after decode to override cfg.LogLevel. Invalid value falls back to debug. Valid: trace, debug, info, warn, error, fatal, panic, disabled. |
LOG_WRITER | init() | When "console", switches from JSON to a human-readable colored writer (time format "04:05.000ms"). Must be set before process start; cannot be changed at runtime. |
ERPC_NOLOGS | init() (build tag !test) | When "1", silences zerolog globally and replaces the writer with io.Discard. Intended for test runs. |
ERPC_NOMETRICS | init() (build tag !test) | When "1", swaps the Prometheus default registry with a no-op. Irreversible within the process. |
ERPC_PPROF_PORT | init() (build tag pprof) | pprof listener port; default 6060. Only effective when built with -tags pprof. Binds 0.0.0.0 — all interfaces. |
ERPC_IGNORE_LOCAL_ENDPOINT_VALIDATION | config analyzer | When "true", skips validation of non-HTTP and local (loopback/localhost) upstream endpoints in erpc validate. [erpc/config_analyzer.go:L1024-1031] |
INSTANCE_ID | shared-state + consensus | First in the instance-identity chain. Used as UpdatedBy in shared-state writes and as {instanceId} in consensus dispute-log filenames. |
POD_NAME | shared-state + consensus | Second in the chain (Kubernetes pod name). |
HOSTNAME | shared-state + consensus | Third in the chain (OS hostname). |
FORCE_TEST_LISTEN_V4 | common/defaults.go | When "true" in test builds, forces server.listenV4=true. |
$VAR / ${VAR} in YAML | before YAML parse | os.ExpandEnv on raw bytes. Unset vars resolve to empty string. Footgun: if the expanded value contains : or {, quote the YAML value. |
$VAR / ${VAR} in server.responseHeaders | HTTP server construction | Expanded once at server startup via os.ExpandEnv. If the expanded value is empty, the header entry is silently dropped from all responses. Runs after full config decode; distinct from YAML-level expansion. [erpc/http_server.go:L135-148] |
$VAR / ${VAR} in provider-generated upstream endpoints | provider resolution | Expanded when the vendor's GenerateConfigs returns each UpstreamConfig.Endpoint string. Distinct from YAML-level expansion — runs at request time, not config-load time. [thirdparty/provider.go:L67-71] |
process.env.<KEY> in TS | sobek runtime creation | Full os.Environ() snapshot passed to TS/JS config runtime as a process.env object; also exposed as a flat env array of "KEY=VALUE" strings. A snapshot — not a live view. Because .env is loaded in init() before the TS bundle is evaluated, .env values are available here too. |
Instance-identity priority chain. Two functions resolve instance ID:
- Shared-state:
INSTANCE_ID→POD_NAME→HOSTNAME→os.Hostname()→"unknown". Whitespace is trimmed at each step.data/shared_state_registry.go:L82-98 - Consensus: same env-var priority order but final fallback is SHA-256(unixNano+pid) truncated to 8 hex chars (computed once via
sync.Once). Characters problematic for filenames (:,/,\,,*,?,") are sanitized to_before the value is used as the{instanceId}token in dispute-log filenames.consensus/export_utils.go:L26-48
Setting INSTANCE_ID explicitly avoids both failure modes.
validate command output schema (JSON):
{
"errors": [],
"warnings": [],
"notices": [],
"resources": {
"totals": { "projectsTotal": 0, "networksTotal": 0, "upstreamsTotal": 0, "rateLimitBudgetsTotal": 0 },
"tree": {
"projects": [{ "id": "", "networks": [{ "id": "", "upstreams": [{ "id": "" }] }] }],
"rateLimiters": { "budgets": [{ "id": "", "rulesCount": 0 }] }
}
}
}Exit 0 if errors is empty, exit 1 otherwise.
Exit codes — util/exit.go:L7-10
1001(ExitCodeERPCStartFailed) — CLI/config load failure orerpc.Initerror.1002(ExitCodeHttpServerFailed) — HTTP or gRPC server fatal error.
Worked examples
All patterns below are distilled from real production fleets; comments explain the non-obvious choices.
1. CI validation gate before deploy. Production deployments run erpc validate in the
pipeline before any image build or Helm upgrade. Exit code 0 means no errors; pipe through
jq .errors for a clean failure message in CI logs without JSON noise:
# exits 0 when errors=[], exits 1 when errors is non-empty
erpc validate --config ./erpc.yaml --format json | jq '.errors'
# Markdown for PR comments or human-readable output in CI
erpc validate --config ./erpc.yaml --format md2. Production log level: error in prod, info in staging, info in dev. Real fleets keep
logLevel: error in production (high-QPS internal workloads) to suppress routine health-check noise,
info in staging and dev where visibility matters. LOG_LEVEL=debug at runtime (without a
deploy) is the escape hatch when diagnosing live issues:
# Production — suppress routine INFO chatter; errors surfaced by structured loglogLevel: error
# Staging / dev — info gives bootstrap/network visibility without trace flood# logLevel: info3. Tuned metrics for multi-region fleet. Production edge deployments drop the user
and composite histogram labels (high-cardinality from many API keys and composite request
patterns) and set explicit bucket boundaries that match real p50/p99 latencies observed in
the fleet. errorLabelMode: compact is always used in production to keep Prometheus
cardinality bounded:
metrics: enabled: true port: 4001 hostV4: "0.0.0.0" # Bucket edges aligned to observed p50 (~100ms) and p99 (~3s) latencies. # Coarser than the default to keep time-series count down on large fleets. histogramBuckets: "0.020,0.100,0.300,0.500,1,3,6,10" # compact keeps error labels bounded; verbose creates a label value per # error class — too many on multi-upstream deployments. errorLabelMode: compact # Drop high-cardinality labels from histograms only; counters keep them. histogramDropLabels: - user - composite4. Graceful shutdown for Kubernetes rolling deploys. Both Kubernetes and PaaS
production deployments use 30s for both shutdown windows. waitBeforeShutdown gives
Kubernetes time to update endpoints before the server starts refusing connections;
waitAfterShutdown lets in-flight requests drain. INSTANCE_ID via the downward API
prevents UpdatedBy collisions across pods in shared-state writes:
server: # Allow Kubernetes to mark the pod NotReady before traffic stops arriving. # Without this, rolling deploys produce connection errors during the gap. waitBeforeShutdown: 30s # Drain in-flight requests before the process exits. waitAfterShutdown: 30sKubernetes pod spec fragment for the downward API:
env:
- name: INSTANCE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name # stable pod name; avoids UpdatedBy = "unknown"5. Per-environment clusterKey for shared-state isolation. Each deployment uses a
distinct clusterKey so prod, staging, and dev never share lock state. The top-level key
propagates automatically unless database.sharedState.clusterKey is set explicitly:
database: sharedState: # Unique per deployment — prod/stage/dev must not share lock state. # If this key collides across two fleets, they race each other's locks. clusterKey: prod-us-east-1 connector: driver: redis redis: uri: ${SHARED_STATE_REDIS_URL}6. Quick smoke-test with a bare endpoint, no config file. Spin up eRPC against a single node to verify routing — no YAML required. Useful for local testing before writing a full config:
erpc --endpoint https://mainnet.infura.io/v3/$INFURA_KEY
# equivalent: erpc start --endpoint https://mainnet.infura.io/v3/$INFURA_KEY7. Inspect the effective config produced by a TypeScript config. TypeScript arrow
functions in selectionPolicy.evalFunc show up as __ts_fn__:fn_N sentinels; everything
else renders as plain YAML. Use this to verify closures were detected by the walker:
erpc dump --config ./erpc.ts --format yaml
# or JSON for programmatic diffing:
erpc dump --config ./erpc.ts --format json | jq .Request/response behavior
The CLI layer itself does not process JSON-RPC requests. The behaviors below govern how the bootstrap interacts with the process environment.
.envfile inos.Getwd()is loaded ininit()beforemain()runs; values are available for YAML$VARsubstitution. Non-existence is silently ignored; other errors are logged at ERROR. [cmd/erpc/main.go:L42-46]LOG_LEVELenv var overridescfg.LogLevelafter config load; a config-filelogLevel: errorcan be trumped byLOG_LEVEL=debugwithout editing the file. [cmd/erpc/main.go:L354-363]validateanddumpsuppress all log output.zerolog.SetGlobalLevel(zerolog.Disabled)is called beforegetConfigruns (validate:cmd/erpc/main.go:L109;dump:cmd/erpc/main.go:L156);LOG_LEVELhas no effect during these commands.- YAML strict-mode rejects unknown fields (
KnownFields(true)); this applies to the JSON round-trip from TypeScript configs too. [common/config.go:L103] - Shorthand upstream URLs (
alchemy://KEY,infura://KEY, etc.) are converted toProviderConfigand removed fromp.UpstreamsatSetDefaultstime; after load they appear inp.Providers, notp.Upstreams. [common/defaults.go:L1164-1172] - Key validation rules enforced by
Config.Validate():server.maxTimeoutmust be non-zero; each project needs at least one upstream or provider;selectionPolicy.evalTimeoutmust be strictly less thanevalInterval;onlyNetworksandignoreNetworkson a provider are mutually exclusive;database.sharedState.lockMaxWaitandupdateMaxWaitmust each be less thanfallbackTimeout;database.sharedState.lockTtlmust be at least as long asfallbackTimeout; connector-level failsafe may not useconsensusorhedge.quantile. [common/validation.go:L15]
Best practices
- Run
erpc validate --config ./erpc.yamlin every CI pipeline before shipping config changes — it catches unknown field typos, missing rate-limit budget references, andselectionPolicycompile errors without starting a server. - Use
erpc dump --config ./erpc.ts --format yamlwhen debugging TypeScript configs to see what the engine actually sees; TS function sentinels (__ts_fn__:fn_N) confirm functions were detected correctly. - Always quote YAML values that may contain env vars with special characters:
password: "${REDIS_PASSWORD}"notpassword: ${REDIS_PASSWORD}— a:in the expanded value will break YAML parsing silently. - Set
INSTANCE_ID(orPOD_NAMEvia the Kubernetes downward API) explicitly in every deployment — without it, shared-stateUpdatedByfalls back to"unknown"and collides across pods; consensus dispute-log filenames get unique hashes but shared-state writes do not. - Do not build with
-tags pprofin production unless you immediately firewall-restrictERPC_PPROF_PORT— the listener binds0.0.0.0, not localhost. - Set
LOG_WRITER=consoleonly for local development; JSON output is required by every log-aggregation platform (Datadog, Grafana Loki, etc.) and the env var cannot be changed at runtime. - In Kubernetes, add a
preStophook or rely onserver.waitBeforeShutdown(default 10 s) to let the pod reach NotReady before requests stop arriving — skip this and you will see connection errors during rolling deploys.
Edge cases & gotchas
--configsetsrequireConfig=trueimplicitly. If the specified path does not exist, the process exits 1001 with no fallback to auto-discovery. [cmd/erpc/main.go:L300-302]- Positional argument works in all subcommands.
erpc validate ./config.yamlanderpc dump ./config.yamlboth work — first positional arg is treated as a config path withrequireConfig=true. [cmd/erpc/main.go:L303-305] - YAML strict-mode rejects unknown fields. A typo in any YAML key is a fatal error — this also applies to the JSON round-trip from TypeScript configs.
- Quoted YAML values required for env vars that may contain special chars.
password: "${REDIS_PASSWORD}"is safe;password: ${REDIS_PASSWORD}is not if the value contains:or{. - TypeScript default-export must be the last statement. If a later expression
overwrites
exports.default, the earlier config object is lost. Error:"config object must be default exported from TypeScript code AND must be the last statement in the file". [common/config.go:L2715-2717] - TS functions in bare arrays are not walked. The
tsLoaderWalkeronly descends into object properties, not bare array function elements. In practiceselectionPolicyfunctions are always object values so this rarely matters. - Orphaned TS function silently dropped. A function not reached by the walker is
dropped by the JSON.stringify replacer — the config field will be absent with no error.
[
common/config.go:L2736-2739] validate/dumpsuppress all log output.zerolog.SetGlobalLevel(zerolog.Disabled)is called beforegetConfig. Warnings fromSetDefaults, legacy migration, and validation do not appear. Usejq .errorson the JSON output.LOG_LEVELhas no effect here.LOG_WRITER=consolemust be set before process start. Configured ininit(), not hot-reloadable. In log-aggregation environments keep the default JSON writer.- pprof binds
0.0.0.0. All interfaces, not localhost-only. Firewall-restrict or avoid-tags pprofin production. ERPC_NOMETRICS=1is irreversible. Swaps the Prometheus default registry process-wide; cannot be undone within the same process.INSTANCE_IDnot stable across restarts = orphaned shared-state locks. Use a deployment-stable value (pod name, container ID).dump --format csv(or any unknown format) exits 1 via a distinct code path. A marshal failure means config loaded but serialisation failed; an unsupported-format failure means serialisation was never attempted. Valid values:yaml,yml,json.- Shared-state falls back to
"unknown"while consensus generates a hash. In environments with no env vars andos.Hostname()failing, shared-stateUpdatedByis"unknown"(collides across pods). Consensus dispute-log filenames get a unique 8-char hash. [data/shared_state_registry.go:L82-98] [consensus/export_utils.go:L26-48] - Provider endpoint env expansion runs after YAML-level expansion. Pipeline:
(1)
os.ExpandEnvon raw YAML bytes → YAML decode →SetDefaults(providers registered). Then at request time: (2) provider'sGenerateConfigsbuilds endpoint strings →expandEnvVarscallsos.ExpandEnv. Double-expansion is possible if the vendor builds an endpoint that itself contains$VARliterals. [thirdparty/provider.go:L67-71] --set/-sis not implemented. The flag was planned as a Helm-style dot-path override but is commented out. Passing-sor--setproduces"flag provided but not defined: -s"from the CLI framework. [cmd/erpc/main.go:L86-90]- Metrics server disabled in test builds.
MetricsConfig.SetDefaultssetsEnabled=trueonly when!util.IsTest(). Tests that need metrics must setmetrics.enabled: trueexplicitly. [common/defaults.go:L750-752] - gRPC shares the HTTP handler when both addresses resolve to the same port. No
separate
grpcServer.Startgoroutine runs. [erpc/init.go:L125] - Eager network bootstrap runs on
appCtx; novel chains trigger the lazy path. Novel chain IDs arriving before bootstrap finishes block the request until init completes or the request context times out. [erpc/networks_registry.go:L195-212] - TypeScript
process.envis a snapshot, not a live view. Built once when the sobek runtime is created fromos.Environ(); a restart-less env var change requires all policy engine runtimes to be recreated.
Observability
The bootstrap/CLI layer emits no Prometheus metrics of its own. Key structured log
messages (zerolog JSON unless LOG_WRITER=console):
| Log message | Level | When |
|---|---|---|
"executing command" (fields: action, version, commit) | INFO | Start of every CLI action |
"" with config field | INFO | Final config logged before init — may contain redacted secrets |
"initializing eRPC core" | INFO | Before NewERPC |
"initializing transports" | INFO | After core init, before server start |
"shutting down gracefully..." | INFO | On context cancel |
"no projects found in config; will add a default 'main' project" | WARN | Synthetic project injected |
"no providers or upstreams found in project; will use default 'public' endpoints repository" | WARN | Public fallback provider injected when project has no upstreams or providers |
"failed to initialize evm json rpc cache: ..." | WARN | Cache init failure (non-fatal) |
"failed to initialize shared state registry: ..." | WARN | Shared-state init failure (non-fatal) |
"invalid log level '...', defaulting to 'debug'" | WARN | Invalid LOG_LEVEL or logLevel value |
"pprof server started at http://localhost:<port>" | INFO | Only when built with -tags pprof |
"networks bootstrap completed" | INFO | After all configured networks initialised |
Source code entry points
cmd/erpc/main.go:L72-L305(opens in a new tab) — signal handling, CLI command definitions,getConfig, auto-discovery,--endpointinjectionerpc/init.go:L19-L180(opens in a new tab) —Init(ctx, cfg): full startup + graceful drain sequencecommon/config.go:L95-L110(opens in a new tab) —LoadConfig:os.ExpandEnv, YAML decode, legacy migration,SetDefaults,Validatecommon/config.go:L2686-L2740(opens in a new tab) —loadConfigFromTypescript: esbuild,tsLoaderWalker, sobek eval, sentinel lifecyclecommon/defaults.go:L49-L176(opens in a new tab) —Config.SetDefaults: full defaults cascade, syntheticmainproject injectioncommon/validation.go:L15(opens in a new tab) —Config.Validate: exhaustive validation rulescmd/erpc/pprof.go(opens in a new tab) — build-tagpprof:init()registering pprof routes and starting0.0.0.0:<port>listenercmd/erpc/initflags.go(opens in a new tab) — build-tag!test:ERPC_NOLOGSandERPC_NOMETRICSinit hooksutil/exit.go:L7-L10(opens in a new tab) — exit codes1001(start failed) and1002(HTTP/gRPC server fatal)erpc/config_analyzer.go(opens in a new tab) —GenerateValidationReport,RenderValidationReportJSON,RenderValidationReportMarkdowncommon/runtime.go(opens in a new tab) —NewRuntime(): creates a sobek runtime, populatesprocess.envmap andenvarray fromos.Environ(); used for TypeScript selection-policy evaluationcommon/compiler.go(opens in a new tab) —CompileTypeScript(esbuild IIFE bundle),CompileFunction(sobek single-function eval),CompileProgram(sobek.Compile with paren-wrap)
Related pages
- HTTP server — server bind addresses, timeouts, and graceful shutdown knobs.
- Deployment — Docker and Kubernetes deployment patterns, including
POD_NAMEdownward API setup. - Projects — the config structure that
startandvalidateboth load. - Rate limiters — budget IDs that validation checks for orphan references.
- Observability — Prometheus metrics exposed on
metrics.port.