chain-adapters/algorand/ as the reference implementation.
The one rule
invariants/universal/* may only ever import core/types.ts (the ChainAdapter and
FacilitatorClient interfaces) and invariants/shared.ts — never
chain-adapters/algorand/* or any other chain-specific module directly. This is what keeps
“chain-agnostic core” true in practice, not just a claim in the README. See
Architecture for the full contract definitions.
What a new adapter implements
1
buildPayment — construct a real, correctly-signed payment
Must produce whatever wire format the target chain’s x402 scheme expects. Algorand’s
implementation (
algorand-adapter.ts) delegates to @x402/avm’s ExactAvmScheme so the
payload it builds is byte-identical to what a real client SDK would produce — never a
hand-rolled approximation.2
tamperPayload — apply one PayloadMutation
The
PayloadMutation union (network, asset, recipient, amount, the five
feePayer* mutations, spliceExtraTxn, custom) is the vocabulary every invariant uses to
express “what if this field were malicious,” without knowing how tampering is actually
implemented for a given chain. A new chain adapter needs to support at minimum the mutations
the universal invariants (U1–U5) use — the chain-specific feePayer* mutations only need
real support if the new chain has an equivalent sponsored-fee/atomic-group concept.3
fundAccount, waitForConfirmation, getExplorerLink
Straightforward chain-native operations — send native currency, poll for confirmation, and
produce a human-clickable explorer URL for evidence trails.
Ground truth over documentation
Algorand’s adapter was built by reading the installed, compiled@x402/core / @x402/avm
package source directly (.d.mts type declarations and compiled .mjs), not the (looser,
sometimes stale) reference documentation. This surfaced real divergences — for example,
network-ids.ts defines its own ALGORAND_TESTNET_CAIP2 / ALGORAND_MAINNET_CAIP2 constants
rather than trusting the installed package’s exports, because those exports turned out to be
truncated 33-character literals cut mid-base64, rather than the full 44-character base64-encoded
genesis hash a valid CAIP-2 Algorand network id actually needs — confirmed against both a live
facilitator’s /verify rejection and algod’s own /v2/transactions/params response. A new chain
adapter should apply the same standard: verify wire shapes against the actual installed SDK and a
real facilitator response, not just what a doc page says.
Writing new invariants, if the chain needs them
Chain-specific correctness properties (like Algorand’sA1–A3) live in
invariants/algorand/ today. A new chain with its own protocol-specific trust boundaries — a
different atomic-group-like primitive, a different key-rotation mechanism — would add a parallel
invariants/<chain>/ directory, following the same pattern as a1-atomic-group-integrity.ts: cite
a source, target a real, code-confirmed facilitator check, and prefer /verify-only sub-cases
for malicious tamper cases so a real bug can’t damage the facilitator’s operational key even
though the suite runs against a real, self-hosted instance.
Wiring it in
cli/setup.ts’s buildScenario() is the only place that currently hardcodes
AlgorandChainAdapter. Supporting a second chain means constructing the new adapter there
(likely behind a config flag) and registering that chain’s invariants into the same
InvariantRegistry alongside the universal ones — the universal invariants themselves need no
changes at all.

