x402
The legalContext extension, the request commitment, and the x402 pairings.
x402 is HTTP 402 payment: the seller answers a request with a PaymentRequired
document listing the options it accepts, and the buyer retries with a payment for one of them. This package reads and
writes x402 version 2 documents. Import the x402 pieces from @integraledger/lcp/x402.
The legalContext extension
Every x402 pairing advertises H in the same place: the document's extensions.legalContext, as x402's extension
mechanism defines it (an info object and a JSON Schema for it).
{
"extensions": {
"legalContext": {
"info": {
"type": "sha256",
"value": "0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
"legalContextUrl": "https://atr.seller.example/0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
},
"schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object" }
}
}
}LEGAL_CONTEXT is the extension's key and LEGAL_CONTEXT_SCHEMA the full schema every pairing writes. The buyer
echoes extensions unchanged in its payment, as x402 requires. Where the pairing's payment carries H in nothing
public, info also carries legalContextAgreementUrl
(the agreement URL).
One document has one legal context. Each pairing that advertises into the same document with the same H and link
leaves it as it is; a different H or link is refused as x402/legal-context-conflict. A challenge offering options
on several rails therefore carries one H, and the buyer's payment on any of them is bound to the same ATR.
The request commitment
requestCommitment({ method, target, body }) commits to the request the challenge answers: the method as received,
the request-target split at its first ? into path and query, and bodyDigest, SHA-256 over the body bytes
exactly as received. The target is at most 8 KiB of visible ASCII starting with /, and the body at most 1 MiB.
tie(accepts, request) is the ATR's binding slot for every x402 pairing: ["x402", { accepts, request }], with every
option exactly as issued. issuedDigest is SHA-256 over the RFC 8785 form of one option or one commitment, for a
seller that keeps a digest of what it issued.
Advertising several options
This example advertises one H into a challenge with two EVM options, one for EIP-3009 and one for Permit2, and shows which pairing serves each option:
import { , } from "@integraledger/lcp";
import {
,
,
,
type ,
type ,
} from "@integraledger/lcp/x402";
const : = {
: "exact",
: "eip155:84532",
: "10000",
: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
: 60,
: { : "USDC", : "2" },
};
const : = { ..., : { ...., : "permit2" } };
const : = {
: 2,
: { : "https://api.seller.example/v1/quote" },
: [, ],
};
const = await (new ().("the ATR's bytes"));
const = `https://atr.seller.example/${}`;
const = .(, , , );
if ("refused" in ) throw new (.);
const = .(, , , );
if ("refused" in ) throw new (.);
.((), ());
const = .();
.("refused" in ? . : `${...} option, H ${. === ? "matches" : "differs"}`);
const = await (new ().("another ATR"));
const = .(, , , );
.("refused" in ? . : "placed");
.(await ({ : "POST", : "/v1/quote?tier=1", : new ().("{}") }));x402/exact/eip155/eip3009 x402/exact/eip155/permit2
1 option, H matches
x402/legal-context-conflict
{
method: 'POST',
path: '/v1/quote',
query: 'tier=1',
bodyDigest: '0x44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a'
}The x402 pairings
The x402 pairings, by scheme. The pairings reference states what each one proves, and Rails gives each rail's field.
| Scheme | Pairings | Where H rides |
|---|---|---|
exact on EVM | x402/exact/eip155/eip3009, x402/exact/eip155/permit2 | The signed authorization's nonce. |
exact on EVM, ERC-7710 | x402/exact/eip155/erc7710-salt, x402/exact/eip155/erc7710 | The salt of the redeemed leaf delegation, through MetaMask's DelegationManager; or, where the delegation signs no hash, the echoed extension only. |
upto on EVM | x402/upto/eip155/permit2 | The Permit2 nonce, for a signed maximum. |
auth-capture on EVM | x402/auth-capture/eip155/eip3009, x402/auth-capture/eip155/permit2 | The escrow payment's salt, or its salt nonce when the option binds a receiver authorizer or policy. |
batch-settlement | x402/batch-settlement/eip155, x402/batch-settlement/solana, x402/batch-settlement/cloudflare | The channel's salt, the opening transaction's memo, or each request's echoed extension. See Channels, sessions and subscriptions. |
exact on other rails | Solana, Stellar, XRPL, Hedera, Algorand, Aptos, Cardano, Casper, Concordium, NEAR, Polkadot Asset Hub, Starknet, Sui, Tron, TON, Lightning | Each rail's own field; see Rails. |
upto on Solana | x402/upto/solana | The Memo instruction of the transaction that opens a one-request payment channel. |
Building on x402
build(choice, h) takes the document, the chosen option, the payer's account and the time in Unix seconds:
interface X402Choice {
required: PaymentRequired;
accepted: PaymentRequirements;
from: Hex;
now: number;
}It returns what the buyer's signer signs, in the rail's own form, and complete, which turns the signer's answer into
the x402 payment. On x402/exact/eip155/eip3009 that is EIP-712 typed data; on the other EVM pairings a request whose
kind names the form ("eip712", or "erc7710" for a delegation); on other rails a transaction or message to sign.
Reading the payment
bound(payment) reads H from the payment; reference(payment) gives the keys to find its settlement;
status(ref, reader) reads the settlement through an EvmReader you supply, which answers three JSON-RPC calls:
eth_getTransactionReceipt, eth_getBlockByNumber for the safe and finalized marks, and
eth_getTransactionByHash. A reader that throws ReaderError, or answers for another network, leaves the payment
pending, never failed. pairingOfPayment(payment) names the pairing that serves a presented EVM payment.
Last updated on