CLI reference
This page lists every command-line flag the smartrouter binary accepts, grouped by
purpose, so you can look up a flag's default and what it does.
You pass the config file as a positional argument. Flags change runtime behaviour.
Authoritative source
This table follows smartrouter --help. If anything here disagrees with the binary,
trust the binary. Run smartrouter --help to check against your build.
Subcommands
| Command | Purpose |
|---|---|
smartrouter <config.yml> |
Start the router. This is the default; the config file is a positional argument. |
smartrouter cache <host:port> |
Run the standalone cache server. The address is a positional argument. |
smartrouter health <config.yml> |
Run a one-shot, spec-driven health probe over every node, URL, websocket, and add-on in a config. It prints a JSON report and exits. Flags: --include-backup, --timeout (default 30s), --skip-websocket-verification, --use-static-spec (required). |
smartrouter version |
Print the version and commit. The first line is only the version, for use in scripts. |
smartrouter test … |
Internal test commands (rpcsmartrouter, connection-server, connection-probe). |
Specs
| Flag | Default | Description |
|---|---|---|
--use-static-spec |
- | Load specs from a file, a directory, or a remote GitHub/GitLab URL. You can repeat the flag. For the same chain id, later sources override earlier ones. |
--github-token |
- | GitHub PAT for private spec repos or higher rate limits. |
--gitlab-token |
- | GitLab PAT for private spec repos. |
RPC node selection (QoS)
| Flag | Default | Description |
|---|---|---|
--strategy |
balanced |
Selection strategy: balanced, latency, sync-freshness, cost, privacy, accuracy, distributed. |
--qos-availability-weight |
0.3 |
Weight of availability in the composite score. |
--qos-latency-weight |
0.3 |
Weight of latency. |
--qos-sync-weight |
0.2 |
Weight of sync freshness. |
--qos-stake-weight |
0.2 |
Weight of node stake. |
--qos-min-selection-chance |
0.01 |
Minimum probability that any node is picked, whatever its score. |
--optimizer-qos-sampling-interval |
1s |
How often selection scores (and OTel QoS events) are sampled. |
--probe-update-weight |
0.25 |
Weight multiplier for probe-driven liveness/latency updates. |
--probe-loop-interval |
5s |
How often nodes are probed for liveness and latency. Probing is always on. The value must be greater than 0. |
See RPC Node selection.
Retry, timeout & failover
| Flag | Default | Description |
|---|---|---|
--set-relay-retry-limit |
2 |
Maximum total retry attempts across all error types (node and protocol). 0 disables retries. |
--default-processing-timeout |
30s |
Total time budget for one relay, including all retries and hedges. |
--min-relay-timeout |
1s |
Lower limit for the per-attempt window, which sets when the router tries the next node. It does not cancel the attempt in flight. The lava-relay-timeout header overrides it. |
--max-caller-relay-timeout |
0 |
The longest time a caller's lava-relay-timeout header can make the router hold one request. The flag only lets the header extend a request past the budget it gets without the header. It never shortens a request. 0 allows no extension. An extended request does not hedge. |
--max-sessions-per-provider |
1000 |
Max concurrent sessions per node. |
--maximum-streams-per-connection |
100 |
Max parallel streams over a single gRPC connection. |
--disable-batch-request-retry |
true |
Don't retry, fail over, or hedge JSON-RPC batch requests. A batch can carry a write. See Batch requests. |
--batch-node-error-on-any |
false |
Treat a batch as a node error if any sub-request fails, instead of only when all fail. |
--max-batch-request-size |
0 |
Max requests per JSON-RPC batch (0 = unlimited). |
See Failover & retry.
Polling relief
These flags lower the polling load that the router's chain trackers put on your
upstreams. Each configured upstream gets its own chain tracker, which polls it for the
latest block separately from the relays you send. All of these flags are process-wide.
You can watch the effect on
rpc_endpoint_tracker_requests_total.
| Flag | Default | Description |
|---|---|---|
--enable-fork-detection |
false |
Turn on block-hash polling (reorg detection on upstreams). Off by default - the larger of the per-pod savings. |
--chain-tracker-poll-divisor |
2 |
The tracker polls every avgBlockTime ÷ divisor. 1 halves the polling rate. 0.25 (one poll per four block times) cuts it eightfold. Allowed [0.25,8]; an out-of-range value reverts to the default. See Polling slower than the chain. |
--shared-state (with --cache-be) |
false |
Share poll observations across router replicas through the cache, so an upstream is polled about once per interval fleet-wide rather than once per pod. See Cache & shared state. |
--chain-tracker-idle-after |
0 (off) |
Idle mode. When a chain has served no client request for this long, its trackers stop polling until the next request. Minimum 10s. Available from v1.6.0. See Pausing an idle chain. |
--chain-tracker-idle-poll-interval |
0 |
While a chain is idle, poll each upstream once per this interval, so a dead upstream is still noticed. 0 polls nothing while idle. No effect without --chain-tracker-idle-after. |
The tracker also skips a poll when something else has already kept the upstream's tip
fresh: the relays it served in the last block time, or (with --shared-state) a poll
that another replica made. Skipped ticks show up on
rpc_endpoint_tracker_gate_skips_total by
source. If requests_total falls while gate_skips_total rises, the relief is
working and the tracker has not stalled.
Pausing an idle chain
--chain-tracker-idle-after stops polling a chain nobody is using. It is off by
default. When it is on and a chain has served no client request for that long, its
trackers skip every poll, so the upstreams of an unused chain are not billed for it.
The next client request wakes the chain. The router polls every upstream of that chain
once, and that request waits for the first answer, up to 1 second, before it is routed.
Later requests are not delayed. While a chain is idle, smartrouter_chain_idle reads
1.
Things to know before you turn it on:
- Pick a window longer than the gap between your requests. Otherwise every request
is a wake.
5mto10mis a good start. - The router's latest block stands still while a chain is idle. An alert on "the
tip stopped moving" fires on an idle chain unless it skips chains where
smartrouter_chain_idleis1. - Subscriptions do not keep a chain awake. An open
eth_subscribeneither keeps a chain active nor wakes it. Unary requests over a websocket do. - A disabled upstream recovers at the next epoch or after the wake. Re-enabling an upstream needs polls, so while a chain is idle it waits.
Polling slower than the chain
A divisor below 1 polls less often than the chain produces blocks. On a fast chain,
this is where the relief is. Measured requests per minute, per endpoint:
| Chain | 2 (default) |
1 |
0.5 |
0.25 |
|---|---|---|---|---|
| Aptos | 600 | 300 | 150 | 75 |
| Solana | 300 | 150 | 75 | 37.5 |
| Base | 60 | 30 | 15 | 7.5 |
| Ethereum | 9.2 | 4.6 | 2.3 | 1.2 |
The staleness window sets the limit at the low end: max(10 × avgBlockTime, 2s). Past
this window, an observation stops counting for consensus, the tip reads unknown, and the
probe scores a healthy upstream as not-alive. This flag does not change the window.
It changes how long a tip can go without a refresh, which is the other side of that
comparison. Both common cases stay well inside the window:
- Idle upstream - only its own poll refreshes the tip, so the gap is the
interval: 4 ×
avgBlockTimeat0.25, against a 10× window. - Served upstream - relays refresh the same tip, so traffic limits the gap.
The edge case is between them. An upstream that trips the traffic gate and then goes
quiet is refreshed by neither, so the worst-case gap becomes
(maxRelaySkips + 1) × interval, which is 20 × avgBlockTime at 0.25. This gap
depends on the product of this flag and the gate's skip budget, not on either one
alone. So the router logs a warning once per chain at startup instead of refusing to
start:
WRN poll cadence can outrun the staleness window when the traffic gate skips
pollInterval=60s worstCaseGapBetweenPolls=5m0s stalenessWindow=2m30s maxRelaySkips=4
This warning is for information only. It is not a failure, and the configuration is
safe for both common cases. If you see it on a chain with bursty traffic, move the
divisor back toward 0.5 or 1.
Consistency tuning
| Flag | Default | Description |
|---|---|---|
--consistency-block-gap-factor |
2 |
Widen the consistency lag gate (blockLagForQosSync × factor). Allowed [2,8]. |
Cache & shared state
| Flag | Default | Description |
|---|---|---|
--cache-be |
- | Address of the cache server (e.g. 127.0.0.1:20100). In Compose the cache address usually comes from cache-be: in the config instead. |
--shared-state |
false |
Share state across router replicas through the cache backend (cache-be or resp-cache): the consumer-consistency "seen block", per-endpoint chain-tracker poll observations (so the fleet polls each upstream about once per interval instead of once per replica), and cross-pod sticky sessions. Poll sharing also requires --cache-be; see the RESP backend caveats. |
--resp-cache-addresses |
- | Comma-separated address(es) of a RESP-compatible backend (Redis/Valkey). Enables the RESP backend, which takes precedence over cache-be. Standalone: the node address; sentinel: the sentinel addresses; cluster: the configuration endpoint. |
--resp-cache-topology |
standalone |
standalone, sentinel, or cluster. |
--secondary-cache-be |
- | Address of an optional read-only secondary cache, queried when the primary produces no hit. |
--secondary-cache-timeout |
50ms |
Time budget for each secondary cache lookup. A lookup that runs over it is treated as a miss. |
--secondary-cache-mode |
read-only |
Secondary cache access mode. Only read-only is supported. |
The RESP backend's full surface is config-file only
You set TLS, credentials, credential rotation, the read/write split, and key prefixing
in the resp-cache: YAML block. Only addresses and topology have flags. See
Redis / Valkey backend.
With --shared-state, every replica publishes each successful poll of an upstream to
the cache. Before it polls, a replica checks whether another replica polled that
upstream within the last block time. If one did, the replica skips the tick and adopts
the peer's block as its own view of the upstream's tip. Four rules keep this safe:
- a replica never borrows its own observation, so a single replica polls exactly as it would without the flag;
- a replica whose own last poll of an upstream failed does not borrow at all (it goes back to local polling at the full rate until its own poll succeeds);
- every replica still polls each upstream itself every few ticks (peer skips count against the same skip budget as relay skips), so a broken path from one pod stays detectable;
- an upstream that was disabled is only re-enabled by that pod's own successful poll; a peer observation never affects poll health.
Latency is never shared, because a peer's round-trip says nothing about this pod's path.
WebSocket
| Flag | Default | Description |
|---|---|---|
--rate-limit-websocket-requests-per-connection |
-1 (unlimited) |
Per-second request rate limit per WS connection. |
--ban-duration-for-websocket-rate-limit-exceeded |
no ban | Ban duration once the WS rate limit is hit. |
--limit-parallel-websocket-connections-per-ip |
0 (unlimited) |
Max parallel WS connections per IP. |
--limit-websocket-connection-idle-time |
1200 |
WS idle timeout (seconds; default 20 min). |
--skip-websocket-verification |
false |
Skip startup WS verification for chains that require ws/wss endpoints. |
CORS & HTTP
| Flag | Default | Description |
|---|---|---|
--cors-origin |
* |
Allowed origins. |
--cors-headers |
simple-cors | Allowed headers (* for all). |
--cors-methods |
GET,POST,PUT,DELETE,OPTIONS |
Allowed methods. |
--cors-credentials |
true |
Access-Control-Allow-Credentials value. |
--cdn-cache-duration |
86400 |
Preflight OPTIONS cache duration (seconds). |
--response-compression |
gzip |
Client-facing compression: gzip, brotli, or off. |
Metrics & telemetry
| Flag | Default | Description |
|---|---|---|
--metrics-listen-address |
disabled |
Prometheus /metrics address, e.g. :7779. |
--usage-otel-enabled |
false |
Emit per-relay usage and QoS events as OTLP logs. |
--usage-otel-endpoint |
- | OTLP/HTTP endpoint (default localhost:4318 / OTEL_EXPORTER_OTLP_ENDPOINT). |
--usage-otel-insecure |
true |
Skip TLS for the OTLP exporter. |
--usage-otel-queue-size |
50000 |
Capacity of the in-memory usage-event queue. When the queue is full, events are dropped. |
--usage-otel-batch-size |
1000 |
Batch-size flush trigger. |
--usage-otel-flush-interval |
500ms |
Time-based flush trigger. |
--usage-otel-export-timeout |
10s |
Per-batch export timeout. |
--usage-otel-service-name |
smartrouter |
OTel service.name. |
--usage-otel-service-instance-id |
hostname-pid | OTel service.instance.id. |
--otel-trace-body |
false |
Record request/response bodies on trace spans. |
See Metrics.
Lifecycle & health
| Flag | Default | Description |
|---|---|---|
--relays-health-enable |
true |
Enable relay health checks. When false, /readyz and /metrics/overall-health always report 503, so keep it on when you use a Kubernetes readiness probe. |
--relays-health-interval |
5m |
Interval between health checks while a chain is healthy. A client relay that an upstream answers with a result delays the probe, so this is how often an idle chain is re-checked. See When readiness changes. |
--relays-health-unhealthy-interval |
15s |
Probe interval while a chain is unhealthy, clamped to --relays-health-interval. It is also the longest a client relay that no upstream answered waits for the next probe. A router that is not ready gets no client relays, so this interval limits how fast /readyz follows upstreams going out of reach and coming back. |
--epoch-duration |
15m |
Duration of each epoch (e.g. 30m). Epochs always run: if unset or 0, the router uses 15m. At each epoch boundary the router re-verifies the upstreams and rebuilds the serving tier. So an upstream that is demoted or recovers mid-epoch can take up to this long to show in smartrouter_endpoint_serving_tier. |
--shutdown-grace-period |
25s |
Graceful-shutdown deadline for in-flight requests and WS clients. |
Logging
| Flag | Default | Description |
|---|---|---|
--log-level |
info |
debug / info / warn / error / fatal. |
--log-format |
text |
text or json. |
--rolling-log-level |
off |
Rolling-log level (off / debug / … / fatal). |
--rolling-log-file-location |
logs/rollingRPC.log |
Rolling-log file path. |
--rolling-log-max-size |
100 |
Max rolling-log size (MB). |
--rolling-log-max-age |
1 |
Max rolling-log age (days). |
--rolling-log-backups |
3 |
Old rolling-log files to keep. |
--rolling-log-format |
json |
Rolling-log format (json / text). |
Debugging & profiling
| Flag | Default | Description |
|---|---|---|
--debug-relays |
false |
Add debug info to relay response metadata (pairs with the lava-debug-relay header). |
--debug-probes |
false |
Add debug info to probe responses. |
--enable-selection-stats |
false |
Emit the lava-selection-stats response header. |
--debug-address |
- | Debug HTTP server for integration tests. It exposes /debug/*, including /debug/sticky-claims. |
--test-mode |
false |
Send dummy data and print all listener metadata. |
--allow-insecure-provider-dialing |
false |
Allow non-TLS upstream connections. Dev/test only. |
--pprof-address |
- | pprof server address (e.g. localhost:6060). |
--pyroscope-address |
- | Pyroscope continuous-profiling address. |
--pyroscope-app-name |
smartrouter |
Pyroscope application name. |
--pyroscope-mutex-profile-fraction |
5 |
Mutex profile sampling rate (1 in N). |
--pyroscope-block-profile-rate |
1 |
Block profile rate (ns; 1 records all). |
--pyroscope-tags |
- | Comma-separated key=value tags. |
Cache subcommand flags
Flags for smartrouter cache <host:port>:
| Flag | Default | Description |
|---|---|---|
--metrics_address |
disabled |
Prometheus metrics address for the cache (e.g. 0.0.0.0:5555). |
--max-items |
2147483648 |
Max number of entries to keep in the cache. |
--expiration |
1h |
TTL for finalized entries. |
--expiration-non-finalized |
500ms |
TTL for non-finalized entries. |
--expiration-multiplier |
1.0 |
Multiplier on the finalized TTL (1.2 = 20% longer). |
--expiration-non-finalized-multiplier |
1.0 |
Multiplier on the non-finalized TTL. |
--expiration-blocks-hashes-to-heights |
48h |
TTL for block-hash→height mappings. |
--expiration-finalized-node-errors |
250ms |
TTL for cached finalized node errors. |
--log_level |
info |
Cache log level (trace…panic). |
Underscore vs. dash
The cache subcommand uses --metrics_address and --log_level (underscores), unlike
the router's --metrics-listen-address / --log-level. This matches the binary.