x402 Conformance
Prove your x402 payments can't double-credit, credit too early or credit on a broken answer, in one CI step.
An open set of conformance and safety checks for x402 V2 facilitators and resource servers, starting with Solana. It checks the properties a seller's money depends on. It never signs, pays or touches mainnet. It complements x402 and its facilitators and doesn't replace them.
Experimental. Passing a check is evidence about one property. It doesn't certify conformance to x402. — Ryan Gillespie, Optitransfer
Project page with the checks and the latest results: https://atsp-protocol.github.io/x402-conformance/
Install
| how | command | needs |
|---|---|---|
| GitHub Action | uses: atsp-protocol/x402-conformance@v1 (see below) |
nothing |
| Python CLI, one-off | pipx run --spec "git+https://github.com/atsp-protocol/x402-conformance@v1" x402conf offline |
pipx or uv |
| Python CLI, installed | pip install "git+https://github.com/atsp-protocol/x402-conformance@v1" |
Python 3.10+, no dependencies |
| TypeScript runner | git clone this repo, then cd ts && npm ci && npm run build |
Node 20+ |
The packages aren't on PyPI (x402-conformance) or npm (x402-conformance-svm) yet, so pip install x402-conformance
and npx x402conf-svm don't work today. Install from the release tag as shown. @v1 follows the latest 1.x release;
pin @v1.2.0 if you want a fixed version.
Quickstart
pipx run --spec "git+https://github.com/atsp-protocol/x402-conformance@v1" x402conf offline
uvx --from "git+https://github.com/atsp-protocol/x402-conformance@v1" x402conf live https://x402.org/facilitator
The first runs the F1-F9 safety suite against the built-in reference and fault-injected facilitator doubles. The
second reads GET /supported from a facilitator and runs S1-S4. Both print JSON.
GitHub Action
Save this as .github/workflows/x402-conformance.yml. It runs as is:
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 # swap in your facilitator; GET /supported only
With no inputs it runs only the F1-F9 safety suite. facilitators adds the live GET /supported checks. To test
your own resource server, point the Action at your paid route and give it the command that starts the server:
- uses: atsp-protocol/x402-conformance@v1
with:
resource-url: http://127.0.0.1:4020/paid
start: node server.js # your server must use X402_FACILITATOR_URL as its facilitator
| input | default | what it does |
|---|---|---|
facilitators |
empty | Space-separated facilitator base URLs, checked with GET /supported only (S1-S4) |
resource-url |
empty | Your paid route; the Action puts a facilitator double in front of it and runs R0-R9 |
start |
empty | Command that starts your server; it receives X402_FACILITATOR_URL |
port |
4402 |
Loopback port for the facilitator double |
offline |
true |
Run the F1-F9 suite against the reference and fault-injected doubles |
results-dir |
x402-conformance |
Where live.json, resource-server.json and offline.json are written |
fail-on-error |
true |
Fail the job when any error-severity check fails |
| output | value |
|---|---|
result |
pass when every requested check passed, otherwise fail |
results-dir |
the directory holding the results JSON |
sarif |
<results-dir>/x402-conformance.sarif, SARIF 2.1.0 of the failed checks |
junit |
<results-dir>/junit.xml, one test case per check |
Example job summary (abridged):
## x402 Conformance: pass
| target | check | result |
| https://x402.org/facilitator | S1 SupportedResponse shape: kinds[], extensions[], signers{} | pass |
| https://x402.org/facilitator | S4 Solana exact kinds advertise a valid extra.feePayer ... | pass |
| reference double | F3 the same payload settled twice succeeds at most once ... | pass |
| reference double | F9 a 503, non-JSON or truncated /settle answer is never ... | pass |
| fault-injected doubles | every fault caught by at least one check | pass |
Failures in code scanning. Every failed check can show up in your repo's Security tab, with a link to what
failed and how to fix it. Add permissions: { security-events: write, contents: read } to the job, id: x402 and
fail-on-error: false to the step above, then:
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ${{ steps.x402.outputs.sarif }}
category: x402-conformance
- run: test "${{ steps.x402.outputs.result }}" = pass
The junit output works with any JUnit test-report viewer.
CLI
x402conf live https://x402.org/facilitator # S1-S4, GET /supported only
x402conf offline # F1-F9 against the reference and fault-injected doubles
x402conf report --dir DIR --sarif out.sarif --junit junit.xml
x402conf devnet --faults --keys DIR # F1-F6 over real /verify and /settle on Solana devnet
live and offline print JSON with one entry per check (check, pass, severity, detail). Save them as
live.json and offline.json in one directory and report converts them to SARIF and JUnit, printing a one-line
summary such as {"checks": 14, "failed": 0, "failed_ids": []}. devnet runs against a self-hosted reference
facilitator on loopback with throwaway keys from --keys, and refuses any other facilitator unless you pass
--operator-permission with the operator's written permission. It needs Node 20+ and a funded devnet fee payer;
evidence/devnet shows a full run.
Exit codes.
| command | 0 | non-zero |
|---|---|---|
x402conf offline |
the reference double passes every check and every fault-injected double is caught | 1 otherwise |
x402conf live |
always, even when a check fails or the facilitator can't be reached | read the pass fields, or use the Action, which does |
x402conf report |
the reports were written | 2 when the directory holds no results JSON |
x402conf devnet |
the facilitator under test passes every check, and with --faults every fault-injected build is caught |
1 otherwise (the current reference exits 1 on F4, see Results) |
x402conf-svm probe (TypeScript) |
R0-R9 all pass | 1 when a check fails, 2 on an error |
Usage errors exit 2.
TypeScript runner for resource servers on @x402/core and @x402/svm, from a clone of this repo:
cd ts && npm ci && npm run build
node dist/src/cli.js probe --resource http://127.0.0.1:4020/paid --start "node your-server.js" --out results.json
ts/README.md explains what the double does for each of R0-R9.
What it catches
Live, read-only (GET /supported):
- S1 the response is missing
kinds,extensionsorsigners - S2 a V2 network isn't written as a CAIP-2 ID
- S3 a Solana kind uses an old V1 name such as
solana-devnetinstead of the published ID - S4 a Solana
exactkind 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_pendingcomes 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
/settleanswer is credited, or the payment can't be retried afterwards
Resource servers (R0-R9). The TypeScript runner puts a facilitator double in front of your server and requests your
paid route under each of the faults above. Your server must answer an unpaid request with a 402 and must not serve
content on a timeout, a broken /settle answer, settlement_pending, a failure or a duplicate.
docs/SPEC.md quotes the spec text behind every check, and docs/FIXES.md explains, for every check, what failed, why it matters and how to fix it.
Results
Live, 2026-10-20, GET /supported only
| facilitator | HTTP | S1 | S2 | S3 | S4 | Solana kinds advertised |
|---|---|---|---|---|---|---|
| x402.dexter.cash | 200 | pass | pass | pass | pass | exact (mainnet, devnet), tab, bridge |
| facilitator.payai.network | 200 | pass | pass | pass | pass | exact and batch-settlement (mainnet, devnet) |
| x402.org/facilitator | 200 | pass | pass | pass | pass | exact (devnet) |
Raw bodies, run.json and SHA256SUMS are in evidence/2026-10-20.
Devnet, 2026-10-20, F1-F6 over real /verify and /settle
I ran F1 to F6 on Solana devnet against my self-hosted reference facilitator (@x402/core and @x402/svm 2.28.0),
then against fault-injected builds of it.
| facilitator | F1 | F2 | F3 | F4 | F5 | F6 |
|---|---|---|---|---|---|---|
self-hosted reference, @x402/svm 2.28.0 |
pass | pass | pass | fail | pass | pass |
F4 fails because the reference facilitator deduplicates by transaction but not by memo, so two different
transactions with the same memo both settled. The payer paid twice, so the seller lost nothing, but a resource server
that ties one purchase to one memo has to enforce that itself. Every fault-injected build was caught by the check
it targets. Transaction links, the full run.json and its hash are in evidence/devnet. Results for
other facilitators go in the facilitator matrix, and I run them against a third party only with the
operator's permission.
How this repo is verified
Scope and acceptance criteria live in proofline.json, a Proofline
contract. It's signed with my owner key: the signature is in proofline.json.sig and the public key is pinned in
guard/owner.pub. On every push to main and on every pull request, CI checks that signature and then runs tools/vv.py, which checks
every acceptance criterion and uploads a receipt (docs/VV.md). Any change to the contract has to be
re-signed before CI passes again. You can check the signature yourself from a clone:
node tools/verify-sig.mjs # no dependencies; exits 0 when the signature matches
npx -y @optitransfer/proofline@next contract verify-sig # the Proofline CLI (Node 22+), same check
The Lean 4 models in lean/ cover the credit rule, nonces, timeouts, settlement_pending reconciliation and
concurrent settles. They use only standard axioms and contain no sorry. They model the rule the suite checks, not any
facilitator's code.
Add the badge
If your project runs the suite in CI, add this to your README:
[](https://github.com/atsp-protocol/x402-conformance)
Who uses it
No public users yet. I only list a user once they've said so publicly. If you run it, open an issue and I'll add you here and to docs/ADOPTION.md.
Free run for your project. Open an issue with your repo or facilitator URL and I'll run the suite against it, post the results, and send a tests-only pull request with the Action and badge. It's free, with no conditions.
Managed services
The core is free and stays free. If you'd rather not run it yourself, or you need more than the open suite, I offer:
- Hosted continuous monitoring of your live endpoints, with alerts on regressions
- Signed conformance reports for each release, with a public verify link
- Custom checks for your scheme, chain or extensions
- Priority support with an agreed response time
The first three customers get design-partner pricing. Monitoring is a monthly subscription, reports are priced per release, and custom checks are fixed-scope work quoted up front. Details are in docs/MANAGED.md. To get in touch, open a Managed services enquiry or email data@optitransfer.ch.
Safety
The suite holds no keys of yours and never signs or pays. Live mode is GET-only, and that's tested. The devnet runner refuses mainnet and any third-party facilitator without written permission.
Security
Please report vulnerabilities through GitHub private vulnerability reporting (Security → Report a vulnerability), not in a public issue. When the suite finds a failing safety check in someone else's facilitator, I tell the operator privately first. The full policy is in SECURITY.md.
Contributing
Issues and pull requests are welcome, and new checks especially. Propose one with the check-proposal issue template, quoting the x402 spec text it rests on. Every check needs a test that fails against a faulty double and passes against the reference. CONTRIBUTING.md lists the local checks to run before you push, and everyone taking part follows the Code of Conduct. Changes are listed in CHANGELOG.md.
Licence
Apache-2.0. See LICENSE and NOTICE (Ryan Gillespie / Optitransfer).
Metadata
Release files for x402-conformance 1.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| x402_conformance-1.2.1.tar.gz | 48.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| x402_conformance-1.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.3 kB
Release files / x402_conformance-1.2.1.tar.gz
| Download URL | x402_conformance-1.2.1.tar.gz |
|---|---|
| Size | 48.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
db9acc66e4a43ba397b57be993f1d0eb97310d86ca3e2ac97c918560ab24cce1
|
|
BLAKE2b-256 checksum How to use checksums |
3a2a2bae80448c6d29bb915f37827b5f593535131659aaf26e09e92a4b10c9b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|
Release files / x402_conformance-1.2.1-py3-none-any.whl
| Download URL | x402_conformance-1.2.1-py3-none-any.whl |
|---|---|
| Size | 47.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
492e596aad866a05a8f77e500b2d4af968d189754aa2ce2682b926e492f32424
|
|
BLAKE2b-256 checksum How to use checksums |
a4734787411f807d5989858db4a9b8efa6200a35820b8f005e4dc7535f8f0eb2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|
Security for x402-conformance 1.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.