The standard Every x402 pairing advertises the ATR hash in the challenge’s extensions.legalContext, with the link to the ATR beside it, and the buyer echoes it in its payment. Source: lcp docs: x402 (opens in a new tab)
The payment then carries it where the rail allows: on EVM chains the payer signs the ATR hash itself as the nonce of its EIP-3009 authorization; on Solana it is the transaction’s Memo instruction; on Stellar, the seller’s muxed address takes the first 8 bytes of the ATR hash as its id. Source: lcp docs: rails (opens in a new tab)
One document has one legal context: a challenge offering options on several rails carries one ATR hash, H, so the buyer’s payment on any of them is bound to the same ATR. Source: lcp docs: x402 (opens in a new tab)
The ATR’s binding slot also commits to the request the challenge answers (method, path, query and a SHA-256 of the body) and to every option exactly as issued. Source: lcp docs: binding (opens in a new tab)
Carrying the reference inside x402’s signed Offer and Receipt artifacts is not available today: their EIP-712 types are closed, so it would need an upstream change. Source: LCP §C.4 (opens in a new tab)
The data Read from the published packages when the site was built; nothing here is typed by hand.
The seller’s challenge for the site’s example sale: H and the link to the ATR, in extensions.legalContext
{
"x402Version": 2,
"resource": {
"url": "https://api.seller.example/v1/market-data"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"amount": "40000000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
"maxTimeoutSeconds": 60,
"extra": {
"name": "USDC",
"version": "2"
}
}
],
"extensions": {
"legalContext": {
"info": {
"type": "sha256",
"value": "0x8448dc1b716a5103287cb8f35e36b614ffd91e630ab8b7911833c54748ad5ca6",
"legalContextUrl": "https://atr.seller.example/0x8448dc1b716a5103287cb8f35e36b614ffd91e630ab8b7911833c54748ad5ca6"
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"type": {
"const": "sha256"
},
"value": {
"type": "string",
"pattern": "^0x[0-9a-f]{64}$"
},
"legalContextUrl": {
"type": "string",
"pattern": "^https://"
}
},
"required": [
"type",
"value",
"legalContextUrl"
]
}
}
}
}The code Compiled against the published packages when the site is built.
The seller writes the ATR and advertises the ATR hash in the challenge
src/snippets/seller.ts // The seller, for one sale: write the record, hash its exact bytes, and put the hash and the record's link in the
// x402 challenge it answers the request with.
import { assemble, hash, isRefusal, newAtrId } from '@integraledger/lcp';
import { exactEip3009, requestCommitment, tie, type HttpRequest, type PaymentRequired } from '@integraledger/lcp/x402';
export async function offer(
challenge: PaymentRequired,
request: HttpRequest,
terms: Uint8Array,
store: (bytes: Uint8Array) => Promise<string>, // your storage: it serves these exact bytes at the link it returns
) {
const answered = await requestCommitment(request);
if (isRefusal(answered)) return answered;
const record = await assemble(newAtrId(), tie(challenge.accepts, answered), [['terms', terms]]);
if (isRefusal(record)) return record;
const h = await hash(record.bytes);
const link = await store(record.bytes);
return exactEip3009.advertise(challenge, h, link, challenge.accepts[0]!);
}
The buyer’s gate compares the ATR with the ATR hash before the wallet signs
src/snippets/buyer.ts // The buyer's agent, before its wallet signs: the gate fetches the record from its link, hashes the exact bytes,
// compares them with the fingerprint the seller advertised, and only then hands the signer a payment that carries it.
import { BINDINGS } from '@integraledger/lcp';
import { transact, type Signer } from '@integraledger/terms';
export async function pay(challenge: unknown, signer: Signer, keep: (record: Uint8Array) => Promise<void>) {
const binding = BINDINGS.find((b) => b.id === 'x402/exact/eip155/eip3009')!;
const result = await transact(challenge, binding, signer, fetch);
if ('decline' in result) throw new Error(`${result.decline.code}: ${result.decline.detail}`);
if ('approve' in result) return result; // an agreement payment for the agent to approve first
await keep(result.bytes); // the record it compared, byte for byte
return result.signed; // the payment, carrying the fingerprint
}