Skip to content
Merged
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
55 changes: 51 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,48 @@ HTTP 402 ("Payment Required") has been reserved since 1999 — waiting for a pay

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

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

## What Would Prove This Should Stop

s402's bet is narrow enough to be wrong in public. Any of the following would falsify it, and we
would rather retire the project than defend it past its evidence:

1. **x402 absorbs the superset.** This README claims x402's two-scheme governance envelope
cannot take on prepaid, stream, escrow and unlock without re-ratification. If x402 ratifies
equivalents, the reason to run a second protocol is gone — the correct response is to fold
the schemes upstream and delete this repo, not to compete.

2. **The prepaid economics do not survive contact with production.** The case for micropayments
rests on batching 1,000 payments into 2 on-chain transactions. If real deployments do not land
near the claimed ~$0.014 per 1,000 calls — because of contention on shared objects, gas-price
regimes, or settlement patterns we have not modelled — then the central advantage over
per-call settlement is arithmetic that only works on paper.

3. **No independent implementation ever passes the vectors.** s402 claims to be a protocol, not a
library. A protocol with exactly one implementation is a library wearing a specification. If
the conformance vectors go unclaimed by any second-language implementation, the honest
description is "the wire format of the s402 npm package," and the spec should be demoted to
internal documentation.

4. **Agent commerce settles somewhere other than HTTP 402.** If agent-to-service payment
converges on mandate-passing over a non-402 channel, then the status code this protocol is
built around is the wrong integration point, and being excellent at it does not matter.

Points 1 and 3 are the ones to watch: both are observable from outside this repo, and neither
depends on our own judgement of our own work.

## Which Scheme Should I Use?

| Your situation | Scheme | Gas per 1K calls | Latency |
Expand Down Expand Up @@ -349,16 +391,21 @@ const requirements: s402PaymentRequirements = {

## Conformance Testing

s402 ships 133 machine-readable JSON test vectors for cross-language conformance. If you're implementing s402 in Go, Python, Rust, or any other language, use these vectors to verify your implementation matches the spec.
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.

```bash
# Vectors are in the npm package
# From the npm package
ls node_modules/s402/test/conformance/vectors/

# Or clone the repo
ls 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`](./typescript/test/conformance/README.md) for the vector format, encoding scheme, and implementation guide.

## Related
Expand Down
1 change: 1 addition & 0 deletions docs/adr/001-protocol-boundaries.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-001: Protocol Boundaries — Facilitator Trust, Receipt Cardinality, Scheme Cap, and Extension Hygiene

**Status:** Accepted
**Implementation:** shipped
**Date:** 2026-04-11
**Supersedes:** (none — first ADR)

Expand Down
1 change: 1 addition & 0 deletions docs/adr/002-s402-is-pure-protocol.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-002: s402 is a Pure Protocol Repo — mcp-server Moves to SweeFi

**Status:** Accepted
**Implementation:** shipped
**Date:** 2026-04-11
**Supersedes:** (none — refines the boundary that ADR-001 § Decision 1 named but did not enforce at the repo level)

Expand Down
1 change: 1 addition & 0 deletions docs/adr/003-upto-scheme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-003: `upto` Scheme — Usage-Based Payments with Settlement Overrides

**Status:** Accepted (implemented — see note)
**Implementation:** shipped
**Date:** 2026-04-12
**Supersedes:** (none)
**Linear:** DAN-284
Expand Down
2 changes: 2 additions & 0 deletions docs/adr/004-extensions-architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ADR-004: Extensions Architecture — Typed, Lifecycle-Aware Plugin System

**Status:** Proposed
**Implementation:** shipped — `src/extensions.ts` (10KB), exported as the `s402/extensions` subpath in `package.json`.
⚠️ **`Status: Proposed` above is stale and is left as-is deliberately** (found 2026-08-16, DAN-855): this was built without the ADR ever being moved to Accepted. Flipping a governance field is a decision, not a cleanup, so it is surfaced here rather than changed unilaterally. It is also the exact gap the `Implementation:` field exists to expose — under `Status` alone, "proposed and never built" and "proposed and shipped" were the same page.
**Date:** 2026-04-12
**Supersedes:** (none — implements the framework that ADR-001 Decision 4 described as hygiene rules)
**Linear:** DAN-285
Expand Down
1 change: 1 addition & 0 deletions docs/adr/005-interop-superset-principle.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-005: Interop When Possible, Superset When Wise

**Status:** Accepted
**Implementation:** upheld
**Date:** 2026-04-14
**Supersedes:** (none — formalizes informal positioning against x402 and MPP)
**Linear:** DAN-313
Expand Down
14 changes: 13 additions & 1 deletion docs/adr/006-version-negotiation-and-scheme-digests.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-006: Version Negotiation and Scheme Content-Hashing

**Status:** Draft
**Implementation:** not-started
**Date:** 2026-04-19
**Supersedes:** implicit version string in `s402Version` field
**Related:** ADR-001 (Protocol Boundaries), ADR-002 (Protocol-Pure), ADR-004 (Extensions), ADR-005 (Interop-Superset), INVARIANTS S3
Expand Down Expand Up @@ -95,7 +96,18 @@ advertised = "sha256-" || base64url_no_pad(digest)

**Target is the prose spec, not derived artifacts.** The markdown spec at `docs/schemes/<scheme>.md` is normative — it is the human-readable law of the scheme. JSON Schema files and BCS type definitions are derivative; their content-hashes are advertised separately in `spec/scheme-digests.json` under a `derived` key but do not drive version negotiation.

**Implementation:** `scripts/compute-scheme-digests.mjs` runs in CI and writes `spec/scheme-digests.json`. Any PR that modifies `docs/schemes/**/*.md` without updating the digests file fails CI.
**Implementation plan — NOT BUILT (verified 2026-08-16, DAN-855).** The intent is that
`scripts/compute-scheme-digests.mjs` runs in CI and writes `spec/scheme-digests.json`, so any PR
modifying `docs/schemes/**/*.md` without updating the digests file fails CI.

⚠️ **None of that exists.** Neither file appears in any commit on any branch (`git log --all --
'**/compute-scheme-digests.mjs' '**/scheme-digests.json'` returns nothing), and CI's five jobs
contain no such gate. This paragraph previously asserted the mechanism in the present tense, which
made an unbuilt plan read as a shipped guarantee — in the one field the ADR convention reserves for
answering exactly that question. The header now carries `Implementation: not-started`.

`docs/schemes/` itself is real (`escrow.md`, `exact.md`, `prepaid.md`), so the thing this would
guard exists; only the guard is missing.

### Amendment chain

Expand Down
1 change: 1 addition & 0 deletions docs/adr/007-settlement-response-envelope.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-007: Settlement Response Envelope

**Status:** Draft (v2 — post /vet wave review)
**Implementation:** shipped
**Date:** 2026-04-19
**Related:** ADR-001 (Protocol Boundaries), ADR-006 (Version Negotiation), ADR-008 (Safety Invariants S9-S13), ADR-009 (Open Gaps), INVARIANTS S7, S8
**Supersedes:** `s402SettleResponse` (legacy flat shape in `typescript/src/scheme.ts`)
Expand Down
1 change: 1 addition & 0 deletions docs/adr/008-safety-invariants-s9-s13.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-008: Safety Invariants S9–S14

**Status:** Draft (v2 — post /vet wave review; S14 added, S11 hardened)
**Implementation:** in-progress — **S11** and **S14** are built (`src/envelope.ts:77`, `:430`, `:443`; S14's constant-time comparison has its own test at `test/envelope.test.ts:429`). **S9, S10, S12 and S13** have no enforcement site found. Determined 2026-08-16 (DAN-855) by searching for the mechanisms, not only the labels — but treat the negatives as *no witness found*, not *proven absent*.
**Date:** 2026-04-19
**Related:** INVARIANTS.md (S1–S8), ADR-004 (Extensions), ADR-007 (Settlement Envelope), ADR-009 (Open Gaps)

Expand Down
1 change: 1 addition & 0 deletions docs/adr/009-open-gaps-rotation-reorg.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-009: Open Gaps — Facilitator Key Rotation, Chain Reorg Tolerance, Scheme Acceptance Process

**Status:** Placeholder — tracked gaps, not yet resolved
**Implementation:** not-started
**Date:** 2026-04-19
**Related:** ADR-006, ADR-007, ADR-008, INVARIANTS S8, S11

Expand Down
1 change: 1 addition & 0 deletions docs/adr/010-safety-invariants-s15-s16.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-010: Safety Invariants S15–S16 — Session Binding and Version Binding

**Status:** Draft
**Implementation:** in-progress — **S16 is half-built**: the protocol version *is* bound into the signed envelope rather than only into transport headers (`src/envelope.ts:54`, `:173`, `:219`, and required at `:268`). Its *scheme-digest* half is blocked on ADR-006, which is `not-started`. **S15** (binding long-running scheme state to the mandate/capability object rather than the signer's key) has no enforcement site in this repo — consistent with ADR-002, since mandates live upstack. Determined 2026-08-16 (DAN-855) by searching for the mechanisms rather than the labels; a label-only search finds where someone wrote "S16", not where it is enforced.
**Date:** 2026-04-21
**Related:** INVARIANTS.md (S1–S8), ADR-006 (Version Negotiation), ADR-007 (Settlement Envelope), ADR-008 (Safety Invariants S9–S14), ADR-009 (Open Gaps)

Expand Down
1 change: 1 addition & 0 deletions docs/adr/011-transport-abstraction.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ADR-011: Transport Abstraction — Payment Rides Any Carrier

**Status:** Accepted (2026-06-28; revised after blind-spot review; Chunks 1a-i, 1a-ii, 1a-iii, and 2 landed + verified — HTTP + MCP + A2A all behind one seam, 1075 tests green)
**Implementation:** shipped
**Date:** 2026-06-28
**Supersedes:** (none)
**Related:** ADR-005 (interop/superset), ADR-002 (s402 is pure protocol), S7 (chain-agnostic boundary)
Expand Down
10 changes: 10 additions & 0 deletions docs/guide/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,16 @@ This is the complete s402 protocol flow. In production, step 3 uses the Sui SDK

## Making It Real

> 🛑 **The snippet below will not connect today.** Sui has deprecated JSON-RPC on public
> fullnodes — `https://fullnode.{testnet,mainnet}.sui.io` returns `-32601` for every core method
> (verified 2026-08-16). A `SuiClient` pointed at a public fullnode is dead on arrival, so treat
> this section as illustrating the *shape* of the integration, not as runnable today. Reaching a
> live network requires the gRPC or GraphQL transport; migration is tracked separately.
>
> Everything **above** this section is unaffected: the s402 protocol layer has no Sui dependency
> (ADR-002, invariant S7), so the encoding, validation and error handling you just worked through
> all still run offline.

To connect this to Sui, replace the mock payment in `client.ts` with real Sui SDK code:

```typescript
Expand Down
4 changes: 2 additions & 2 deletions docs/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: s402 Wire Format Specification v1 — the formal, field-by-field de

This document defines the s402 wire format — the exact encoding, field definitions, validation rules, and error semantics for the s402 HTTP 402 payment protocol. It is the authoritative reference for any implementation in any language.

The TypeScript reference implementation lives at [github.com/s402-protocol/core](https://github.com/s402-protocol/core). Machine-readable conformance test vectors ship in the npm package (161 vectors across 13 files).
The TypeScript reference implementation lives at [github.com/s402-protocol/core](https://github.com/s402-protocol/core). Machine-readable conformance test vectors ship in the npm package (167 vectors across 14 files).

## 1. Terminology

Expand Down Expand Up @@ -510,7 +510,7 @@ An implementation is **s402-conformant** if it:
2. Validates all required fields per §4.1, §5.1, and §6
3. Rejects malformed input with the appropriate error code from §8
4. Strips unknown keys on decode per §10
5. Passes the 161 machine-readable conformance test vectors shipped in the `s402` npm package
5. Passes **every** machine-readable conformance test vector shipped in the `s402` npm package (167 vectors across 14 files as of v0.9.0)

The conformance vectors cover: encode, decode, body transport, x402 compat normalization, receipt format/parse, settlement verification, validation rejection, key stripping, and roundtrip identity. See the [Conformance Vectors guide](/guide/conformance) for the vector format and implementation instructions.

Expand Down
24 changes: 24 additions & 0 deletions mcp-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

A minimal Sui-native MCP server that advertises **three coexisting payment protocols** (x402, s402, stripe-mpp) inside a single `payment[]` envelope. Built as a reference implementation for [MCP SEP-2007](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2007).

> ## 🛑 This demo cannot currently reach the network
>
> **Sui has deprecated JSON-RPC on public fullnodes.** Every core method now returns `-32601`
> on **both testnet and mainnet** (verified 2026-08-16):
>
> ```
> curl -s -X POST https://fullnode.testnet.sui.io:443 -H 'Content-Type: application/json' \
> -d '{"jsonrpc":"2.0","id":1,"method":"sui_getChainIdentifier","params":[]}'
> # {"error":{"code":-32601,"message":"Method not found. JSON-RPC on public fullnodes has been
> # deprecated. Please migrate to gRPC or GraphQL endpoints."}}
> ```
>
> `src/server.ts` and `src/agent.ts` both construct a `SuiClient` over that transport, so the
> end-to-end run does not work today. The **protocol layer is unaffected** — `s402` itself has
> no Sui dependency and never had one (ADR-002, invariant S7), so the envelope shapes, encoding
> and conformance vectors this demo illustrates are all still correct and all still tested.
>
> **What still works offline:** reading the `tools/list` envelope below, and the `s402` package's
> own suite (`cd ../typescript && pnpm vitest run`).
>
> Migration to gRPC/GraphQL is tracked separately — it is a migration, not a doc fix, and it is
> not being attempted inside a documentation pass. **This warning is removed when that lands**;
> a stale blocker notice is its own defect.

## The headline artifact

The `tools/list` response advertises three payment options per tool:
Expand Down
45 changes: 45 additions & 0 deletions typescript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- **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
report the real number. The consequential one was §13 *Conformance*, criterion 5: that clause
defines what makes an implementation s402-conformant, so an implementer in Go, Python or Rust
reading the spec was told they conformed at 161 vectors, leaving six shipped vectors outside
the definition of conformance.
- **The README told you to look for the conformance vectors in a directory that does not exist
in a clone.** It pointed at `test/conformance/vectors/`, which is generated by
`scripts/prepare-publish.sh` at publish time and is git-ignored. Cloning the repo and following
the instructions produced *No such file or directory*. The README now points clones at
`spec/vectors/` — the canonical, version-controlled location — and explains how the two paths
relate, so the npm path stays correct for package consumers.

- **An architecture decision record claimed a CI check that has never existed.** ADR-006 stated,
in the present tense, that `scripts/compute-scheme-digests.mjs` "runs in CI and writes
`spec/scheme-digests.json`", failing any PR that changed a scheme doc without updating digests.
Neither file appears in any commit on any branch, and CI has no such job. It was the only ADR
carrying an `Implementation:` field, so the project's single answer to "was this built?" was
false. The paragraph is now marked as an unbuilt plan and the record reads
`Implementation: not-started`.

### Added

- **Every ADR now records whether it was actually built.** All twelve carry an `Implementation:`
field (`shipped` · `in-progress` · `not-started` · `upheld`), each determined against the code
rather than the prose. `Status: Accepted` only ever meant *decided* — so a decision that shipped
and one that was ratified and quietly never built were indistinguishable on the page. The
conceived-to-shipped ratio is now countable: **7 of 12 shipped.** Two findings fell straight out
of the exercise: ADR-004's extensions framework ships as the `s402/extensions` subpath while its
`Status` still reads *Proposed*, and ADR-010's S16 turns out to be half-built — version binding
is enforced in the envelope, its scheme-digest half blocked on ADR-006.
- **A regression test that fails when a documented vector count drifts from reality**
(`test/spec-doc-counts.test.ts`). It counts `spec/vectors/` and compares against every
`"<N> vectors across <M> files"` claim in `README.md` and `docs/specification.md`, and it
refuses to pass vacuously: if the wording changes so no claim is found, the test fails rather
than silently verifying nothing. Counts written into prose are derived values maintained by
hand, and three independent wrong numbers in one repo is what that looks like after a while.
- **The README now says who s402 is for, who it is not for, and what would prove it should
stop.** The last of those is a falsification section naming four observable conditions that
would retire the project — chiefly x402 absorbing the superset schemes, and no independent
implementation ever passing the conformance vectors.

## [0.8.0] - 2026-06-28

The transport-abstraction release (ADR-011): **one seam, three carriers — HTTP, MCP, and A2A.**
Expand Down
Loading
Loading