An x402 facilitator for Stellar — verify, settle, supported
Conformance · Documentation · Known Gaps · Accensa org · x402 spec
Developer infrastructure for x402 on Stellar, built on the Apache-2.0
@x402/stellarpackage. Independent of the merchant back-office inaccensa-appandaccensa-contracts— a seller can use those without this, and an agent can use this without those.
Warning
This is a conformance spike, not a production facilitator. It exists to answer one question: can an unmodified canonical x402 client complete a payment against a facilitator we operate on Stellar testnet? As of 2026-08-14 the answer is yes, twice, with settled transactions anyone can verify — and four of the five upstream server components still fail. See Conformance for both halves. It makes no availability claim and is not deployed anywhere you can reach.
x402 turns HTTP 402 into a machine-native payment flow: a client requests a resource, the
server replies 402 with terms, the client signs a payment authorization and retries, and
a facilitator verifies and settles on-chain before the resource is returned.
The facilitator is the piece a seller cannot easily run themselves. It has to validate Soroban authorization entries strictly — correctly signed, authorizing exactly the declared call, asset, amount and recipient, not replayed, not expired — submit the invocation, and cover the network fee so the buyer needs only the payment asset. Get any of that subtly wrong and the failure is silent: payments that look settled and are not, or authorizations that grant more than the payer understood.
@x402/stellar already ships ExactStellarScheme, which implements the
SchemeNetworkFacilitator interface — verify, settle, getExtra, getSigners —
and validates:
- auth-entry structure and credential type
- expiration against a maximum ledger
- facilitator safety — the facilitator must not be a party to the transfer
- absence of sub-invocations — no authorization the payer did not see
- payer signature status, and that no other signatures are pending
- via simulation, that there is exactly one transfer event matching the expected sender, recipient, amount and asset
None of that is reimplemented here. It is the part most dangerous to get subtly wrong, and rewriting it would duplicate the package the ecosystem is standardizing on.
This repo is the transport around it. @x402/core ships no facilitator router — it
gives you x402Facilitator with verify(), settle() and getSupported(), and the HTTP
surface is yours to write. That surface, plus configuration, caller authentication and
operational concerns, is what lives here.
Published docs for the whole organisation, including this service, are at https://accensa.github.io/accensa-app/docs/facilitator/overview.
In-repo, refer to the Documentation Hub for detailed role-based guides:
Reference material: Architecture · Bazaar discovery · MCP server · Conformance · Deployment · Operations · Authentication · Threat model · Audit readiness · Privacy · Glossary
Sibling repositories in the Accensa organisation:
accensa-app (merchant dashboard, indexer,
@accensa/sdk) and
accensa-contracts (Soroban receipt
anchoring and refund vault). A seller can use those without this, and an agent can use
this without those.
The service loads a .env file at startup in non-production environments
(NODE_ENV !== 'production') — nothing breaks when the file is absent, and
variables set in the real environment always win over .env, so a stale local
file cannot silently override what a deployment injected. Production skips the
file entirely: there, the environment comes from the orchestrator.
cp .env.example .env # then fill in FACILITATOR_SECRET
npm start # or: npm run dev (adds --watch)
curl localhost:3402/healthz
curl localhost:3402/readyzFACILITATOR_SECRET is a signing key. .env is gitignored — never commit it.
npm test # unit tests — no network, no funded account, no .env
npm run lint # eslint
npm run format:check # prettier, check only
npm run licenses # fails on any AGPL in the dependency pathAll four run in CI on every push and pull request, across Node 20 and 22.
The end-to-end conformance run is separate, because it needs testnet and two funded accounts:
FACILITATOR_SECRET=$(stellar keys show facilitator) npm start &
ALICE_SECRET=$(stellar keys show alice) npm run e2eThe X402 Facilitator handles sensitive transaction and search query data. Our approach is to collect only what is necessary, and to aggressively purge it according to strict retention policies. For detailed information, see our Privacy Policy.
Acceptance is tested at the wire level with stock SDK code, not by reading a claim. What holds today on testnet:
-
/supportedemits the Stellarextrablock includingareFeesSponsored - Every rejection carries a non-null
invalidReason— across malformed bodies, unregistered scheme/network pairs, and scheme-level failures - The spec's
payload: {transaction}shape is accepted verbatim - An unmodified canonical client completes a payment end-to-end
- Settled transaction hash published — two, below
- The x402 repository's e2e suite — 1 of 5 server components passes
-
stellar:pubnet
An unmodified typescript/http/fetch client received a 402 with terms, signed a payment
authorization, retried, and this facilitator verified and settled it on-chain — paying the
network fee itself, so areFeesSponsored is observed rather than merely advertised.
| Transaction | Ledger | Settled |
|---|---|---|
5f1bd15a…5558 |
4134781 | 08:15:33Z |
ff798145…0590 |
4134928 | 08:27:49Z |
Both report "successful": true from Horizon. Verify without trusting this file:
curl -s https://horizon-testnet.stellar.org/transactions/5f1bd15aec8ca3c6390689ed7fed82506f6c3d8eb8ed325a05a8b83974925558 \
| jq '{successful, ledger, created_at}'typescript/http/next passes. express, fastify, hono and mcp do not — two with
Payment response header not found, two with upstream's 402 facilitator_error. This
reproduced identically across two runs in which the harness ordered the combinations
differently, so it is structural rather than flaky. Exactly one settlement occurs per
run; the four failures never reach the chain.
It cannot currently be diagnosed, because this facilitator emits four lines of output across an entire run — three startup banners and an exit code. Two of the failures mean this service returned an error, and there is no record of what it was. That makes #7 the blocking item rather than a nice-to-have; the investigation is #64.
The full record, including the treasury prerequisite that had to be solved first and how
to reproduce both results, is in docs/CONFORMANCE.md.
Responses use the canonical field names — VerifyResponse carries invalidReason and
invalidMessage; SettleResponse carries errorReason, errorMessage, transaction
and network. The transport-layer HTTP rejections (such as 401 Unauthorized or 429 Too Many Requests) also conform to the VerifyResponse shape to ensure a client has one parser, not three. For an exhaustive taxonomy of all emitted reasons, see REASONS.md.
- Bazaar is built but unproven against a second implementation. Discovery, search and
automatic cataloging landed on 2026-08-12 and are documented in
docs/BAZAAR.md: a catalog datastore with migrations,GET /discovery/resourceswith the full upstream filter set,GET /discovery/searchwith lexical and hybrid (dense-embedding + reranking) retrieval, automatic cataloging off the payment path,EXTENSION-RESPONSESreporting, and an MCP server (docs/MCP.md). A search-evaluation harness and judgement set live ineval/. On 2026-08-14 another party's client read this catalog for the first time and its listing was rejected — upstream registers wildcard*route templates and this repo's validation hard-drops them asinvalid_routeTemplate(#65). - No deployment. There is a
Dockerfile, adocker-compose.ymlanddocs/DEPLOYMENT.md, but no instance is running at a URL anyone can hit. Availability targets and a status page are tracked in #19. - No persistence by default. The catalog has a PostgreSQL schema in
migrations/and uses it whenDATABASE_URLis set; the settlement path holds nothing durable, tracked in #10. exactonly. Theuptoscheme has no Stellar specification yet; design notes inaccensa-contracts/docs/ADR-002.
Issues and pull requests welcome. Given the status above, the most useful contribution is a conformance failure: point a canonical client at it and report what breaks.
Apache-2.0 — see LICENSE. Chosen to match upstream @x402/* so work here can
be contributed back.