x402
| ID | Check Name | Spec Reference | What It Checks | Pass Criteria |
|---|---|---|---|---|
X402-01 | 402 Response Status | x402 spec, HTTP semantics | An unpaid request must be answered with status code 402 | Response status is exactly 402, not 401/403/other |
X402-02 | Payment Header Present | x402 built-on-stellar guide | The 402 response must include a payment header | Either PAYMENT-REQUIRED or X-Payment header is present (both are checked — the spec itself is not yet consistent, see note in README). An x402 v1 challenge in the response body fails this check, and X402-03–05 still inspect it; see the note on v1 below |
X402-03 | Header Payload Decodable | x402 spec §payment-required-object | The header value must be valid base64 that decodes to JSON | atob() + JSON.parse() succeed without error |
X402-04 | Required Fields Present | x402 spec §payment-required-object | The payload must include the core payment terms, under the field names its own advertised version requires | Checked in every payment option in accepts, each reported by its index when the challenge offers more than one; a challenge with no options fails. In each option, every field the advertised x402Version requires is present: for v2 scheme, network, amount, asset, payTo and maxTimeoutSeconds (spec 5.1.2); for v1 scheme, network, maxAmountRequired, asset, payTo, resource, description and maxTimeoutSeconds (v1 spec 5.1). Each is a non-empty string, except maxTimeoutSeconds, a positive number of seconds; a field present with the wrong type is reported as such rather than as missing. The price field follows the version: maxAmountRequired for v1, amount for v2 (renamed in v2, which also moves the resource out of the option). The version is read from the challenge rather than accepting whichever name happens to appear, because a service advertising x402Version: 2 while emitting the v1 field name is not conformant to the version it claims — and reporting that as a merely absent price would hide the actual defect. An unrecognised version fails: the field names cannot be checked against a version whose schema is unknown. |
X402-05 | Network Identifier Valid | x402 v2 spec §11.1; CAIP-2 | Every advertised network id is CAIP-2 | Every option's network is a CAIP-2 identifier, namespace:reference with a 3 to 8 character lowercase namespace and a reference of at most 32 characters, as x402 v2 requires. Where the namespace's own CAIP-2 definition fixes the reference, that is checked too: stellar is testnet or pubnet; eip155 is the chain id in base 10 (eip155:84532, not eip155:0x14a34); solana is the first 32 characters of the base58 genesis hash. A well-formed id in another namespace passes, and the result says only the format was checked there: x402 v2 asks for CAIP-2 and nothing more, so failing it would report Wasit's own lack of rules as the target's defect. An option without a network is left to X402-04. |
X402-06 | Signature Resubmit Accepted | x402 spec §payment-flow | A resubmitted request carrying a valid signature must be accepted | Response is no longer 402; a 2xx returns the original resource. The challenge is re-read immediately before signing, so the payment answers a challenge the target issued just now rather than a stale one. Settles a real payment — see the cost note below. A 2xx alone does not pass. The settlement the response reports in its PAYMENT-RESPONSE header (x402 v2 HTTP transport) must name a Stellar transaction hash, and that transaction is then looked up on Stellar RPC and held to the advertised terms exactly as MPP-01 does: it must have succeeded and emitted exactly one transfer event, from this run's payer, to the advertised payTo, for the advertised amount of the advertised asset. A missing header, a reported failure, or a hash that does not match fails. A settlement_pending response, which the spec defines as broadcast but unconfirmed, is reconciled on chain rather than failed. Uses the same RPC wait as MPP-01. On Base Sepolia (eip155:84532) the reference is an EVM transaction hash, and the receipt's ERC-20 Transfer log is held to the same terms: the transaction succeeded and logged exactly one token transfer, from this run's payer, to payTo, for amount of asset (an ERC-721 transfer, which shares the event signature, is not counted). The receipt is awaited for 30 blocks before the transaction counts as missing; an RPC that stops advancing gives no verdict. |
X402-07 | Invalid Signature Rejected (negative) | x402 spec §payment-flow; exact scheme on Stellar | A payment whose authorization signature is wrong must be REJECTED | The target answers with a non-2xx status. The payment is built exactly as for X402-06, then only the client's authorization signature is corrupted: in the exact scheme on Stellar the client signs a Soroban authorization entry rather than the envelope, and one byte of that entry's signature is flipped. The transaction still decodes and carries the same amount, payer and recipient, so a target can refuse it only by verifying the signature. Measured against the x402.org facilitator on 2026-09-30, the rejection is invalid_exact_stellar_payload_simulation_failed, reached at the Soroban simulation that checks authorization; the pre-0.6.0 corruption, which overwrote the base64 tail and broke XDR decoding, drew invalid_exact_stellar_payload_malformed instead, and a target that decoded the envelope without verifying the signature passed it. Rejection is established only by an answer: a target that cannot be reached, or whose challenge cannot be read, produces no verdict and is reported as ERROR or SKIP, and a payload with no authorization signature to corrupt reports ERROR (setup). On Base Sepolia the client signs an EIP-3009 transferWithAuthorization off-chain; the first byte of that signature is flipped and the authorization left intact, so the signer it recovers to is no longer the payer and only signature verification can refuse it. Measured against the x402.org facilitator on 2026-10-05: refused with 402. |
Note on the x402 payment checks' cost (Week 2). X402-06 and X402-07
are not free. X402-06 settles a real payment against the target, and X402-07
attempts one with a corrupted signature; both move or risk moving testnet funds
from the payer key, and repeated runs spend repeatedly. Like MPP-01 this is
inherent rather than an implementation choice — a payment flow that was never
exercised cannot be verified. X402-01 through X402-05 read the challenge
only and cost nothing; --read-only (CLI) or readOnly: true (MCP) restricts a
run to those. The payment checks are also skipped entirely when no payer key is
present, so the default posture is the cheap one.
Note on cascading failures (Week 2). The read-only checks inspect
progressively deeper parts of one challenge: the status, then the header, then
its payload, then the fields inside it. When one fails, the checks after it have
nothing left to inspect, and they are skipped rather than failed. A target
answering 404 produces one finding, not five. The same applies across the
payment checks: when X402-06 cannot exercise the payment flow at all,
X402-07 is skipped rather than credited with a rejection it never observed.
Note on x402 v1 challenges (0.5.0). x402 v1 signals payment in the 402
response body, as a PaymentRequirementsResponse with x402Version: 1
(transports-v1/http.md).
v2 moved it into the PAYMENT-REQUIRED header
(transports-v2/http.md),
and the exact scheme on Stellar is defined for v2 only, with CAIP-2 network
identifiers (scheme_exact_stellar.md:
"❌ v1 - we don't plan to support v1 for now"). So a v1 challenge from a
Stellar service fails X402-02, and the failure says a v1 challenge was found
in the body. Its terms are still worth reading, so X402-03–05 inspect the
body instead of being skipped: X402-04 applies the v1 field names, and
X402-05 reports whether the network is a CAIP-2 identifier (v1 used plain
names such as base-sepolia, and the failure says so). X402-06 and
X402-07 are skipped: Wasit pays through the v2 exact scheme, so no
payment is built or sent, and neither check has a verdict. The same holds for
any challenge the payment client cannot read. Before 0.5.0 both reported FAIL
in that case, contradicting the X402-07 row above, and a v1 challenge left
X402-03–05 skipped.
Note on networks (0.7.0). X402-01–05 read the challenge only, so they
apply to an x402 service on any chain. The payment checks pay through the
exact scheme on the network the run names: stellar:testnet (the default),
stellar:pubnet, or eip155:84532 (Base Sepolia, with the EIP-3009 method,
where the facilitator pays the gas). When a challenge offers several options, the payment is built for
the option on that network; a challenge with no such option, or only one in
another scheme, gets X402-06 and X402-07 skipped with the networks it
does offer, since nothing was paid and nothing refused. Asking for any other network, for pubnet without an RPC endpoint, or paying
with a key for the wrong chain, stops the run before any payment, as no
settlement could be verified.