Skip to main content

x402 Conformance

Prove your x402 payments can't double-credit, credit too early or credit on a broken answer, in one CI step.

ci release GitHub Marketplace licence Tested with x402-conformance

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, 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 instead of the published ID
  • 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 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:

[![Tested with x402-conformance](https://img.shields.io/badge/tested%20with-x402--conformance-2ea44f)](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)

Source distribution for x402-conformance 1.2.1
File Size Uploaded Provenance
x402_conformance-1.2.1.tar.gz 48.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for x402-conformance 1.2.1
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page