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.
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 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.
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.
Payment History environment configuration
Section titled “Payment History environment configuration”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.
Profiles
Section titled “Profiles”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.
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.