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.
Facilitator: TOML configuration
Section titled “Facilitator: TOML configuration”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).
Facilitator secrets (environment only)
Section titled “Facilitator secrets (environment only)”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.
Profiles
Section titled “Profiles”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.
Payer: SUBETHA_* environment variables
Section titled “Payer: SUBETHA_* environment variables”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.
Local stack context
Section titled “Local stack context”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.