Skip to content
Integra Protocol

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.

SchemePairingsWhere H rides
exact on EVMx402/exact/eip155/eip3009, x402/exact/eip155/permit2The signed authorization's nonce.
exact on EVM, ERC-7710x402/exact/eip155/erc7710-salt, x402/exact/eip155/erc7710The salt of the redeemed leaf delegation, through MetaMask's DelegationManager; or, where the delegation signs no hash, the echoed extension only.
upto on EVMx402/upto/eip155/permit2The Permit2 nonce, for a signed maximum.
auth-capture on EVMx402/auth-capture/eip155/eip3009, x402/auth-capture/eip155/permit2The escrow payment's salt, or its salt nonce when the option binds a receiver authorizer or policy.
batch-settlementx402/batch-settlement/eip155, x402/batch-settlement/solana, x402/batch-settlement/cloudflareThe channel's salt, the opening transaction's memo, or each request's echoed extension. See Channels, sessions and subscriptions.
exact on other railsSolana, Stellar, XRPL, Hedera, Algorand, Aptos, Cardano, Casper, Concordium, NEAR, Polkadot Asset Hub, Starknet, Sui, Tron, TON, LightningEach rail's own field; see Rails.
upto on Solanax402/upto/solanaThe 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.

Edit on GitHub

Last updated on

On this page