Skip to content

Configuration reference

Everything on this page describes the local, non-production SubEtha stack and is taken from the pinned product sources. All key/secret values shown are placeholders (0x…) — supply your own via the environment, never in a config file, and never commit them anywhere.

The facilitator daemon starts from a single TOML config file. The annotated reference is apps/facilitator/fac.example.toml; the full operator runbook is docs/FACILITATOR.md. The facilitator itself is source-only — it is built from the product repository workspace and started from its build output; there is no published package for it. Everything is explicit: the daemon has no default network, contracts, or keys, so it can never silently run against the wrong chain.

key required / default meaning
profile (top level) unset = treated as non-local "local" | "testnet" | "production". Explicit classification only — never inferred from bind address or chain id
network.rpc_url required EVM RPC endpoint (credential-free URL — check warns on embedded credentials)
network.indexer_url required zERC20 indexer
network.decider_url required decider-prover (batch/Nova path only — not probed by check)
network.chain_id required must match what the RPC reports
network.token / network.verifier / network.hub required the zERC20 deployment (checked for deployed code)
listen.host 127.0.0.1 non-loopback binds require SUBETHA_ADMIN_TOKEN
listen.port 4032
listen.trusted_proxies unset IP addresses / CIDR blocks fed verbatim to Express’s standard trust proxy
settlement.treasury required mint destination — the FeeSplit address in split mode
settlement.permit_burner optional deployed PermitBurner; enables the gasless permit path when set
settlement.journal required crash-recovery JSONL path, or "none" to opt out (warned; local experiments only — pending work is lost on restart)
settlement.challenge_ttl_seconds 300 (min 1) challenge lifetime
settlement.expired_challenge_retention_seconds 3600 (min 0) how long an expired open challenge’s match data stays in memory for late-inclusion checks and investigation
fee.* omit the table for no fee bps, min_per_payment, charge_to ("payer" or "provider"), recipient, splitter. In split mode splitter must equal settlement.treasury, min_per_payment must be "0", bps >= 1
daemon.finalize_interval_ms 15000 (min 1000) finalize-loop period; failures back off exponentially (up to 10×)
daemon.shutdown_grace_ms 10000 SIGTERM: max wait for in-flight work before forced exit
daemon.rate_limit_per_min 120 shared per-key limit on the money endpoints (/challenge /verify /settle); 0 disables
daemon.max_open_challenges 1000 (min 1) global cap on open challenges — POST /challenge answers 503 at the cap
daemon.challenge_per_key_per_min 20 per-key issuance rate on POST /challenge (429); <= 0 disables

A config can be validated without starting the daemon (check probes config, RPC, contracts, splitter, journal and keys, and exits non-zero with a line per problem).

Keys have no defaults and are never read from the config file.

variable required used for
SUBETHA_TREASURY_PK always seed derivation (in split mode: entropy only) + custodial fee-sweep signing
SUBETHA_RELAYER_PK always submitting teleport mints + relaying gasless permit burns
SUBETHA_ADMIN_TOKEN non-loopback bind, any non-local profile, and when profile is unset Authorization: Bearer … for /admin/*. Unset on a local loopback config ⇒ /admin/* is locked (all requests get 401)

The well-known anvil dev keys are accepted (with a warning) only when all four predicates hold: profile = "local", loopback listen.host, loopback RPC host, and chain_id 31337. Any missing predicate aborts startup. Use placeholders like 0x… everywhere else — real keys belong only in your own environment management.

Payments in the pinned release are local-experiment only. The top-level profile key classifies the deployment; classification is explicit only, and an unset profile is treated as non-local (fail-safe). A non-local profile (testnet, production, or unset-with-token) starts gated: payment acceptance is disabled (/challenge, /verify, /settle refuse with profile-gating errors — see the wire protocol reference) and chain-changing finalization is frozen.

The TypeScript agent tools (apps/agent-tools/src/env.ts) and the Python payer’s SubethaClient.from_env (python/src/subetha/client.py) read the same variable names. The human operator sets keys and spending limits here — nothing security-relevant is ever taken from (or exposed to) an agent’s tool arguments.

variable default meaning
SUBETHA_PAYER_PK — (required to pay) 32-byte hex private key of the paying account. Quoting does not need it. Placeholder only in docs — never commit a real key
SUBETHA_RPC_URL http://127.0.0.1:8545 EVM RPC endpoint for chain reads (and, in self-transfer mode, broadcasting)
SUBETHA_MODE permit permit (gasless: sign only) or self-transfer (broadcast the burn yourself, paying gas)
SUBETHA_TIMEOUT_MS 30000 HTTP timeout (milliseconds, positive integer)
SUBETHA_ALLOWED_HOSTS localhost,127.0.0.1 comma-separated hostname allowlist checked before any request leaves; * allows all (the default is the safe local-stack-only posture)
SUBETHA_ALLOWED_NETWORK eip155:31337 CAIP-2 network pin for acceptable offers
SUBETHA_ALLOWED_TOKEN unset token contract address pin; unset accepts any token on the allowed network
SUBETHA_MAX_PER_PAYMENT unset per-payment cap in token base units
SUBETHA_MAX_TOTAL unset total-spend cap in token base units (per network|token pair)
SUBETHA_APPROVE_ABOVE unset payments quoted above this (base units) require a human yes through an approval channel; unset = approval flow off
SUBETHA_APPROVAL_TIMEOUT_MS 120000 how long to wait for the human approval

Base-unit variables must be plain integers; malformed values are rejected at load with a named error rather than silently ignored.

All defaults above (loopback URLs, chain id 31337, anvil dev keys) describe the local development stack. Bringing that stack up is documented in the product repository: docs/RUNNING-THE-STACK.md and docs/BUILDING-THE-STACK.md.