Skip to content
Integra Protocol

Seller

Assemble the ATR, advertise its hash, check the payment, and read the settlement.

The seller is the party serving the resource. Its side of the binding has four steps: assemble the ATR for this request, advertise H in the challenge, read H back from the payment it receives, and read the settlement from the rail. This guide walks them on x402 with x402/exact/eip155/eip3009. Every pairing has the same members, so the shape is the same on every surface.

sequenceDiagram
  participant B as Buyer
  participant S as Seller
  participant St as Seller's storage
  participant F as Facilitator
  B->>S: GET /v1/quote
  Note over S: requestCommitment, tie, assemble
  S->>St: write the ATR's bytes at the link
  Note over S: advertise H and the link in the 402
  S-->>B: 402 PaymentRequired
  B->>S: the payment, carrying H
  Note over S: bound(payment) equals the H it issued
  S->>F: verify and settle
  F-->>S: the settlement transaction
  Note over S: reference, then status through a reader

The facilitator is the x402 role that verifies and settles. It is the seller's to choose; this package does not call it.

1. Assemble the ATR for this request

The ATR's binding slot ties the record to one payment. On x402, tie(accepts, request) records every option the challenge offers, exactly as issued, and the commitment to the request it answers: the method, the path, the query and SHA-256 over the body exactly as received. The party slots carry whatever the parties agreed, as bytes.

Write the ATR's bytes to your own storage before the challenge goes out, and serve them unchanged at an https link. If the write fails, send no challenge: a buyer that cannot fetch the record declines.

3. Advertise H

advertise(doc, h, link, offer) returns a copy of the challenge with extensions.legalContext set to {"type":"sha256","value":H,"legalContextUrl":link}, and leaves every other extension and every option as they were. On pairings that carry H in the option itself, such as x402/exact/solana (extra.memo), it also places H there.

4. Check the payment

bound(payment) reads H from what the buyer presented: here, the EIP-3009 authorization's nonce. Match it with hashEquals against the H you issued for this request. The token contract verifies the signature when it executes the transfer; bound does not.

Refuse a payment whose H you did not issue for this request, and one whose H you have already accepted. Those two facts are your records, not this package's.

5. Read the settlement

reference(payment) gives the keys for finding this payment on chain: the network, the token, the time bound, a digest of the transfer, and the log that carries H. After the facilitator settles, status reads the settlement transaction through a reader you supply, and recover reads H back from that transaction alone.

The whole flow

This program runs every step above. A Map stands in for the seller's storage, a random key for the buyer's signer, and a reader built from one receipt for the chain. No network is used.

import { , ,  } from "@integraledger/lcp";
import { , type EvmReader, type  } from "@integraledger/lcp/evm";
import {
  ,
  ,
  ,
  type ,
  type ,
} from "@integraledger/lcp/x402";
import type {  } from "viem";
import { ,  } from "viem/accounts";

const :  = {
  : "exact",
  : "eip155:84532",
  : "10000",
  : "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
  : "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
  : 60,
  : { : "USDC", : "2" },
};
const :  = {
  : 2,
  : { : "https://api.seller.example/v1/quote" },
  : [],
};

// 1. Assemble the ATR for this request.
const  = await ({ : "GET", : "/v1/quote", : new () });
if ("refused" in ) throw new (.);
const  = new ().('{"text":"One quote for 10000 base units of USDC."}');
const  = await ((), (., ), [["terms", ]]);
if ("refused" in ) throw new (.);

// 2. Store the bytes and link them.
const  = `https://atr.seller.example/${.}`;
const  = new ([[, .]]);

// 3. Advertise H.
const  = .(, ., , );
if ("refused" in ) throw new (.);
.("advertised:", .(.?.["legalContext"]?.) === .({
  : "sha256",
  : .,
  : ,
}));

// The buyer's side, in brief: see the buyer guide.
const  = (());
const  = await .({ : , : , : ., : 1790000000 }, .);
if ("refused" in ) throw new (.);
const  = .(await .(. as ));
if ("refused" in ) throw new (.);

// 4. Check the payment.
const  = await .();
if (typeof  !== "string") throw new (.);
.("bound to the issued H:", (, .), .());

// 5. Read the settlement. The facilitator's settle answer names the transaction.
const  = await .();
if ("refused" in ) throw new (.);
const :  = `0x${"11".(32)}`;
const : EvmReader = {
  : "eip155:84532",
  : async () => ({
    : 1,
    : 100n,
    : [
      {
        : . as ,
        : [, `0x${..(2).().(64, "0")}`, ],
        : "0x",
      },
    ],
  }),
  : async () => ( === "finalized" ? 100n : 105n),
  : async () => null,
};
const  = await .({ : ., : ., ,  }, );
.("status:", ., "finality" in  ? . : .);
const  = await .({ : ., : .,  }, );
.("recovered from the chain alone:", typeof  === "string" && (, .));
advertised: true
bound to the issued H: true true
status: settled finalized
recovered from the chain alone: true

What the seller keeps

Keep H, the link and the reference for each payment. The bytes live in your storage at the link; the buyer keeps its own copy.

What this package does not do

It holds no key and signs nothing. It does not call the facilitator, move funds, or decide whether to serve a request. It never compares amount, payee, asset, timing or payer with the ATR's content: the binding is the one thing it checks.

On other surfaces

The members are the same everywhere; what tie, advertise and bound handle differs:

Edit on GitHub

Last updated on

On this page