Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 82 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
[![CI](https://github.com/s402-protocol/core/actions/workflows/ci.yml/badge.svg)](https://github.com/s402-protocol/core/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/s402.svg)](https://www.npmjs.com/package/s402)

**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`) for normalizing x402 input.
**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](./AGENTS.md)). The reference implementation on Sui uses Programmable Transaction Blocks to reduce 1,000 payments to just 2 on-chain transactions via the Prepaid scheme, cutting per-call effective gas from $0.007 to $0.000014 and making micropayments economically viable for AI agents for the first time.
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](./AGENTS.md)).

The Sui reference implementation ships separately as [`@sweefi/sui`](https://www.npmjs.com/package/@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](./docs/whitepaper.md).

```bash
npm install s402
Expand All @@ -16,6 +18,31 @@ deno add npm:s402

> **ESM-only.** This package ships ES modules only (`"type": "module"`). Requires Node.js >= 20. CommonJS `require()` is not supported.

## The one demo — 60 seconds, no wallet

```bash
git clone https://github.com/s402-protocol/core.git && cd core
pnpm install
pnpm demo
```

**What 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`](https://www.npmjs.com/package/@sweefi/sui).

Nothing in the demo touches a network, a key, or a testnet faucet. Source:
[`typescript/examples/quickstart.mjs`](./typescript/examples/quickstart.mjs).

> Two larger demos ship in this repo and need more than 60 seconds:
> [`mcp-demo/`](./mcp-demo) (three payment protocols in one MCP envelope — **read its README
> first, it cannot reach the network today**) and [`demo-api/`](./demo-api) (a paid HTTP
> endpoint).

## Governing Principle

> **We interop when possible. We superset when wise.**
Expand All @@ -37,13 +64,20 @@ HTTP 402 ("Payment Required") has been reserved since 1999 — waiting for a pay
| **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 gas per 1K calls on Base (broken) | $0.014 gas per 1K calls (prepaid) |
| **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](./docs/whitepaper.md#the-gas-comparison).
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.

## Who This Is For
Expand All @@ -59,6 +93,38 @@ say so. Also skip it if you want a payments *product*: s402 is a wire format and
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.

## What Is True Today

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. |

## Receipts

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.

## Which Scheme Should I Use?

| Your situation | Scheme | Gas per 1K calls | Latency |
Expand Down Expand Up @@ -220,7 +286,7 @@ import {
isX402,
toX402Requirements,
fromX402Requirements,
} from 's402/compat';
} from 's402/compat/x402';

// Normalize x402 JSON (V1 or V2) to s402 format
const requirements = normalizeRequirements(rawJsonObject);
Expand Down Expand Up @@ -278,11 +344,17 @@ if (result.success) {
## Sub-path Exports

```typescript
import { ... } from 's402'; // Everything
import { ... } from 's402'; // Everything
import type { ... } from 's402/types'; // Types + constants only
import { ... } from 's402/http'; // HTTP encode/decode
import { ... } from 's402/compat'; // x402 interop
import { ... } from 's402/errors'; // Error types
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 tests
```

## Implementing a Scheme
Expand Down Expand Up @@ -350,9 +422,9 @@ const requirements: s402PaymentRequirements = {

## Design Principles

1. **Protocol-agnostic core, Sui-native reference.** `s402` defines chain-agnostic protocol types and HTTP encoding. The reference implementation (`@sweefi/sui`, coming soon) will exploit Sui's unique properties — PTBs, object model, sub-second finality. Other chains can implement s402 schemes using their own primitives.
1. **Protocol-agnostic core, Sui-native reference.** `s402` defines chain-agnostic protocol types and HTTP encoding. The reference implementation, [`@sweefi/sui`](https://www.npmjs.com/package/@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.

2. **Optional x402 compat.** The `s402/compat` subpath 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.
2. **Optional x402 compat.** The `s402/compat/x402` subpath 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.

3. **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.

Expand Down
82 changes: 82 additions & 0 deletions demo-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# s402 demo API

A paid HTTP endpoint, running on your machine, with **no wallet, no keys and no network**.

It answers the one question protocol types cannot: *what does a 402 exchange actually look like
over the wire?* Payment verification is stubbed with in-file mock schemes — this demonstrates the
**protocol handshake**, not settlement.

## Run it

```bash
pnpm install # from the repo root
pnpm --filter s402-demo-api dev
```

Then, in another terminal:

```bash
curl -i http://localhost:3402/api/joke
```

**Expected — verified 2026-08-22:**

```
HTTP/1.1 402 Payment Required
payment-required: eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbImV4YWN0Il0s…
```

That header is the entire protocol surface a client needs. Decode it:

```bash
curl -sD- -o/dev/null http://localhost:3402/api/joke \
| grep -i '^payment-required:' | sed 's/^[^:]*: *//' | tr -d '\r' \
| base64 -d | python3 -m json.tool
```

```json
{
"s402Version": "1",
"accepts": ["exact"],
"network": "sui:testnet",
"asset": "0x2::sui::SUI",
"amount": "1000000",
"payTo": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
```

## What is here

| Route | Price | Paid? |
|---|---|---|
| `/` | — | Browser UI |
| `/api/catalog` | — | Free — lists every endpoint and its price |
| `/api/stats` | — | Free |
| `/api/joke` | 0.001 SUI | **402** |
| `/api/wisdom` | 0.005 SUI | **402** |
| `/api/alpha` | see catalog | **402** |

## What this proves, and what it does not

**Proves:** the server helpers build a well-formed 402, and the header encoding survives a real
HTTP round trip to a real client.

**Does not prove:** that anyone got paid. The facilitator here is a mock that approves
everything, and `payTo` is 64 a's — deliberately not a real address. Settlement needs
[`@sweefi/sui`](https://www.npmjs.com/package/@sweefi/sui) and a chain.

## Configuration

All optional; the defaults run standalone.

| Env var | Default |
|---|---|
| `PORT` | `3402` |
| `S402_NETWORK` | `sui:testnet` |
| `S402_PAY_TO` | `0x` + 64 × `a` |

## Smaller demo

To see the wire format and the conformance vectors without starting a server, run `pnpm demo`
from the repo root instead. It is faster and covers the encoder, the x402 compat layer, and all
167 vectors.
4 changes: 2 additions & 2 deletions demo-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
"version": "0.1.0",
"type": "module",
"private": true,
"description": "Demo API showcasing the s402 protocol pay for endpoints with SUI",
"description": "Demo API showcasing the s402 protocol \u2014 pay for endpoints with SUI",
"scripts": {
"dev": "tsx src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"s402": "^0.6.0"
"s402": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.0",
Expand Down
2 changes: 1 addition & 1 deletion demo-api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
if (url === '/api/catalog') {
json(res, 200, {
protocol: 's402',
version: '0.3',
version: S402_VERSION,
network: NETWORK,
endpoints: routes.map((r) => ({
path: r.path,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"private": true,
"name": "s402-protocol",
"type": "module",
"description": "s402 protocol monorepo spec, TypeScript, Python",
"description": "s402 protocol monorepo \u2014 spec, TypeScript, Python",
"scripts": {
"demo": "pnpm --filter s402 demo",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ packages:
- 'typescript'
- 'server-ts'
- 'mcp-demo'
- 'demo-api'
42 changes: 42 additions & 0 deletions typescript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **Every `s402/compat` import in the README failed at module resolution.** The README told you
to `import { normalizeRequirements, … } from 's402/compat'` in four places — the opening
paragraph, the compat example, the sub-path export table, and Design Principle 2. That subpath
is not exported; copying the example produced
`ERR_PACKAGE_PATH_NOT_EXPORTED: Package subpath './compat' is not defined by "exports"`. The
real path is **`s402/compat/x402`**, which exports all five documented functions. The sub-path
table was also missing six real entries (`server`, `receipts`, `extensions`, `test-utils`,
`compat/mpp`, `compat/l402`).
- **The README described a published package as unreleased.** Design Principle 1 called the Sui
reference implementation `@sweefi/sui`, *"coming soon"* — while `@sweefi/sui` is on npm and
`@sweefi/server` alongside it. The same document simultaneously described that implementation
in the **present tense** two paragraphs earlier, so one fact appeared in two tenses. Both now
say what is true: it is published, and it lives in a separate package.
- **The demo API misreported the protocol version it speaks.** `GET /api/catalog` returned a
hardcoded `"version": "0.3"` while `S402_VERSION` is `"1"`. Anyone curling the catalog to see
what the protocol advertises got a number that contradicts every payload the same server emits.
- **Gas figures were stated without their conditions.** The comparison table presented modelled
estimates as flat facts and omitted the case the project's own whitepaper is careful to name:
**x402 on Solana (~$0.25 per 1K calls) is cheaper than s402 Exact on Sui (~$7.00) for one-shot
calls.** The table now marks the numbers as modelled, links the method, and states where a
competitor wins.

### Added

- **One demo you can run in sixty seconds, with no wallet, no keys and no network** — `pnpm demo`.
It encodes a 402 into a single HTTP header, decodes it from the client side, absorbs an x402 V1
payment body through the compat layer, and runs the 167 published conformance vectors against
the code in your clone. Source: `typescript/examples/quickstart.mjs`.
The demo reports **how many malformed headers were refused *and how many leaked through***,
because a suite that only ever watches things pass cannot distinguish a working validator from
one that returns `true` unconditionally. That counter was itself verified by poisoning a vector
and confirming the run turns red and exits non-zero.
- **A README that says what is true today, and how to check it.** Two new sections: *What Is True
Today* (shipped / published / partial / not-runnable, per component) and *Receipts* (a command
for every claim the README makes about itself).
- **`demo-api/` is reachable again.** It was absent from `pnpm-workspace.yaml`, had no README, and
depended on a published `s402@^0.6.0` while this repo ships `0.9.0` — so it was neither wired to
the workspace nor documented anywhere. It is now a workspace package on `workspace:*`, it
typechecks against the current source, and it has a README with the verified route (`/api/joke`,
not `/api/data`), the decoded 402 body, and an explicit statement that its facilitator is a mock
that settles nothing.

- **The documented size of the conformance suite was wrong everywhere it appeared.** The repo
stated it three different ways — `README.md` said 133 vectors, `docs/specification.md` said
161 in two places — while `spec/vectors/` holds **167 vectors across 14 files**. All three now
Expand Down
Loading
Loading