Configuration Reference¶
RPC Plane is configured via a TOML file (default: rpc-plane.toml in the current directory).
Run rpc-plane init to generate a starter file. Use -c /path/to/config.toml to specify a different path.
Environment variable interpolation: any value can reference env vars:
url = "https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}"
url = "https://endpoint.quiknode.pro/$QUICKNODE_KEY" # braces optional
Unset variables expand to an empty string and the proxy logs a warning naming each one — a typo like ${HELIUS_API_KY} therefore surfaces at startup instead of as silent 401s. If you hardcode the full URL and token instead of using a variable, nothing is referenced and no warning is emitted. rpc-plane check reports the same unset-variable warnings before testing connectivity.
The proxy also validates provider entries on startup: provider names must be unique (a duplicate is a hard load error) and every url must be non-empty.
[server]¶
[server]
listen = "127.0.0.1:9400" # address for the proxy (JSON-RPC endpoint)
metrics_listen = "127.0.0.1:9401" # address for Prometheus metrics + /health
# listen_backlog = 4096 # OS TCP listen backlog (default 4096)
# pool_max_idle_per_host = 512 # idle outbound connections per provider (default 512)
# worker_threads = 4 # Tokio worker threads (default: number of logical CPUs)
| Key | Default | Description |
|---|---|---|
listen |
"127.0.0.1:9400" |
TCP address the proxy listens on. Change to 0.0.0.0 to expose on all interfaces. |
metrics_listen |
"127.0.0.1:9401" |
Metrics and health endpoint. Serves GET /metrics (Prometheus) and GET /health (JSON). |
listen_backlog |
4096 |
OS TCP listen backlog for both sockets. Raise net.core.somaxconn to match (sysctl -w net.core.somaxconn=4096). |
pool_max_idle_per_host |
512 |
Max idle outbound connections kept per provider. Set to at least your expected peak concurrency to avoid cold TCP handshakes under load. |
worker_threads |
number of logical CPUs | Number of Tokio runtime worker threads. Set to a fixed count to dedicate cores when sharing a host with other services. Requires a restart to change. |
Note
server.listen, metrics_listen, and listen_backlog require a restart to change — they are not hot-reloaded.
[health]¶
Controls how providers are probed and scored.
[health]
interval_ms = 1000 # probe each provider this often
window_secs = 60 # sliding window for error rate
slot_drift_threshold = 10 # slots behind tip → drifting
circuit_open_failures = 5 # consecutive failures → circuit open
circuit_error_threshold = 0.5 # error rate threshold → circuit open
circuit_cooldown_secs = 30 # wait before half-open probe
# Score weights (auto-normalised, do not need to sum to 1)
w_latency = 0.4
w_error = 0.3
w_slot = 0.2
w_success = 0.1
Probe settings¶
| Key | Default | Description |
|---|---|---|
interval_ms |
1000 |
How often (ms) to send a getSlot probe to each provider. |
window_secs |
60 |
Sliding window (seconds) over which error rate is computed. |
slot_drift_threshold |
10 |
Slots behind network tip before slot freshness score drops to 0. |
Circuit breaker¶
| Key | Default | Description |
|---|---|---|
circuit_open_failures |
5 |
Consecutive probe failures that trip the circuit open. |
circuit_error_threshold |
0.5 |
Rolling error rate threshold (0.0–1.0) that opens the circuit. |
circuit_cooldown_secs |
30 |
Seconds before attempting a half-open probe after the circuit opens. |
Health score weights¶
| Key | Default | What it controls |
|---|---|---|
w_latency |
0.4 |
Round-trip latency component |
w_error |
0.3 |
Error rate component |
w_slot |
0.2 |
Slot freshness component |
w_success |
0.1 |
Recent probe success rate |
See Health scoring for the full formula.
[routing]¶
[routing]
strategy = "best_score" # routing strategy for reads
max_retries = 2 # retries on transient errors
cost_aware = false # enable cost_efficiency_score weight
cost_weight = 0.2 # w_cost when cost_aware = true
broadcast_writes = false # fan out write methods to all providers
write_methods = ["sendTransaction"] # methods routed as writes
| Key | Default | Description |
|---|---|---|
strategy |
"best_score" |
Read routing strategy. See Routing strategies. |
max_retries |
2 |
Maximum provider retries on retryable errors (HTTP 429/5xx, RPC -32003/-32005/-32603). |
cost_aware |
false |
When true, adds a cost efficiency component to the score. Requires [providers.pricing]. |
cost_weight |
0.2 |
Weight of the cost efficiency score when cost_aware = true. |
broadcast_writes |
false |
When true, sends every method in write_methods to all healthy providers simultaneously. Maximises transaction landing probability at the cost of N× provider traffic. |
write_methods |
["sendTransaction"] |
Methods classified as writes. With broadcast_writes = true they fan out to all providers; otherwise they route sequentially through the failover list. simulateTransaction is not a write by default (it's read-only and not in the landing path) — add it here to broadcast it too. |
[[providers]]¶
At least one provider is required. Add multiple [[providers]] blocks for multi-provider routing.
[[providers]]
name = "helius"
url = "https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}"
weight = 1
[[providers]]
name = "quicknode"
url = "https://your-endpoint.quiknode.pro/${QUICKNODE_API_KEY}"
weight = 1
http3 = true
| Key | Required | Default | Description |
|---|---|---|---|
name |
Yes | — | Unique identifier. Must be unique across providers (a duplicate is a load error). Used in logs and metric labels. |
url |
Yes | — | Full HTTP URL including API key. Supports env var interpolation. |
weight |
No | 1 |
Relative weight for weighted_random strategy. No effect on other strategies. |
http3 |
No | false |
Use HTTP/3 (QUIC) for outbound connections to this provider, falling back to HTTP/2 if the negotiation fails. Experimental: depends on a pinned reqwest built with the unstable HTTP/3 feature; treat as best-effort until upstream stabilizes it. |
methods |
No | (all) | Restrict this provider to a set of JSON-RPC methods. Omit to serve every method. Use it to add a submission-only endpoint (e.g. a transaction-landing service) — methods = ["sendTransaction"] — so it never receives reads it can't answer. Providers that exclude getSlot skip the health probe and are scored from live request outcomes. See Submission-only providers. |
[providers.pricing]¶
Optional cost tracking. Enables cost-aware routing when routing.cost_aware = true.
[[providers]]
name = "helius"
url = "https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}"
[providers.pricing]
model = "credits"
monthly_budget_usd = 200
| Key | Description |
|---|---|
model |
Pricing model: "credits", "compute_units", or "flat". |
monthly_budget_usd |
Monthly budget cap in USD. |
[reporting]¶
Sends telemetry to a remote endpoint. Absent by default — the proxy runs with no outbound connections.
[reporting]
endpoint = "http://localhost:3000/api/ingest"
api_key = "rp_live_xxxx"
flush_interval_ms = 60000
| Key | Description |
|---|---|
endpoint |
HTTP endpoint to POST telemetry batches to. |
api_key |
Bearer token sent with each batch. |
flush_interval_ms |
Aggregation window and POST interval (ms). Minimum 10000. Default 60000. |
Full example¶
[server]
listen = "127.0.0.1:9400"
metrics_listen = "127.0.0.1:9401"
[health]
interval_ms = 1000
window_secs = 60
slot_drift_threshold = 10
circuit_open_failures = 5
circuit_error_threshold = 0.5
circuit_cooldown_secs = 30
w_latency = 0.4
w_error = 0.3
w_slot = 0.2
w_success = 0.1
[routing]
strategy = "best_score"
max_retries = 2
[[providers]]
name = "helius"
url = "https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}"
weight = 1
[[providers]]
name = "quicknode"
url = "https://your-endpoint.quiknode.pro/${QUICKNODE_API_KEY}"
weight = 1
[[providers]]
name = "triton"
url = "https://your-pool.rpcpool.com/${TRITON_API_KEY}"
weight = 1