Docs / Protocol reference
x402 extension
LOPW is an x402 v2 extension named lopw. It changes nothing in verify or settle: any conformant facilitator works, and a buyer that ignores the extension still gets a normal x402 purchase. Checked against x402 2.28.0.
| Item | Carried in |
|---|---|
| Warranty offer | The 402 PaymentRequired.extensions.lopw: { info: { version, network, registry, service_id, offer }, schema }. The buyer echoes it in PaymentPayload.extensions.lopw |
| Payment | Standard x402 exact scheme, unchanged |
| Service receipt | The settlement response PAYMENT-RESPONSE.extensions.lopw: { receipt }, plus duplicate only in the DUPLICATE_RECEIPT fault mode of reference services |
The offer in the 402
HTTP/1.1 402 Payment Required
// PaymentRequired (x402 v2 envelope), with:
"extensions": {
"lopw": {
"info": {
"version": 1,
"network": "testnet", // the binding's network name
"registry": "C…", // Bond Registry contract address
"service_id": "…64 hex…",
"offer": "AAAAEQAAAAEAAAAK…" // base64 ScVal XDR of SignedOffer { offer, signature }
},
"schema": { "type": "object" }
}
}offer is declared a dynamic info field (dynamicInfoFields: ["offer"]) so per-request nonces do not break x402’s echo validation. The middleware issues an offer only when the service is Active and only for the payment requirement whose network matches the binding’s CAIP-2 id; otherwise the 402 carries plain x402 terms.
The buyer's echo
// PaymentPayload (sent by the buyer with the payment), with:
"extensions": { "lopw": { "info": { …the same info object… }, "schema": { "type": "object" } } }The buyer SDK copies extensions.lopw from the requirements into the payload (enrichPaymentPayload). The seller only issues receipts for offers it signed itself: it decodes the echoed offer and verifies the signature with its own key before building the receipt.
The receipt in PAYMENT-RESPONSE
HTTP/1.1 200 OK
PAYMENT-RESPONSE: <base64 settlement response>
// decoded settlement response, with:
"success": true, "transaction": "…64 hex…", "payer": "G…",
"extensions": {
"lopw": {
"receipt": "AAAAEQAAAAEAAAAO…", // base64 ScVal XDR of SignedReceipt { receipt, signature }
"duplicate": "…" // optional; reference services' fault injection only
}
}The receipt is attached through x402’s enrichSettlementResponse hook, after the facilitator reports a successful settlement, using the settlement transaction as payment_reference and the payer as buyer. It travels in the standard x402 envelope every client already decodes (decodePaymentResponseHeader).
Handler inputs
| Response header | Read by the middleware as |
|---|---|
LOPW-Data-As-Of | receipt.data_as_of (Unix seconds; absent means 0) |
LOPW-Oracle-Evidence | receipt.oracle: JSON with oracle_timestamp, oracle_price, reported_value as decimal strings; the oracle contract and asset come from the terms |
The middleware reads the HTTP status, the raw request (method, original URL, raw body) and the response body through request-scoped context (lopwRequestContext()) and x402’s transport context. The two headers are ordinary response headers that a client may read as well.
Why LOPW keeps its own receipt format
x402 ships a generic offer-receipt extension that signs JWS or EIP-712 payloads with resource URL, payer and transaction, but no terms, request or response hashes, or oracle evidence, and a Soroban contract cannot cheaply verify JWS. LOPW receipts must be verifiable on-chain, so LOPW keeps XDR plus Ed25519. The two extensions can be served side by side.
SDK surface on the wire
| Side | Package | Exports |
|---|---|---|
| Seller | @lopw/seller-middleware | createLopwExtension(config) (returns a ResourceServerExtension with refresh()), lopwRequestContext(), LOPW_EXTENSION_KEY, DATA_AS_OF_HEADER, ORACLE_EVIDENCE_HEADER, types LopwSellerConfig, LopwOfferInfo, LopwReceiptInfo, ReceiptFaults |
| Buyer | @lopw/buyer-sdk | LopwBuyer, BuyerPolicy, WarrantyRecord, WarrantedResponse, ReceiptStore, FileReceiptStore, MemoryReceiptStore, WarrantyRefused, LOPW_EXTENSION_KEY |
| Both | @lopw/protocol | Types, Predicate, XDR encoders, offerHash/receiptHash/termsHash/requestHash/responseHash, sign and verify, base64 codecs, evaluatePredicate/breaches, StellarBinding, getNetwork, CLAIM_ERRORS |