Conformance Test Vectors
s402 ships 161 machine-readable JSON test vectors that define correct protocol behavior. Both the TypeScript and Python implementations pass all 161 vectors — producing byte-identical wire output. Use these vectors to verify any new implementation (Go, Rust, Java) without reading a single line of TypeScript or Python.
Why This Matters
Most protocols define behavior in prose. Prose is ambiguous. s402 defines behavior in executable JSON vectors that any language can load and run. If your implementation passes all 161 vectors, it's s402-compliant.
The vectors live in spec/vectors/ at the root of the monorepo. Both the TypeScript and Python test suites read from this single directory — one source of truth.
The vectors cover:
- Encode/decode — requirements, payloads, settle responses (header + body transport)
- Key stripping — unknown fields MUST be stripped at decode (defense-in-depth)
- Validation rejection — 39 malformed inputs that MUST be rejected
- Roundtrip identity — encode → decode → re-encode = identical output
- x402 compat — normalizing x402 V1/V2 input to s402 format
- Receipt format/parse — the
X-S402-Receiptwire format
Getting the Vectors
From npm (recommended)
npm pack s402
tar xzf s402-*.tgz
ls package/test/conformance/vectors/The vectors ship in every npm install s402 — no separate download needed.
From source
git clone https://github.com/s402-protocol/core.git
ls core/test/conformance/vectors/Vector Format
Each JSON file is an array of test cases:
[
{
"description": "Human-readable test name",
"input": { ... },
"expected": { ... },
"shouldReject": false
}
]Accept vectors (shouldReject: false): Your implementation MUST produce expected when given input.
Reject vectors (shouldReject: true): Your implementation MUST reject the input. The expectedErrorCode field tells you which error to expect.
Vector Files
| File | Count | What it tests |
|---|---|---|
requirements-encode.json | 19 | Object → base64 header |
requirements-decode.json | 21 | Base64 header → object (including key stripping) |
payload-encode.json | 10 | Payment payload → base64 header |
payload-decode.json | 9 | Base64 header → payment payload |
settle-encode.json | 7 | Settle response → base64 header |
settle-decode.json | 7 | Base64 header → settle response |
body-transport.json | 6 | JSON body transport (no base64) |
compat-normalize.json | 10 | x402 V1/V2 → s402 normalization |
receipt-format.json | 6 | Receipt fields → header string |
receipt-parse.json | 4 | Header string → receipt fields |
settlement-verification.json | 7 | Settlement digest-binding verification |
validation-reject.json | 46 | Malformed inputs that must fail |
roundtrip.json | 9 | encode → decode → re-encode identity |
Quick Start (Go example)
package s402test
import (
"encoding/json"
"os"
"testing"
)
type Vector struct {
Description string `json:"description"`
Input map[string]any `json:"input"`
Expected map[string]any `json:"expected"`
ShouldReject bool `json:"shouldReject"`
ExpectedErrCode string `json:"expectedErrorCode"`
}
func TestRequirementsEncode(t *testing.T) {
data, _ := os.ReadFile("vectors/requirements-encode.json")
var vectors []Vector
json.Unmarshal(data, &vectors)
for _, v := range vectors {
t.Run(v.Description, func(t *testing.T) {
result := EncodePaymentRequired(v.Input) // your implementation
if result != v.Expected["header"] {
t.Errorf("got %s, want %s", result, v.Expected["header"])
}
})
}
}Quick Start (Python example)
import json, pytest
def load_vectors(name):
with open(f"vectors/{name}.json") as f:
return json.load(f)
@pytest.mark.parametrize("v", load_vectors("requirements-encode"))
def test_requirements_encode(v):
if v["shouldReject"]:
with pytest.raises(S402Error):
encode_payment_required(v["input"])
else:
result = encode_payment_required(v["input"])
assert result == v["expected"]["header"]Key Implementation Notes
Encoding scheme
s402 uses Unicode-safe base64:
JSON.stringify(obj)— serialize to JSON string- UTF-8 encode the string
- Base64 encode the bytes
For ASCII-only content (the common case), this matches plain btoa(json).
Key stripping on decode
All decode functions MUST strip unknown keys. Only known fields survive. This is a security measure — untrusted HTTP input cannot inject arbitrary fields into your application.
The full list of known keys per decode path is documented in test/conformance/README.md.
JSON key ordering
Vectors use JavaScript's JSON.stringify key ordering (insertion order). Your serializer must produce keys in the same order to get identical base64 output. See the conformance README for language-specific guidance.
Regenerating Vectors
If you modify s402's encoding logic:
npx tsx test/conformance/generate-vectors.ts
pnpm test # verify conformance tests still passVectors are generated from source — never hand-written. The generator runs the actual encode/decode functions and captures their output.