Skip to content

Configuration reference

This page describes the SubEtha stack from the pinned product sources. The example 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 in the pinned release: 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; 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.bps required when fee is present basis points; in split mode >= 1
fee.min_per_payment required when fee is present minimum fee in base units; split mode requires "0"
fee.charge_to required when fee is present "payer" or "provider"
fee.recipient required for custodial fee mode fee recipient
fee.splitter required for split mode must equal settlement.treasury
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. From the product repository root, run node apps/facilitator/dist/cli.js check --config fac.toml after building; start uses node apps/facilitator/dist/cli.js start --config fac.toml. The check command 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.

The optional participant-scoped Payment History integration is configured through environment variables rather than TOML secrets. The persistence path must be set alongside the identity and participant bindings for the HTTP history surface:

variable required meaning
PAYMENT_HISTORY_PATH required for persistence JSONL path for durable payment-history records
HISTORY_IDENTITY_SECRET required with history persistence secret used to sign short-lived participant identity tokens
HISTORY_PAYER_TENANT / HISTORY_PAYER_ACTOR required with history persistence payer tenant and actor binding
HISTORY_PROVIDER_TENANT / HISTORY_PROVIDER_ACTOR required with history persistence provider tenant and actor binding
HISTORY_PAYER_ADDRESS / HISTORY_PROVIDER_ADDRESS required for wallet login wallet addresses for challenge/signature login

The six persistence/participant variables are an all-or-nothing construction group: if any is missing, the standalone history dependencies are not attached. Wallet login additionally requires both address variables. Proof issuer settings are also all-or-nothing; partial proof configuration fails closed at startup. These values are environment/secret-manager inputs; do not put real secrets or private-key material in TOML, source, shell history, or documentation. See the Payment History reference for participant authorization and lifecycle behavior. The proof issuer group is HISTORY_ISSUER_PRIVATE_KEY_PATH, HISTORY_ISSUER_MANIFEST_PATH, HISTORY_ISSUER_TRUST_ANCHOR_PATH, and HISTORY_ARTIFACT_PATH; optional HISTORY_ISSUER_ID, HISTORY_ISSUER_KEY_ID, and HISTORY_PROOF_TTL_SECONDS refine the complete configuration.

Payment acceptance in the pinned release is gated by the top-level profile key; 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. With profile unset and no admin token, or with a non-local profile and no admin token, startup refuses; a local loopback config may start without the token, but /admin/* is locked and returns 401. There is no documented environment-over-TOML precedence rule; use the product runbook as the source of truth for any future precedence changes.

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.