Multi-chain HTTP 402 Payment Required facilitator. It verifies payment payloads
off-chain and settles them on-chain, on the upstream x402 TypeScript ecosystem
(@bankofai/x402-core + @bankofai/x402-tron + @bankofai/x402-evm).
A TypeScript/Node service. The earlier Python/FastAPI implementation is kept under
legacy/ as a behavioral reference.
verify/settle/supportedendpoints backed by@bankofai/x402-core.- TRON
exact(EIP-3009 / Permit2) +exact_gasfree; EVM (BSC)exact. upto(Permit2 up-to-max settlement, TRON + EVM) andbatch-settlement(channel deposit/voucher/claim/settle/refund, TRON + EVM).- Non-custodial signing — settlement keys never enter this process; wallets are
resolved through
@bankofai/agent-walletand only signing crosses the boundary. - Settlement persistence keyed on the on-chain authorization identity, with seller-scoped query APIs.
- API-key auth, dynamic rate limiting, Prometheus metrics.
- 1Password-or-local secret configuration.
- GasFree Open API transparent proxy (HMAC) for TRON
exact_gasfree.
- Node 22+
- PostgreSQL
- A wallet provider resolvable by
@bankofai/agent-wallet(unlocked viaAGENT_WALLET_PASSWORD) - Optional: 1Password service-account token (
OP_SERVICE_ACCOUNT_TOKEN)
npm ci
FACILITATOR_SERVICE_ENV=dev npm run devDefault listen address: http://0.0.0.0:8001.
| Script | Purpose |
|---|---|
npm run dev |
Run with tsx watch (reload on change) |
npm run build |
Compile TypeScript to dist/ |
npm start |
Run the compiled server (dist/index.js) |
npm run typecheck |
tsc --noEmit |
npm test |
Unit tests (vitest) |
Choose a YAML configuration source explicitly. Set FACILITATOR_SERVICE_ENV=dev or
FACILITATOR_SERVICE_ENV=prod to select the matching baked-in environment config, or
set FACILITATOR_CONFIG_PATH to an explicit YAML file; the explicit path takes
precedence. The process fails before startup when neither is set.
Required: database.url, facilitator.networks (≥1 network, listed = enabled).
Secrets resolve env first, then 1Password (each onepassword.* value is a
vault/item/field ref, used when OP_SERVICE_ACCOUNT_TOKEN / onepassword.token
is set). Relevant env vars:
| Var | Purpose |
|---|---|
FACILITATOR_SERVICE_ENV |
dev or prod; selects the matching baked-in config file |
FACILITATOR_CONFIG_PATH |
Explicit config path; overrides FACILITATOR_SERVICE_ENV |
AGENT_WALLET_PASSWORD |
Unlock the agent-wallet provider |
TRON_GRID_API_KEY |
TronGrid rate limits (shared across TRON networks) |
GASFREE_API_KEY[_NILE|_MAINNET] / GASFREE_API_SECRET[...] |
GasFree relayer creds (gate exact_gasfree) |
UPSTREAM_NILE_BASE / UPSTREAM_MAINNET_BASE |
Override GasFree upstream bases |
OP_SERVICE_ACCOUNT_TOKEN |
1Password service-account token |
RATE_LIMIT_STORE |
memory (default) or redis for shared counters across replicas |
RATE_LIMIT_REDIS_URL / REDIS_URL |
Redis connection URL (required when RATE_LIMIT_STORE=redis; needs the optional ioredis dep) |
TRUST_PROXY_FOR_RATELIMIT |
true to key anonymous limits on X-Forwarded-For (set only when the direct peer is a trusted proxy; the rightmost XFF entry is used, so append-style proxies like nginx $proxy_add_x_forwarded_for are safe. Default off keys on the socket peer) |
BSC transaction creation and broadcast use the primary RPC. Receipt confirmation waits up to 15 seconds on the primary, then up to 45 seconds on the independent fallback for the same transaction hash; it never rebroadcasts the transaction.
Fees were removed from the TRON facilitator schemes in SDK
1.0.1— theexact/uptoproxies transfer exactlyamountand the GasFree relayer handles its own fee terms. There is nobase_feeconfig and no/fee/quoteendpoint.
| Method | Path | Notes |
|---|---|---|
GET |
/health |
Liveness (no auth / rate-limit) |
GET |
/supported |
Supported scheme/network kinds |
POST |
/verify |
Verify a payment payload |
POST |
/settle |
Settle on-chain; rate-limited; persists a settlement |
GET |
/payments/tx/{hash} |
Lookup by settlement tx hash |
GET |
/payments?network=&nonce=[&asset=&payer=] |
Lookup by authorization identity |
GET |
/payments |
Authenticated seller's settlement feed (?limit=&offset=) |
GET |
/metrics |
Prometheus (main port, or a separate monitoring.port) |
ALL |
/mainnet/*, /nile/* |
GasFree transparent proxy (HMAC) |
Lookups are seller-scoped when the request carries a valid X-API-KEY.
The settlements table (created on startup) is keyed on
(network, scheme, asset, payer, nonce) — the on-chain authorization identity — with
a partial-unique index enforcing one successful settlement per authorization. The
shared sellers / api_keys_plus tables are reused unchanged for auth and seller
scoping. The legacy payment_records table is not used.
The @bankofai/x402-* packages (x402-core, x402-evm, x402-tron) are consumed from
npm, declared as ^1.0.1 in package.json.
docker build -t x402-facilitator .
docker run -p 8001:8001 -p 9001:9001 \
-e FACILITATOR_SERVICE_ENV=dev \
-e OP_SERVICE_ACCOUNT_TOKEN \
-v "$PWD/logs:/app/logs" \
x402-facilitatorThe container runs as non-root (uid/gid 1000); make sure the host logs/
directory is writable by that uid before bind-mounting it. The agent-wallet
password is resolved from OP_SERVICE_ACCOUNT_TOKEN (1Password) when set;
otherwise pass it directly via AGENT_WALLET_PASSWORD. Port 9001 is only
needed when monitoring.port differs from server.port.
Both config/facilitator.config.dev.yaml and
config/facilitator.config.prod.yaml are baked into the image. Select one at
runtime with FACILITATOR_SERVICE_ENV=dev or FACILITATOR_SERVICE_ENV=prod; no
config-directory mount is required. FACILITATOR_CONFIG_PATH remains available
for an explicit custom path.
OP_SERVICE_ACCOUNT_TOKEN must be injected only at container runtime (for
example by the deployment platform's secret environment-variable facility);
it is never stored in the image or either YAML file.
Feature-complete and unit-tested; not yet validated against live chains (real
verify+settle on tron:0xcd8690dc / eip155:97 and GasFree end-to-end are pending), and
without integration tests yet.