x402 Conformance
Prove your x402 payments can't double-credit, credit too early or credit on a broken answer, in one CI step.
GitHub Marketplace Fix guide Spec mapping Changelog
Experimental. An open, Apache-2.0 set of conformance and safety checks for
x402 V2 facilitators and resource servers, starting with Solana. It complements
x402 and its facilitators and doesn't replace them. It never signs, pays or touches mainnet, and live mode only sends
GET /supported. Passing a check is evidence about one property; it doesn't certify conformance to x402.
Run it
GitHub Action. Save as .github/workflows/x402-conformance.yml:
name: x402 conformance
on: [push, pull_request]
jobs:
x402:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: atsp-protocol/x402-conformance@v1
with:
facilitators: https://x402.org/facilitator # GET /supported only
With no inputs it runs only the F1-F9 safety suite. To test your own resource server, add
resource-url (your paid route) and start (the command that starts your server, which receives
X402_FACILITATOR_URL). The Action writes JSON results, a SARIF 2.1.0 file for GitHub code scanning and a
JUnit XML report; every failure links to its fix in docs/FIXES.md.
One command, nothing to install (with pipx or uv):
pipx run --spec x402-conformance x402conf offline
uvx --from x402-conformance x402conf live https://x402.org/facilitator
CLI (Python 3.10+, no dependencies):
pip install x402-conformance
x402conf live https://x402.org/facilitator # S1-S4, GET /supported only
x402conf offline # F1-F9 against reference and fault-injected doubles
x402conf report --dir DIR --sarif out.sarif --junit junit.xml
What it checks
| id | fails when |
|---|---|
Live, read-only (GET /supported) | |
| S1 | the response is missing kinds, extensions or signers |
| S2 | a V2 network isn't written as a CAIP-2 ID |
| S3 | a Solana kind uses an old V1 name such as solana-devnet |
| S4 | a Solana exact kind has no valid fee payer, or one that isn't a listed signer |
| Safety: against doubles, on devnet and in your resource server | |
| F1 | a malformed payment is accepted instead of rejected |
| F2 | an underpayment or dust amount is accepted |
| F3 | the same payment is settled, or served, twice |
| F4 | a used memo or nonce is replayed in a new transaction |
| F5 | a payment counts as settled before it's confirmed, or settlement_pending comes without a transaction |
| F6 | a facilitator timeout or garbage answer turns into a credit |
| F7 | a pending payment retried with the same body never reaches its final result, or is credited more than once |
| F8 | a payment that failed on chain stays "pending" instead of failing |
| F9 | a 503, an HTML page or a truncated /settle answer is credited, or the payment can't be retried |
| R0-R9 | your resource server serves paid content under any of these faults (TypeScript runner in ts/, for servers on @x402/core and @x402/svm) |
Results
Live, 2026-10-20 (GET /supported only)
| facilitator | S1 | S2 | S3 | S4 |
|---|---|---|---|---|
| x402.dexter.cash | pass | pass | pass | pass |
| facilitator.payai.network | pass | pass | pass | pass |
| x402.org/facilitator | pass | pass | pass | pass |
Raw bodies and hashes: evidence/2026-10-20.
Devnet, 2026-10-20 (F1-F6 over real /verify and /settle)
Against my self-hosted reference facilitator (@x402/core 2.28.0 with the @x402/svm 2.28.0
exact scheme) on Solana devnet, using a throwaway test token and throwaway keypairs. No mainnet, no real funds.
| check | result | on chain (devnet explorer) |
|---|---|---|
| F1 malformed payload fails closed | pass | none, /verify only |
| F2 dust amount is rejected | pass | none, /verify only |
| F3 the same payload settled twice succeeds once | pass | 3Z6SFs… |
| F4 a used memo in a new transaction is rejected | fail | 2L3Jxe…, 2U31kB… |
| F5 success only after confirmation | pass | 3GyFs6… |
| F6 a timeout or garbage answer never credits | pass | see run.json |
About F4. The reference facilitator deduplicates by transaction (F3 passes) but doesn't track the memo across different transactions, so two different transactions carrying the same memo both settled. Each was a real payment signed by the payer, so the seller was paid twice rather than losing money, but a resource server that binds one purchase to one memo has to reject the second payment itself. The result is recorded as it came out.
Every fault-injected build (open_on_malformed, accept_dust, success_unsent) was caught by the
check it targets. Setup transaction: NoU2pE….
Full results and hashes: evidence/devnet.
Free and open core
Apache-2.0. Every check, the Action, the devnet runner and the Lean 4 models of the credit rule are free, forever. Free run for your project: open an issue with your repo or facilitator URL and I'll run the suite, post the results and send a tests-only pull request with the Action and badge.
Managed services
Optional, on top of the free suite: hosted continuous monitoring of your live endpoints, signed conformance reports per release, custom checks for your scheme or chain, and priority support. Details in docs/MANAGED.md; get in touch through a Managed services enquiry or data@optitransfer.ch.