source field — visible in ward test / ward fuzz output and in the JSON
report — cites exactly what it’s checking against, so a PASS or FAIL is always traceable back to
either the x402 protocol spec, the USENIX Security ‘26 facilitator study, or a specific Algorand
protocol primitive.
The full set
Must-have vs. stretch
ward test exits non-zero if any must-have invariant fails (U1, U2, U3, U4, A1 —
see MUST_HAVE_IDS in src/cli/setup.ts), making it safe to gate a CI pipeline or a release
process on. Stretch invariants (U5, A2, A3) still run and report every time, but a failure
there doesn’t fail the overall run — they cover real but narrower correctness properties (bounded
rejection cost, early opt-in detection, post-rekey authorization) that matter less universally than
the five must-haves.
Universal vs. Algorand-specific
Universal (U1–U5)
Import only
core/types.ts and invariants/shared.ts. They express properties every x402
facilitator must satisfy regardless of settlement chain — verify/settle consistency, retry
safety, concurrency safety, allowlist enforcement, and bounded rejection cost.Algorand (A1–A3)
The only invariants allowed to import
chain-adapters/algorand/*. They express properties
specific to Algorand’s transaction model — atomic transaction groups, ASA opt-in semantics,
and account rekeying.Reading a result
Every invariant produces anInvariantResult:
evidence is never a black box — it’s the literal request bodies sent and response bodies
received for every sub-case an invariant runs, collected via util/evidence.ts’s
EvidenceCollector. ward fuzz <id> prints this in full; ward test’s table shows a compact
evidenceSummary, with the full trail available in the written JSON report.
Start with the headline invariant
A1 is the sharpest, most Algorand-specific test in the suite — read it first to see what
“adversarial” actually means in Ward’s context.

