Chain-agnostic HTTP 402 protocol. Six payment schemes for AI agent commerce. Wire-compatible with x402. Zero runtime dependencies. Includes an optional compat layer (s402/compat/x402) for normalizing x402 input.
s402 is a chain-agnostic HTTP 402 wire format — types, HTTP encoding, scheme registry, and error handling for six payment schemes. The protocol layer contains no chain-specific logic (see S7 invariant).
The Sui reference implementation ships separately as @sweefi/sui. Its Prepaid scheme uses Programmable Transaction Blocks to settle 1,000 API calls in 2 on-chain transactions instead of 1,000 — a modelled effective gas cost of ~$0.000014 per call against ~$0.007 for one-shot Exact. Those are estimates under stated price and congestion assumptions, not measurements from production traffic: the model, its inputs, and the cases where a competitor is cheaper are all in the whitepaper's gas section.
npm install s402
pnpm add s402
bun add s402
deno add npm:s402ESM-only. This package ships ES modules only (
"type": "module"). Requires Node.js >= 20. CommonJSrequire()is not supported.
git clone https://github.com/s402-protocol/core.git && cd core
pnpm install
pnpm demoWhat you will see: a server encoding a 402 into a single HTTP header, a client decoding it with no shared code, an x402 payment body being absorbed through the compat layer, and the 167 published conformance vectors run against the code you just cloned — including 45 malformed headers that must be refused.
What it proves: the wire format works, on this build, in your terminal. What it does
not prove: anything about settlement on Sui — that needs a chain, and lives in
@sweefi/sui.
Nothing in the demo touches a network, a key, or a testnet faucet. Source:
typescript/examples/quickstart.mjs.
Two larger demos ship in this repo and need more than 60 seconds:
mcp-demo/(three payment protocols in one MCP envelope — read its README first, it cannot reach the network today) anddemo-api/(a paid HTTP endpoint).
We interop when possible. We superset when wise.
s402 does not fight x402 or Stripe MPP head-on. s402 absorbs them as payment-in formats where their design choices are legitimate (exact, upto), and supersets them on primitives their business models cannot ship (prepaid with on-chain ceiling, streaming with rate enforcement, escrow with arbiter, Seal-encrypted unlock).
This is the Postgres-eats-MySQL move: the superset always eats the subset because adopters never lose what they had — they only gain. The asymmetry is in s402's favor because competitors' constraints forbid reciprocating. Stripe cannot accept s402 schemes without bypassing card-processing margin. x402's 2-scheme governance envelope cannot absorb s402's 5 without re-ratification.
See ADR-005 for the full reasoning.
HTTP 402 ("Payment Required") has been reserved since 1999 — waiting for a payment protocol that actually works. Coinbase's x402 proved the concept on EVM. s402 takes it further by leveraging what makes Sui different.
| x402 (Coinbase) | s402 | |
|---|---|---|
| Settlement | Two-step: verify then settle (temporal gap) | Atomic: verify + settle in one PTB |
| Finality | 12+ second blocks (EVM L1) | ~400ms (Sui) |
| Payment models | Exact (one-shot) only | Six schemes: Exact, Prepaid, Escrow, Unlock, Stream, Upto |
| Micro-payments | ~$1.60 per 1K calls on Base* | ~$0.014 per 1K calls (prepaid)* |
| Coin handling | approve + transferFrom | Native coinWithBalance + splitCoins |
| Agent auth | None | AP2 mandate delegation |
| Direct mode | No | Yes (no facilitator needed) |
| Receipts | Off-chain | On-chain NFT proofs |
| Compatibility | n/a | Optional x402 compat layer (s402/compat) |
* Modelled, not measured. Gas figures are estimates under stated ETH-price and congestion
assumptions; the model and its inputs are in the whitepaper.
Read that table before quoting these — it is more honest than a two-column summary can be, and it
names the case we lose: x402 on Solana ($0.25 per 1K calls) is cheaper than s402 Exact on Sui
($7.00) for one-shot calls. s402 Prepaid wins on fixed overhead regardless of volume, not on
per-transaction cost.
s402 is Sui-native by design. These advantages come from Sui's object model, PTBs, and sub-second finality. They can't be replicated on EVM — and they don't need to be. x402 already handles EVM well. s402 handles Sui better.
Use s402 if you are charging for an HTTP endpoint that AI agents call, and either you settle on Sui, or you need a payment model x402 does not have — metered draw-down, streaming with rate enforcement, escrow with an arbiter, or pay-to-decrypt. Also use it if you are implementing the protocol in another language: the wire format is specified independently of this codebase and ships with conformance vectors to check yourself against.
Do not use s402 if you are EVM-only and exact covers you — x402 is the better fit and we
say so. Also skip it if you want a payments product: s402 is a wire format and a set of types
with zero runtime dependencies. It does not move money, custody funds, or run a facilitator for
you. Fiat and card rails are out of scope and will stay that way.
The honest tense, as of the current release. Nothing below is described in the present tense before it ships.
| State | |
|---|---|
s402 wire format (this repo) |
Shipped — 0.9.0 on npm. Types, HTTP encoding, scheme registry, error taxonomy, six schemes defined. Zero runtime dependencies. |
| Conformance vectors | Shipped — 167 across 14 files, in spec/vectors/, run by pnpm demo. |
@sweefi/sui (Sui adapter) |
Published — PTB builders and the payment adapter. Settlement lives here, not in this repo. |
@sweefi/server (HTTP middleware) |
Published. |
| Unlock scheme | Partial — depends on encryption key-server infrastructure; under active development. |
mcp-demo/ |
Built, not currently runnable end-to-end — Sui deprecated JSON-RPC on public fullnodes. Its README says so first. |
| Architecture decisions | 12 ADRs: 7 shipped, 1 upheld, 2 in-progress, 2 not-started. Each carries an Implementation: field, so "ratified" and "built" are distinguishable. |
Claims in this README are checkable. These are the checks.
| Claim | How you check it |
|---|---|
| The wire format works | pnpm demo — encode, decode, x402 compat, and 167 vectors, offline |
| The test suite is green | cd typescript && pnpm vitest run → 1108 tests across 29 files |
| It typechecks and builds | cd typescript && pnpm typecheck && pnpm build |
| Malformed input is refused | pnpm demo reports how many rejected and how many leaked through. The second number is the real one |
| 167 vectors, 14 files | ls spec/vectors/ | wc -l, and the demo runs them |
| Zero runtime dependencies | cat typescript/package.json — there is no dependencies key |
| Which decisions were built | grep -h -o '\*\*Implementation:\*\* *[a-z-]*' docs/adr/*.md | sort | uniq -c |
Last observed on a clean clone of main: typecheck clean, 1108/1108 tests across 29 files,
build 23 files. If any of the above does not run for you, that is a bug — please open an issue.
| Your situation | Scheme | Gas per 1K calls | Latency |
|---|---|---|---|
| One-time API call, simplest path | Exact | $7.00 | ~400ms |
| High-frequency API (10+ calls) | Prepaid | $0.014 | ~0ms per call |
| Buyer needs dispute protection | Escrow | $7.00 | ~400ms |
| Selling encrypted content | Unlock | $7.00 | ~400ms |
| Real-time billing (per-second) | Stream | variable | ~400ms setup |
Quick decision: Use Prepaid for AI agents making repeated API calls. Use Exact for everything else (it's the x402-compatible default). See the full guide for details.
s402 <-- You are here. Protocol spec. Zero runtime deps.
|
|-- Types Payment requirements, payloads, responses
|-- Schemes Client/Server/Facilitator interfaces per scheme
|-- HTTP Encode/decode for HTTP headers (base64 JSON)
|-- Compat Optional x402 migration aid
|-- Errors Typed error codes with recovery hints
|
@sweefi/sui <-- Sui adapter: 40 PTB builders + SuiPaymentAdapter + createS402Client
@sweefi/server <-- Chain-agnostic HTTP: s402Gate middleware + wrapFetchWithS402
@sweefi/ui-core <-- State machine + PaymentAdapter interface
@sweefi/vue <-- Vue 3 plugin + useSweefiPayment() composable
@sweefi/react <-- React context + useSweefiPayment() hook
s402 is chain-agnostic protocol plumbing. It defines what gets sent over HTTP. The Sui-specific how lives in @sweefi/sui.
One-shot payment. Client builds a signed transfer PTB, facilitator verifies + broadcasts atomically.
Client Server Facilitator
|--- GET /api/data ------->| |
|<-- 402 + requirements ---| |
| | |
| (build PTB, sign) | |
|--- GET + x-payment ----->|--- verify + settle ---->|
| |<--- { success, tx } ----|
|<-- 200 + data -----------| |
This is the x402-compatible baseline. An x402 client can talk to an s402 server using this scheme with zero modifications.
Deposit-based access. Agent deposits funds into an on-chain Balance shared object targeted at a specific provider. API calls happen off-chain. Provider batch-claims accumulated usage. Move module enforces rate caps — no trust required.
Phase 1 (deposit — one on-chain TX):
Agent deposits 10 SUI → Balance shared object created
Gas: ~$0.007
Phase 2 (usage — off-chain, zero gas):
Agent makes 1,000 API calls
Server tracks usage, no on-chain TX per call
Phase 3 (claim — one on-chain TX):
Provider claims accumulated $1.00 from Balance
Gas: ~$0.007
─────────────────────────────────────────────────────
Total gas: $0.014 for 1,000 calls
Per-call effective gas: $0.000014
This is the agent-native payment pattern. Without prepaid, per-call settlement costs $7.00 in gas for $1.00 of API usage (economically impossible). With prepaid, it costs $0.014 (economically trivial).
Use cases: AI agent API budgets, high-frequency API access, compute metering.
Time-locked vault with arbiter dispute resolution. Full state machine: ACTIVE -> DISPUTED -> RELEASED / REFUNDED.
- Buyer deposits funds, locked until release or deadline
- Buyer confirms delivery -> funds release to seller (receipt minted)
- Deadline passes -> permissionless refund (anyone can trigger)
- Either party disputes -> arbiter resolves
Use cases: digital goods delivery, freelance payments, trustless commerce.
Pay-to-decrypt encrypted content. Escrow + encrypted content delivery. The buyer pays into escrow; on release, the EscrowReceipt unlocks encrypted content stored on Walrus. Currently powered by Sui SEAL.
This scheme depends on encryption key server infrastructure and is under active development.
Per-second micropayments via on-chain StreamingMeter. Client deposits funds into a shared object; recipient claims accrued tokens over time.
Phase 1 (402 exchange):
Client builds stream creation PTB --> facilitator broadcasts
Result: StreamingMeter shared object on-chain
Phase 2 (ongoing access):
Client includes x-stream-id header --> server checks on-chain balance
Server grants access as long as stream has funds
Use cases: AI inference sessions, video streaming, real-time data feeds.
import type {
s402PaymentRequirements,
s402PaymentPayload,
s402SettleResponse,
} from 's402';import {
encodePaymentRequired,
decodePaymentRequired,
encodePaymentPayload,
decodePaymentPayload,
detectProtocol,
} from 's402';
// Server: build 402 response
const requirements: s402PaymentRequirements = {
s402Version: '1',
accepts: ['exact', 'stream'],
network: 'sui:mainnet',
asset: '0x2::sui::SUI',
amount: '1000000', // 0.001 SUI in MIST
payTo: '0x0000000000000000000000000000000000000000000000000000000000000001',
};
response.status = 402;
response.headers.set('payment-required', encodePaymentRequired(requirements));
// Client: read 402 response
const header = response.headers.get('payment-required')!;
const reqs = decodePaymentRequired(header);
console.log(reqs.accepts); // ['exact', 'stream']
console.log(reqs.amount); // '1000000'import {
normalizeRequirements,
isS402,
isX402,
toX402Requirements,
fromX402Requirements,
} from 's402/compat/x402';
// Normalize x402 JSON (V1 or V2) to s402 format
const requirements = normalizeRequirements(rawJsonObject);
// Convert s402 -> x402 V1 for legacy clients
const x402Reqs = toX402Requirements(requirements);import { s402Error, s402ErrorCode } from 's402';
try {
await facilitator.settle(payload, requirements);
} catch (e) {
if (e instanceof s402Error) {
console.log(e.code); // 'INSUFFICIENT_BALANCE'
console.log(e.retryable); // false
console.log(e.suggestedAction); // 'Top up wallet balance...'
}
}import { s402Client } from 's402';
const client = new s402Client();
// Register scheme implementations (from @sweefi/sui or your own)
client.register('sui:mainnet', exactScheme);
client.register('sui:mainnet', streamScheme);
// Auto-selects best scheme from server's accepts array
const payload = await client.createPayment(requirements);import { s402Facilitator } from 's402';
const facilitator = new s402Facilitator();
facilitator.register('sui:mainnet', exactFacilitatorScheme);
// Atomic verify + settle
const result = await facilitator.process(payload, requirements);
if (result.success) {
console.log(result.txDigest); // Sui transaction digest
}import { ... } from 's402'; // Everything
import type { ... } from 's402/types'; // Types + constants only
import { ... } from 's402/http'; // HTTP encode/decode
import { ... } from 's402/server'; // s402Gate server helpers
import { ... } from 's402/errors'; // Error types
import { ... } from 's402/receipts'; // On-chain receipt parsing
import { ... } from 's402/extensions'; // Extension registry
import { ... } from 's402/compat/x402'; // x402 interop
import { ... } from 's402/compat/mpp'; // Stripe MPP interop
import { ... } from 's402/compat/l402'; // L402 interop
import { ... } from 's402/test-utils'; // Fixtures for your own testss402 is designed as a plugin system. Each payment scheme implements three interfaces:
import type {
s402ClientScheme, // Client: build payment payload
s402ServerScheme, // Server: build payment requirements
s402FacilitatorScheme, // Facilitator: verify + settle
s402DirectScheme, // Optional: settle without facilitator
} from 's402';The reference Sui implementation of the schemes is available in @sweefi/sui.
s402 uses the same HTTP headers as x402 V1:
| Header | Direction | Content |
|---|---|---|
payment-required |
Server -> Client | Base64-encoded s402PaymentRequirements JSON |
x-payment |
Client -> Server | Base64-encoded s402PaymentPayload JSON |
payment-response |
Server -> Client | Base64-encoded s402SettleResponse JSON |
Note: x402 V2 renamed the client payment header to
payment-signature. s402 usesx-payment(matching x402 V1). All header names are lowercase per HTTP/2 (RFC 9113 §8.2.1). x402 V2 servers accept both headers, so s402 clients work with both versions. If your server needs to accept x402 V2 clients, also checkpayment-signature.
The presence of s402Version in the decoded JSON distinguishes s402 from x402. Clients and servers can auto-detect the protocol using detectProtocol().
Servers can advertise s402 support at /.well-known/s402.json:
{
"s402Version": "1",
"schemes": ["exact", "stream", "escrow", "unlock", "prepaid"],
"networks": ["sui:mainnet"],
"assets": ["0x2::sui::SUI", "0xdba...::usdc::USDC"],
"directSettlement": true,
"mandateSupport": true,
"protocolFeeBps": 50
}HTTPS is required. s402 payment data (requirements, payloads, settlement responses) travels in HTTP headers as base64-encoded JSON. Without TLS, this data is visible to any network observer. All production deployments MUST use HTTPS.
Requirements expiration. Servers SHOULD set expiresAt on payment requirements to prevent replay of stale 402 responses. The facilitator rejects expired requirements before processing.
const requirements: s402PaymentRequirements = {
s402Version: '1',
accepts: ['exact'],
network: 'sui:mainnet',
asset: '0x2::sui::SUI',
amount: '1000000',
payTo: '0x0000000000000000000000000000000000000000000000000000000000000001',
expiresAt: Date.now() + 5 * 60 * 1000, // 5-minute window
};-
Protocol-agnostic core, Sui-native reference.
s402defines chain-agnostic protocol types and HTTP encoding. The reference implementation,@sweefi/sui, is published and exploits Sui's properties — PTBs, object model, sub-second finality. Other chains can implement s402 schemes using their own primitives. -
Optional x402 compat. The
s402/compat/x402subpath provides a migration aid for codebases with x402-formatted JSON. It normalizes x402 V1 (maxAmountRequired) and V2 (amount) to s402 format. This is opt-in — the core protocol has no x402 dependency. -
Scheme-specific verification. Each scheme has its own verify logic. Exact verify (signature recovery + dry-run) is fundamentally different from stream verify (deposit check + rate validation). The facilitator dispatches — it doesn't share logic.
-
Zero runtime dependencies.
s402is pure TypeScript protocol definitions. No Sui SDK, no crypto libraries, no HTTP framework. Chain-specific code belongs in adapters. -
Errors tell you what to do. Every error code includes
retryable(can the client try again?) andsuggestedAction(what should it do?). Agents can self-recover.
s402 ships machine-readable JSON test vectors for cross-language conformance — 167 vectors across 14 files. If you're implementing s402 in Go, Python, Rust, or any other language, use these vectors to verify your implementation matches the spec.
# From the npm package
ls node_modules/s402/test/conformance/vectors/
# Or from a clone of this repo
ls spec/vectors/The two paths hold the same files. spec/vectors/ is the canonical, version-controlled
location; scripts/prepare-publish.sh copies it into test/conformance/vectors/ at publish
time so it lands inside the npm tarball. In a fresh clone only spec/vectors/ exists —
test/conformance/vectors/ is generated and git-ignored.
See test/conformance/README.md for the vector format, encoding scheme, and implementation guide.
- SweeFi — Open-source payment SDK built on s402. 10 packages including PTB builders, MCP tools, CLI, and UI components.
- Sui Gas Station — Sponsored transaction infrastructure for Sui.
Apache-2.0 — see LICENSE for details.