From 0e98963a8d9e1eb82d6d42e672ef6ee2171d537c Mon Sep 17 00:00:00 2001 From: "eul.noh" Date: Tue, 30 Jun 2026 10:54:16 +0900 Subject: [PATCH 1/3] docs(x402): fix gasless upto setup and v2 header names (verified e2e) Found via real end-to-end runs against the public Sepolia facilitator (0-ETH buyer paying gasless `upto`): - Seller quickstart omitted `declareEip2612GasSponsoringExtension()` on the `upto` route. Without it the facilitator can't sponsor the buyer's Permit2 approval, so a 0-ETH buyer is rejected with `permit2_allowance_required` once its standing allowance is exhausted. Add the extension + explanation, and add `@x402/extensions` to install/package list. - Header names were the x402 v1 spelling. This (v2) SDK uses `payment-response` (receipt), `payment-signature` (request), `payment-required` (402). Fix the buyer receipt code, the how-it-works diagram/text, and prose across pages. - Soften the `receipt.amount` note (it is optional in SettleResponse). --- x402/facilitator.mdx | 9 ++++++++- x402/how-it-works.md | 14 +++++++------- x402/quickstart-buyers.mdx | 11 ++++++----- x402/quickstart-sellers.mdx | 35 +++++++++++++++++++++++++++++------ x402/sdk.md | 1 + 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/x402/facilitator.mdx b/x402/facilitator.mdx index 5e81e1b..63565ab 100644 --- a/x402/facilitator.mdx +++ b/x402/facilitator.mdx @@ -89,7 +89,7 @@ never configure the facilitator at all; they discover it implicitly from the sel // POST /verify and POST /settle request { "x402Version": 2, - "paymentPayload": { /* the decoded X-PAYMENT payload the buyer signed */ }, + "paymentPayload": { /* the decoded payment payload the buyer signed */ }, "paymentRequirements": { /* the matching accept from the 402 */ } } ``` @@ -136,6 +136,13 @@ When a seller offers an `upto` route, the SDK surfaces the `facilitatorAddress` `eip2612GasSponsoring` extension in the `402` challenge automatically, and a compatible buyer signs the gasless approval path with no extra configuration. +:::note +Gasless `upto` requires the **seller** to declare the EIP-2612 extension on the route +(`declareEip2612GasSponsoringExtension()`). Without it the facilitator can't sponsor the buyer's +Permit2 approval, and a 0-ETH buyer is rejected with `permit2_allowance_required` — see +[Quickstart: Sellers → Accepting `upto` and gasless payments](./quickstart-sellers.mdx#accepting-upto-and-gasless-payments). +::: + ## Self-hosting HPP's facilitator is a deployment of the standard open-source x402 facilitator, so you are not locked diff --git a/x402/how-it-works.md b/x402/how-it-works.md index ec1d463..dd16fe8 100644 --- a/x402/how-it-works.md +++ b/x402/how-it-works.md @@ -16,7 +16,7 @@ until a valid payment is attached. When an unpaid request arrives, it replies with `402` and a list of acceptable payment terms (`accepts`). When a paid request arrives, it verifies and settles the payment, then returns the resource. - **Client (the buyer).** A browser, a backend, or an AI agent that wants the resource. It reads the - `402`, signs a stablecoin authorization for one of the offered terms, and retries with an `X-PAYMENT` header. + `402`, signs a stablecoin authorization for one of the offered terms, and retries with a `payment-signature` header. - **Facilitator.** A service that the resource server delegates two jobs to: **verify** a payment signature, and **settle** it onchain. HPP operates the facilitators so sellers never run an RPC node or hold a settlement key. See [Facilitator](./facilitator.mdx). @@ -39,7 +39,7 @@ until a valid payment is attached. │ (EIP-3009 / Permit2) │ │ │ │ │ │ 4. GET /paid/resource │ │ - │ X-PAYMENT: │ │ + │ payment-signature: │ │ │ ───────────────────────────────► │ 5. verify(payload) │ │ │ ──────────────────────────────► │ │ │ ◄────────────────────────────── │ @@ -48,13 +48,13 @@ until a valid payment is attached. │ │ ──────────────────────────────► │ ──► onchain tx │ │ ◄────────────────────────────── │ │ 8. 200 OK + resource │ │ - │ X-PAYMENT-RESPONSE: │ │ + │ payment-response: │ │ │ ◄─────────────────────────────── │ │ ``` HPP's resource-server middleware uses **serve-then-settle**: it verifies the payment first, serves the response, and settles after a successful result (`status < 400`). The buyer gets a settlement receipt -back in the `X-PAYMENT-RESPONSE` header. +back in the `payment-response` header. ## The 402 challenge @@ -87,7 +87,7 @@ The body of the `402` response (x402 version 2) tells the buyer exactly how it m - `extra` carries the EIP-712 domain the buyer needs to sign (`exact`), and scheme-specific data such as the `facilitatorAddress` for gas-sponsored `upto`. - A multi-network or multi-scheme seller returns several `accepts`; the buyer's SDK selects one (next - section). The settlement receipt comes back base64-encoded in the `X-PAYMENT-RESPONSE` header and + section). The settlement receipt comes back base64-encoded in the `payment-response` header and decodes to `{ success, transaction, network, payer, amount? }`. ## Payment schemes @@ -117,8 +117,8 @@ only USDC.e. See [Facilitator → Gasless settlement](./facilitator.mdx#gasless- > a subset of that. Each payment authorization is **single-use**: it carries a unique nonce and a validity window, so a -captured `X-PAYMENT` header cannot be replayed for a second charge, and the buyer signs a fresh -authorization for every request. +captured `payment-signature` header cannot be replayed for a second charge, and the buyer signs a +fresh authorization for every request. ## Next diff --git a/x402/quickstart-buyers.mdx b/x402/quickstart-buyers.mdx index 92f4e93..6ffd552 100644 --- a/x402/quickstart-buyers.mdx +++ b/x402/quickstart-buyers.mdx @@ -151,12 +151,13 @@ console.log(res.status); // 200 console.log(await res.json()); // the resource // Onchain settlement receipt, if the server returned one. -const header = res.headers.get("x-payment-response"); +const header = res.headers.get("payment-response"); if (header) { const receipt = decodePaymentResponseHeader(header); // { success, transaction, network, payer, amount?, ... } console.log("settled tx:", receipt.transaction, "on", receipt.network); - // For `upto`, `receipt.amount` is the ACTUAL amount charged (≤ the authorized max). + // `receipt.amount`, when present, is the actual amount charged — for `upto` + // this can be less than the authorized maximum. } ``` @@ -199,9 +200,9 @@ The verify/settle decision happens on the resource server via the facilitator; t / `SettleError` are raised *there*, not on the buyer — see [Facilitator → Verify and settle](./facilitator.mdx#verify-and-settle). -> **Use the SDK client to encode payments.** The `X-PAYMENT` header has a specific encoding that the -> resource-server middleware validates. Hand-rolling the header will be rejected — always pay through -> `wrapFetchWithPayment` (or the MCP/agent wrappers built on the same client). +> **Use the SDK client to encode payments.** The `payment-signature` header has a specific encoding +> that the resource-server middleware validates. Hand-rolling the header will be rejected — always pay +> through `wrapFetchWithPayment` (or the MCP/agent wrappers built on the same client). ## Agents and MCP diff --git a/x402/quickstart-sellers.mdx b/x402/quickstart-sellers.mdx index ff6c0af..dce04f4 100644 --- a/x402/quickstart-sellers.mdx +++ b/x402/quickstart-sellers.mdx @@ -16,7 +16,7 @@ the SDK supports. ## 1. Install ```bash -npm install @x402/express @x402/core @x402/evm express +npm install @x402/express @x402/core @x402/evm @x402/extensions express ``` ## 2. Register USDC.e @@ -124,13 +124,36 @@ app.listen(4021, () => console.log("x402 resource server on :4021")); ``` That's it. An unpaid `POST /paid/hello` now returns `402` with your terms; a request carrying a valid -`X-PAYMENT` header is verified, served, and settled onchain — and the handler returns the resource. +`payment-signature` header is verified, served, and settled onchain — and the handler returns the resource. -## Accepting both schemes +## Accepting `upto` and gasless payments -To offer `exact` and `upto` on the same route, list both in `accepts` (priority order — the buyer's -SDK picks the first it supports) and register both schemes on the network. `upto` routes can also -advertise gasless settlement; see [Facilitator → Gasless settlement](./facilitator.mdx#gasless-settlement-upto). +To also accept the `upto` scheme, list it in the route's `accepts` (in priority order — the buyer's +SDK picks the first it supports) and register it on the network alongside `exact`. + +For **gasless `upto`** — letting a buyer with **zero native ETH** pay — the route must declare the +**EIP-2612 gas-sponsoring extension**. Without it, the facilitator cannot sponsor the buyer's Permit2 +approval, and a buyer without a standing allowance is rejected with `permit2_allowance_required`. + +```ts +import { declareEip2612GasSponsoringExtension } from "@x402/extensions"; + +const routes: RoutesConfig = { + "POST /paid/hello": { + description: "A paid hello-world endpoint.", + accepts: [ + { scheme: "exact", network: "eip155:190415", payTo: PAY_TO, price, maxTimeoutSeconds: 600 }, + { scheme: "upto", network: "eip155:190415", payTo: PAY_TO, price, maxTimeoutSeconds: 600 }, + ], + // Required for gasless `upto`: lets the facilitator sponsor the buyer's + // one-time Permit2 approval via EIP-2612 (so a 0-ETH wallet can pay). + extensions: { ...declareEip2612GasSponsoringExtension() }, + }, +}; +``` + +The facilitator must advertise `eip2612GasSponsoring` for the network (HPP Mainnet and Sepolia both +do — see [Facilitator → Gasless settlement](./facilitator.mdx#gasless-settlement-upto)). ## Next diff --git a/x402/sdk.md b/x402/sdk.md index e836452..d3f09f0 100644 --- a/x402/sdk.md +++ b/x402/sdk.md @@ -18,6 +18,7 @@ recognizes Base, Polygon, and other EVM chains. | `@x402/core` | both | `x402Client`, `HTTPFacilitatorClient`, `RoutesConfig` types. | | `@x402/evm` | both | EVM scheme clients (`exact`, `upto`) and the `DEFAULT_STABLECOINS` asset map. | | `@x402/express` | sellers | `paymentMiddleware` and `x402ResourceServer` for Express. | +| `@x402/extensions` | sellers | Route extensions, e.g. `declareEip2612GasSponsoringExtension()` for gasless `upto`. | | `@x402/fetch` | buyers | `wrapFetchWithPayment` to make a paying `fetch`. | | `viem` | buyers | Wallet account and chain utilities (`privateKeyToAccount`, `defineChain`). | From 3830e0c02c18ed172b6396923123ae608f29ae81 Mon Sep 17 00:00:00 2001 From: "eul.noh" Date: Tue, 30 Jun 2026 11:03:21 +0900 Subject: [PATCH 2/3] =?UTF-8?q?docs(x402):=20correct=20gas/ETH=20guidance?= =?UTF-8?q?=20=E2=80=94=20facilitator=20relays=20both=20schemes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified e2e against the public Sepolia facilitator with a 0-ETH buyer: both `exact` and `upto` settle on-chain while the buyer's ETH stays 0 — the facilitator submits settlement and pays the gas. The previous wording claimed `exact` needs buyer ETH and only `upto` could be gasless; that is backwards. Correct the Buyers prerequisites/intro and the Networks & Token funding section: the buyer needs no ETH for `exact`, and none for `upto` when the seller enables EIP-2612 sponsoring (only a non-sponsored upto's one-time Permit2 approval costs the buyer a little ETH). --- x402/networks-and-token.mdx | 8 +++++--- x402/quickstart-buyers.mdx | 14 ++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/x402/networks-and-token.mdx b/x402/networks-and-token.mdx index 9194f7c..ac8c054 100644 --- a/x402/networks-and-token.mdx +++ b/x402/networks-and-token.mdx @@ -75,13 +75,15 @@ Because USDC.e has **6 decimals**, prices in the x402 `accepts` are expressed as ## Funding -Because every payment is denominated in **USDC.e**, a buyer needs a USDC.e balance before it can pay — -ETH alone only covers gas (and `upto` can be made gasless, needing no ETH at all). +Because every payment is denominated in **USDC.e**, a buyer needs a USDC.e balance before it can pay. +The facilitator submits the settlement transaction and pays the gas, so a **buyer normally needs no ETH**. -- **Gas (ETH).** On testnet, get HPP Sepolia ETH from the [HPP Sepolia Faucet](https://faucet.hpp.io/). - **USDC.e.** USDC.e is Bridged USDC. Bring it onto HPP by bridging from the parent chain — see the [Bridge guide](/community/bridge). For testnet USDC.e to run the quickstarts, reach out through [Official Links](/community/official-links). +- **ETH (rarely needed).** Only a buyer paying `upto` against a seller *without* EIP-2612 sponsoring + needs a little ETH, for the one-time Permit2 approval. Get testnet ETH from the + [HPP Sepolia Faucet](https://faucet.hpp.io/). Sellers/operators also need ETH to deploy contracts. Verify a balance any time on the [block explorer](https://explorer.hpp.io/) (mainnet) or [Sepolia explorer](https://sepolia-explorer.hpp.io/) by looking up the USDC.e contract above. diff --git a/x402/quickstart-buyers.mdx b/x402/quickstart-buyers.mdx index 6ffd552..fd16cd2 100644 --- a/x402/quickstart-buyers.mdx +++ b/x402/quickstart-buyers.mdx @@ -16,10 +16,12 @@ USDC.e authorization with your wallet, and retries automatically — your code j - A wallet (private key) holding **USDC.e** on the target network — payments are denominated in USDC.e, not ETH. See [Networks & Token → Funding](./networks-and-token.mdx#funding) for how to acquire it. -- For the **`exact`** scheme, the wallet also needs a little **ETH** for gas. -- For **`upto`**, the buyer normally needs a one-time Permit2 approval. On HPP this approval can be - gas-sponsored via EIP-2612, so a wallet with **only USDC.e and zero ETH** can pay — see - [Facilitator → Gasless settlement](./facilitator.mdx#gasless-settlement-upto). +- The facilitator submits the settlement transaction and pays the gas, so the buyer **does not need ETH**: + - **`exact`** — the buyer only signs an EIP-3009 authorization; the facilitator relays it. Zero ETH. + - **`upto`** — also relayed, but it needs a one-time Permit2 approval. On HPP that approval is + gas-sponsored via EIP-2612 when the seller enables it, so the buyer still needs **zero ETH** (see + [Facilitator → Gasless settlement](./facilitator.mdx#gasless-settlement-upto)). Only against a seller + or chain *without* sponsoring does the buyer pay a little ETH for that first approval. :::caution Use a dedicated test wallet on **HPP Sepolia** while integrating. Never hard-code a mainnet private @@ -36,8 +38,8 @@ npm install @x402/core @x402/evm @x402/fetch viem ## 2. Wrap fetch with payment Build a scheme client backed by your wallet, hand it to an `x402Client`, and wrap `fetch`. The buyer -needs **USDC.e** to pay; for `exact` it also needs a little ETH for gas, while `upto` can be made -[gasless](./facilitator.mdx#gasless-settlement-upto). +needs **USDC.e** to pay; the facilitator submits settlement and pays the gas, so the buyer needs no +ETH for `exact` and — with seller-enabled sponsoring — none for [`upto`](./facilitator.mdx#gasless-settlement-upto) either. From 702bc005d2a5d246660ec8f5fdddafbb5c762d00 Mon Sep 17 00:00:00 2001 From: "eul.noh" Date: Tue, 30 Jun 2026 11:11:33 +0900 Subject: [PATCH 3/3] docs(x402): align gas framing in how-it-works; clarify seller upto snippet - how-it-works: state that the facilitator relays settlement and pays gas for `exact` (buyer needs no ETH), and that `upto`'s only extra cost is the one-time Permit2 approval, sponsored when the seller enables it. Keeps the scheme overview consistent with the Buyers/Networks guidance. - sellers: note the "Accepting upto" snippet replaces the step-4 route and reuses the same `price` / `PAY_TO`. --- x402/how-it-works.md | 13 ++++++++----- x402/quickstart-sellers.mdx | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/x402/how-it-works.md b/x402/how-it-works.md index dd16fe8..b8bc75e 100644 --- a/x402/how-it-works.md +++ b/x402/how-it-works.md @@ -99,8 +99,9 @@ HPP supports two upstream-standard schemes: ### `exact` Pay an **exact** amount using [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) -(`transferWithAuthorization`). The buyer signs a transfer for the precise price; the facilitator -submits it. Simple and final — best when the price is known up front. +(`transferWithAuthorization`). The buyer only signs a transfer for the precise price; the facilitator +submits it onchain and pays the gas, so the buyer needs no ETH. Simple and final — best when the price +is known up front. ### `upto` @@ -108,9 +109,11 @@ Authorize **up to** a maximum using [Permit2](https://github.com/Uniswap/permit2 actual amount consumed (which may be less than the cap). This fits metered or usage-based pricing where the final cost isn't known until the work runs. -On HPP, `upto` also supports **gasless settlement**: the one-time Permit2 approval can be sponsored -via [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612), so a paying agent needs **zero native ETH** — -only USDC.e. See [Facilitator → Gasless settlement](./facilitator.mdx#gasless-settlement-upto). +Like `exact`, the settlement itself is relayed by the facilitator. The one extra cost is `upto`'s +**one-time Permit2 approval** — and on HPP that approval is sponsored via +[EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) **when the seller enables it**, so a paying agent +needs **zero native ETH**, only USDC.e. See +[Facilitator → Gasless settlement](./facilitator.mdx#gasless-settlement-upto). > Both schemes are part of the standard x402 specification. A facilitator advertises exactly which > `(network, scheme)` pairs it supports at its `/supported` endpoint, and the seller's accepts must be diff --git a/x402/quickstart-sellers.mdx b/x402/quickstart-sellers.mdx index dce04f4..4571f93 100644 --- a/x402/quickstart-sellers.mdx +++ b/x402/quickstart-sellers.mdx @@ -138,6 +138,8 @@ approval, and a buyer without a standing allowance is rejected with `permit2_all ```ts import { declareEip2612GasSponsoringExtension } from "@x402/extensions"; +// Replaces the route from step 4. `PAY_TO` and the `price` object are the same +// as before (here `price` is the { amount, asset, extra } object shown above). const routes: RoutesConfig = { "POST /paid/hello": { description: "A paid hello-world endpoint.",