@three-ws/x402-preflight

@three-ws/x402-preflight

Ask an x402 seller whether it can actually settle, before you sign anything. Fetches and verifies a signed, time-bounded payability attestation, then routes you to a rail that works. The missing reliability layer for agent payments.

npx -y @three-ws/x402-preflight
View on GitHubStar the repo

@three-ws/x402-preflight

Ask an x402 seller whether it can actually settle, before you sign anything.

x402 tells your agent what a resource costs. It says nothing about whether the seller can currently settle what it charges. Those look identical right up until your agent has already signed, and the difference is paid for by the buyer: a burned signature, a consumed blockhash, a 502, and no service.

npm i @three-ws/x402-preflight
import { guardedFetch } from '@three-ws/x402-preflight';

// A drop-in fetch that will not spend your money on a seller who cannot deliver.
const fetch402 = guardedFetch({ prefer: ['solana:mainnet'] });

const res = await fetch402('https://three.ws/api/x402/echo');

That's it. Before the request that could trigger payment, the seller's signed payability attestation is fetched once (cached for its own lifetime), verified offline, and either the request goes through on a rail that works, or it throws before your agent signs anything.


#Why this exists

On 2026-08-28, three.ws was the broken seller. Its fee sponsor held 0.000899107 SOL against a 0.02 SOL floor, so every transaction it fee-paid died at simulation with InsufficientFundsForRent. 95 payment attempts, 0 settled, three hours.

Every one of those buyers could have known before signing, from state the server already had. So we published the state, signed it, and wrote the client that reads it.

#What you get

#preflight(origin, opts?)

Fetch and verify a seller's attestation. Throws PreflightError on transport failure, a malformed body, or any check that does not pass.

import { preflight } from '@three-ws/x402-preflight';

const { report } = await preflight('https://three.ws');
console.log(report.networks['solana:mainnet']);
// { payable: false, reason: 'sponsor_below_floor', retry_after: 300,
//   settle: { rate: 0, attempts: 95, window_hours: 3, confidence: 0.9 },
//   alternates: ['eip155:8453'] }

#assertPayable(origin, network, opts?)

Throw unless that rail can settle right now. The error carries everything you need to recover without another round trip.

import { assertPayable, PreflightError } from '@three-ws/x402-preflight';

try {
  await assertPayable('https://three.ws', 'solana:mainnet');
} catch (err) {
  if (err instanceof PreflightError) {
    err.reason;      // 'sponsor_below_floor'
    err.alternates;  // ['eip155:8453']   <- pay here instead
    err.retryAfter;  // 300               <- or come back in 5 minutes
  }
}

unknown counts as not payable. Your agent is about to make an irreversible transfer, so the safe reading of "I cannot tell" is to not pay. Pass allowUnknown: true to take the other trade-off, which is a decision worth writing down at the call site.

#guardedFetch(opts?)

A fetch you can pass anywhere a fetch is accepted.

const fetch402 = guardedFetch({
  prefer: ['solana:mainnet', 'eip155:8453'],
  onSkip: (info) => log.warn('seller unpayable', info),
});

#verifyPreflight(envelope, expect?)

The trust boundary, exported on its own from @three-ws/x402-preflight/verify. No I/O, no Node built-ins: it runs unchanged in a browser, a service worker and on an edge runtime.

import { verifyPreflight } from '@three-ws/x402-preflight/verify';

verifyPreflight(envelope, { subject: 'https://three.ws', issuer: KNOWN_KEY });
// { valid: true, reason: 'ok', issuer: '...', digest: '...', expired: false }

It checks all of this, so you cannot forget one:

It never throws. A hostile seller must not be able to crash a paying agent's loop, so every input returns a verdict.

#The CLI

npx @three-ws/x402-preflight https://three.ws
https://three.ws  signed by 8k2n…Wq4
  attestation verified, valid for another 47s

  NOT PAYABLE   solana:mainnet
    reason   sponsor_below_floor
    settle   0.0% of 95 attempts over 3h (confidence 0.9)
    retry    after 300s
    instead  eip155:8453

  PAYABLE       eip155:8453
    reason   ok
    settle   98.0% of 412 attempts over 3h (confidence 0.98)

Exit codes are the point, so it composes:

Code Meaning
0 Payable
1 Verified, and not payable
2 Could not be verified (no attestation, bad signature, expired, timeout)
npx @three-ws/x402-preflight https://three.ws --network solana:mainnet && ./pay.sh

--json prints the raw envelope for jq. --issuer <pubkey> pins the signer you trust. --timeout <ms> bounds a slow seller.

#Why the attestation is signed

An unsigned health endpoint is a courtesy. A signed, time-bounded one is evidence, and three properties follow that a status page cannot give you:

Attributable. A client that paid after reading payable: true holds a statement signed by the seller's own key saying the seller believed it could settle, at a stated instant. Between autonomous agents transacting with strangers, that is the difference between "it broke" and "you said it would work".

Unreplayable. Every attestation carries expires_at, and verification rejects an expired one. A seller cannot serve a cached healthy attestation through an outage, and an attacker who captures one cannot replay it later.

Relayable. Because it verifies offline against a public key, a registry or an index can cache and forward attestations without becoming trusted. Discovery services can rank sellers by measured settleability instead of by self-description.

#Honesty rules the format enforces

#What it does not promise

#Serving one yourself

The wire format is public and unencumbered. Any x402 seller can publish an attestation and any client can verify it; nothing routes through three.ws.

Full contract, including the canonicalization rules and the normative verification steps: specs/x402-preflight.md.

If you measure nothing yet, report "unknown". That is honest and still useful: a client learns you are not claiming health you cannot back up.

#License

Apache-2.0