x402
Build internet-native payments with the x402 open protocol - HTTP 402 Payment Required for on-chain micropayments with no accounts or API keys. Use when developing paid APIs, paywalled content, AI agent payment flows, or MCP tools that charge per call. Covers the TypeScript, Pyth
Install
npx skills add https://github.com/tenequm/skills/tree/main/skills/x402
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install tenequm-skills@llmmart
git clone https://github.com/tenequm/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole tenequm/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
x402 Protocol Development
x402 is an open standard (Apache-2.0) that activates the HTTP 402 Payment Required status code for programmatic, on-chain payments. Originally created by Coinbase, now maintained by the x402 Foundation. No accounts, sessions, or API keys required - clients pay with signed crypto transactions directly over HTTP.
When to Use
- Building a paid API that accepts crypto micropayments
- Adding paywall to web content or endpoints
- Enabling AI agents to autonomously pay for resources
- Integrating MCP tools that require payment
- Building agent-to-agent (A2A) payment flows
- Working with EVM (Base, Ethereum, MegaETH, Monad, Polygon, Stable, Arbitrum), Solana, Stellar, Aptos, NEAR, or XRPL payment settlement
- Implementing usage-based billing with the
uptoscheme (LLM tokens, bandwidth, compute) - Running an in-process facilitator (self-facilitation) without external facilitator dependency
Core Architecture
Three roles in every x402 payment:
- Resource Server - protects endpoints, returns 402 with payment requirements
- Client - signs payment authorization, retries request with payment header
- Facilitator - verifies signatures, settles transactions on-chain
Payment flow (HTTP transport):
Client -> GET /resource -> Server returns 402 + PAYMENT-REQUIRED header
Client -> signs payment -> retries with PAYMENT-SIGNATURE header
Server -> POST /verify to Facilitator -> POST /settle to Facilitator
Server -> returns 200 + PAYMENT-RESPONSE header + resource data
Quick Start: Seller (TypeScript + Express)
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const app = express();
const payTo = "0xYourWalletAddress";
const facilitator = new HTTPFacilitatorClient({ url: "https://x402.org/facilitator" });
const server = new x402ResourceServer(facilitator)
.register("eip155:84532", new ExactEvmScheme());
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: [
{ scheme: "exact", price: "$0.001", network: "eip155:84532", payTo },
],
description: "Weather data",
mimeType: "application/json",
},
},
server,
),
);
app.get("/weather", (req, res) => {
res.json({ weather: "sunny", temperature: 70 });
});
app.listen(4021);
Install: npm install @x402/express @x402/core @x402/evm
Quick Start: Buyer (TypeScript + Axios)
import { x402Client, wrapAxiosWithPayment } from "@x402/axios";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import axios from "axios";
const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const client = new x402Client();
registerExactEvmScheme(client, { signer });
const api = wrapAxiosWithPayment(axios.create(), client);
const response = await api.get("http://localhost:4021/weather");
// Payment handled automatically on 402 response
Install: npm install @x402/axios @x402/evm viem
Quick Start: Seller (Python + FastAPI)
from fastapi import FastAPI
from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption
from x402.http.middleware.fastapi import PaymentMiddlewareASGI
from x402.http.types import RouteConfig
from x402.mechanisms.evm.exact import ExactEvmServerScheme
from x402.server import x402ResourceServer
app = FastAPI()
facilitator = HTTPFacilitatorClient(FacilitatorConfig(url="https://x402.org/facilitator"))
server = x402ResourceServer(facilitator)
server.register("eip155:84532", ExactEvmServerScheme())
routes = {
"GET /weather": RouteConfig(
accepts=[PaymentOption(scheme="exact", pay_to="0xYourAddress", price="$0.001", network="eip155:84532")],
mime_type="application/json",
description="Weather data",
),
}
app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server)
@app.get("/weather")
async def get_weather():
return {"weather": "sunny", "temperature": 70}
Install: pip install "x402[fastapi,evm]"
Quick Start: Seller (Go + Gin)
import (
x402http "github.com/x402-foundation/x402/go/v2/http"
ginmw "github.com/x402-foundation/x402/go/v2/http/gin"
evm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/server"
)
facilitator := x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{URL: facilitatorURL})
routes := x402http.RoutesConfig{
"GET /weather": {
Accepts: x402http.PaymentOptions{
{Scheme: "exact", Price: "$0.001", Network: "eip155:84532", PayTo: evmAddress},
},
Description: "Weather data",
MimeType: "application/json",
},
}
r.Use(ginmw.X402Payment(ginmw.Config{
Routes: routes,
Facilitator: facilitator,
Schemes: []ginmw.SchemeConfig{{Network: "eip155:84532", Server: evm.NewExactEvmScheme()}},
}))
Install: go get github.com/x402-foundation/x402/go/v2
Multi-Network Support (EVM + Solana)
Servers can accept payment on multiple networks simultaneously:
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { ExactSvmScheme } from "@x402/svm/exact/server";
const server = new x402ResourceServer(facilitator)
.register("eip155:84532", new ExactEvmScheme())
.register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme());
// Route config with both networks
"GET /weather": {
accepts: [
{ scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: evmAddress },
{ scheme: "exact", price: "$0.001", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress },
],
}
Clients register both schemes and auto-select based on server requirements:
const client = new x402Client();
registerExactEvmScheme(client, { signer: evmSigner });
registerExactSvmScheme(client, { signer: svmSigner });
Supported Networks
| Network | CAIP-2 ID | Status |
|---|---|---|
| Base Mainnet | eip155:8453 |
Mainnet |
| Base Sepolia | eip155:84532 |
Testnet |
| MegaETH Mainnet | eip155:4326 |
Mainnet (MegaUSD default, 18 decimals) |
| Solana Mainnet | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
Mainnet |
| Solana Devnet | solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 |
Testnet |
| Stellar Mainnet | stellar:pubnet |
Mainnet (TypeScript SDK only) |
| Stellar Testnet | stellar:testnet |
Testnet (TypeScript SDK only) |
| Aptos Mainnet | aptos:1 |
Mainnet (TypeScript SDK only) |
| Aptos Testnet | aptos:2 |
Testnet (TypeScript SDK only) |
| Monad Mainnet | eip155:143 |
Mainnet |
| Polygon Mainnet | eip155:137 |
Mainnet |
| Polygon Amoy | eip155:80002 |
Testnet |
| Stable Mainnet | eip155:988 |
Mainnet |
| Stable Testnet | eip155:2201 |
Testnet |
| Arbitrum One | eip155:42161 |
Mainnet |
| Arbitrum Sepolia | eip155:421614 |
Testnet |
| XDC Network Mainnet | eip155:50 |
Mainnet (USDC) |
| XDC Apothem Testnet | eip155:51 |
Testnet (USDC) |
| Mezo Mainnet | eip155:31612 |
Mainnet (mUSD, 18 decimals, Permit2 + EIP-2612) |
| Mezo Testnet | eip155:31611 |
Testnet (mUSD, Permit2 + EIP-2612) |
| Avalanche | eip155:43114 |
Runtime registration only (no default asset; community facilitators) |
| Radius Mainnet | eip155:723487 |
Mainnet (SBC default) |
| Radius Testnet | eip155:72344 |
Testnet (SBC default) |
| ADI Chain | eip155:36900 |
Mainnet (USDC.e default) |
| HPP Mainnet | eip155:190415 |
Mainnet (Bridged USDC default) |
| HPP Sepolia | eip155:181228 |
Testnet (Bridged USDC default) |
| TON Mainnet | tvm:-239 |
Mainnet (jetton transfers; Python + TypeScript SDK) |
| TON Testnet | tvm:-3 |
Testnet |
| Hedera Mainnet | hedera:mainnet |
Mainnet (HBAR + HTS tokens) |
| Hedera Testnet | hedera:testnet |
Testnet |
| Algorand Mainnet | algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k |
Mainnet (USDC ASA) |
| Algorand Testnet | algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDe |
Testnet (USDC ASA) |
| Keeta Mainnet | keeta:21378 |
Mainnet (TypeScript SDK) |
| Keeta Testnet | keeta:1413829460 |
Testnet (TypeScript SDK) |
| Concordium Mainnet | ccd:9dd9ca4d19e9393877d2c44b70f89acb |
Mainnet (native CCD, 6 decimals; TypeScript SDK) |
| Concordium Testnet | ccd:4221332d34e1694168c2a0c0b3fd0f27 |
Testnet (native CCD; TypeScript SDK) |
| Igra Mainnet | eip155:38833 |
Mainnet (USDC, Permit2 only - no EIP-3009, no EIP-2612) |
| NEAR Mainnet | near:mainnet |
Mainnet (NEP-141 USDC, relayer-sponsored; TypeScript SDK) |
| NEAR Testnet | near:testnet |
Testnet (TypeScript SDK) |
| XRPL Mainnet | xrpl:0 |
Mainnet (XRP + IOUs, no fee sponsorship; TypeScript SDK) |
| XRPL Testnet | xrpl:1 |
Testnet |
| XRPL Devnet | xrpl:2 |
Devnet |
Algorand CAIP-2 ids changed. The reference is the URL-safe base64 genesis hash truncated to the first 32 characters. The older padded full-hash form (
algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=) no longer matches; SDKs normalize legacy ids on input, but emit the truncated form.
Default facilitator (https://x402.org/facilitator) supports Base Sepolia, Solana Devnet, Algorand Testnet, Stellar Testnet, Aptos Testnet, Hedera Testnet, and XRPL Testnet. On Base Sepolia it advertises exact, upto, and batch-settlement, plus the builder-code, eip2612GasSponsoring, and erc20ApprovalGasSponsoring extensions.
Not a production default. Upstream now states explicitly that the public
x402.orgfacilitator is intended for development and testnet workflows - do not assume it is the default path for production mainnet routes. See the facilitator directory for production options.
SDK Packages
TypeScript v2.20.0 (npm, GitHub)
| Package | Purpose |
|---|---|
@x402/core |
Core types, client, server, facilitator |
@x402/evm |
EVM exact + upto schemes (EIP-3009, Permit2). Upto via @x402/evm/upto/* subpaths |
@x402/svm |
Solana scheme (SPL TransferChecked) |
@x402/stellar |
Stellar scheme (SEP-41 Soroban token transfers) |
@x402/aptos |
Aptos scheme (Fungible Asset transfers) |
@x402/avm |
Algorand (AVM) scheme |
@x402/hedera |
Hedera scheme (HBAR + HTS fungible-asset transfers) |
@x402/tvm |
TON scheme (jetton transfers) |
@x402/keeta |
Keeta scheme (exact) |
@x402/concordium |
Concordium scheme (native CCD, exact) |
@x402/near |
NEAR scheme (NEP-366 SignedDelegate + NEP-141 ft_transfer, relayer-sponsored) |
@x402/xrpl |
XRPL scheme (payer-signed Payment, no fee sponsorship). Tagged 2.20.0 but not yet on npm - build from source |
@x402/express |
Express middleware |
@x402/fastify |
Fastify middleware |
@x402/hono |
Hono edge middleware |
@x402/next |
Next.js middleware |
@x402/axios |
Axios interceptor |
@x402/fetch |
Fetch wrapper |
@x402/paywall |
Browser paywall UI |
@x402/mcp |
MCP client + server |
@x402/extensions |
Bazaar, offer-receipt, payment-identifier, sign-in-with-x, gas sponsoring |
Python v2.17.0 (PyPI, GitHub)
pip install "x402[httpx]" # Async HTTP client
pip install "x402[requests]" # Sync HTTP client
pip install "x402[fastapi]" # FastAPI server
pip install "x402[flask]" # Flask server
pip install "x402[evm]" # EVM support
pip install "x402[svm]" # Solana support
pip install "x402[tvm]" # TON support
pip install "x402[mcp]" # MCP integration
pip install "x402[extensions]" # Extensions (bazaar, gas sponsoring, etc.)
pip install "x402[all]" # Everything
Convenience bundles: clients (httpx + requests), servers (flask + fastapi), mechanisms (evm + svm + tvm).
Go v2.20.0 (GitHub)
The Go module path carries a /v2 suffix - the bare .../x402/go path no longer resolves tagged releases.
go get github.com/x402-foundation/x402/go/v2
Java (Java 17+, GitHub)
A fourth official binding is in the repo (PaymentFilter, FacilitatorClient, X402HttpClient). Not published to a package registry yet - build from source.
Key Concepts
- Client/Server/Facilitator: The three roles in every payment. Client signs, server enforces, facilitator settles on-chain. See
references/core-concepts.md - Wallet: Both payment mechanism and identity for buyers/sellers. See
references/core-concepts.md - Networks & Tokens: CAIP-2 identifiers, EIP-3009 tokens on EVM, SPL on Solana, custom token config. See
references/core-concepts.md - Scheme: Payment method.
exact= transfer exact amount;upto= authorize max, settle actual usage (shipping SDKs are EVM Permit2 only; a draft SVM binding via Solana payment channels is spec-stage);batch-settlement= commit at request time, settle asynchronously;auth-capture= escrow / authorize-then-capture with void, refund, reclaim. Seereferences/evm-scheme.md,references/svm-scheme.md,references/stellar-scheme.md,references/upto-scheme.md,references/aptos-scheme.md,references/near-scheme.md,references/xrpl-scheme.md,references/protocol-spec.md - Self-facilitation: Run an in-process facilitator instead of calling an external URL. See
references/typescript-sdk.md,references/go-sdk.md - Transport: How payment data is transmitted (HTTP headers, MCP
_meta, A2A metadata). Seereferences/transports.md - Extensions: Optional features (bazaar discovery, offer-receipt attestations, payment-identifier idempotency, sign-in-with-x auth, gas sponsoring, builder-code attribution, http-message-signatures, auth-hints). See
references/extensions.md - Hooks: Lifecycle callbacks on client/server/facilitator (TS, Python, Go). See
references/lifecycle-hooks.md - Protocol types:
PaymentRequired,PaymentPayload,SettlementResponse. Seereferences/protocol-spec.md - Custom tokens: Use
registerMoneyParserfor non-USDC tokens, Permit2 for non-EIP-3009 tokens. Seereferences/evm-scheme.md - Mainnet deployment: Switch facilitator URL, network IDs, and wallet addresses. See
references/core-concepts.md
References
| File | Content |
|---|---|
references/core-concepts.md |
HTTP 402 foundation, client/server/facilitator roles, wallet identity, networks, tokens, custom token config, dynamic registration, self-hosted facilitator, mainnet deployment |
references/protocol-spec.md |
v2 protocol types, payment flow, facilitator API, error codes |
references/typescript-sdk.md |
TypeScript SDK patterns for server, client, MCP, paywall, facilitator |
references/python-sdk.md |
Python SDK patterns for server, client, MCP (server + client), facilitator |
references/go-sdk.md |
Go SDK patterns for server, client, MCP, facilitator, signers, custom money parser |
references/evm-scheme.md |
EVM exact scheme: EIP-3009, Permit2, default asset resolution, registerMoneyParser, custom tokens |
references/svm-scheme.md |
Solana exact scheme: SPL TransferChecked, verification rules, duplicate settlement mitigation |
references/stellar-scheme.md |
Stellar exact scheme: SEP-41 Soroban token transfers, ledger-based expiration, fee sponsorship, TypeScript SDK only |
references/upto-scheme.md |
Upto (usage-based) scheme: authorize max amount, settle actual usage. EVM via Permit2 only |
references/aptos-scheme.md |
Aptos exact scheme: fungible asset transfers, fee payer sponsorship, TypeScript SDK only |
references/near-scheme.md |
NEAR exact scheme: NEP-366 SignedDelegate, NEP-141 ft_transfer, relayer gas sponsorship, full-access-key requirement, NEP-145 storage registration |
references/xrpl-scheme.md |
XRPL exact scheme: payer-signed Payment, no fee sponsorship, explicit AssetAmount pricing, sequence vs ticketSequence |
references/transports.md |
HTTP, MCP, A2A transport implementations |
references/extensions.md |
Bazaar, payment-identifier, sign-in-with-x, gas sponsoring (eip2612 + erc20) extensions |
references/lifecycle-hooks.md |
Client/server/facilitator hooks (TypeScript, Python, Go), hook chaining, MCP hooks |
Official Resources
- GitHub: https://github.com/x402-foundation/x402
- Spec: https://github.com/x402-foundation/x402/tree/main/specs
- Docs: https://docs.x402.org
- Website: https://x402.org
- Facilitator directory - named production and community facilitators
- Third-party SDKs - Rust (
x402-rs), Java (Mogami), Ruby (x402-rails), and others beyond the three official bindings - Third-party extensions - ecosystem extension packages
- Community: Slack at http://slack.x402.org/ (the project moved off Discord)
- Foundation Charter: https://github.com/x402-foundation/x402/tree/main/foundation
Files (skills)
-
references
-
aptos-scheme.md 4.8 KB
# Aptos Exact Scheme Reference The `exact` scheme on Aptos uses native Fungible Asset transfers with optional fee payer (gas) sponsorship by the facilitator. ## SDK Support | SDK | Status | |-----|--------| | TypeScript (`@x402/aptos`) | Full support (client, server, facilitator) | | Go | Not supported | | Python | Not supported | Install: `npm install @x402/aptos` ## Network Identifiers | Network | CAIP-2 ID | Chain ID | |---------|-----------|----------| | Aptos Mainnet | `aptos:1` | 1 | | Aptos Testnet | `aptos:2` | 2 | ## Supported Tokens Any Aptos fungible asset. Default: USDC (6 decimals). Use the USDC contract address for the target network. Address format: 64 hex characters with `0x` prefix (regex: `/^0x[a-fA-F0-9]{64}$/`). ## Protocol Flow 1. Client requests protected resource 2. Server returns `402` with PaymentRequirements (includes `extra.feePayer` if gas sponsored) 3. Client builds fee payer transaction using `0x1::primary_fungible_store::transfer` (or `0x1::fungible_asset::transfer`) 4. Client signs transaction (signature covers payload only, NOT fee payer address) 5. Client serializes via BCS encoding, Base64 encodes, sends in `PAYMENT-SIGNATURE` header 6. Server forwards to facilitator for verification 7. Facilitator validates structure, signature, and payment details 8. Server performs work, then requests settlement from facilitator 9. Facilitator adds fee payer signature (if sponsored) and submits to Aptos 10. Server returns response with `PAYMENT-RESPONSE` header ## PaymentRequirements ```json { "scheme": "exact", "network": "aptos:1", "amount": "1000000", "asset": "<APTOS_USDC_MAINNET>", "payTo": "<APTOS_RECIPIENT_ADDRESS>", "maxTimeoutSeconds": 60, "extra": { "feePayer": "<APTOS_FEE_PAYER_ADDRESS>" } } ``` - `extra.feePayer`: If present, facilitator pays gas. If absent, client pays own gas. ## Verification Rules Facilitator verification: 1. Verify x402Version is 2 2. Verify scheme is "exact" 3. Verify network matches (CAIP-2) 4. For sponsored tx: verify fee payer is managed by facilitator 5. Deserialize BCS-encoded transaction and verify the signature **cryptographically**. This MUST NOT rely on transaction simulation, which substitutes an invalid dummy signature and never checks the submitted one 6. Verify chain ID matches expected network 7. Verify sender's public key matches derived address 8. For sponsored tx: verify max gas <= 500,000 units (prevent gas drain) 9. For sponsored tx: verify fee payer address matches 10. Verify sender != fee payer 11. Verify transaction not expired (5-second buffer) 12. Verify contains fungible asset transfer (`0x1::primary_fungible_store::transfer` or `0x1::fungible_asset::transfer`) 13. Verify transfer targets correct asset address 14. Verify transfer amount matches exactly 15. Verify transfer recipient matches exactly 16. Verify sender has sufficient balance 17. Simulate transaction ## Supported Signature Schemes - Ed25519 (single, most common) - MultiEd25519 (multi-signature) - SingleKey (Ed25519, Secp256k1, Secp256r1) - MultiKey (multiple keys) ## TypeScript Usage ### Server ```typescript import { x402ResourceServer } from "@x402/core/server"; import { ExactAptosScheme } from "@x402/aptos/exact/server"; const server = new x402ResourceServer(facilitator) .register("aptos:2", new ExactAptosScheme()); ``` ### Client ```typescript import { x402Client } from "@x402/core"; import { ExactAptosScheme } from "@x402/aptos/exact/client"; import { createClientSigner } from "@x402/aptos"; const signer = createClientSigner(process.env.APTOS_PRIVATE_KEY); const client = new x402Client(); client.register("aptos:*", new ExactAptosScheme(signer)); ``` ### Facilitator ```typescript import { x402Facilitator } from "@x402/core"; import { ExactAptosScheme } from "@x402/aptos/exact/facilitator"; import { toFacilitatorAptosSigner } from "@x402/aptos"; const facilitator = new x402Facilitator(); facilitator.register("aptos:2", new ExactAptosScheme( toFacilitatorAptosSigner(aptosAccount), true // sponsorTransactions (default) )); ``` ## Multi-Signer Load Balancing The Aptos facilitator supports multiple fee payer addresses. `getExtra()` randomly selects from available signers. `getSigners()` returns all addresses. ## Non-Sponsored Transactions If `extra.feePayer` is absent, the client pays their own gas: 1. Client constructs a regular transaction including gas payment 2. Client fully signs the transaction 3. Facilitator submits the fully-signed transaction directly via `submitTransaction()` ## Key Import Paths | Purpose | Import | |---------|--------| | Client scheme | `@x402/aptos/exact/client` | | Server scheme | `@x402/aptos/exact/server` | | Facilitator scheme | `@x402/aptos/exact/facilitator` | | Signer utilities | `@x402/aptos` | | Constants | `@x402/aptos` (APTOS_MAINNET_CAIP2, APTOS_TESTNET_CAIP2, USDC_MAINNET_FA, USDC_TESTNET_FA, MAX_GAS_AMOUNT, APTOS_ADDRESS_REGEX) | -
core-concepts.md 16.9 KB
# Core Concepts ## HTTP 402 - The Foundation HTTP 402 Payment Required is a standard but historically dormant HTTP status code. x402 activates it to enable frictionless, API-native payments for: - Machine-to-machine (M2M) payments (AI agents) - Pay-per-use models (API calls, paywalled content) - Micropayments without account creation or traditional payment rails Using 402 keeps the protocol natively web-compatible and easy to integrate into any HTTP-based service. No new protocols, no special infrastructure - just HTTP. ### V2 Payment Headers | Header | Direction | Encoding | Content | |--------|-----------|----------|---------| | `PAYMENT-REQUIRED` | Server to Client | Base64 JSON | PaymentRequired object | | `PAYMENT-SIGNATURE` | Client to Server | Base64 JSON | PaymentPayload with signed authorization | | `PAYMENT-RESPONSE` | Server to Client | Base64 JSON | SettlementResponse with tx hash | Both headers must be valid Base64-encoded JSON strings for cross-implementation compatibility. ### V1 to V2 Header Migration | V1 Header | V2 Header | |-----------|-----------| | `X-PAYMENT` | `PAYMENT-SIGNATURE` | | `X-PAYMENT-RESPONSE` | `PAYMENT-RESPONSE` | ## Client / Server Roles ### Client (Buyer) The entity requesting access to a paid resource. Can be: - Human-operated applications - Autonomous AI agents - Programmatic services acting on behalf of users **Responsibilities:** 1. Send HTTP request to resource server 2. Handle 402 response and extract payment details 3. Construct a valid payment payload (sign authorization) 4. Retry request with `PAYMENT-SIGNATURE` header Clients do not need accounts, credentials, or session tokens beyond their crypto wallet. All interactions are stateless and occur over standard HTTP. ### Server (Seller) The resource provider enforcing payment for access. Can be: - API services - Content providers - Any HTTP-accessible resource requiring monetization **Responsibilities:** 1. Define payment requirements per route 2. Respond with 402 + `PAYMENT-REQUIRED` header when no valid payment is attached 3. Verify incoming payment payloads (locally or via facilitator) 4. Settle transactions on-chain 5. Return the resource on successful payment Servers do not need to manage client identities or maintain session state. Verification and settlement are handled per request. #### Duplicate Settlement on Solana If your server settles payments directly on Solana (without a facilitator), a race condition exists: the same signed payment can be submitted multiple times before on-chain confirmation. Solana's RPC returns "success" for each submission. Mitigation: maintain a short-lived in-memory cache of transaction payloads being settled. Reject duplicates with `"duplicate_settlement"` error. Evict entries after 120 seconds. If using a facilitator, the SVM libraries include built-in `SettlementCache` protection. #### Optimistic Settlement: Data Served Before On-Chain Confirmation x402 is optimistic by design: the server verifies the payment off-chain (`/verify`), serves the resource, then settles on-chain (`/settle`) - often after the response has already been sent. Settlement can therefore FAIL *after* the buyer received the data (service delivered, money not collected). Treat `/settle` as fire-and-forget you must reconcile: record settlement outcomes (e.g. via the `onAfterSettle` / `onSettleFailure` hooks), and for high-value resources gate delivery on a successful settle. The standard failure signal is a `402` carrying a `PAYMENT-RESPONSE` header with `success: false` and an `errorReason` (see `references/transports.md`). ## Facilitator An optional but recommended service that simplifies payment verification and settlement. ### What It Does - **Verifies payments**: Confirms client's payment payload meets server's requirements - **Settles payments**: Submits validated payments to blockchain and monitors confirmation - **Returns results**: Sends verification and settlement results back to server ### What It Does NOT Do - Does NOT hold funds or act as a custodian - Does NOT control the payment amount or destination (these are signed by the client) - Cannot steal funds - tampering with the transaction fails signature checks ### Why Use One - **Reduced complexity**: Servers don't need direct blockchain connectivity - **Protocol consistency**: Standardized verification/settlement flows - **Faster integration**: Start accepting payments with minimal blockchain development - **Gas abstraction**: Facilitator sponsors gas fees, buyers don't need native tokens (XRPL is the exception - it cannot sponsor fees) ### What Is Local and What Is a Network Call Registering a scheme on the resource server does **not** create a local verification path. `server.register(network, scheme)` supplies the logic for *building* payment requirements - price parsing, default-asset resolution, constructing the 402. Both `verifyPayment()` and `settlePayment()` always round-trip to a `FacilitatorClient`, even when a scheme is registered for that network. Budget for that latency and for the facilitator being a hard dependency on the request path. ### Running Your Own Facilitator: Serialize Your Settles A facilitator settling EVM payments from a single signer EOA must manage its own transaction ordering. Concurrent `/settle` calls race on the **Ethereum account nonce** of the facilitator's own EOA - not on the EIP-3009 authorization nonce - and the SDKs ship no queue, mutex, or nonce coordination in the settle path. Settle-time simulation does not protect against this: simulation is a read-only call, so every concurrent simulation passes while only the first writer's transaction lands. Serialize settles per signer, or run multiple signer addresses (the EVM facilitator signer supports multiple addresses for load balancing and key rotation). ### Live Facilitators Multiple production facilitators are available. The ecosystem is permissionless - anyone can run a facilitator. | Facilitator | Networks | Use Case | |-------------|----------|----------| | x402.org (default) | Base Sepolia, Solana Devnet, Algorand Testnet, Stellar Testnet, Aptos Testnet, Hedera Testnet, XRPL Testnet | Testing/development, no setup needed | | [Facilitator directory](https://docs.x402.org/dev-tools/facilitators) | Base, Solana, Polygon, NEAR, XRPL, etc. | Production use | | Self-hosted | Any EVM chain | Full control | On Base Sepolia the default facilitator advertises `exact`, `upto`, **and** `batch-settlement`, plus the `builder-code`, `eip2612GasSponsoring`, and `erc20ApprovalGasSponsoring` extensions. Query `GET https://x402.org/facilitator/supported` for the live list rather than assuming. > **The default facilitator is not a production default.** Upstream states explicitly that the public `x402.org` facilitator is intended for development and testnet workflows - do not assume it is the path for production mainnet routes. **Key insight**: Facilitators support NETWORKS, not specific tokens. Any EIP-3009 token works on EVM networks, any SPL/Token-2022 token works on Solana, any SEP-41 token works on Stellar, and any fungible asset works on Aptos, as long as the facilitator supports that network. ## Wallet In x402, a wallet is both a payment mechanism and a form of unique identity. ### For Buyers - Store USDC/crypto - Sign payment payloads (EIP-712 for EVM, Ed25519 for Solana/Aptos, BCS signing for Aptos) - Authorize on-chain payments programmatically - Wallets enable AI agents to transact without account creation ### For Sellers - Receive USDC/crypto payments - Define payment destination in server configuration (the `payTo` address) ### Recommended Wallet Solutions - **CDP Wallet API** (https://docs.cdp.coinbase.com/wallet-api-v2/docs/welcome): Recommended for programmatic payments and secure key management - **viem** / **ethers** HD wallets: For EVM - **@solana/kit**: For Solana - **Aptos TypeScript SDK**: For Aptos ## Networks and Token Support ### CAIP-2 Network Identifiers x402 v2 uses CAIP-2 (Chain Agnostic Improvement Proposal) for unambiguous cross-chain support. Format: `{namespace}:{reference}` - **EVM**: `eip155:<chainId>` (e.g., `eip155:8453` for Base) - **Solana**: `solana:<genesisHash>` (e.g., `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` for mainnet) - **Aptos**: `aptos:<chainId>` (e.g., `aptos:1` for mainnet) - **Stellar**: `stellar:<network>` (e.g., `stellar:pubnet` for mainnet) - **TON**: `tvm:<workchain>` (e.g., `tvm:-239` for mainnet) - **Hedera**: `hedera:<network>` (e.g., `hedera:mainnet`) - **Algorand**: `algorand:<truncatedGenesisHash>` (e.g., `algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k` for mainnet). The reference is the URL-safe base64 genesis hash **truncated to the first 32 characters** - full genesis hashes appear only in on-chain transaction `gh` fields, never in CAIP-2 `network` values. SDKs normalize legacy full-hash ids on input but emit the truncated form (`normalizeAlgorandNetwork`). - **NEAR**: `near:<network>` (e.g., `near:mainnet`, `near:testnet`) - **XRPL**: `xrpl:<networkId>` (e.g., `xrpl:0` mainnet, `xrpl:1` testnet, `xrpl:2` devnet) - **Keeta**: `keeta:<chainId>` (e.g., `keeta:21378` for mainnet, `keeta:1413829460` for testnet) - **Concordium**: `ccd:<genesisHash>` (e.g., `ccd:9dd9ca4d19e9393877d2c44b70f89acb` for mainnet) ### Token Support **EVM**: Any ERC-20 token implementing EIP-3009 (`transferWithAuthorization`). For the `exact` scheme with Permit2 fallback: Any ERC-20 token via `permitWitnessTransferFrom` (requires one-time Permit2 approval). **Solana**: Any SPL token or Token-2022 token. **Aptos**: Any fungible asset via `0x1::primary_fungible_store::transfer`. Supports sponsored (gasless) transactions. TypeScript SDK only. **Stellar**: Any Soroban token implementing SEP-41. Uses `transfer(from, to, amount)`. TypeScript SDK only. Ledger-based expiration (~12 ledgers, ~60 seconds). **Hedera**: HBAR (native token) or any HTS fungible token. HBAR is used via asset id `0.0.0` with amounts in tinybars (1 HBAR = 10^8 tinybars). **USDC** is the default token, supported across all networks. When you use price strings like `"$0.001"`, the system infers USDC. ### Specifying Payment Amounts Two options: **1. Price String (USDC shorthand)** ```typescript { price: "$0.001" } // Infers USDC ``` **2. TokenAmount / AssetAmount (custom tokens)** TypeScript: ```typescript { price: { amount: "10000", // Atomic units asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // Token address extra: { name: "USDC", version: "2" } // EIP-712 values } } ``` Python: ```python from x402.schemas import AssetAmount PaymentOption( price=AssetAmount( amount="10000", asset="0x036CbD53842c5426634e7929541eC2318f3dCF7e", extra={"name": "USDC", "version": "2"}, ), ) ``` ### Using Custom EIP-3009 Tokens To use a token other than USDC, you need: 1. **Token Address**: Contract address of your EIP-3009 token 2. **EIP-712 Name**: Token's name for EIP-712 signatures (read `name()` on the contract) 3. **EIP-712 Version**: Token's version for EIP-712 signatures (read `version()` on the contract) ### Adding New Networks (Dynamic Registration) v2 uses dynamic network registration - support any EVM network without modifying source code. **TypeScript:** ```typescript import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server"; import { ExactEvmScheme } from "@x402/evm/exact/server"; const facilitator = new HTTPFacilitatorClient({ url: "https://your-facilitator.com" }); const server = new x402ResourceServer(facilitator); server.register("eip155:*", new ExactEvmScheme()); "GET /api/data": { accepts: [{ scheme: "exact", price: "$0.001", network: "eip155:43114", // Avalanche mainnet payTo: "0xYourAddress", }], } ``` **Go:** ```go schemes := []ginmw.SchemeConfig{ {Network: x402.Network("eip155:43114"), Server: evm.NewExactEvmScheme()}, } ``` **Python:** ```python server = x402ResourceServer(facilitator) server.register("eip155:43114", ExactEvmServerScheme()) ``` ### Running Your Own Facilitator **TypeScript:** ```typescript import { x402Facilitator } from "@x402/core"; import { ExactEvmScheme } from "@x402/evm/exact/facilitator"; const facilitator = new x402Facilitator(); facilitator.register("eip155:43114", new ExactEvmScheme({ privateKey: process.env.FACILITATOR_KEY })); ``` ### Quick Reference | Network | CAIP-2 ID | Token Support | Default Facilitator | |---------|-----------|---------------|-------------------| | Base Mainnet | `eip155:8453` | Any EIP-3009 | Production facilitators | | Base Sepolia | `eip155:84532` | Any EIP-3009 | x402.org (testnet) | | MegaETH Mainnet | `eip155:4326` | MegaUSD (18 decimals) | Community | | Monad Mainnet | `eip155:143` | USDC | Community | | Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Any SPL/Token-2022 | Production facilitators | | Solana Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | Any SPL/Token-2022 | x402.org (testnet) | | Aptos Mainnet | `aptos:1` | Any Fungible Asset | Community | | Aptos Testnet | `aptos:2` | Any Fungible Asset | Community | | Stellar Mainnet | `stellar:pubnet` | Any SEP-41 Soroban token | Community | | Stellar Testnet | `stellar:testnet` | Any SEP-41 Soroban token | x402.org (testnet) | | Polygon Mainnet | `eip155:137` | USDC | Production facilitators | | Polygon Amoy | `eip155:80002` | USDC | Community | | Stable Mainnet | `eip155:988` | USDT0 | Community | | Stable Testnet | `eip155:2201` | USDT0 | Community | | Arbitrum One | `eip155:42161` | USDC | Production facilitators | | Arbitrum Sepolia | `eip155:421614` | USDC | Community | | XDC Network Mainnet | `eip155:50` | USDC (Bridged, EIP-3009) | Community | | XDC Apothem Testnet | `eip155:51` | USDC (Bridged, EIP-3009) | Community | | Mezo Mainnet | `eip155:31612` | mUSD (18 decimals, Permit2 + EIP-2612) | Community | | Mezo Testnet | `eip155:31611` | mUSD (Permit2 + EIP-2612) | Community | | Radius Mainnet | `eip155:723487` | SBC (Permit2 + EIP-2612) | Community | | Radius Testnet | `eip155:72344` | SBC (Permit2 + EIP-2612) | Community | | ADI Chain | `eip155:36900` | USDC.e (EIP-3009) | Community | | HPP Mainnet | `eip155:190415` | Bridged USDC | Community | | HPP Sepolia | `eip155:181228` | Bridged USDC | Community | | TON Mainnet | `tvm:-239` | Jettons (USDT default) | Community | | Hedera Mainnet | `hedera:mainnet` | HBAR + HTS tokens | Community | | Algorand Mainnet | `algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k` | USDC ASA | Community | | Algorand Testnet | `algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDe` | USDC ASA | x402.org (testnet) | | Igra Mainnet | `eip155:38833` | USDC (Permit2 only) | Community | | NEAR Mainnet | `near:mainnet` | NEP-141 USDC | Community | | NEAR Testnet | `near:testnet` | NEP-141 USDC | Community | | XRPL Mainnet | `xrpl:0` | XRP + issued currencies | Community | | XRPL Testnet | `xrpl:1` | XRP + issued currencies | x402.org (testnet) | | Keeta Mainnet | `keeta:21378` | TypeScript SDK | Community | | Keeta Testnet | `keeta:1413829460` | TypeScript SDK | Community | | Concordium Mainnet | `ccd:9dd9ca4d19e9393877d2c44b70f89acb` | Native CCD (6 decimals) | Community | | Concordium Testnet | `ccd:4221332d34e1694168c2a0c0b3fd0f27` | Native CCD | Community | | Any EVM | `eip155:<chainId>` | Any EIP-3009 | Self-hosted or community | ### Why EIP-3009? 1. **Gas abstraction**: Buyers don't need ETH/native tokens for gas 2. **One-step payments**: No separate `approve()` transaction required 3. **Universal facilitator support**: Any EIP-3009 token works with any EVM facilitator 4. **Security**: Transfers authorized by cryptographic signatures with time bounds and nonces ## SDK Support Matrix | Component | TypeScript | Go | Python | |-----------|:---:|:---:|:---:| | Core (Server/Client/Facilitator) | Yes | Yes | Yes | | EVM (exact/eip3009) | Yes | Yes | Yes | | SVM (exact/spl) | Yes | Yes | Yes | | Stellar (exact/soroban) | Yes | No | No | | Aptos (exact/fungible) | Yes | No | No | | TON/TVM (exact/jetton) | Yes | No | Yes | | Keeta (exact) | Yes | No | No | | Concordium (exact) | Yes | No | No | | NEAR (exact/NEP-141) | Yes | No | No | | XRPL (exact/Payment) | Yes | No | No | ### HTTP Framework Integrations | Role | TypeScript | Go | Python | |------|------------|-----|--------| | Server | Express, Fastify, Hono, Next.js | Gin, Echo, net/http | FastAPI, Flask | | Client | Fetch, Axios | net/http | httpx, requests | ## Going to Production (Mainnet) ### 1. Switch Facilitator URL ```typescript const facilitator = new HTTPFacilitatorClient({ url: "https://api.cdp.coinbase.com/platform/v2/x402" }); ``` See the [facilitator directory](https://docs.x402.org/dev-tools/facilitators) for available production facilitators. ### 2. Update Network Identifiers | Testnet | Mainnet | |---------|---------| | `eip155:84532` (Base Sepolia) | `eip155:8453` (Base Mainnet) | | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` (Devnet) | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` (Mainnet) | ### 3. Use Real Wallet Addresses Ensure `payTo` addresses are real mainnet addresses where you want to receive USDC. ### 4. Test with Small Amounts First Mainnet transactions involve real money. Always verify payments arrive correctly before going live. -
evm-scheme.md 12.6 KB
# EVM Scheme Reference ## Schemes Overview | Scheme | Description | |--------|-------------| | **exact** | Transfers a fixed amount; facilitator pays gas, client controls fund flow via signatures | | **upto** | Usage-based; client authorizes a max, facilitator settles actual amount consumed | Both schemes use Permit2 as their foundation. The `exact` scheme additionally supports EIP-3009 for compatible tokens. ## Asset Transfer Methods (Exact Scheme) | Method | Use Case | Recommendation | |--------|----------|----------------| | **EIP-3009** | Tokens with native `transferWithAuthorization` (e.g., USDC) | Recommended (simplest, truly gasless) | | **Permit2** | Any ERC-20 token | Universal fallback | | **ERC-7710** | Smart accounts with delegation support | Smart account option | If no `assetTransferMethod` is specified in payload `extra`, implementations prioritize `eip3009` first, then `permit2`. ## Proxy Contracts Both schemes use deterministic CREATE2-deployed proxy contracts: | Contract | Address | Purpose | |----------|---------|---------| | `x402ExactPermit2Proxy` | `0x402085c248EeA27D92E8b30b2C58ed07f9E20001` | Exact-amount Permit2 settlement | | `x402UptoPermit2Proxy` | `0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002` | Variable-amount Permit2 settlement | | Permit2 (canonical) | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | Uniswap Permit2 | | Multicall3 | `0xcA11bde05977b3631167028862bE2a173976CA11` | Batched reads | Both proxy contracts inherit `x402BasePermit2Proxy` which provides shared logic: reentrancy guard, `_settle()` internal, `_executePermit()` for EIP-2612, and common error types (`InvalidAmount`, `InvalidDestination`, `InvalidOwner`, `PaymentTooEarly`, `Permit2612AmountMismatch`). ### Exact Proxy Witness ```solidity struct Witness { address to; uint256 validAfter; } ``` Always transfers the exact `permit.permitted.amount`. ### Upto Proxy Witness ```solidity struct Witness { address to; address facilitator; uint256 validAfter; } ``` Adds `facilitator` field - only `msg.sender == witness.facilitator` can settle. Settles for any `amount <= permit.permitted.amount`. ## Method 1: EIP-3009 (Exact Only) Uses `transferWithAuthorization` directly on compatible token contracts (like USDC). ### EIP-712 Authorization Types ```javascript const authorizationTypes = { TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }, ], }; ``` ### EIP-3009 Verification 1. Verify EIP-712 signature recovers to `authorization.from` (supports EOA, EIP-1271 smart wallets, ERC-6492 counterfactual) 2. Verify payer has sufficient token balance 3. Verify `authorization.value` exactly matches required amount 4. Verify `validBefore > now + 6s` and `validAfter <= now` 5. Verify recipient and token/network match requirements 6. Simulate `transferWithAuthorization` on-chain ## Method 2: Permit2 (Both Exact and Upto) Uses `permitWitnessTransferFrom` from the canonical Permit2 contract combined with `x402Permit2Proxy`. ### One-Time Setup (Three Options) 1. **Direct approval** - user submits `approve(Permit2, MaxUint256)` transaction 2. **EIP-2612 permit** (extension `eip2612GasSponsoring`) - gasless off-chain signature; facilitator calls `settleWithPermit()` 3. **ERC-20 approval gas sponsoring** (extension `erc20ApprovalGasSponsoring`) - client signs (not broadcasts) an `approve()` tx; facilitator broadcasts it atomically before settling ### Permit2 EIP-712 Types ```javascript const permit2WitnessTypes = { PermitWitnessTransferFrom: [ { name: "permitted", type: "TokenPermissions" }, { name: "spender", type: "address" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, { name: "witness", type: "Witness" }, ], TokenPermissions: [ { name: "token", type: "address" }, { name: "amount", type: "uint256" }, ], Witness: [ { name: "to", type: "address" }, { name: "validAfter", type: "uint256" }, ], // Exact scheme; Upto adds { name: "facilitator", type: "address" } }; ``` The `spender` is the `x402Permit2Proxy` contract (not the facilitator), which enforces funds go only to `witness.to`. ### Permit2 Verification (All Paths) 1. Verify `spender` is the correct `x402Permit2Proxy` address 2. Verify `witness.to` matches `requirements.payTo` 3. Verify `deadline > now + 6s` and `witness.validAfter <= now` 4. Verify `permitted.amount` matches (exact) or covers (upto) required amount 5. Verify `permitted.token` matches requirements 6. Verify EIP-712 signature (EOA, EIP-1271, or deployed smart contract fallthrough to simulation) 7. Simulation branch: - **Standard**: simulate `x402Permit2Proxy.settle()` - **EIP-2612 extension**: validate permit fields, simulate `settleWithPermit()` - **ERC-20 approval extension**: validate signed tx, simulate bundle ### Permit2 Settlement (Three Paths) 1. **EIP-2612 path** - calls `settleWithPermit(permit2612, permit, owner, witness, signature)` atomically 2. **ERC-20 approval path** - delegates to `extensionSigner.sendTransactions([signedApproveTx, { to: proxy, data: settleCalldata }])` 3. **Standard path** - calls `settle(permit, owner, witness, signature)` directly ## Multicall3 Batched Reads All SDKs use Multicall3 (`tryAggregate`) to batch diagnostic reads in a single RPC round-trip. Used for: checking proxy deployment, token balance, Permit2 allowance, ETH balance for gas. ```typescript import { multicall, MULTICALL3_ADDRESS } from "@x402/evm"; const results = await multicall(signer.readContract.bind(signer), calls); ``` **Go**: `evm.Multicall(ctx, signer, calls)` - same semantics. ## Universal Signature Verification (Go) `VerifyUniversalSignature()` handles: 1. Parse ERC-6492 wrapper if present 2. If 65-byte signature + no factory: try EOA ECDSA recovery (optimization) 3. Otherwise: `GetCode` to check deployment 4. Undeployed + has ERC-6492 factory + `allowUndeployed`: return false but preserve deployment info 5. Deployed: EIP-1271 verification ## ERC-6492 Factory Allowlist (Security) ERC-6492 verification simulates a counterfactual smart-wallet's deploy factory before checking the signature. To close a factory-call-injection vector, the allowed deploy factories are now gated by an explicit allowlist - the sole gate in current SDKs: - TypeScript: `eip6492AllowedFactories` | Go: `EIP6492AllowedFactories` | Python: `eip6492_allowed_factories: list[str]` - An empty or omitted list **disables** counterfactual ERC-6492 deployment entirely and returns `eip6492_factory_not_allowed`. - The previous `DeployERC4337WithEIP6492` boolean config field was **removed** across all three SDKs (breaking for facilitator implementers that deployed ERC-4337 smart wallets via EIP-6492). ### Wallet Compatibility Payments verify and settle consistently across plain EOAs, deployed smart accounts (ERC-4337 / ERC-7579), counterfactual ERC-6492 wallets, and ERC-7702-delegated EOAs. Pre-verification mirrors on-chain signature checking, so a payment that passes `verify` is the same one that succeeds at `settle`. **Not every wallet type works on every path.** The combination that catches people out: - **ERC-6492 counterfactual + Permit2 (`exact`, `upto`, or `batch` deposit) is NOT supported.** Permit2's `permitWitnessTransferFrom` calls `isValidSignature` on the payer at settlement, and the Permit2 path does not deploy the wallet first. ERC-6492 counterfactual support covers `exact` (EIP-3009) and `batch-settlement`, not the Permit2 routes. - **The token itself can veto smart-wallet payments.** If a token's EIP-3009 implementation only calls `ecrecover`, every non-EOA wallet type fails on-chain no matter how x402 is configured - this is a property of the deployed token, not of the SDK. - **`payerAuthorizer` must be an EOA address.** Upstream publishes a full wallet-type (A-E) support matrix and a three-step wallet-type detection procedure at [docs.x402.org/advanced-concepts/wallet-compatibility](https://docs.x402.org/advanced-concepts/wallet-compatibility). ## Verify-Time Guards - **Asset must be a deployed contract**: EVM facilitator `verify` calls `eth_getCode` on the asset address early in `verifyEIP3009`, `verifyPermit2`, and `verifyUptoPermit2`. Any address with no bytecode (an EOA) is rejected with `asset_not_deployed_contract`. This closes a silent no-op where `eth_call` on an EOA returns empty data without reverting, so on-chain simulation passes but settlement transfers nothing. - **`validAfter` is set to 0**: clients set the EIP-3009/Permit2 authorization `validAfter` to 0 to reduce on-chain timing failures when payloads are queued or block timestamps lag behind client clocks. Expiration is still bounded by `validBefore`/`deadline` derived from `maxTimeoutSeconds`. ## Extensions ### EIP-2612 Gas Sponsoring Extension key: `eip2612GasSponsoring`. Client signs an EIP-2612 `permit(owner, Permit2, amount, deadline, v, r, s)` off-chain. Facilitator atomically executes it via `settleWithPermit()`. ### ERC-20 Approval Gas Sponsoring Extension key: `erc20ApprovalGasSponsoring`. Fallback for tokens without EIP-2612. Client signs (not broadcasts) a raw `approve(Permit2, MaxUint256)` transaction. Facilitator broadcasts it before settling. ## Default Asset Resolution When a server uses price string syntax (`"$0.001"`), the SDK resolves to the chain's default stablecoin: | Network | Token | Address | Decimals | Transfer Method | EIP-2612 | |---------|-------|---------|----------|-----------------|----------| | Base Sepolia (`eip155:84532`) | USDC | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | 6 | EIP-3009 | - | | Base Mainnet (`eip155:8453`) | USD Coin | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | 6 | EIP-3009 | - | | MegaETH (`eip155:4326`) | MegaUSD | `0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7` | 18 | Permit2 | Yes | | Monad (`eip155:143`) | USD Coin | `0x754704Bc059F8C67012fEd69BC8A327a5aafb603` | 6 | EIP-3009 | - | | ADI Chain (`eip155:36900`) | USDC.e | `0x9cb8142aEBBcdc60AF7c97Af897A67A8f3CA71C2` | 6 | EIP-3009 | - | | HPP (`eip155:190415`) | Bridged USDC | `0x401eCb1D350407f13ba348573E5630B83638E30D` | 6 | EIP-3009 | - | | HPP Sepolia (`eip155:181228`) | Bridged USDC | `0x401eCb1D350407f13ba348573E5630B83638E30D` | 6 | EIP-3009 | - | | Mezo Mainnet (`eip155:31612`) | Mezo USD (mUSD) | `0xdD468A1DDc392dcdbEf6db6e34E89AA338F9F186` | 18 | Permit2 | Yes | | XDC Network (`eip155:50`) | USDC (Bridged) | `0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1` | 6 | EIP-3009 | - | | XDC Apothem (`eip155:51`) | USDC (Bridged) | `0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4` | 6 | EIP-3009 | - | | Igra Mainnet (`eip155:38833`) | USDC | `0xA5b8BF902b2844dA17d4506cc827F7F1681735E7` | 6 | Permit2 | No | Igra's USDC implements **neither EIP-3009 nor EIP-2612**, so it is Permit2-only and cannot use either gasless path - the payer needs a one-time direct `approve(Permit2, ...)` transaction. ### Custom Tokens with registerMoneyParser **TypeScript (Server):** ```typescript import { ExactEvmScheme } from "@x402/evm/exact/server"; const server = new ExactEvmScheme(); server.registerMoneyParser(async (amount, network) => { if (network === "eip155:8453") { return { amount: (amount * 1e18).toString(), asset: "0xYourTokenAddress", extra: { assetTransferMethod: "permit2" }, }; } return null; }); ``` **Go:** ```go evmScheme := evm.NewExactEvmScheme().RegisterMoneyParser( func(amount float64, network x402.Network) (*x402.AssetAmount, error) { return &x402.AssetAmount{ Amount: fmt.Sprintf("%.0f", amount*1e18), Asset: "0xYourTokenAddress", Extra: map[string]interface{}{"assetTransferMethod": "permit2"}, }, nil }, ) ``` ## Client RPC Configuration ```typescript // Single RPC config new ExactEvmScheme(signer, { rpcUrl: "https://..." }); // Per-chain config new ExactEvmScheme(signer, { 8453: { rpcUrl: "..." }, 137: { rpcUrl: "..." } }); ``` ## Signer Types ### ClientEvmSigner Required: `address`, `signTypedData()` Optional (for extensions): `readContract()`, `signTransaction()`, `getTransactionCount()`, `estimateFeesPerGas()` ### FacilitatorEvmSigner Required: `getAddresses()`, `readContract()`, `verifyTypedData()`, `writeContract()`, `sendTransaction()`, `waitForTransactionReceipt()`, `getCode()` Supports multiple addresses for load balancing and key rotation. ## SDK Support | SDK | Exact EIP-3009 | Exact Permit2 | Upto | |-----|---------------|---------------|------| | TypeScript (`@x402/evm`) | Full | Full (+ EIP-2612, ERC-20 approval extensions) | Full | | Go | Full | Full (+ ERC-20 approval) | - | | Python | Full | Full | - | -
extensions.md 15.1 KB
# x402 Extensions Reference Extensions add optional functionality beyond core payment mechanics. Servers advertise them in `PaymentRequired.extensions`, clients echo them in `PaymentPayload.extensions`. Client-echoed extension info is validated field-by-field against the server declaration; a mismatch is rejected with `extension_echo_mismatch`. **Dynamic info fields (2.16.0):** an extension can mark certain info fields as regenerated per `PaymentRequired` response (via a `dynamicInfoFields` capability) so they are excluded from the strict client-echo comparison, while all other fields stay strictly compared. Wired into offer-receipt (`["offers"]`) and sign-in-with-x (`["nonce", "issuedAt", "expirationTime"]`). Standard extension structure: ```json { "extensions": { "extension-name": { "info": { /* extension-specific data */ }, "schema": { /* JSON Schema validating info */ } } } } ``` ## Bazaar (Resource Discovery) Enables resource discovery and cataloging. Servers declare endpoint specs so facilitators can catalog them in a discovery service. Supports two transport types: **HTTP** and **MCP**. ### Transport Types **HTTP** (`input.type: "http"`) - standard REST endpoints. Method is auto-inferred from route key (e.g., `"GET /weather"`) and injected by `bazaarResourceServerExtension`. **MCP** (`input.type: "mcp"`) - Model Context Protocol tools. Identified by `toolName` field. Transport defaults to `"streamable-http"` per MCP spec, optionally `"sse"`. ### MCP Input Fields | Field | Required | Description | |-------|----------|-------------| | `type` | Yes | Always `"mcp"` | | `toolName` | Yes | MCP tool name | | `description` | No | Human-readable tool description | | `transport` | No | `"streamable-http"` (default) or `"sse"` | | `inputSchema` | Yes | JSON Schema for tool arguments | | `example` | No | Example tool arguments | ### SDK Usage **TypeScript:** ```typescript import { declareDiscoveryExtension } from "@x402/extensions/bazaar"; // HTTP endpoint - method auto-inferred from route key extensions: { ...declareDiscoveryExtension({ input: { city: "San Francisco" }, inputSchema: { properties: { city: { type: "string" } }, required: ["city"] }, output: { example: { weather: "sunny", temperature: 72 } }, }), } // MCP tool - toolName discriminates from HTTP extensions: { ...declareDiscoveryExtension({ toolName: "financial_analysis", description: "Analyze financial data", inputSchema: { type: "object", properties: { ticker: { type: "string" } }, required: ["ticker"] }, output: { example: { pe_ratio: 28.5 } }, }), } ``` **Go:** ```go import "github.com/x402-foundation/x402/go/v2/extensions/bazaar" Extensions: bazaar.DeclareDiscoveryExtension(bazaar.DiscoveryInfo{ Output: map[string]interface{}{ "type": "json", "example": map[string]interface{}{"weather": "sunny"}, }, }) ``` **Python:** ```python from x402.extensions.bazaar import declare_discovery_extension, OutputConfig extensions = declare_discovery_extension( input={"city": "San Francisco"}, input_schema={"properties": {"city": {"type": "string"}}, "required": ["city"]}, output=OutputConfig(example={"weather": "sunny"}), ) ``` ### Server Extension (Method Enrichment) `bazaarResourceServerExtension` auto-injects the HTTP method from request context into `info.input.method`. ```typescript import { bazaarResourceServerExtension } from "@x402/extensions/bazaar"; const resourceServer = new x402ResourceServer(facilitatorClient) .registerExtension(bazaarResourceServerExtension); ``` Go: `bazaar.BazaarResourceServerExtension` | Python: `bazaar_resource_server_extension` ### WithBazaar Facilitator Client ```typescript import { withBazaar } from "@x402/extensions/bazaar"; const client = withBazaar(new HTTPFacilitatorClient({ url })); const resources = await client.extensions.discovery.listResources({ type: "http", limit: 10 }); ``` Go: `bazaar.WithBazaar(facilitatorClient)` then `facilitator.ListDiscoveryResources(ctx, params)` ### Discovery API ``` GET /discovery/resources?type=http&limit=10&offset=0 ``` ### Troubleshooting: Why a Service Is Not in the Catalog Whether and how a resource appears in a facilitator's catalog is an implementation detail of the **facilitator operator**, not something a server controls. Two consequences catch people out: - **A server-side declaration alone catalogs nothing.** Cataloging happens when a facilitator processes a `PaymentPayload` that includes the echoed `bazaar` extension - so a route nobody has paid for yet will not appear, no matter how it is declared. - **A missing `EXTENSION-RESPONSES` header is not a failure signal.** Facilitators *may* return it; its absence carries no meaning. --- ## Offer-Receipt (Signed Attestations) Enables cryptographically signed offers and receipts for audit trails, verified reviews, and dispute resolution. TypeScript only. ### Signature Formats | Format | Use Case | |--------|----------| | `jws` | Cross-chain, supports did:key/did:jwk/did:web | | `eip712` | EVM-native, ECDSA recovery | ### Server Setup ```typescript import { createOfferReceiptExtension, createJWSOfferReceiptIssuer, declareOfferReceiptExtension, } from "@x402/extensions/offer-receipt"; const issuer = createJWSOfferReceiptIssuer( "did:web:api.example.com#key-1", { kid: "did:web:api.example.com#key-1", algorithm: "ES256", format: "jws", sign: mySignFn }, ); const resourceServer = new x402ResourceServer(facilitatorClient) .registerExtension(createOfferReceiptExtension(issuer)); extensions: { ...declareOfferReceiptExtension({ includeTxHash: false, offerValiditySeconds: 300 }), } ``` ### Client Usage ```typescript import { extractOffersFromPaymentRequired, decodeSignedOffers, findAcceptsObjectFromSignedOffer, extractReceiptFromResponse, verifyReceiptMatchesOffer, } from "@x402/extensions/offer-receipt"; const offers = extractOffersFromPaymentRequired(paymentRequired); const decoded = decodeSignedOffers(offers); const requirements = findAcceptsObjectFromSignedOffer(decoded[0], paymentRequired.accepts); const receipt = extractReceiptFromResponse(response); const valid = verifyReceiptMatchesOffer(receipt, decoded[0], [myWalletAddress]); ``` ### DID Key Resolution `extractPublicKeyFromKid(kid)` supports `did:key` (Ed25519, secp256k1, P-256), `did:jwk`, and `did:web` (fetches `/.well-known/did.json`). ### Signer Authorization (not just signature validity) Verifiers MUST distinguish between **signature validity** and **signer authorization**. A valid signature proves a specific key signed the artifact; it does not prove that key was authorized to sign on behalf of the service identified by `resourceUrl`. Two authorization mechanisms are specified: - `did:web` resolution against the service domain - DNS TXT records at `_controllers.<domain>` carrying `v=1;controller=did:pkh:...` Checking only the signature leaves a receipt forgeable by anyone who can produce a well-formed signature over the offer. --- ## Payment Identifier (Idempotency) Enables clients to provide an `id` for request deduplication and safe retries. ### SDK Usage **TypeScript (server):** ```typescript import { declarePaymentIdentifierExtension, paymentIdentifierResourceServerExtension } from "@x402/extensions/payment-identifier"; extensions: { [PAYMENT_IDENTIFIER]: declarePaymentIdentifierExtension(false) } resourceServer.registerExtension(paymentIdentifierResourceServerExtension); ``` **TypeScript (client):** ```typescript import { appendPaymentIdentifierToExtensions } from "@x402/extensions/payment-identifier"; appendPaymentIdentifierToExtensions(extensions); // Adds ID only if server declared extension ``` **Go (client):** ```go import "github.com/x402-foundation/x402/go/v2/extensions/paymentidentifier" err := paymentidentifier.AppendPaymentIdentifierToExtensions(extensions, "") ``` **Go (facilitator):** ```go id, err := paymentidentifier.ExtractPaymentIdentifier(payload, true) // validate=true ``` ### ID Format - **Length**: 16-128 characters - **Characters**: alphanumeric, hyphens, underscores (`^[a-zA-Z0-9_-]+$`) ### Idempotency Behavior | Scenario | Server Response | |----------|-----------------| | New `id` | Process normally | | Same `id`, same payload | Return cached response | | Same `id`, different payload | 409 Conflict | | `required: true`, no `id` | 400 Bad Request | --- ## Sign-In With X (Wallet Authentication) CAIP-122 wallet-based authentication. Clients prove wallet ownership by signing a challenge, allowing servers to skip payment for addresses that previously paid. TypeScript, Python (`x402.extensions.sign_in_with_x`, added Python v2.11.0), and Go (server + client, `go/v2/extensions/signinwithx`, added Go v2.16.0 - also covers undeployed EIP-6492 and SVM signers). ### Supported Chains | Chain | Type | Message Format | |-------|------|---------------| | EVM (`eip155:*`) | `eip191` | EIP-4361 (SIWE) | | Solana (`solana:*`) | `ed25519` | Sign-In With Solana (SIWS) | ### Server ```typescript import { createSIWxResourceServerExtension, declareSIWxExtension } from "@x402/extensions/sign-in-with-x"; // Declare on the route - no `domain`, no `resourceUri` extensions: { ...declareSIWxExtension({ statement: "Sign in", expirationSeconds: 300 }) } // Register with an operator-configured public origin (required) server.registerExtension( createSIWxResourceServerExtension({ origin: "https://api.example.com" }), ); ``` `DeclareSIWxOptions` now carries only `statement`, `version`, `network`, and `expirationSeconds`. > **Security: origin binding.** `origin` is required and must be the external, browser-visible origin - not the upstream listener address behind a reverse proxy. The server validates `domain` and the `uri` origin against this configured value, **not** against request-derived values such as the `Host` header; deriving trust from request headers allowed a signature made for another site to be replayed. The `uri` origin must match exactly (scheme, host, and port) - the check was tightened from a prefix match. Client sends `SIGN-IN-WITH-X` HTTP header (Base64-encoded JSON with signature). ### Breaking: result shapes SIWx validation and verification results are discriminated unions across all three SDKs. The old `{ valid, error, address }` shape is gone: ```typescript type SIWxValidationResult = | { isValid: true } | { isValid: false; invalidReason: SIWxValidationCode; invalidMessage: string }; ``` Verify success carries `payer`, not `address`. Python uses `is_valid` / `invalid_reason` / `invalid_message` / `payer`. Go moved `Origin` onto `CreateResourceServerExtension()` and removed `Domain` / `ResourceURI` from `DeclareOptions`. These shipped as **minor** releases, not major. ### SIWx error codes Validation: `invalid_siwx_domain_mismatch`, `invalid_siwx_uri_mismatch`, `invalid_siwx_issued_at`, `invalid_siwx_issued_at_too_old`, `invalid_siwx_issued_at_in_future`, `invalid_siwx_expiration_time`, `invalid_siwx_expired`, `invalid_siwx_not_before`, `invalid_siwx_not_yet_valid`, `invalid_siwx_nonce`. Verification: `invalid_siwx_signature`, `invalid_siwx_chain_id`, `invalid_siwx_unsupported_chain`, `invalid_siwx_malformed_signature`, `invalid_siwx_verifier_error`. Solana SIWx verification rejects small-order Ed25519 public keys (tweetnacl accepted identity-point forgeries). --- ## Gas Sponsoring Extensions (EVM) Two extensions enable gasless Permit2 approval flows. ### eip2612GasSponsoring For tokens implementing **EIP-2612**. Client signs off-chain permit; facilitator calls `settleWithPermit()`. ```typescript import { declareEip2612GasSponsoringExtension } from "@x402/extensions"; extensions: { ...declareEip2612GasSponsoringExtension() } ``` Go: `eip2612gassponsor.DeclareEip2612GasSponsoringExtension()` ### erc20ApprovalGasSponsoring For tokens **without** EIP-2612. Client signs a raw `approve()` transaction; facilitator broadcasts atomically before settling. ```typescript import { declareErc20ApprovalGasSponsoringExtension } from "@x402/extensions"; extensions: { ...declareErc20ApprovalGasSponsoringExtension() } ``` Go: `erc20approvalgassponsor.DeclareExtension()` ### Gas Sponsoring Comparison | Feature | eip2612GasSponsoring | erc20ApprovalGasSponsoring | |---------|---------------------|---------------------------| | Token requirement | Must implement EIP-2612 | Any ERC-20 | | Client signs | Off-chain EIP-2612 permit | Full EVM transaction | | Gas funding needed | No (off-chain signature) | Yes (if client lacks gas) | | Settlement method | `settleWithPermit` | Atomic batch (fund + approve + settle) | --- ## Builder Code (On-Chain Attribution) The `builder-code` extension enables on-chain attribution tracking for x402 payments. Attribution is encoded as an ERC-8021 Schema 2 CBOR "builder code" appended to the settlement transaction calldata via the EVM `calldataSuffix`/`dataSuffix` plumbing, so integrators and tooling can be credited for the payments they originate (app, service, and wallet parties can each attach a code). The service-code field `s` accepts multiple codes (a string or an array / `[]string`), so layered clients (e.g. an MCP middleware) can attribute several participants on-chain. **Capped at 5** (`MAX_SERVICE_CODES`): facilitators silently truncate excess entries, so a sixth code is dropped without error. Note this cap is an implementation constant - it does not appear in `specs/extensions/builder_code.md`. SDK helpers: TypeScript (`@x402/extensions/builder-code`), Go (`go/v2/extensions/buildercode`, `DeclareBuilderCodeExtension` + client/server/facilitator + CBOR), and Python (`x402.extensions.builder_code`). See `specs/extensions/builder_code.md`. ## HTTP Message Signatures (Agent Identity) The `http-message-signatures` extension establishes the identity of the paying agent through cryptographic request signatures (RFC 9421). Cloudflare's `cloudflare:402` network binding uses it to bind a payment to a verifiable agent identity. Spec-defined; no SDK helper yet. See `specs/extensions/http-message-signatures.md`. ## Auth Hints (Authentication Discovery) The `auth-hints` extension provides authentication hints for specific payment requirements. When a `402` response includes multiple `accepts[]` entries and only some require authentication, `auth-hints` lets the client discover which entries need auth - and how to obtain credentials - before committing to a payment method, avoiding an extra round trip. It is a Server-to-Client extension; the facilitator is not involved. Spec-defined; no SDK helper yet. See `specs/extensions/extension-auth-hints.md`. --- ## SDK Support Matrix | Extension | TypeScript | Go | Python | |-----------|------------|-----|--------| | bazaar | Yes | Yes | Yes | | bazaar (facilitator client - search) | Yes | Yes | Yes | | offer-receipt | Yes | No | No | | sign-in-with-x | Yes | Yes | Yes | | payment-identifier | Yes | Yes | Yes | | eip2612GasSponsoring | Yes | Yes | Yes | | erc20ApprovalGasSponsoring | Yes | Yes | Yes | | builder-code | Yes | Yes | Yes | The `http-message-signatures` and `auth-hints` extensions are defined in the protocol spec but do not yet have SDK helpers in any language. `builder-code` now ships all three (Python landed as `x402.extensions.builder_code` plus `x402.mechanisms.evm.data_suffix`). -
go-sdk.md 15.4 KB
# Go SDK Reference Version: 2.20.0 | Module: `github.com/x402-foundation/x402/go/v2` | Go 1.24+ > **Module path:** as of v2.14.0 the module is `github.com/x402-foundation/x402/go/v2`. The old bare `.../x402/go` path no longer resolves tagged releases (it falls back to pseudo-versions). Update all imports to include `/v2`. ## Recent Additions (v2.8-v2.20) - **SVM blockhash hints (v2.20.0)** - `extra.recentBlockhash` / `extra.lastValidBlockHeight` construction hints, matching the TypeScript and Python field names exactly. - **Spec-compatible malformed-header errors (v2.20.0)** - a malformed `PAYMENT-SIGNATURE` header now returns `invalid_payload`. - **Request body preservation (v2.20.0)** - bodies are no longer lost when a request is retried with payment. - **SIWx origin binding (v2.19.0)** - `Origin` is now passed to `CreateResourceServerExtension()`, and `Domain` / `ResourceURI` were **removed** from `DeclareOptions`. Result types became discriminated unions. Solana SIWx rejects small-order Ed25519 public keys (v2.20.0). - **Batch-settlement fixes (v2.19.0)** - unauthenticated path traversal and pre-verification channel mutation fixed; `SettleDeposit` no longer double-counts channel balance after a confirmed deposit. - **MCP interop (v2.18.0)** - payment matching selects the advertised `accepts` entry matching the payment payload instead of always using the first entry. - **Wallet compatibility (v2.17.0)** - payments verify + settle across plain EOAs, ERC-4337 / ERC-7579 smart accounts, counterfactual ERC-6492 wallets, and ERC-7702-delegated EOAs; pre-verification mirrors on-chain signature checking. ERC-6492 gated by `EIP6492AllowedFactories`. - **`FacilitatorSupportValidator` hook (v2.17.0)** - resource server fails fast at `Initialize()` when a scheme delegates a capability (e.g. batch-settlement `receiverAuthorizer`) the facilitator does not advertise. Batch-settlement `authorizerSigner` is now optional; missing authorizer signatures error with `ErrAuthorizerNotConfigured` (`invalid_batch_settlement_evm_authorizer_not_configured`). - **`sign-in-with-x` (v2.16.0)** - Go gains SIWX server + client (`go/v2/extensions/signinwithx`): SIWX storage, auth hooks, EVM EIP-191 sign/verify, HTTP auth retry; also covers undeployed EIP-6492 and SVM. `dynamicInfoFields` capability added. - **Networks (v2.15.0)** - Mezo mainnet (`eip155:31612`, mUSD 18 decimals), XDC Network (`eip155:50`) and XDC Apothem (`eip155:51`) in EVM default-asset resolution. - **builder-code (v2.15.0)** - ERC-8021 attribution with `dataSuffix` helpers (`ResolveDataSuffix`, `AppendDataSuffix`, `BuilderCodeFacilitatorExtension`) threaded through all EVM settle paths; multiple service codes (`[]string`). - **Verify/timing (v2.15.0)** - EVM verify rejects EOA asset addresses (`asset_not_deployed_contract`); authorization `validAfter` set to 0 and default resource-server `maxTimeoutSeconds` raised from 60 to 300. - **Go module `/v2` path** - module is now `github.com/x402-foundation/x402/go/v2` so consumers resolve tagged releases instead of pseudo-versions (v2.14.0). - **`builder-code` extension** - Go SDK helper at `go/v2/extensions/buildercode` (ERC-8021 Schema 2 attribution; client/server/facilitator + CBOR). - **`batch-settlement` scheme** - commit-now / settle-asynchronously EVM mechanism via `go/v2/mechanisms/evm/batch-settlement`. - **Networks** - ADI Chain (`eip155:36900`) and HPP / HPP Sepolia (`eip155:190415` / `eip155:181228`), plus Radius (`eip155:723487` / `eip155:72344`), in EVM default-asset resolution. - **Security (v2.13.0)** - ERC-6492 factory-injection fix (`eip6492AllowedFactories` allowlist now the sole gate; `DeployERC4337WithEIP6492` removed); SVM dedup keyed on tx message hash; facilitator HTTP-200 + `isValid:false` now a hard gate failure. - **`EXTENSION-RESPONSES` header** - decoded and logged by the HTTP facilitator client. - Echo and `net/http` middleware adapters (documented below) landed in v2.8.0. ## Installation ```bash go get github.com/x402-foundation/x402/go/v2 ``` ## Server: Gin ```go import ( x402http "github.com/x402-foundation/x402/go/v2/http" ginmw "github.com/x402-foundation/x402/go/v2/http/gin" evm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/server" svm "github.com/x402-foundation/x402/go/v2/mechanisms/svm/exact/server" ) facilitator := x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{URL: facilitatorURL}) routes := x402http.RoutesConfig{ "GET /weather": { Accepts: x402http.PaymentOptions{ {Scheme: "exact", Price: "$0.001", Network: "eip155:84532", PayTo: evmAddress}, {Scheme: "exact", Price: "$0.001", Network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", PayTo: svmAddress}, }, Description: "Weather data", MimeType: "application/json", }, } r.Use(ginmw.X402Payment(ginmw.Config{ Routes: routes, Facilitator: facilitator, Schemes: []ginmw.SchemeConfig{ {Network: "eip155:84532", Server: evm.NewExactEvmScheme()}, {Network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", Server: svm.NewExactSvmScheme()}, }, Timeout: 30 * time.Second, })) ``` ## Server: Echo ```go import ( x402http "github.com/x402-foundation/x402/go/v2/http" echomw "github.com/x402-foundation/x402/go/v2/http/echo" evm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/server" ) e.Use(echomw.X402Payment(echomw.Config{ Routes: routes, Facilitator: facilitator, Schemes: []echomw.SchemeConfig{{Network: "eip155:84532", Server: evm.NewExactEvmScheme()}}, })) ``` ## Server: net/http (Standard Library) ```go import ( x402http "github.com/x402-foundation/x402/go/v2/http" nethttpmw "github.com/x402-foundation/x402/go/v2/http/nethttp" evm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/server" ) handler := nethttpmw.X402Payment(nethttpmw.Config{ Routes: routes, Facilitator: facilitator, Schemes: []nethttpmw.SchemeConfig{{Network: "eip155:84532", Server: evm.NewExactEvmScheme()}}, })(yourHandler) http.ListenAndServe(":4021", handler) ``` ## Client: HTTP ```go import ( x402 "github.com/x402-foundation/x402/go/v2" x402http "github.com/x402-foundation/x402/go/v2/http" evm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/client" evmsigners "github.com/x402-foundation/x402/go/v2/signers/evm" ) client := x402.Newx402Client() evmSigner, _ := evmsigners.NewClientSignerFromPrivateKey(evmKey) client.Register("eip155:*", evm.NewExactEvmScheme(evmSigner)) httpClient := x402http.Newx402HTTPClient(client) wrappedClient := x402http.WrapHTTPClientWithPayment(http.DefaultClient, httpClient) req, _ := http.NewRequest("GET", "http://localhost:4021/weather", nil) resp, _ := wrappedClient.Do(req) ``` ## Lifecycle Hooks ### Client Hooks ```go client.OnBeforePaymentCreation(func(ctx context.Context, pc x402.PaymentCreationContext) (*x402.AbortResult, error) { return nil, nil // Continue; return &x402.AbortResult{Reason: "blocked"} to abort }) client.OnAfterPaymentCreation(func(ctx context.Context, pc x402.PaymentCreatedContext) error { return nil }) client.OnPaymentCreationFailure(func(ctx context.Context, fc x402.PaymentCreationFailureContext) (*x402.RecoveredPayloadResult, error) { return nil, nil }) ``` ### Server Hooks ```go server.OnBeforeVerify(func(ctx context.Context, vc x402.VerifyContext) (*x402.AbortResult, error) { return nil, nil }) server.OnAfterVerify(func(ctx context.Context, vc x402.VerifyResultContext) error { return nil }) server.OnVerifyFailure(func(ctx context.Context, fc x402.VerifyFailureContext) (*x402.RecoveredVerifyResult, error) { return nil, nil }) server.OnBeforeSettle(func(ctx context.Context, sc x402.SettleContext) (*x402.AbortResult, error) { return nil, nil }) server.OnAfterSettle(func(ctx context.Context, sc x402.SettleResultContext) error { return nil }) server.OnSettleFailure(func(ctx context.Context, fc x402.SettleFailureContext) (*x402.RecoveredSettleResult, error) { return nil, nil }) ``` ### OnProtectedRequest Hook ```go httpServer.OnProtectedRequest(func(ctx context.Context, reqCtx x402http.HTTPRequestContext, route x402http.RouteConfig) (*x402http.ProtectedRequestHookResult, error) { if apiKey := reqCtx.Headers.Get("X-API-Key"); isValidKey(apiKey) { return &x402http.ProtectedRequestHookResult{GrantAccess: true}, nil } return nil, nil // Continue to payment flow }) ``` ### Policies ```go client := x402.Newx402Client( x402.WithPolicy(x402.PreferNetwork("eip155:84532")), x402.WithPaymentSelector(customSelector), ) client.RegisterPolicy(x402.PreferScheme("exact")) ``` ## Client Extensions ```go client.RegisterExtension(myClientExtension) // implements ClientExtension { Key(), EnrichPaymentPayload() } ``` ## Dynamic Pricing and PayTo ```go routes := x402http.RoutesConfig{ "GET /weather": { Accepts: x402http.PaymentOptions{ { Scheme: "exact", Price: x402http.DynamicPriceFunc(func(ctx context.Context, reqCtx x402http.HTTPRequestContext) (x402.Price, error) { if reqCtx.QueryParams["premium"] == "true" { return "$0.01", nil } return "$0.001", nil }), Network: "eip155:84532", PayTo: x402http.DynamicPayToFunc(func(ctx context.Context, reqCtx x402http.HTTPRequestContext) (string, error) { return getWalletForRegion(reqCtx), nil }), }, }, }, } ``` ## Custom Unpaid Response ```go "GET /weather": { Accepts: paymentOptions, UnpaidResponseBody: func(ctx context.Context, reqCtx x402http.HTTPRequestContext) (*x402http.UnpaidResponse, error) { return &x402http.UnpaidResponse{ ContentType: "application/json", Body: map[string]interface{}{"preview": "partial data"}, }, nil }, } ``` ## Bazaar Discovery Extension ```go import "github.com/x402-foundation/x402/go/v2/extensions/bazaar" Extensions: bazaar.DeclareDiscoveryExtension(bazaar.DiscoveryInfo{ Output: map[string]interface{}{"type": "json", "example": map[string]interface{}{"weather": "sunny"}}, }) // WithBazaar facilitator client facilitator := bazaar.WithBazaar(x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{URL: url})) resources, _ := facilitator.ListDiscoveryResources(ctx, &bazaar.ListDiscoveryResourcesParams{Type: "http", Limit: 20}) ``` ## Other Extensions ```go import "github.com/x402-foundation/x402/go/v2/extensions/paymentidentifier" // Payment identifier import "github.com/x402-foundation/x402/go/v2/extensions/eip2612gassponsor" // EIP-2612 gas sponsor import "github.com/x402-foundation/x402/go/v2/extensions/erc20approvalgassponsor" // ERC-20 approval gas sponsor import "github.com/x402-foundation/x402/go/v2/extensions/buildercode" // builder-code (ERC-8021 attribution) ``` The `buildercode` package exposes `DeclareBuilderCodeExtension` plus client/server/facilitator helpers and CBOR encoding for ERC-8021 Schema 2 attribution. ## Custom PaywallProvider ```go provider := x402http.NewPaywallBuilder(). WithNetwork(&x402http.EVMPaywallHandler{}). WithNetwork(&x402http.SVMPaywallHandler{}). WithConfig(&x402http.PaywallConfig{AppName: "My App", Testnet: false}). Build() server.RegisterPaywallProvider(provider) ``` ## MCP Server ```go import x402mcp "github.com/x402-foundation/x402/go/v2/mcp" paymentWrapper := x402mcp.NewPaymentWrapper(resourceServer, requirements) mcpServer.AddTool( mcp.NewTool("weather", mcp.WithDescription("Get weather data")), paymentWrapper.Wrap(func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { return mcp.NewToolResultText(`{"weather": "sunny"}`), nil }), ) ``` ## Facilitator (Self-hosted) ```go facilitator := x402.Newx402Facilitator() facilitator.Register([]x402.Network{"eip155:84532"}, evm.NewExactEvmScheme(evmSigner)) facilitator.RegisterExtension(x402.NewFacilitatorExtension("myKey")) // Verify/Settle accept []byte, auto-detect V1/V2 result, _ := facilitator.Verify(ctx, payloadBytes, requirementsBytes) result, _ := facilitator.Settle(ctx, payloadBytes, requirementsBytes) ``` ## Custom Money Parser ```go evmScheme := evm.NewExactEvmScheme().RegisterMoneyParser( func(amount float64, network x402.Network) (*x402.AssetAmount, error) { return &x402.AssetAmount{ Amount: fmt.Sprintf("%.0f", amount*1e18), Asset: "0xYourTokenAddress", Extra: map[string]interface{}{"assetTransferMethod": "permit2"}, }, nil }, ) ``` ## Upto Scheme (Usage-Based Billing) ```go import ( uptoclient "github.com/x402-foundation/x402/go/v2/mechanisms/evm/upto/client" uptoserver "github.com/x402-foundation/x402/go/v2/mechanisms/evm/upto/server" ) // Server: register upto scheme server.Register("eip155:84532", uptoserver.NewUptoEvmScheme()) // Route config with scheme "upto" and max price routes := x402http.RoutesConfig{ "GET /api/generate": { Accepts: x402http.PaymentOptions{ {Scheme: "upto", Price: "$0.10", Network: "eip155:84532", PayTo: address}, }, }, } // In handler: set actual settlement amount x402http.SetSettlementOverrides(w, x402http.SettlementOverrides{Amount: "50000"}) // raw atomic units // Client: register upto scheme client.Register("eip155:*", uptoclient.NewUptoEvmScheme(evmSigner)) ``` ## Wildcard Registration ```go client. Register("eip155:*", evm.NewExactEvmScheme(defaultSigner)). // Fallback for all EVM Register("eip155:1", evm.NewExactEvmScheme(mainnetSigner)) // Override for mainnet ``` ## Key Import Paths | Purpose | Import | |---------|--------| | Core types | `github.com/x402-foundation/x402/go/v2` | | HTTP utilities | `github.com/x402-foundation/x402/go/v2/http` | | Gin middleware | `github.com/x402-foundation/x402/go/v2/http/gin` | | Echo middleware | `github.com/x402-foundation/x402/go/v2/http/echo` | | net/http middleware | `github.com/x402-foundation/x402/go/v2/http/nethttp` | | EVM exact server | `github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/server` | | EVM exact client | `github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/client` | | EVM exact facilitator | `github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/facilitator` | | EVM upto server | `github.com/x402-foundation/x402/go/v2/mechanisms/evm/upto/server` | | EVM upto client | `github.com/x402-foundation/x402/go/v2/mechanisms/evm/upto/client` | | EVM upto facilitator | `github.com/x402-foundation/x402/go/v2/mechanisms/evm/upto/facilitator` | | SVM exact server | `github.com/x402-foundation/x402/go/v2/mechanisms/svm/exact/server` | | SVM exact client | `github.com/x402-foundation/x402/go/v2/mechanisms/svm/exact/client` | | EVM signers | `github.com/x402-foundation/x402/go/v2/signers/evm` | | SVM signers | `github.com/x402-foundation/x402/go/v2/signers/svm` | | MCP support | `github.com/x402-foundation/x402/go/v2/mcp` | | Bazaar extension | `github.com/x402-foundation/x402/go/v2/extensions/bazaar` | | Payment identifier | `github.com/x402-foundation/x402/go/v2/extensions/paymentidentifier` | | EIP-2612 gas sponsor | `github.com/x402-foundation/x402/go/v2/extensions/eip2612gassponsor` | | ERC-20 approval sponsor | `github.com/x402-foundation/x402/go/v2/extensions/erc20approvalgassponsor` | | Builder-code extension | `github.com/x402-foundation/x402/go/v2/extensions/buildercode` | | Sign-in-with-x extension | `github.com/x402-foundation/x402/go/v2/extensions/signinwithx` | | Batch-settlement (EVM) | `github.com/x402-foundation/x402/go/v2/mechanisms/evm/batch-settlement` | -
lifecycle-hooks.md 7.8 KB
# Lifecycle Hooks Reference All three x402 roles (client, server, facilitator) support lifecycle hooks for logging, spending limits, access control, and custom behavior. All SDKs support method chaining. ## Server Hooks ### x402ResourceServer (Transport-agnostic) * **onBeforeVerify** - Runs before payment verification. Return `{ abort: true, reason }` to reject. * **onAfterVerify** - Runs after successful verification. May also return `{ abort: true, reason, message? }` to reject the payment *after* it verified. When a hook aborts here, `onVerifiedPaymentCanceled` is dispatched with reason `after_verify_aborted` so schemes can release any reserved state. * **onVerifiedPaymentCanceled** - Runs when a verified payment is cancelled. Fires at most once per payment. Use it to clear reservations a scheme took during verification. * **onVerifyFailure** - Runs on verification failure. Return `{ recovered: true, result }` to override. * **onBeforeSettle** - Runs before settlement. Return `{ abort: true, reason }` to reject. * **onAfterSettle** - Runs after successful settlement. * **onSettleFailure** - Runs on settlement failure. Return `{ recovered: true, result }` to override. **TypeScript:** ```typescript import { x402ResourceServer } from "@x402/core"; const server = new x402ResourceServer(facilitatorClient); server.onAfterSettle(async (context) => { await recordPayment({ payer: context.result.payer, transaction: context.result.transaction, amount: context.requirements.amount, network: context.requirements.network, }); }); ``` **Python (async):** ```python from x402 import x402ResourceServer server = x402ResourceServer(facilitator_client) async def record_payment(context): await db.record_payment( payer=context.result.payer, transaction=context.result.transaction, ) server.on_after_settle(record_payment) ``` **Python (sync):** Use `x402ResourceServerSync` with sync callables. **Go:** ```go server := x402.Newx402ResourceServer(facilitatorClient) server.OnAfterSettle(func(ctx x402.SettleResultContext) error { return db.RecordPayment(Payment{ Payer: ctx.Result.Payer, Transaction: ctx.Result.Transaction, }) }) ``` ### x402HTTPResourceServer (HTTP-specific) * **onProtectedRequest** - Runs on every request to a protected route. * Return `{ grantAccess: true }` to bypass payment (e.g., API key auth). * Return `{ abort: true, reason }` to return 403. * Return `void`/`nil` to continue to payment flow. **TypeScript:** ```typescript const httpServer = new x402HTTPResourceServer(server, routes); httpServer.onProtectedRequest(async (context, routeConfig) => { const apiKey = context.adapter.getHeader("X-API-Key"); if (apiKey && await isValidApiKey(apiKey)) { return { grantAccess: true }; } }); ``` **Go:** ```go httpServer.OnProtectedRequest(func(ctx context.Context, reqCtx x402http.HTTPRequestContext, route x402http.RouteConfig) (*x402http.ProtectedRequestHookResult, error) { apiKey := reqCtx.Adapter.GetHeader("X-API-Key") if apiKey != "" && isValidAPIKey(apiKey) { return &x402http.ProtectedRequestHookResult{GrantAccess: true}, nil } return nil, nil }) ``` ## Client Hooks ### x402Client (Transport-agnostic) * **onBeforePaymentCreation** - Runs before creating a payment payload. Return `{ abort: true, reason }` to cancel. * **onAfterPaymentCreation** - Runs after successful payload creation. * **onPaymentCreationFailure** - Runs on failure. Return `{ recovered: true, payload }` to provide fallback. **TypeScript:** ```typescript client.onBeforePaymentCreation(async (context) => { const maxAmount = BigInt("10000000"); // 10 USDC const requestedAmount = BigInt(context.selectedRequirements.amount); if (requestedAmount > maxAmount) { return { abort: true, reason: "Payment exceeds spending limit" }; } }); ``` **Go:** ```go client.OnBeforePaymentCreation(func(ctx context.Context, pc x402.PaymentCreationContext) (*x402.AbortResult, error) { maxAmount := big.NewInt(10_000_000) requestedAmount := new(big.Int) requestedAmount.SetString(pc.Requirements.Amount, 10) if requestedAmount.Cmp(maxAmount) > 0 { return &x402.AbortResult{Reason: "Payment exceeds spending limit"}, nil } return nil, nil }) ``` ### x402HTTPClient (HTTP-specific) * **onPaymentRequired** - Runs when a 402 response is received. * Return `{ headers }` to retry with alternate headers before paying (e.g., API key fallback). * Return `void` to proceed directly to payment. ```typescript httpClient.onPaymentRequired(async ({ paymentRequired }) => { const apiKey = process.env.API_KEY; if (apiKey) { return { headers: { "Authorization": `Bearer ${apiKey}` } }; } }); ``` ## Facilitator Hooks Same verify/settle pattern as server hooks. **TypeScript:** ```typescript facilitator.onAfterVerify(async (context) => { const discovered = extractDiscoveryInfo(context.paymentPayload, context.requirements, true); if (discovered) { bazaarCatalog.add({ resource: discovered.resourceUrl, accepts: [context.requirements] }); } }); ``` **Go:** ```go facilitator.OnAfterVerify(func(ctx x402.FacilitatorVerifyResultContext) error { // Bazaar catalog population, compliance checks, metrics return nil }) ``` ## Hook Chaining All SDKs support method chaining: ```typescript server .onBeforeVerify(validatePayment) .onAfterVerify(logVerification) .onBeforeSettle(checkBalance) .onAfterSettle(recordTransaction); ``` ```go server.OnBeforeVerify(validate).OnAfterVerify(log).OnBeforeSettle(check).OnAfterSettle(record) ``` ## Extension Hooks ### Server Extensions (ResourceServerExtension) ```typescript server.registerExtension({ key: "my-extension", enrichDeclaration: async (declaration, transportContext) => enrichedDeclaration, enrichPaymentRequiredResponse: async (declaration, context) => ({ info: {}, schema: {} }), enrichSettlementResponse: async (declaration, context) => ({ info: {}, schema: {} }), }); ``` ### Client Extensions (ClientExtension) ```typescript client.registerExtension({ key: "eip2612GasSponsoring", enrichPaymentPayload: async (paymentPayload, paymentRequired) => enrichedPayload, }); ``` Go: `client.RegisterExtension(myExtension)` - implements `ClientExtension` interface with `Key()` and `EnrichPaymentPayload()`. ## MCP Hooks ```typescript const paidTool = createPaymentWrapper(resourceServer, requirements, { onBeforeExecution: async (toolName, args, paymentPayload) => {}, onAfterExecution: async (toolName, result, paymentPayload) => {}, onAfterSettlement: async (toolName, settlementResponse) => {}, }); ``` ## Python Naming Convention | TypeScript | Python | |------------|--------| | `onBeforeVerify` | `on_before_verify` | | `onAfterSettle` | `on_after_settle` | | `onBeforePaymentCreation` | `on_before_payment_creation` | | `onProtectedRequest` | `on_protected_request` | Sync variants: `x402ResourceServerSync`, `x402ClientSync`, `x402FacilitatorSync` ## Hook Support Matrix | Hook | TypeScript | Go | Python | |------|------------|-----|--------| | Client: onBeforePaymentCreation | Yes | Yes | Yes | | Client: onAfterPaymentCreation | Yes | Yes | Yes | | Client: onPaymentCreationFailure | Yes | Yes | Yes | | Client: onPaymentRequired (HTTP) | Yes | No | No | | Client: registerExtension | Yes | Yes | No | | Server: onBeforeVerify | Yes | Yes | Yes | | Server: onAfterVerify (abortable) | Yes | Yes | Yes | | Server: onVerifiedPaymentCanceled | Yes | Yes | Yes | | Server: onVerifyFailure | Yes | Yes | Yes | | Server: onBeforeSettle | Yes | Yes | Yes | | Server: onAfterSettle | Yes | Yes | Yes | | Server: onSettleFailure | Yes | Yes | Yes | | Server: onProtectedRequest (HTTP) | Yes | Yes | No | | Facilitator: all verify/settle hooks | Yes | Yes | Yes | | Extension: enrichDeclaration | Yes | Yes | Yes | | Extension: enrichPaymentRequiredResponse | Yes | No | No | | Extension: enrichSettlementResponse | Yes | No | No | -
near-scheme.md 5.6 KB
# NEAR Exact Scheme Reference The `exact` scheme on NEAR uses a NEP-366 `SignedDelegate` authorizing exactly one NEP-141 `ft_transfer`. A facilitator-selected **relayer** sponsors the on-chain transaction, so the payer needs no NEAR for gas. **TypeScript only** - `@x402/near` (published on npm at 2.20.0). Python and Go not implemented. NEAR previously appeared in the spec with no SDK; it graduated to a shipped TypeScript implementation in this release line. ## Network Identifiers | Network | CAIP-2 ID | Default RPC | |---------|-----------|-------------| | NEAR Mainnet | `near:mainnet` | `https://rpc.mainnet.fastnear.com` | | NEAR Testnet | `near:testnet` | `https://rpc.testnet.fastnear.com` | Defaults point at FastNEAR's keyless public endpoints - the legacy `*.near.org` public RPC is deprecated. Override per network through the signer configuration when a private or archival node is required. The default `x402.org` facilitator does **not** support NEAR. A [community NEAR facilitator](https://docs.x402.org/dev-tools/facilitators) is listed in the docs facilitator directory. ## Default Assets Circle USDC NEP-141 contract accounts, 6 decimals, used as the fallback for simple money inputs: | Network | Contract account | |---------|------------------| | `near:mainnet` | `17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1` | | `near:testnet` | `3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af` | ## Payload ```json { "payload": { "signedDelegateAction": "<base64-encoded Borsh SignedDelegate>" } } ``` A base64-encoded Borsh NEP-366 `SignedDelegate` whose delegate action represents exactly one NEP-141 `ft_transfer`. ## Protocol Flow 1. Server returns `402` with NEAR payment requirements 2. Client builds a NEP-366 delegate action wrapping one `ft_transfer` to `payTo` for the exact amount, with `1` yoctoNEAR attached (NEP-141 requires this) 3. Client signs the delegate action with a **full-access key** (ed25519 or secp256k1) 4. Facilitator verifies the signed delegate against the payment requirements 5. Facilitator submits it through a relayer account, sponsoring gas 6. Settlement reports `success: true` only once the inner `ft_transfer` receipt has succeeded on-chain ## Verification Rules - Version, scheme, network, and requirement consistency (asset, recipient, amount, timeout) - NEP-366 `SignedDelegate` signature (ed25519 or secp256k1) - Exactly one `ft_transfer` to `payTo` for the exact `amount`, with `1` yoctoNEAR attached - Deterministic `maxTimeoutSeconds` to `max_block_height` window - Replay protection via the on-chain access-key nonce (`view_access_key`) - **Full-access key required** - standard function-call keys are rejected - Chain-state preflight: account existence, deployed token code, `ft_balance_of`, and `storage_balance_of` (NEP-145), **failing closed on any RPC error** Settlement re-verifies, deduplicates concurrent submissions via an in-memory `SettlementCache`, then submits through the relayer. ## Footgun: Full-Access Key Required The payer must sign with a full-access key. Function-call access keys - the standard, safer NEAR pattern for dApp interaction - are rejected. Wallets that only expose function-call keys cannot pay via this scheme. ## Footgun: Storage Registration (NEP-145) Verification checks `storage_balance_of` on the token contract. On NEAR, a recipient that has never been registered with a NEP-141 token has no storage deposit, and the transfer will fail. Register the `payTo` account with the token contract before going live - this is NEAR's analogue of the Solana "destination ATA must exist" footgun. ## TypeScript Usage ### Client ```typescript import { x402Client } from "@x402/core/client"; import { createClientNearSigner } from "@x402/near"; import { ExactNearScheme } from "@x402/near/exact/client"; const signer = createClientNearSigner({ accountId: "alice.testnet", secretKey: process.env.NEAR_SECRET_KEY, // ed25519:... full-access key }); const client = new x402Client(); client.register("near:*", new ExactNearScheme(signer)); ``` ### Resource Server ```typescript import { x402ResourceServer } from "@x402/core/server"; import { ExactNearScheme } from "@x402/near/exact/server"; const server = new x402ResourceServer(); server.register("near:*", new ExactNearScheme()); ``` ### Facilitator ```typescript import { x402Facilitator } from "@x402/core/facilitator"; import { createFacilitatorNearSigner } from "@x402/near"; import { ExactNearScheme } from "@x402/near/exact/facilitator"; const signer = createFacilitatorNearSigner({ relayers: [{ accountId: "relayer.testnet", secretKey: process.env.NEAR_RELAYER_KEY }], }); const facilitator = new x402Facilitator(); facilitator.register("near:testnet", new ExactNearScheme(signer)); ``` `ExactNearFacilitatorOptions` has a single field, `maxSponsoredGas?: bigint`, bounding what the relayer will sponsor. `createClientNearSigner` and `createFacilitatorNearSigner` are JSON-RPC-backed reference implementations accepting an optional `rpcUrls` map. Substitute any implementation of the `ClientNearSigner` / `FacilitatorNearSigner` interfaces (for example KMS-backed, or a custom relayer). ## Key Exports | Purpose | Export | |---------|--------| | Client signer | `createClientNearSigner({ accountId, secretKey, rpcUrls? })` | | Facilitator signer | `createFacilitatorNearSigner({ relayers, rpcUrls? })` | | RPC provider factory | `createProviderFactory` | | Duplicate settlement cache | `SettlementCache` | | Network constants | `NEAR_MAINNET_CAIP2`, `NEAR_TESTNET_CAIP2` | | Scheme (all roles) | `ExactNearScheme` from `@x402/near/exact/{client,server,facilitator}` | -
protocol-spec.md 22.2 KB
# x402 Protocol Specification (v2) ## Protocol Version: 2 x402 is a three-layer architecture: 1. **Types** - Core data structures independent of transport and scheme 2. **Logic (Schemes)** - Payment formation/verification per network 3. **Representation (Transports)** - How payment data is transmitted ## Core Payment Flow 1. Client requests resource from server 2. Server responds with payment required signal + `PaymentRequired` data 3. Client creates `PaymentPayload` with signed authorization 4. Client retries request with payment payload attached 5. Server POSTs to facilitator `/verify` 6. Facilitator validates signature, balance, time window 7. Server POSTs to facilitator `/settle` 8. Facilitator broadcasts transaction to blockchain 9. Server responds with success + `SettlementResponse` ## Core Types ### PaymentRequired Sent by server when payment is needed: ```json { "x402Version": 2, "error": "PAYMENT-SIGNATURE header is required", "resource": { "url": "https://api.example.com/premium-data", "description": "Access to premium market data", "mimeType": "application/json" }, "accepts": [ { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" } } ], "extensions": {} } ``` #### PaymentRequired Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `x402Version` | number | Yes | Protocol version (must be 2) | | `error` | string | No | Human-readable error message | | `resource` | ResourceInfo | Yes | Protected resource metadata | | `accepts` | PaymentRequirements[] | Yes | Acceptable payment methods | | `extensions` | object | No | Protocol extensions data | #### PaymentRequirements Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `scheme` | string | Yes | Payment scheme (e.g., "exact", "upto") | | `network` | string | Yes | CAIP-2 network ID (e.g., "eip155:84532") | | `amount` | string | Yes | Amount in atomic token units | | `asset` | string | Yes | Token contract address or ISO 4217 currency code for fiat | | `payTo` | string | Yes | Recipient wallet address or role constant (e.g., "merchant") | | `maxTimeoutSeconds` | number | Yes | Max time for payment completion | | `extra` | object | No | Scheme-specific data | #### ResourceInfo Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | Yes | URL of the protected resource | | `description` | string | No | Human-readable description | | `mimeType` | string | No | MIME type of response | | `serviceName` | string | No | Service name for discovery. Printable ASCII, max 32 chars | | `tags` | string[] | No | Topical tags for discovery filtering. Max 5, each printable ASCII max 32 chars | | `iconUrl` | string | No | Absolute http/https URL to a service icon. Max 2048 chars | ### PaymentPayload Sent by client with payment authorization: ```json { "x402Version": 2, "resource": { "url": "https://api.example.com/premium-data", "description": "Access to premium market data", "mimeType": "application/json" }, "accepted": { "scheme": "exact", "network": "eip155:84532", "amount": "10000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" } }, "payload": { "signature": "0x2d6a7588...", "authorization": { "from": "0x857b06519E91e3A54538791bDbb0E22373e36b66", "to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "value": "10000", "validAfter": "1740672089", "validBefore": "1740672154", "nonce": "<EXAMPLE_NONCE>" } }, "extensions": {} } ``` > The `nonce` is an opaque 32-byte value (64 hex chars). Examples use `<EXAMPLE_NONCE>` as a placeholder; real implementations generate it randomly per authorization. | Field | Type | Required | Description | |-------|------|----------|-------------| | `x402Version` | number | Yes | Protocol version | | `resource` | ResourceInfo | No | Resource being accessed | | `accepted` | PaymentRequirements | Yes | Chosen payment method | | `payload` | object | Yes | Scheme-specific signed data | | `extensions` | object | No | Protocol extensions data | ### SettlementResponse Returned after successful settlement: ```json { "success": true, "transaction": "0x1234567890abcdef...", "network": "eip155:84532", "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `success` | boolean | Yes | Whether settlement succeeded | | `errorReason` | string | No | Error if failed | | `payer` | string | No | Payer's wallet address | | `transaction` | string | Yes | Blockchain tx hash | | `network` | string | Yes | CAIP-2 network ID | | `amount` | string | No | Actual settled amount (used by `upto` scheme; may differ from requested) | | `extensions` | object | No | Protocol extensions data (e.g., signed receipts) | ### VerifyResponse ```json { "isValid": true, "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `isValid` | boolean | Yes | Whether authorization is valid | | `invalidReason` | string | No | Reason if invalid | | `payer` | string | No | Payer's wallet address | | `extra` | object | No | Scheme-specific additional data | ## TypeScript Type Definitions ```typescript interface ResourceInfo { url: string; description: string; mimeType: string; } type PaymentRequirements = { scheme: string; network: Network; asset: string; amount: string; payTo: string; maxTimeoutSeconds: number; extra: Record<string, unknown>; }; type PaymentRequired = { x402Version: number; error?: string; resource: ResourceInfo; accepts: PaymentRequirements[]; extensions?: Record<string, unknown>; }; type PaymentPayload = { x402Version: number; resource: ResourceInfo; accepted: PaymentRequirements; payload: Record<string, unknown>; extensions?: Record<string, unknown>; }; ``` ## Facilitator HTTP API ### POST /verify Verifies payment without executing on-chain. **Request:** ```json { "x402Version": 2, "paymentPayload": { /* PaymentPayload */ }, "paymentRequirements": { /* PaymentRequirements */ } } ``` The `x402Version` field is required in both `/verify` and `/settle` request bodies. **Success Response:** ```json { "isValid": true, "payer": "0x..." } ``` **Error Response:** ```json { "isValid": false, "invalidReason": "insufficient_funds", "payer": "0x..." } ``` ### POST /settle Executes payment by broadcasting to blockchain. **Request:** Same structure as `/verify` (includes `x402Version`). Note: While the request structure is identical, some schemes assign different semantics to fields at settlement time. In the `upto` scheme, `amount` in `paymentRequirements` is the maximum at verification but the actual amount to charge at settlement. **Success Response:** ```json { "success": true, "payer": "0x...", "transaction": "0x...", "network": "eip155:84532" } ``` ### GET /supported Lists supported schemes/networks/extensions. **Response:** ```json { "kinds": [ { "x402Version": 2, "scheme": "exact", "network": "eip155:84532" }, { "x402Version": 2, "scheme": "exact", "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" } ], "extensions": [], "signers": { "eip155:*": ["0x1234..."], "solana:*": ["CKPKJWNd..."] } } ``` #### SupportedResponse Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `kinds` | array | Yes | Array of SupportedKind objects | | `extensions` | array | Yes | Extension identifiers the facilitator has implemented | | `signers` | object | Yes | Map of CAIP-2 patterns to public signer addresses | Each `SupportedKind` object: | Field | Type | Required | Description | |-------|------|----------|-------------| | `x402Version` | number | Yes | Protocol version (2) | | `scheme` | string | Yes | Payment scheme (e.g., "exact") | | `network` | string | Yes | CAIP-2 network ID | | `extra` | object | No | Scheme-specific configuration | ## Exact Scheme - EVM Asset Transfer Methods The `exact` scheme on EVM supports three asset transfer methods: | Method | Use Case | Notes | |--------|----------|-------| | **EIP-3009** | Tokens with native `transferWithAuthorization` (e.g., USDC) | Recommended. Simplest, truly gasless. | | **Permit2** | Any ERC-20 token (universal fallback) | Uses `x402ExactPermit2Proxy` contract. | | **ERC-7710** | Smart accounts with delegation support | Uses `DelegationManager.redeemDelegations()`. | If no `assetTransferMethod` is specified in `extra`, implementations prioritize `eip3009` (if compatible) then `permit2`. ### Permit2 (Exact) Contract Canonical `x402ExactPermit2Proxy` address: `0x402085c248EeA27D92E8b30b2C58ed07f9E20001` (same across all EVM chains via CREATE2). ### ERC-7710 Payload Structure ```json { "payload": { "delegationManager": "0xDelegationManagerAddress", "permissionContext": "0x...", "delegator": "0x857b06519E91e3A54538791bDbb0E22373e36b66" } } ``` Verification is done entirely through simulation of `delegationManager.redeemDelegations()`. No trusted list of DelegationManager implementations needed. ## Exact Scheme - SVM (Solana) Uses `TransferChecked` for SPL tokens. Key verification requirements: - Strict instruction layout: Compute Unit Limit, Compute Unit Price, TransferChecked (plus optional Lighthouse/Memo instructions for Phantom/Solflare wallets) - Fee payer must NOT appear in any instruction accounts - Compute unit price bounded (reference: <= 5 lamports/CU) - Destination must equal ATA PDA for `(payTo, asset)` - Transfer amount must exactly equal `PaymentRequirements.amount` ### Duplicate Settlement Mitigation (SVM) Race condition: same tx submitted to `/settle` multiple times returns "success" each time. Mitigate with short-term in-memory cache of tx payloads, evict after 120 seconds. Error code: `duplicate_settlement`. ## Exact Scheme - Aptos Uses `0x1::primary_fungible_store::transfer` for fungible assets. Key details: - Network IDs: `aptos:1` (mainnet), `aptos:2` (testnet) - `extra.feePayer`: address of facilitator account that sponsors gas (optional) - Payload contains BCS-serialized, Base64-encoded signed transaction - Supports sponsored (gasless) and non-sponsored transactions - Verification: deserialize BCS transaction, verify signature, check transfer params, simulate via Aptos REST API - Signature schemes: Ed25519, MultiEd25519, SingleKey, MultiKey ## Batch-Settlement Scheme `batch-settlement` is a payment scheme where the client provides a cryptographic payment commitment at request time, but value transfer is **not** executed synchronously during that request. The commitment is accepted, access is granted immediately, and financial settlement happens later through a process defined by the network binding (payment channels, fiat billing, stablecoin invoices, batched on-chain settlement). Use it when per-request on-chain settlement is impractical: gas fees exceed per-request value, block confirmation is too slow for HTTP latency, or settlement runs asynchronously from HTTP. It supports **dynamic pricing** - the client commits up to `PaymentRequirements.amount` (the maximum), and the server may charge a lower actual price, communicated via `PAYMENT-RESPONSE`. SDK support: TypeScript (`@x402/evm/batch-settlement/{client,server,facilitator}` plus `*/file-storage` and `server/redis-storage`), Go (`go/v2/mechanisms/evm/batch-settlement`), and Python (`x402.mechanisms.evm.batch_settlement`); EVM and Cloudflare network bindings. See `specs/schemes/batch-settlement/`. As of 2.17.0 the facilitator `authorizerSigner` (Go/TS) / `receiver_authorizer_signer` (Python) is **optional**. A facilitator that omits it no longer advertises a `receiverAuthorizer` in `/supported`, and servers must then supply their own claim/refund authorizer - claim/refund without one errors with `invalid_batch_settlement_evm_authorizer_not_configured` (Go `ErrAuthorizerNotConfigured`). A server intending to delegate fails fast at `initialize()` (via `validateFacilitatorSupport`) when the facilitator advertises no usable `receiverAuthorizer`. ## Auth-Capture Scheme `auth-capture` is a payment scheme where funds can be held and settled later. The client authorizes a maximum amount; the facilitator either locks funds in escrow for later capture (two-phase) or sends them directly with refund capability (single-shot). Unlike `exact`, it supports returning funds via **void, refund, and reclaim** - useful for escrow, pre-authorization, and refundable purchases. The **captureAuthorizer** is the entity allowed to authorize, capture, void, refund, or charge a payment. A TypeScript **client** scheme shipped in v2.14.0 (`@x402/evm/auth-capture/client`); server/facilitator support is still landing. See `specs/schemes/auth-capture/`. ## Spec-Stage Chains (No SDK Yet) Beyond the SDK-supported networks, the spec defines `exact` schemes for additional chains with no SDK implementation (spec only): **Cardano, Sui, Starknet, and Casper**. (NEAR graduated to `@x402/near`, joining earlier graduates `@x402/concordium` and `@x402/keeta`; XRPL arrived with both spec and SDK.) See `specs/schemes/exact/scheme_exact_<chain>.md`. - **Starknet** (`starknet:SN_MAIN` / `starknet:SN_SEPOLIA`) - the client signs a SNIP-12 typed-data message authorizing exactly one `transfer` from its account contract; the facilitator executes it via `execute_from_outside_v2`. Defines 12 `invalid_exact_starknet_*` codes plus `settlement_pending`. - **Casper** (`casper:casper` / `casper:casper-test`) - uses the `transfer_with_authorization` entry point from CEP-3009, Casper's adaptation of EIP-3009 for CEP-18 tokens. Other spec-only bindings: a Cloudflare variant of `batch-settlement` (`specs/schemes/batch-settlement/scheme_batch_settlement_cloudflare.md`), and a **draft** SVM binding for `upto` built on the Solana payment-channels program (`specs/schemes/upto/scheme_upto_svm.md`). ## Network Identifiers (CAIP-2) Format: `{namespace}:{reference}` | Network | CAIP-2 ID | |---------|-----------| | Base Mainnet | `eip155:8453` | | Base Sepolia | `eip155:84532` | | Ethereum Mainnet | `eip155:1` | | Polygon Mainnet | `eip155:137` | | Polygon Amoy | `eip155:80002` | | Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | | Solana Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | | Avalanche Mainnet | `eip155:43114` | | Avalanche Fuji | `eip155:43113` | | MegaETH Mainnet | `eip155:4326` | | Monad Mainnet | `eip155:143` | | Stellar Mainnet | `stellar:pubnet` | | Stellar Testnet | `stellar:testnet` | | Aptos Mainnet | `aptos:1` | | Aptos Testnet | `aptos:2` | | Sei Mainnet | `eip155:1329` | | Sei Testnet | `eip155:713715` | | SKALE Mainnet | `eip155:1187947933` | | SKALE Testnet | `eip155:324705682` | | Radius Mainnet | `eip155:723487` | | Radius Testnet | `eip155:72344` | | TON Mainnet | `tvm:-239` | | TON Testnet | `tvm:-3` | | Hedera Mainnet | `hedera:mainnet` | | Hedera Testnet | `hedera:testnet` | | Algorand Mainnet | `algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k` | | Algorand Testnet | `algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDe` | | Igra Mainnet | `eip155:38833` | | NEAR Mainnet | `near:mainnet` | | NEAR Testnet | `near:testnet` | | XRPL Mainnet | `xrpl:0` | | XRPL Testnet | `xrpl:1` | | XRPL Devnet | `xrpl:2` | | Mezo Mainnet | `eip155:31612` | | Mezo Testnet | `eip155:31611` | | XDC Network Mainnet | `eip155:50` | | XDC Apothem Testnet | `eip155:51` | | Keeta Mainnet | `keeta:21378` | | Keeta Testnet | `keeta:1413829460` | | Concordium Mainnet | `ccd:9dd9ca4d19e9393877d2c44b70f89acb` | | Concordium Testnet | `ccd:4221332d34e1694168c2a0c0b3fd0f27` | ## Discovery API (Bazaar) ### GET /discovery/resources List discoverable x402 resources. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `type` | string | No | - | Filter by resource type ("http" or "mcp") | | `payTo` | string | No | - | Filter by payment recipient address | | `scheme` | string | No | - | Filter by payment scheme (e.g., "exact") | | `network` | string | No | - | Filter by CAIP-2 network | | `extensions` | string | No | - | Filter by extension key present on the resource | | `limit` | number | No | 20 | Max results (1-100) | | `offset` | number | No | 0 | Pagination offset | ### GET /discovery/search Semantic search over discoverable resources, with cursor-based pagination (`query`, `type`, `limit`, `cursor`). The response shape is defined in the Bazaar extension specification. ## Error Codes | Code | Description | |------|-------------| | `insufficient_funds` | Client lacks enough tokens | | `invalid_exact_evm_payload_signature` | Invalid EIP-712 signature | | `invalid_exact_evm_payload_authorization_valid_before` | Authorization expired | | `invalid_exact_evm_payload_authorization_valid_after` | Authorization not yet valid | | `invalid_exact_evm_payload_authorization_value_mismatch` | Amount does not exactly match required | | `invalid_exact_svm_payload_amount_mismatch` | Solana amount does not exactly match required | | `permit2_amount_mismatch` | Permit2 amount does not exactly match required | | `permit2_allowance_required` | Client has not approved the Permit2 contract (HTTP 412 Precondition Failed). Prose in the EVM spec spells it uppercase, but all three SDKs emit lowercase on the wire | | `invalid_exact_evm_payload_recipient_mismatch` | Recipient mismatch | | `invalid_network` | Network not supported | | `invalid_payload` | Malformed payload | | `invalid_payment_requirements` | Payment requirements invalid or malformed | | `asset_not_deployed_contract` | EVM asset address has no bytecode (an EOA); verify rejects it before a no-op settlement | | `invalid_batch_settlement_evm_authorizer_not_configured` | Batch-settlement claim/refund attempted but no `receiverAuthorizer` is configured or advertised | | `eip6492_factory_not_allowed` | Counterfactual ERC-6492 wallet's deploy factory is not in the `eip6492AllowedFactories` allowlist | | `invalid_scheme` | Scheme not supported | | `unsupported_scheme` | Scheme not supported by facilitator | | `invalid_x402_version` | Version not supported | | `invalid_transaction_state` | Blockchain transaction failed or rejected | | `duplicate_settlement` | Same SVM transaction submitted to /settle multiple times | | `invalid_upto_evm_payload_settlement_exceeds_amount` | Upto: settled more than authorized | | `invalid_batch_settlement_evm_channel_id_invalid` | Batch-settlement channel id failed validation | | `invalid_batch_settlement_evm_verification_state_unavailable` | Batch-settlement verification state could not be read | | `invalid_batch_settlement_evm_permit2_allowance_required` | Batch-settlement deposit needs a Permit2 allowance | | `invalid_exact_stellar_payload_fee_exceeds_maximum` | Stellar client fee bid exceeds `maxTransactionFeeStroops` | | `invalid_exact_hedera_payload_signature_invalid` | Hedera payer did not sign the frozen transaction body | | `invalid_siwx_*` | 15 sign-in-with-x codes; see `references/extensions.md` | | `unexpected_verify_error` | Unexpected verify error | | `unexpected_settle_error` | Unexpected settle error | ## Extensions Structure Extensions use a standardized key-value map in both `PaymentRequired` and `PaymentPayload`: ```json { "extensions": { "extension-name": { "info": { /* extension-specific data */ }, "schema": { /* JSON Schema validating info */ } } } } ``` Clients must echo the extension from `PaymentRequired` into their `PaymentPayload`. They may append additional info but cannot delete or overwrite existing data. ### Available Extensions | Extension | Description | SDK Support | |-----------|-------------|-------------| | `bazaar` | Discovery layer for x402 endpoints and MCP tools | TS, Go, Python | | `offer-receipt` | Signed offers (402 responses) and receipts (200 responses) for proof-of-interaction | TS | | `payment-identifier` | Idempotency via unique payment IDs | TS, Go, Python | | `sign-in-with-x` | CAIP-122 wallet authentication for re-access without repaying | TS | | `eip2612GasSponsoring` | Facilitator sponsors gas for EIP-2612 permit approvals | TS, Go, Python | | `erc20ApprovalGasSponsoring` | Facilitator sponsors gas for ERC-20 approvals | TS, Go, Python | | `builder-code` | On-chain attribution via ERC-8021 builder codes in settlement calldata | Spec only | | `http-message-signatures` | RFC 9421 cryptographic identity for the paying agent | Spec only | | `auth-hints` | Signals which `accepts[]` entries require authentication | Spec only | ## Security Considerations - **Replay prevention**: EIP-3009 nonces + blockchain-level nonce tracking + time windows; Permit2 nonces for upto scheme; Solana blockhash expiration + duplicate settlement cache - **Trust minimization**: Facilitators cannot modify amount or destination - they only broadcast - **Signature verification**: All authorizations cryptographically signed by payer - **Time constraints**: `validAfter`/`validBefore` (EIP-3009) or `validAfter`/`deadline` (Permit2) bound authorization lifetime - **ERC-7710 race condition**: Mitigated via private mempool submission and reputation signals - **Batch-settlement channel storage**: an untrusted `channelId` from an incoming payload previously reached the file-storage path builder (only lowercased) and reserved or wrote channel state *before* the voucher signature was verified, so a crafted `channelId` could escape the storage root and mutate arbitrary channel files. Fixed in all three SDKs by validating the id (`invalid_batch_settlement_evm_channel_id_invalid`) and splitting verification into a read-only before-verify and an atomic after-verify - **Signature verification must be cryptographic, not simulated**: Aptos verification MUST NOT rely on transaction simulation, which substitutes a dummy signature and never checks the submitted one. Hedera facilitators MUST verify the inferred payer actually signed the frozen transaction body before sponsoring it - **Facilitators must not trust client-supplied fees**: Stellar facilitators MUST derive the settlement fee from a fresh simulation at settle time and MUST NOT use the client's fee bid -
python-sdk.md 15.2 KB
# Python SDK Reference Version: 2.17.0 ## Recent Additions (v2.7-v2.17) - **SVM blockhash hints (v2.17.0)** - `extra.recentBlockhash` / `extra.lastValidBlockHeight` construction hints, matching TypeScript and Go field names exactly. - **httpx streaming retry fix (v2.17.0)** - request bodies are no longer lost when a request is retried with payment. - **`builder-code` (v2.17.0)** - full Python support via `x402.extensions.builder_code` (client/server/facilitator + CBOR) plus `x402.mechanisms.evm.data_suffix`. Python now covers every extension that TypeScript and Go do except `offer-receipt`. - **SIWx breaking changes (v2.16.0)** - results use `is_valid`, `invalid_reason`, `invalid_message`, and `payer` instead of `valid`, `error`, and `address` (filed upstream under a literal "Removals" heading). Origin binding is now required; see `references/extensions.md`. - **Paywall-bypass fix (v2.15.0)** - Flask middleware no longer skips settlement on 3xx responses, which allowed paid content behind a redirect to be delivered without on-chain payment. Flask and FastAPI now return 500 rather than a silent empty 402 on unexpected settlement errors. - **Signer flexibility (v2.15.0)** - the EVM signer auto-wraps any `eth_account` `BaseAccount`, not only `LocalAccount`. - **Wallet compatibility (v2.14.0)** - payments verify + settle across plain EOAs, ERC-4337 / ERC-7579 smart accounts, counterfactual ERC-6492 wallets, and ERC-7702-delegated EOAs; ERC-6492 support in `exact` + `batch-settlement`, gated by `eip6492_allowed_factories`. Batch-settlement `receiver_authorizer_signer` is now optional, with a fail-fast `initialize()` check when the facilitator advertises no usable `receiverAuthorizer`. - **Networks (v2.13.0)** - Mezo mainnet (`eip155:31612`, mUSD 18 decimals), XDC Network (`eip155:50`) and XDC Apothem (`eip155:51`) in EVM default-asset resolution. - **Verify guard (v2.13.0)** - EVM verify rejects EOA asset addresses (no bytecode) across EIP-3009 / Permit2 exact / Permit2 upto with `asset_not_deployed_contract`; authorization `validAfter` set to 0; payment-creation failure hooks now run when after-payment hooks raise. - **SVM client cache (v2.13.1)** - exact SVM client caches mint metadata to avoid repeated mint-account RPC fetches. - **`upto` scheme** - usage-based EVM billing (client/server/facilitator) via `x402.mechanisms.evm.upto`, added in v2.8.0. - **`batch-settlement` scheme** - commit-now / settle-asynchronously EVM mechanism via `x402.mechanisms.evm.batch_settlement` (cumulative vouchers, single on-chain claim; `FileChannelStorage`), in v2.11.0. - **`siwx` (sign-in-with-x) extension** - CAIP-122 wallet auth now available in Python (v2.11.0). - **Networks** - ADI Chain (`eip155:36900`) and HPP / HPP Sepolia (`eip155:190415` / `eip155:181228`, Bridged USDC) added to EVM default-asset resolution (v2.11.0); plus Radius and Arbitrum earlier. - **TVM (TON) exact mechanism** - `x402.mechanisms.tvm` for TON testnet/mainnet jetton transfers (Python SDK only), added in v2.10.0. - **Security (v2.12.0)** - ERC-6492 factory-injection fix (`eip6492_allowed_factories: list[str]` is now the sole gate; empty/omitted disables and returns `eip6492_factory_not_allowed`; `DeployERC4337WithEIP6492` removed); SVM exact dedup keyed on tx message hash (cache-bypass fix). - **Lifecycle hooks + adapter pattern** - missing lifecycle hooks and extension/scheme-level adapter pattern added (v2.11.0); failure hooks now run after after-hook errors. - **MCP fixes (v2.12.0)** - client-factory helpers exported from `x402.mcp`; FastMCP `CallToolResult` metadata preserved when attaching payment responses. - **`EXTENSION-RESPONSES` header** - decoded and logged by the HTTP facilitator client. ## Installation ```bash pip install "x402[httpx]" # Async HTTP client pip install "x402[requests]" # Sync HTTP client pip install "x402[fastapi]" # FastAPI server pip install "x402[flask]" # Flask server pip install "x402[evm]" # EVM support (eth-account, web3, eth-abi) pip install "x402[svm]" # Solana support (solders, solana) pip install "x402[tvm]" # TON support (pytoniq) pip install "x402[mcp]" # MCP integration pip install "x402[extensions]" # Extensions (bazaar, gas sponsoring, payment-identifier, etc.) pip install "x402[all]" # Everything ``` Convenience bundles: `clients` (httpx + requests), `servers` (flask + fastapi), `mechanisms` (evm + svm + tvm). ## Server: FastAPI ```python from fastapi import FastAPI from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption from x402.http.middleware.fastapi import PaymentMiddlewareASGI from x402.http.types import RouteConfig from x402.mechanisms.evm.exact import ExactEvmServerScheme from x402.mechanisms.svm.exact import ExactSvmServerScheme from x402.server import x402ResourceServer app = FastAPI() facilitator = HTTPFacilitatorClient(FacilitatorConfig(url="https://x402.org/facilitator")) server = x402ResourceServer(facilitator) server.register("eip155:84532", ExactEvmServerScheme()) server.register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", ExactSvmServerScheme()) routes = { "GET /weather": RouteConfig( accepts=[ PaymentOption(scheme="exact", pay_to="0xAddr", price="$0.001", network="eip155:84532"), PaymentOption(scheme="exact", pay_to="SvmAddr", price="$0.001", network="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"), ], mime_type="application/json", description="Weather data", ), } app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server) @app.get("/weather") async def get_weather(): return {"weather": "sunny", "temperature": 70} ``` Alternative function-based middleware: ```python from x402.http.middleware.fastapi import payment_middleware @app.middleware("http") async def x402_middleware(request, call_next): handler = payment_middleware(routes, server) return await handler(request, call_next) ``` ## Server: Flask ```python from flask import Flask, jsonify from x402.http import FacilitatorConfig, HTTPFacilitatorClientSync, PaymentOption from x402.http.middleware.flask import PaymentMiddleware from x402.http.types import RouteConfig from x402.mechanisms.evm.exact import ExactEvmServerScheme from x402.server import x402ResourceServerSync app = Flask(__name__) facilitator = HTTPFacilitatorClientSync(FacilitatorConfig(url="https://x402.org/facilitator")) server = x402ResourceServerSync(facilitator) server.register("eip155:84532", ExactEvmServerScheme()) routes = { "GET /weather": RouteConfig( accepts=[PaymentOption(scheme="exact", pay_to="0xAddr", price="$0.001", network="eip155:84532")], mime_type="application/json", description="Weather data", ), } PaymentMiddleware(app, routes=routes, server=server) @app.get("/weather") def get_weather(): return jsonify({"weather": "sunny", "temperature": 70}) ``` ## Client: httpx (Async) ```python from eth_account import Account from x402 import x402Client from x402.http import x402HTTPClient from x402.http.clients import x402HttpxClient from x402.mechanisms.evm import EthAccountSigner from x402.mechanisms.evm.exact.register import register_exact_evm_client client = x402Client() account = Account.from_key(os.getenv("EVM_PRIVATE_KEY")) register_exact_evm_client(client, EthAccountSigner(account)) http_client = x402HTTPClient(client) async with x402HttpxClient(client) as http: response = await http.get("http://localhost:4021/weather") await response.aread() print(response.text) settle = http_client.get_payment_settle_response(lambda name: response.headers.get(name)) ``` ## Client: requests (Sync) ```python from x402 import x402ClientSync from x402.http.clients import x402_requests from x402.mechanisms.evm.exact.register import register_exact_evm_client_sync client = x402ClientSync() register_exact_evm_client_sync(client, EthAccountSigner(account)) session = x402_requests(client) response = session.get("http://localhost:4021/weather") ``` ## Client: Solana Support ```python from x402.mechanisms.svm import KeypairSigner from x402.mechanisms.svm.exact.register import register_exact_svm_client svm_signer = KeypairSigner.from_base58(os.getenv("SVM_PRIVATE_KEY")) register_exact_svm_client(client, svm_signer) ``` ## Lifecycle Hooks ### Client Hooks ```python client.on_before_payment_creation(lambda ctx: None) # AbortResult to abort client.on_after_payment_creation(lambda ctx: None) # Observe client.on_payment_creation_failure(lambda ctx: None) # RecoveredPayloadResult to recover ``` ### Server Hooks ```python server.on_before_verify(lambda ctx: None) # AbortResult to abort server.on_after_verify(lambda ctx: None) server.on_verify_failure(lambda ctx: None) # RecoveredVerifyResult to recover server.on_before_settle(lambda ctx: None) server.on_after_settle(lambda ctx: None) server.on_settle_failure(lambda ctx: None) # RecoveredSettleResult to recover ``` ### Policies ```python from x402.client_base import prefer_network, prefer_scheme, max_amount client.register_policy(prefer_network("eip155:84532")) client.register_policy(prefer_scheme("exact")) client.register_policy(max_amount(1000000)) ``` ## Dynamic Pricing and PayTo ```python routes = { "GET /weather": RouteConfig( accepts=[PaymentOption( scheme="exact", price=lambda ctx: "$0.01" if "premium" in ctx.path else "$0.001", pay_to=lambda ctx: get_wallet_for_region(ctx), network="eip155:84532", )], ), } ``` ## Route Response Customization ```python from x402.http.types import HTTPResponseBody routes = { "GET /weather": RouteConfig( accepts=[...], unpaid_response_body=lambda ctx: HTTPResponseBody( content_type="application/json", body={"error": "Payment required", "preview": {"temp": 70}}, ), settlement_failed_response_body=lambda ctx, result: HTTPResponseBody( content_type="application/json", body={"error": "Settlement failed", "reason": result.error_reason}, ), custom_paywall_html="<html>Custom paywall</html>", ), } ``` ## Extensions ### Bazaar Discovery ```python from x402.extensions.bazaar import bazaar_resource_server_extension server.register_extension(bazaar_resource_server_extension) routes = { "GET /weather": RouteConfig( accepts=[...], extensions={"bazaar": {"output": {"type": "json", "example": {"weather": "sunny"}}}}, ), } ``` ## Extensions: Gas Sponsoring ```python from x402.extensions.eip2612_gas_sponsoring import declare_eip2612_gas_sponsoring_extension from x402.extensions.erc20_approval_gas_sponsoring import declare_erc20_approval_gas_sponsoring_extension # EIP-2612 gas sponsoring extensions = declare_eip2612_gas_sponsoring_extension() # ERC-20 approval gas sponsoring extensions = declare_erc20_approval_gas_sponsoring_extension() ``` ## Extensions: Payment Identifier ```python from x402.extensions.payment_identifier import declare_payment_identifier_extension extensions = declare_payment_identifier_extension(required=False) ``` ## MCP Server ```python from x402.mcp import create_payment_wrapper, PaymentWrapperConfig paid = create_payment_wrapper(resource_server, PaymentWrapperConfig(accepts=accepts)) @mcp_server.tool("financial_analysis", "Financial analysis", schema) @paid def handler(args, context): return {"content": [{"type": "text", "text": "Analysis result"}]} ``` Async variant: `from x402.mcp.server_async import create_payment_wrapper` ## MCP Client ```python from x402.mcp import create_x402_mcp_client_from_config x402_mcp = create_x402_mcp_client_from_config(mcp_client, { "schemes": [{"network": "eip155:84532", "client": ExactEvmClientScheme(signer)}], "auto_payment": True, }) result = x402_mcp.call_tool("get_weather", {"city": "NYC"}) ``` Async: `from x402.mcp.client_async import x402MCPClient` ## Facilitator (Self-hosted) ```python from x402.facilitator import x402Facilitator, x402FacilitatorSync from x402.mechanisms.evm.exact.facilitator import ExactEvmFacilitatorScheme facilitator = x402Facilitator() facilitator.register(["eip155:84532"], ExactEvmFacilitatorScheme(signer)) facilitator.register_extension(my_extension) result = await facilitator.verify(payload_bytes, requirements_bytes) result = await facilitator.settle(payload_bytes, requirements_bytes) ``` ## Facilitator Client Auth ```python from x402.http.facilitator_client_base import AuthProvider, AuthHeaders, FacilitatorConfig class MyAuth: def get_auth_headers(self) -> AuthHeaders: return AuthHeaders( verify={"Authorization": "Bearer ..."}, settle={"Authorization": "Bearer ..."}, supported={"Authorization": "Bearer ..."}, ) facilitator = HTTPFacilitatorClient(FacilitatorConfig(url="...", auth_provider=MyAuth())) ``` ## Custom Pricing with AssetAmount ```python from x402.schemas import AssetAmount PaymentOption( scheme="exact", pay_to="0xAddr", price=AssetAmount(amount="10000", asset="0x036CbD...", extra={"name": "USDC", "version": "2"}), network="eip155:84532", ) ``` ## Async/Sync Duality | Async | Sync | |-------|------| | `x402Client` | `x402ClientSync` | | `x402ResourceServer` | `x402ResourceServerSync` | | `x402Facilitator` | `x402FacilitatorSync` | | `HTTPFacilitatorClient` | `HTTPFacilitatorClientSync` | | `x402HTTPClient` | `x402HTTPClientSync` | | `x402HTTPResourceServer` | `x402HTTPResourceServerSync` | FastAPI middleware uses async variants. Flask middleware uses sync variants. ## Key Import Paths | Purpose | Import | |---------|--------| | Core client | `from x402 import x402Client` | | Core client (sync) | `from x402 import x402ClientSync` | | Resource server | `from x402.server import x402ResourceServer` | | Resource server (sync) | `from x402.server import x402ResourceServerSync` | | Facilitator | `from x402.facilitator import x402Facilitator` | | HTTP facilitator client | `from x402.http import HTTPFacilitatorClient, FacilitatorConfig` | | FastAPI middleware | `from x402.http.middleware.fastapi import PaymentMiddlewareASGI` | | Flask middleware | `from x402.http.middleware.flask import PaymentMiddleware` | | Route config | `from x402.http.types import RouteConfig` | | Payment option | `from x402.http import PaymentOption` | | EVM server scheme | `from x402.mechanisms.evm.exact import ExactEvmServerScheme` (an alias for `ExactEvmScheme`) | | EVM client register | `from x402.mechanisms.evm.exact.register import register_exact_evm_client` | | EVM signer | `from x402.mechanisms.evm import EthAccountSigner` | | SVM server scheme | `from x402.mechanisms.svm.exact import ExactSvmServerScheme` | | SVM client register | `from x402.mechanisms.svm.exact.register import register_exact_svm_client` | | SVM signer | `from x402.mechanisms.svm import KeypairSigner` | | httpx client | `from x402.http.clients import x402HttpxClient` | | requests client | `from x402.http.clients import x402_requests` | | MCP payment wrapper | `from x402.mcp import create_payment_wrapper, PaymentWrapperConfig` | | MCP client factory | `from x402.mcp import create_x402_mcp_client_from_config` | | Bazaar extension | `from x402.extensions.bazaar import bazaar_resource_server_extension` | | Schemas | `from x402.schemas import Network, AssetAmount` | | Policies | `from x402.client_base import prefer_network, prefer_scheme, max_amount` | -
stellar-scheme.md 7 KB
# Stellar Exact Scheme Reference The `exact` scheme on Stellar uses Soroban smart contracts with the SEP-41 token standard. Clients sign auth entries (not full transactions); facilitators rebuild and submit transactions, always sponsoring fees. **TypeScript only** - `@x402/stellar` package. Python and Go not yet implemented. ## Canonical addresses Well-known Stellar Soroban contract addresses referenced by placeholder elsewhere in this file. All values are public, on-chain identifiers (not secrets). | Placeholder | Value | |-------------|-------| | `<STELLAR_USDC_MAINNET>` | `CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75` (USDC SEP-41 token contract, Stellar Mainnet) | | `<STELLAR_USDC_TESTNET>` | `CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA` (USDC SEP-41 token contract, Stellar Testnet) | ## Protocol Flow 1. Server responds with `PaymentRequired` containing `extra.areFeesSponsored` (always true) 2. Client builds a Soroban `invokeHostFunction` operation calling `transfer(from, to, amount)` on the token contract 3. Client simulates the transaction to identify auth entry requirements 4. Client signs auth entries with ledger-based expiration (NOT the full transaction) 5. Client serializes and base64-encodes the transaction (XDR format) 6. Facilitator decodes, validates, rebuilds transaction with its own source account 7. Facilitator re-simulates, signs (optional fee bump), and submits to Stellar network ## Network Identifiers (CAIP-28) | Network | CAIP-2 ID | |---------|-----------| | Stellar Mainnet | `stellar:pubnet` | | Stellar Testnet | `stellar:testnet` | Default facilitator (`https://x402.org/facilitator`) supports Stellar Testnet. ## Default Assets (USDC) | Network | Contract Address | Decimals | |---------|-----------------|----------| | Mainnet | `<STELLAR_USDC_MAINNET>` | 7 | | Testnet | `<STELLAR_USDC_TESTNET>` | 7 | ## RPC Configuration - **Testnet**: Default RPC at `https://soroban-testnet.stellar.org` (HTTP allowed) - **Mainnet**: No default RPC - must supply custom URL (HTTPS enforced). See [Stellar RPC Providers](https://developers.stellar.org/docs/data/apis/rpc/providers#publicly-accessible-apis) - **Horizon URLs**: Testnet `https://horizon-testnet.stellar.org`, Mainnet `https://horizon.stellar.org` ## Key Differences from EVM/SVM | Property | EVM | SVM | Stellar | |----------|-----|-----|---------| | Expiration | Timestamp-based | Blockhash (~60-90s) | Ledger sequence (~5s per ledger) | | Client signs | EIP-712 typed data | Full transaction (partial) | Auth entries only | | Fee sponsorship | Facilitator pays gas | Facilitator is fee payer | Always sponsored | | Token standard | ERC-20 (EIP-3009/Permit2) | SPL TransferChecked | SEP-41 Soroban | ## Fee Configuration - Base fee: 10,000 stroops (0.001 XLM) minimum - Default max facilitator fee: 50,000 stroops - **The facilitator MUST NOT use the client's fee bid when rebuilding the transaction.** It derives the settlement fee from a fresh simulation at settle time: `simulationResourceFee + inclusionBuffer`, where the inclusion buffer MUST be at least 100 stroops. A client fee bid above `maxTransactionFeeStroops` is rejected with `invalid_exact_stellar_payload_fee_exceeds_maximum` - Ledger-based expiration: `currentLedger + ceil(maxTimeoutSeconds / estimatedLedgerCloseTime)` - Signature expiration ledger tolerance: 2 ledgers (for RPC skew) ## PaymentRequirements ```json { "scheme": "exact", "network": "stellar:testnet", "amount": "1000000", "asset": "<STELLAR_USDC_TESTNET>", "payTo": "GA3D5...STELLAR_ADDRESS", "maxTimeoutSeconds": 60, "extra": { "areFeesSponsored": true } } ``` ## TypeScript Usage ### Client ```typescript import { createEd25519Signer } from "@x402/stellar"; import { ExactStellarScheme } from "@x402/stellar/exact/client"; import { x402Client } from "@x402/core"; const signer = createEd25519Signer(privateKey, "stellar:testnet"); const client = new x402Client(); client.register("stellar:*", new ExactStellarScheme(signer)); ``` ### Server ```typescript import { ExactStellarScheme } from "@x402/stellar/exact/server"; import { x402ResourceServer } from "@x402/core/server"; const server = new x402ResourceServer(facilitator) .register("stellar:testnet", new ExactStellarScheme()); ``` ### Facilitator ```typescript import { createEd25519Signer } from "@x402/stellar"; import { ExactStellarScheme } from "@x402/stellar/exact/facilitator"; const signers = [createEd25519Signer(privateKey, "stellar:testnet")]; const scheme = new ExactStellarScheme(signers, { rpcConfig: { url: "https://soroban-testnet.stellar.org" }, maxTransactionFeeStroops: 50_000, selectSigner: (addrs) => addrs[0], // Optional: custom signer selection feeBumpSigner: createEd25519Signer(feeBumpKey, "stellar:testnet"), // Optional }); ``` ### Custom Token (registerMoneyParser) ```typescript const serverScheme = new ExactStellarScheme(); serverScheme.registerMoneyParser(async (amount, network) => { if (network === "stellar:testnet") { return { amount: Math.round(amount * 1e7).toString(), asset: "CUSTOM_TOKEN_CONTRACT_ADDRESS", extra: { token: "CUSTOM" }, }; } return null; }); ``` ## Facilitator Verification Rules (MUST) 1. Verify x402Version is 2 2. Verify scheme is "exact" and network is valid Stellar network 3. Transaction has exactly 1 `invokeHostFunction` operation 4. Facilitator addresses MUST NOT appear as transaction source, operation source, or in auth entries 5. Contract address matches `requirements.asset` 6. Function is `transfer` with 3 arguments (from, to, amount) 7. `to` equals `requirements.payTo` 8. Amount equals `requirements.amount` (as i128) 9. Re-simulation succeeds 10. Client fee within acceptable bounds (>= minResourceFee, <= maxTransactionFeeStroops) 11. Validate simulation events: exactly 1 transfer event matching expected sender/recipient/amount/asset 12. Auth entries: only `sorobanCredentialsAddress` type, no facilitator addresses, signature expiration within tolerance, no sub-invocations ## Settlement Flow 1. Re-verify payment 2. Parse transaction envelope, extract Soroban data 3. Select signer (round-robin by default, configurable) 4. Rebuild transaction with facilitator as source, facilitator-chosen fee 5. Sign inner transaction 6. Optionally wrap in FeeBumpTransaction (if `feeBumpSigner` configured) 7. Submit to network, poll for confirmation ## Address Types - **G-address**: Standard Stellar accounts (56 chars) - **C-address**: Soroban contract addresses (56 chars, used for token assets) - **M-address**: Muxed accounts (69 chars, multiplexed sub-accounts) ## Utility Functions ```typescript import { validateStellarDestinationAddress, // G, C, or M address validateStellarAssetAddress, // C address only (contract) isStellarNetwork, // Check valid Stellar CAIP-2 getRpcUrl, // Get RPC URL with custom override getNetworkPassphrase, // Get network passphrase convertToTokenAmount, // Decimal to smallest units getEstimatedLedgerCloseTimeSeconds, // For expiration calculations } from "@x402/stellar"; ``` -
svm-scheme.md 11.3 KB
# Solana (SVM) Exact Scheme Reference The `exact` scheme on Solana uses `TransferChecked` for SPL tokens. The client creates a partially-signed versioned transaction; the facilitator adds its fee-payer signature and broadcasts. ## Canonical addresses Well-known Solana base58 mint and program addresses referenced by placeholder elsewhere in this file. All values are public, on-chain identifiers (not secrets). | Placeholder | Value | |-------------|-------| | `<USDC_SOL_MINT>` | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` (USDC mint, Solana Mainnet) | | `<USDC_SOL_DEVNET_MINT>` | `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU` (USDC mint, Solana Devnet/Testnet) | | `<SPL_TOKEN_PROGRAM>` | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` (SPL Token program) | | `<SPL_TOKEN_2022_PROGRAM>` | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` (Token-2022 program) | | `<LIGHTHOUSE_PROGRAM>` | `L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95` (Lighthouse instruction-guard program, injected by Phantom/Solflare) | | `<MEMO_PROGRAM>` | `MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr` (SPL Memo program) | ## Protocol Flow 1. Server responds with `PaymentRequired` containing `extra.feePayer` (facilitator's public key) 2. Client builds a Solana transaction with `TransferChecked` instruction 3. Client signs the transaction (partial signature - fee payer signature missing) 4. Client serializes and base64-encodes the partially-signed transaction 5. Client sends `PaymentPayload` with the base64 transaction 6. Facilitator decodes, verifies, and adds its fee-payer signature 7. Facilitator broadcasts the fully-signed transaction to Solana ## PaymentRequirements ```json { "scheme": "exact", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "amount": "1000", "asset": "<USDC_SOL_MINT>", "payTo": "<EXAMPLE_RECIPIENT>", "maxTimeoutSeconds": 60, "extra": { "feePayer": "<EXAMPLE_FEE_PAYER>" } } ``` - `asset`: Public key of the token mint (e.g., USDC mint) - `extra.feePayer`: Facilitator's public key that pays transaction fees - `extra.recentBlockhash` (optional): a recent blockhash supplied by the **resource server** for the client to use as the transaction lifetime, saving a `getLatestBlockhash` round-trip and pinning the transaction to a blockhash the settling RPC has observed. When absent or malformed the client MUST fetch its own. Only set this when your RPC's view of recent blockhashes matches the settling sponsor's - a stale or fork-divergent blockhash makes the transaction expire or fail to land - `extra.lastValidBlockHeight` (optional): decimal string marking the last block height at which `recentBlockhash` is valid. Informational only; ignored when `recentBlockhash` is absent Both are **construction hints, not bindings**. They do not bind the submitted transaction to the hinted blockhash, and facilitator verification does not compare the transaction's blockhash against `extra.recentBlockhash`. ## PaymentPayload ```json { "x402Version": 2, "accepted": { "scheme": "exact", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "..." }, "payload": { "transaction": "AAAAAAAAAAAAA...AAAAAAAAAAAAA=" } } ``` The `transaction` field contains the base64-encoded, serialized, partially-signed versioned Solana transaction. ## Facilitator Verification Rules (MUST) ### 1. Instruction Layout The decompiled transaction MUST contain 3 to 7 instructions in this order (the static-path ceiling was raised from 6 to 7 in TS v2.14.0 to accommodate wallets that inject multiple Lighthouse assertions): 1. `ComputeBudget::SetComputeUnitLimit` (discriminator `2`) 2. `ComputeBudget::SetComputeUnitPrice` (discriminator `3`) 3. `SPL Token` or `Token-2022` `TransferChecked` 4-7. (Optional) Lighthouse or Memo program instructions - Allowed optional programs: Lighthouse (`<LIGHTHOUSE_PROGRAM>`) and Memo (`<MEMO_PROGRAM>`) - Phantom injects 1 Lighthouse instruction; Solflare injects 2 - Memo instructions enable transaction uniqueness #### Simulation-Based Smart-Wallet Verification (Path 2) When `enableSmartWalletVerification` is set, transactions the static positional path rejects (smart-wallet-wrapped layouts, extra instructions) are re-verified by simulating the transaction and inspecting CPI inner instructions for a matching `TransferChecked`. This accepts payments from any allowlisted smart-wallet program (Squads, Swig, SPL Governance, Metaplex Core, Lighthouse) without a per-wallet parser, with fee-payer isolation (Address Lookup Table resolution), operator-configurable compute-budget caps, post-settlement transfer verification (TOCTOU defense), and seller-required memo enforcement. ### 2. Fee Payer Safety - Fee payer address MUST NOT appear in any instruction's `accounts` - Fee payer MUST NOT be the `authority` for TransferChecked - Fee payer MUST NOT be the `source` of transferred funds ### 3. Compute Budget Validity - Compute unit price MUST be bounded (<= 5,000,000 microlamports = 5 lamports per CU) - Default compute unit limit: 20,000 ### 4. Transfer Destination - TransferChecked program MUST be either `spl-token` (`<SPL_TOKEN_PROGRAM>`) or `token-2022` (`<SPL_TOKEN_2022_PROGRAM>`) - Destination MUST equal the Associated Token Account PDA for `(owner=payTo, mint=asset)` under the selected token program ### 5. Amount Exactness - `amount` in TransferChecked MUST equal `PaymentRequirements.amount` exactly ### 6. Simulation - Facilitator signs the transaction with the fee payer's signer, then simulates to verify it would succeed ## Footgun: Token Accounts Must Exist On-Chain x402 Solana transactions contain only `[ComputeBudget x2, TransferChecked, optional Lighthouse/Memo]` - there is no `createAssociatedTokenAccount` instruction. If the Associated Token Account for the payment mint does not already exist on-chain, `TransferChecked` fails simulation with `InvalidAccountData`, the facilitator rejects the payment, and the buyer's wallet never prompts - the user sees only a bare 402 with no signing prompt. This applies to **both sides**: check the payer's ATA and the `payTo` ATA before debugging anything else on SVM. `payTo` is the wallet **owner** address; the facilitator derives the ATA from it. A related symptom: balance readers show no data for a fresh address because `getTokenAccountBalance` returns nothing when the account does not exist. ## Footgun: `maxTimeoutSeconds` Is Capped by Blockhash Lifetime Setting `maxTimeoutSeconds` above roughly 90 seconds does nothing on Solana. The transaction's lifetime is bounded by its blockhash, which expires in ~60-90 seconds regardless of the declared timeout - the spec's own settlement-cache reasoning notes that after that window "the transaction's blockhash will have expired and it cannot land on-chain regardless." Treat values above ~90s as unenforceable unless durable nonces or an on-chain deadline check are in play. ## Footgun: Wallet-Injected Instructions The static verification path validates exact instruction structure, so wallets that inject their own guard instructions can push a transaction outside the allowed layout - rejected with `invalid_exact_svm_payload_transaction_instructions_length` or a simulation/signature failure. Phantom injects 1 Lighthouse instruction and Solflare 2 (which is why the ceiling is 7), but smart-wallet CPI wrapping is a broader incompatibility class; use `enableSmartWalletVerification` for those. Dropping the explicit compute-unit-limit instruction is also rejected (`invalid_compute_limit_instruction`) - the compute budget instructions are required structure, not optional optimization. ## Duplicate Settlement Mitigation (RECOMMENDED) Solana's transaction deduplication ensures only one transfer executes on-chain, but the RPC returns "success" for each submission of the same transaction. A malicious client can exploit this by submitting the same payment to `/settle` multiple times before the first confirms. ### In-Memory Settlement Cache All SDKs provide a `SettlementCache` that prevents this race condition: - **Cache key**: the transaction **message hash** (as of TS v2.14.0 / Python 2.12.0 / Go 2.13.0). Earlier versions keyed on the full Base64 signed-transaction string, which let an attacker bypass the cache by randomizing the mutable fee-payer signature slot - keying on the message hash closes that bypass. - **TTL**: 120 seconds (covers Solana blockhash lifetime of ~60-90s plus margin) - **Behavior**: If key exists in cache, reject with `duplicate_settlement` error; otherwise insert and proceed - **Eviction**: Entries older than 120 seconds are automatically removed - **Thread safety**: Go uses `sync.Mutex`; Python uses `threading.Lock`; TypeScript relies on single-threaded event loop ### SDK Implementation **TypeScript**: Built-in `SettlementCache` class. Enabled by default. ```typescript import { SettlementCache } from "@x402/svm"; const cache = new SettlementCache(); new ExactSvmScheme(signer, cache); // optional - one is created if omitted ``` **Go**: Thread-safe `SettlementCache` with `sync.Mutex`. Must pass a shared instance to both V1 and V2 scheme registrations: ```go import "github.com/x402-foundation/x402/go/v2/mechanisms/svm" cache := svm.NewSettlementCache() ``` **Python**: Thread-safe `SettlementCache` using `threading.Lock`. Same API as Go. ```python from x402.mechanisms.svm.settlement_cache import SettlementCache cache = SettlementCache() ``` ### Operational Cautions Duplicate rejection is a **client-visible behavior change**, not a free correctness win. Two things to weigh before enabling it on a live endpoint: - **Honest retries get rejected too.** A client that loses the response and retries the same signed payment inside the TTL receives `duplicate_settlement`, not the resource. Clients that deliberately fire parallel requests reusing one signed payment - and expect all of them served - break outright. The alternative is **idempotent replay**: serve the cached response for a repeated payment header and coalesce in-flight duplicates, rather than rejecting. - **Cache only successful settlements.** Caching failures makes them authoritative: a first settle that fails on-chain, cached, then replayed to every retry, can never succeed. A failed settle must stay retryable. More broadly, an on-chain settlement is only settled once the transaction status confirms it. Submitting with `skipPreflight` and treating RPC acceptance as success will report `success: true` for a transaction that landed in a block with `meta.err` set - fetch the status and require `meta.err === null` before returning success. ## Multi-Signer Load Balancing The SVM facilitator supports multiple fee payer addresses. `getExtra()` randomly selects from available signers to distribute load. `getSigners()` returns all available addresses. ## Supported Solana Assets - Any SPL token (Token Program) - Token-2022 program tokens ## Common Token Mints | Token | Network | Mint Address | |-------|---------|-------------| | USDC | Mainnet | `<USDC_SOL_MINT>` | | USDC | Devnet/Testnet | `<USDC_SOL_DEVNET_MINT>` | ## Network Identifiers | Network | CAIP-2 ID | V1 Name | |---------|-----------|---------| | Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | `solana` | | Solana Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | `solana-devnet` | | Solana Testnet | `solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z` | `solana-testnet` | ## SDK Support | SDK | Status | |-----|--------| | TypeScript (`@x402/svm`) | Full (client, server, facilitator) | | Go | Full (facilitator) | | Python | Full (facilitator) | -
transports.md 7.7 KB
# Transport Implementations x402 supports three transports: HTTP, MCP, and A2A. All use the same core types (`PaymentRequired`, `PaymentPayload`, `SettlementResponse`) but differ in how data is transmitted. ## HTTP Transport The original and primary transport. Uses HTTP status codes and headers. Response bodies are a server implementation concern - all x402 protocol information is communicated through headers. ### Headers | Header | Direction | Content | |--------|-----------|---------| | `PAYMENT-REQUIRED` | Server -> Client | Base64-encoded `PaymentRequired` JSON | | `PAYMENT-SIGNATURE` | Client -> Server | Base64-encoded `PaymentPayload` JSON | | `PAYMENT-RESPONSE` | Server -> Client | Base64-encoded `SettlementResponse` JSON | ### Flow **Step 1: Client requests resource** ```http GET /weather HTTP/1.1 Host: api.example.com ``` **Step 2: Server responds 402** ```http HTTP/1.1 402 Payment Required Content-Type: application/json PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOi... {} ``` **Step 3: Client retries with payment** ```http GET /weather HTTP/1.1 Host: api.example.com PAYMENT-SIGNATURE: eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2... ``` **Step 4: Server responds with data** ```http HTTP/1.1 200 OK Content-Type: application/json PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGl... {"weather": "sunny", "temperature": 70} ``` **Step 5 (failure): Server responds with payment failure** ```http HTTP/1.1 402 Payment Required Content-Type: application/json PAYMENT-RESPONSE: eyJzdWNjZXNzIjpmYWxzZSwiZXJyb3JSZWFzb24iOi... {} ``` On failure, the `PAYMENT-RESPONSE` header still contains the settlement result (with `success: false` and `errorReason`). Note that a bare `402` is ambiguous to clients - it cannot distinguish "you did not pay" from "your valid payment failed to settle". Always attach the `PAYMENT-RESPONSE` header on settlement failure so the client can tell the two apart and retry correctly. ### Browser Clients: CORS Header Exposure A browser-based buyer cannot read the payment headers unless the server exposes them by their **V2, un-prefixed** names. The V1 `X-`-prefixed names are not interchangeable, and a server still listing them against a V2 SDK produces a silent failure: the wrapper cannot read `PAYMENT-REQUIRED` and throws "failed to parse payment requirements", or the retry's `PAYMENT-SIGNATURE` is stripped. ``` Access-Control-Expose-Headers: PAYMENT-REQUIRED, PAYMENT-RESPONSE Access-Control-Allow-Headers: PAYMENT-SIGNATURE ``` ### HTTP Error Mapping | x402 Error | HTTP Status | |------------|-------------| | Payment Required | 402 | | Invalid Payment | 400 | | Payment Failed | 402 | | Server Error | 500 | | Success | 200 | ## MCP Transport (Model Context Protocol) For AI agents and MCP clients paying for tools. ### Flow **Step 1: Client calls tool without payment** ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "financial_analysis", "arguments": { "ticker": "AAPL" } } } ``` **Step 2: Server returns payment required** ```json { "jsonrpc": "2.0", "id": 1, "result": { "isError": true, "structuredContent": { "x402Version": 2, "error": "Payment required", "resource": { "url": "mcp://tool/financial_analysis" }, "accepts": [{ "scheme": "exact", "network": "eip155:84532", "..." : "..." }] }, "content": [{ "type": "text", "text": "{\"x402Version\":2,...}" }] } } ``` Server provides `PaymentRequired` in both `structuredContent` (preferred) and `content[0].text` (JSON string fallback). **Step 3: Client retries with payment in `_meta`** ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "financial_analysis", "arguments": { "ticker": "AAPL" }, "_meta": { "x402/payment": { "x402Version": 2, "accepted": { "scheme": "exact", "..." : "..." }, "payload": { "signature": "0x...", "authorization": { "..." : "..." } } } } } } ``` **Step 4: Server returns result with settlement** ```json { "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "Analysis result..." }], "_meta": { "x402/payment-response": { "success": true, "transaction": "0x1234...", "network": "eip155:84532", "payer": "0x857b..." } } } } ``` ### MCP Key Fields | Field | Location | Purpose | |-------|----------|---------| | `_meta["x402/payment"]` | Request params | Client sends PaymentPayload | | `_meta["x402/payment-response"]` | Response result | Server sends SettlementResponse | | `structuredContent` | Error response | PaymentRequired data | | `isError: true` | Response result | Signals payment required | ## A2A Transport (Agent-to-Agent Protocol) For agent-to-agent payments using JSON-RPC task-based state. ### Flow **Step 1: Agent sends task, server requests payment** Server returns `state: "input-required"` with payment metadata: ```json { "result": { "kind": "task", "id": "task-123", "status": { "state": "input-required", "message": { "role": "agent", "parts": [{ "kind": "text", "text": "Payment is required." }], "metadata": { "x402.payment.status": "payment-required", "x402.payment.required": { "x402Version": 2, "resource": { "url": "...", "description": "..." }, "accepts": [{ "scheme": "exact", "network": "eip155:8453", "..." : "..." }] } } } } } } ``` **Step 2: Client submits payment** ```json { "params": { "message": { "taskId": "task-123", "role": "user", "parts": [{ "kind": "text", "text": "Payment authorization." }], "metadata": { "x402.payment.status": "payment-submitted", "x402.payment.payload": { "x402Version": 2, "accepted": { "..." : "..." }, "payload": { "signature": "0x...", "authorization": { "..." : "..." } } } } } } } ``` **Step 3: Server settles and completes** ```json { "result": { "kind": "task", "id": "task-123", "status": { "state": "completed", "message": { "role": "agent", "parts": [{ "kind": "text", "text": "Payment successful." }], "metadata": { "x402.payment.status": "payment-completed", "x402.payment.receipts": [{ "success": true, "transaction": "0x1234...", "network": "eip155:8453", "payer": "0x857b..." }] } } } } } ``` ### A2A Payment Status Lifecycle | Status | Task State | Description | |--------|-----------|-------------| | `payment-required` | `input-required` | Server requests payment | | `payment-rejected` | `failed` | Client declined | | `payment-submitted` | `working` | Payment received | | `payment-verified` | `working` | Payment validated | | `payment-completed` | `completed` | Settled on-chain | | `payment-failed` | `failed` | Verification/settlement failed | ### A2A Extension Declaration Agents declare x402 support in their AgentCard: ```json { "capabilities": { "extensions": [{ "uri": "https://github.com/google-a2a/a2a-x402/v0.1", "description": "x402 on-chain payment support", "required": true }] } } ``` Clients activate via HTTP header: ```http X-A2A-Extensions: https://github.com/google-a2a/a2a-x402/v0.1 ``` ### A2A Metadata Keys | Key | Direction | Content | |-----|-----------|---------| | `x402.payment.status` | Both | Payment lifecycle status | | `x402.payment.required` | Server -> Client | PaymentRequired object | | `x402.payment.payload` | Client -> Server | PaymentPayload object | | `x402.payment.receipts` | Server -> Client | Array of SettlementResponse | | `x402.payment.error` | Server -> Client | Error code string | -
typescript-sdk.md 34.2 KB
# TypeScript SDK Reference ## Packages (v2.20.0) | Package | Purpose | |---------|---------| | `@x402/core` | Core types, `x402Client`, `x402ResourceServer`, `x402HTTPResourceServer`, `x402Facilitator`, `HTTPFacilitatorClient` | | `@x402/evm` | EVM exact + upto schemes (EIP-3009, Permit2). Upto: `@x402/evm/upto/client`, `@x402/evm/upto/server`, `@x402/evm/upto/facilitator` | | `@x402/svm` | Solana scheme (SPL TransferChecked) | | `@x402/stellar` | Stellar scheme (SEP-41 Soroban token transfers) | | `@x402/aptos` | Aptos scheme (Fungible Asset transfers) | | `@x402/avm` | Algorand (AVM) scheme | | `@x402/hedera` | Hedera scheme (HBAR + HTS fungible-asset transfers) | | `@x402/tvm` | TON scheme (jetton transfers, W5R1/Highload V3 facilitator) | | `@x402/keeta` | Keeta scheme (exact) | | `@x402/concordium` | Concordium scheme (native CCD, exact) | | `@x402/near` | NEAR scheme (NEP-366 SignedDelegate, NEP-141 `ft_transfer`, relayer-sponsored) | | `@x402/xrpl` | XRPL scheme (payer-signed `Payment`; tagged 2.20.0 but not yet published to npm) | | `@x402/express` | Express.js middleware | | `@x402/fastify` | Fastify middleware | | `@x402/hono` | Hono edge middleware | | `@x402/next` | Next.js middleware (`paymentProxy`, `withX402`) | | `@x402/axios` | Axios interceptor | | `@x402/fetch` | Fetch wrapper | | `@x402/paywall` | Browser paywall UI (EVM + SVM) | | `@x402/mcp` | MCP client + server | | `@x402/extensions` | Bazaar, offer-receipt, sign-in-with-x, payment-identifier, eip2612-gas-sponsoring, erc20-approval-gas-sponsoring | ## Recent Changes **v2.20.0** - New `@x402/near` (NEP-366 SignedDelegate + NEP-141 `ft_transfer`, relayer-sponsored) and `@x402/xrpl` (payer-signed XRPL `Payment`, no fee sponsorship) mechanism packages. Igra mainnet (`eip155:38833`) added to default-asset resolution. SVM `extra.recentBlockhash` / `extra.lastValidBlockHeight` construction hints. `createAuthHeaders` now throws when it returns a flat object instead of one keyed by facilitator path (`verify`/`settle`/`supported`) - previously this silently dropped authentication on every request. Request bodies are preserved on payment retry (`@x402/fetch`). SIWx Solana rejects small-order Ed25519 public keys. **v2.19.0** - **Security:** SIWx binds to an operator-configured `origin` rather than request-derived values; `createSIWxResourceServerExtension({ origin })` replaces the previously-documented `siwxResourceServerExtension`, and `domain`/`resourceUri` were removed from `declareSIWxExtension`. SIWx result types became discriminated unions (`isValid`/`invalidReason`/`invalidMessage`, `payer` instead of `address`) with 15 `invalid_siwx_*` codes. Batch-settlement EVM path-traversal and pre-verification channel-mutation fixes. Algorand CAIP-2 ids truncated to 32 chars (legacy ids normalized on input). **v2.18.0** - Core schemas accept explicit `null` for optional wire fields from Python/Go peers and normalize it to `undefined` (MCP cross-SDK interop). MCP payment matching selects the `accepts` entry matching the payload instead of always the first. `onAfterVerify` hooks can abort, dispatching `onVerifiedPaymentCanceled` with reason `after_verify_aborted`. **v2.17.0** - Expanded wallet compatibility: plain EOAs, deployed smart accounts (ERC-4337 / ERC-7579), counterfactual ERC-6492 wallets, and ERC-7702-delegated EOAs; pre-verification mirrors on-chain signature checking (a payment that passes `verify` succeeds at `settle`), with ERC-6492 now in `exact` + `batch-settlement`. Added `validateFacilitatorSupport` hook on `SchemeNetworkServer`, wired into `x402ResourceServer.initialize()` to fail fast on facilitator capability mismatch. Batch-settlement facilitator `authorizerSigner` made optional (`receiverAuthorizer` declared optional in `/supported`; new error `invalid_batch_settlement_evm_authorizer_not_configured`). New `@x402/concordium` package (native CCD, `ccd:*`). **v2.16.0** - `dynamicInfoFields` extension capability: mark per-response nonce/timestamp fields to exclude from client-echo `extension_echo_mismatch` validation; wired into offer-receipt and sign-in-with-x. **v2.15.0** - Transport-agnostic `parsePaymentResult` returning `HTTPResourceResponse` (`{ status, body, header }`) so clients surface server-delivered payment errors without branching. New networks in default-asset resolution: Mezo mainnet (`eip155:31612`, mUSD 18 decimals), XDC Network (`eip155:50`) and XDC Apothem (`eip155:51`). New `@x402/tvm` (TON) and `@x402/keeta` mechanism packages. builder-code extension implemented (`@x402/extensions/builder-code`, multiple service codes + `calldataSuffix`). EVM verify rejects EOA asset addresses (`asset_not_deployed_contract`); `validAfter` set to 0; wildcard route/network matching hardened. **v2.14.0** - `auth-capture` TypeScript client scheme (`@x402/evm/auth-capture/client`). Full `batch-settlement` TS SDK surface (`@x402/evm/batch-settlement/{client,server,facilitator}` plus `*/file-storage` and `server/redis-storage` subpaths). SVM exact: simulation-based smart-wallet verification (`enableSmartWalletVerification`) for allowlisted programs (Squads, Swig, SPL Governance, Metaplex Core, Lighthouse), static instruction-count ceiling raised 6 -> 7, plus security fixes (dedup keyed on tx message hash; compute-unit-price cap bypass). EVM: ERC-6492 factory-injection fix (`eip6492AllowedFactories` allowlist sole gate, `DeployERC4337WithEIP6492` removed). Bazaar service metadata threaded into `PaymentRequired.resource`; full schema validation moved to middleware (shallow validation removed from core). **v2.13.0** - Networks ADI Chain (`eip155:36900`) and HPP / HPP Sepolia (`eip155:190415` / `eip155:181228`) in default-asset resolution. Extension hook adapters for client/HTTP flows. `paymentPayload.accepted.extra` may carry additive client fields. Security: paywall-bypass via encoded path separators (`%2F`/`%5C`) closed; reflected-XSS surface removed from fallback paywall HTML. **v2.12.0** - Bazaar service-metadata fields (`serviceName`, `tags`, `iconUrl`) on `ResourceInfo`, with `@x402/extensions/bazaar` validation helpers. Scheme-extensibility surface: optional `schemeHooks`, verify/route/settle skip primitives, `VerifyResponse`/`SettleResponse` `extra`, `onPaymentResponse` client hook, `processPaymentResult` utility. `EXTENSION-RESPONSES` header decoded and logged by `HTTPFacilitatorClient`. `viem` floor raised to `^2.48.11`. **v2.11.0** - `ResourceServerExtension` can register resource-server verify/settle hooks. Extension mutation policy enforced (`enrichPaymentRequiredResponse` may only fill vacant `payTo`/`amount`/`asset`). Paid MCP tools can declare Bazaar discovery metadata via `PaymentWrapperConfig.extensions`. Fixed USD prices with 7+ decimal places. **v2.10.0** - SIWE dependency migrated from `siwe` to `@signinwithethereum/siwe` (same `SiweMessage` API). **v2.9.0** - Upto scheme (usage-based billing) with full client/server/facilitator via `@x402/evm/upto/*`. Fastify adapter. New default assets: Stable (988/2201), Polygon (137), Arbitrum One/Sepolia, Mezo testnet. Repo moved to `x402-foundation/x402`. ## Core Subpath Exports `@x402/core` exports from multiple subpaths: | Subpath | Key Exports | |---------|-------------| | `@x402/core` | `x402Version` | | `@x402/core/client` | `x402Client` | | `@x402/core/server` | `x402ResourceServer`, `x402HTTPResourceServer`, `HTTPFacilitatorClient`, `RouteConfigurationError` | | `@x402/core/facilitator` | `x402Facilitator` | | `@x402/core/http` | `encodePaymentSignatureHeader`, `decodePaymentSignatureHeader`, `encodePaymentRequiredHeader`, `encodePaymentResponseHeader` | | `@x402/core/types` | `PaymentRequired`, `PaymentRequirements`, `PaymentPayload`, `Network`, `VerifyError`, `SettleError`, `FacilitatorResponseError` | ## Core Types ```typescript type Network = `${string}:${string}`; // CAIP-2 format: "eip155:84532", "solana:EtWTRA..." type Money = string | number; // "$0.001", 0.001 type AssetAmount = { asset: string; amount: string; extra?: Record<string, unknown> }; type Price = Money | AssetAmount; type PaymentRequirements = { scheme: string; network: Network; asset: string; amount: string; payTo: string; maxTimeoutSeconds: number; extra: Record<string, unknown>; }; type PaymentRequired = { x402Version: number; error?: string; resource: ResourceInfo; accepts: PaymentRequirements[]; extensions?: Record<string, unknown>; }; type PaymentPayload = { x402Version: number; resource?: ResourceInfo; accepted: PaymentRequirements; payload: Record<string, unknown>; extensions?: Record<string, unknown>; }; type ResourceInfo = { url: string; description?: string; mimeType?: string }; ``` ## Mechanism Subpath Exports Each mechanism (`@x402/evm`, `@x402/svm`, `@x402/stellar`, `@x402/aptos`) exports: | Subpath | Key Export | Role | |---------|-----------|------| | `/exact/client` | `ExactEvmScheme`, `registerExactEvmScheme` | Client-side signing | | `/exact/server` | `ExactEvmScheme`, `registerExactEvmScheme` | Server-side requirements | | `/exact/facilitator` | `ExactEvmScheme`, `registerExactEvmScheme` | Facilitator verify/settle | Replace `Evm` with `Svm`, `Stellar`, or `Aptos` accordingly. ### Upto Subpath Exports (`@x402/evm` only) | Subpath | Key Export | Role | |---------|-----------|------| | `@x402/evm/upto/client` | `UptoEvmScheme` | Client-side max-amount signing | | `@x402/evm/upto/server` | `UptoEvmScheme` | Server-side with `setSettlementOverrides` | | `@x402/evm/upto/facilitator` | `UptoEvmScheme` | Facilitator verify/settle (variable amount) | ### Auth-Capture Subpath Exports (`@x402/evm`) | Subpath | Role | |---------|------| | `@x402/evm/auth-capture/client` | Client-side detect + sign auth-capture payloads (escrow / authorize-then-capture with void, refund, reclaim). Client scheme shipped in v2.14.0; server/facilitator still landing. | ### Batch-Settlement Subpath Exports (`@x402/evm`) | Subpath | Role | |---------|------| | `@x402/evm/batch-settlement/client` | Client-side cumulative-voucher signing (commit now, settle async) | | `@x402/evm/batch-settlement/server` | Server-side channel management | | `@x402/evm/batch-settlement/facilitator` | Facilitator verify/claim | | `@x402/evm/batch-settlement/{client,server}/file-storage`, `server/redis-storage` | Optional storage backends (split out so default bundles avoid Node-fs/Redis) | ## Server: Express ```typescript import express from "express"; import { paymentMiddleware } from "@x402/express"; import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { ExactSvmScheme } from "@x402/svm/exact/server"; const app = express(); const payTo = "0xYourWalletAddress"; const facilitator = new HTTPFacilitatorClient({ url: "https://x402.org/facilitator" }); const server = new x402ResourceServer(facilitator) .register("eip155:84532", new ExactEvmScheme()) .register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()); app.use( paymentMiddleware( { "GET /weather": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo }, { scheme: "exact", price: "$0.001", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress }, ], description: "Weather data", mimeType: "application/json", }, }, server, ), ); app.get("/weather", (req, res) => { res.json({ report: { weather: "sunny", temperature: 70 } }); }); app.listen(4021); ``` ### Express: Three Middleware Variants ```typescript // 1. paymentMiddleware(routes, server, paywallConfig?, paywall?, syncFacilitatorOnStart?) // Simplest - pass routes and a pre-configured x402ResourceServer // 2. paymentMiddlewareFromHTTPServer(httpServer, paywallConfig?, paywall?, syncFacilitatorOnStart?) // Use when you need HTTP-level hooks (onProtectedRequest, etc.) import { paymentMiddlewareFromHTTPServer } from "@x402/express"; import { x402HTTPResourceServer } from "@x402/core/server"; const httpServer = new x402HTTPResourceServer(server, routes); httpServer.onProtectedRequest(async (context, routeConfig) => { // Grant free access, abort, or continue to payment if (isFreeUser(context)) return { grantAccess: true }; }); app.use(paymentMiddlewareFromHTTPServer(httpServer)); // 3. paymentMiddlewareFromConfig(routes, facilitatorClients?, schemes?, paywallConfig?, paywall?, syncFacilitatorOnStart?) // Quick config-based setup - creates server internally import { paymentMiddlewareFromConfig } from "@x402/express"; app.use(paymentMiddlewareFromConfig(routes, facilitator, [ { network: "eip155:84532", server: new ExactEvmScheme() }, ])); ``` ## Server: Next.js ### Middleware Proxy (for pages) ```typescript // middleware.ts import { paymentProxy } from "@x402/next"; import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { createPaywall } from "@x402/paywall"; import { evmPaywall } from "@x402/paywall/evm"; import { svmPaywall } from "@x402/paywall/svm"; const facilitator = new HTTPFacilitatorClient({ url: facilitatorUrl }); const server = new x402ResourceServer(facilitator); server.register("eip155:84532", new ExactEvmScheme()); const paywall = createPaywall() .withNetwork(evmPaywall) .withNetwork(svmPaywall) .withConfig({ appName: "My App", testnet: true }) .build(); export default paymentProxy( { "/protected": { accepts: [{ scheme: "exact", price: "$0.001", network: "eip155:84532", payTo }], description: "Protected content", mimeType: "text/html", }, }, server, undefined, paywall, ); export const config = { matcher: ["/protected/:path*"] }; ``` ### Route Handler (for API routes) ```typescript // app/api/weather/route.ts import { withX402 } from "@x402/next"; import { NextRequest, NextResponse } from "next/server"; const handler = async (req: NextRequest) => { return NextResponse.json({ weather: "sunny" }, { status: 200 }); }; export const GET = withX402( handler, { accepts: { scheme: "exact", payTo: "0x123...", price: "$0.01", network: "eip155:84532" }, description: "Weather API", }, server, ); ``` ### Next.js: Three Variants ```typescript // Proxy variants: paymentProxy, paymentProxyFromHTTPServer, paymentProxyFromConfig // Route handler variants: withX402, withX402FromHTTPServer // All accept (paywallConfig?, paywall?, syncFacilitatorOnStart?) optional args ``` ## Server: Hono ```typescript import { Hono } from "hono"; import { paymentMiddleware } from "@x402/hono"; const app = new Hono(); app.use("/weather", paymentMiddleware({ /* same route config */ }, server)); // Also available: paymentMiddlewareFromHTTPServer, paymentMiddlewareFromConfig ``` ## Server: Fastify ```typescript import Fastify from "fastify"; import { paymentMiddleware } from "@x402/fastify"; const app = Fastify(); app.register(paymentMiddleware({ /* same route config */ }, server)); // Also available: paymentMiddlewareFromHTTPServer, paymentMiddlewareFromConfig ``` ## Route Configuration ```typescript interface RouteConfig { accepts: PaymentOption | PaymentOption[]; // Single or array resource?: string; description?: string; mimeType?: string; customPaywallHtml?: string; unpaidResponseBody?: (context: HTTPRequestContext) => HTTPResponseBody | Promise<HTTPResponseBody>; settlementFailedResponseBody?: (context: HTTPRequestContext, settleResult) => HTTPResponseBody | Promise<HTTPResponseBody>; extensions?: Record<string, unknown>; } interface PaymentOption { scheme: string; payTo: string | ((context: HTTPRequestContext) => string | Promise<string>); price: Price | ((context: HTTPRequestContext) => Price | Promise<Price>); network: Network; maxTimeoutSeconds?: number; extra?: Record<string, unknown>; } // Routes map: "METHOD /path" => RouteConfig, supports wildcards and params type RoutesConfig = Record<string, RouteConfig> | RouteConfig; // Patterns: "GET /api/*", "/api/[id]", "/api/:id" ``` ## Client: Axios ```typescript import { x402Client, wrapAxiosWithPayment, x402HTTPClient } from "@x402/axios"; import { registerExactEvmScheme } from "@x402/evm/exact/client"; import { registerExactSvmScheme } from "@x402/svm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; import { createKeyPairSignerFromBytes } from "@solana/kit"; import { base58 } from "@scure/base"; import axios from "axios"; const evmSigner = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`); const client = new x402Client(); registerExactEvmScheme(client, { signer: evmSigner }); // Optional: add Solana support const svmSigner = await createKeyPairSignerFromBytes(base58.decode(process.env.SVM_PRIVATE_KEY)); registerExactSvmScheme(client, { signer: svmSigner }); const api = wrapAxiosWithPayment(axios.create({ baseURL: "http://localhost:4021" }), client); const response = await api.get("/weather"); // Read settlement response const httpClient = new x402HTTPClient(client); const settlement = httpClient.getPaymentSettleResponse( name => response.headers[name.toLowerCase()], ); ``` ## Client: Fetch ```typescript import { x402Client, wrapFetchWithPayment, x402HTTPClient } from "@x402/fetch"; import { registerExactEvmScheme } from "@x402/evm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`); const client = new x402Client(); registerExactEvmScheme(client, { signer }); const fetchWithPayment = wrapFetchWithPayment(fetch, client); const response = await fetchWithPayment("http://localhost:4021/weather", { method: "GET" }); const data = await response.json(); // Sending a body? Set Content-Type explicitly. // The wrapper rebuilds the request via `new Request(input, init)`, and the Fetch spec // assigns `text/plain;charset=UTF-8` to a string body with no Content-Type. The initial // 402 does not care, but the paid retry can be rejected as a bad content type. await fetchWithPayment("http://localhost:4021/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ticker: "AAPL" }), }); // Read settlement response const httpClient = new x402HTTPClient(client); const settlement = httpClient.getPaymentSettleResponse(name => response.headers.get(name)); ``` ## Client: x402Client API ```typescript const client = new x402Client(paymentRequirementsSelector?); // Registration (chainable) client.register(network, schemeNetworkClient); // v2 client.registerV1(network, schemeNetworkClient); // v1 // Policies - filter requirements before selection client.registerPolicy((version, reqs) => reqs.filter(r => BigInt(r.amount) < BigInt("1000000"))); client.registerPolicy((version, reqs) => reqs.filter(r => r.network.startsWith("eip155:"))); // Client extensions - enrich payment payloads client.registerExtension({ key: "eip2612GasSponsoring", enrichPaymentPayload: async (payload, required) => payload }); // Lifecycle hooks (chainable) client.onBeforePaymentCreation(async (ctx) => { /* return { abort: true, reason } to abort */ }); client.onAfterPaymentCreation(async (ctx) => { /* ctx.paymentPayload available */ }); client.onPaymentCreationFailure(async (ctx) => { /* return { recovered: true, payload } to recover */ }); // Create payment const payload = await client.createPaymentPayload(paymentRequired); // Static factory const client = x402Client.fromConfig({ schemes: [...], policies: [...], paymentRequirementsSelector }); ``` ## Server: x402ResourceServer API ```typescript const server = new x402ResourceServer(facilitatorClients?); // facilitatorClients: FacilitatorClient | FacilitatorClient[] (defaults to HTTPFacilitatorClient) // Registration (chainable) server.register(network, schemeNetworkServer); // Extensions server.registerExtension(extension); // ResourceServerExtension server.hasExtension(key); server.getExtensions(); server.enrichExtensions(declaredExtensions, transportContext); // Lifecycle hooks (chainable) server.onBeforeVerify(async (ctx) => { /* return { abort: true, reason, message? } */ }); server.onAfterVerify(async (ctx) => { /* ctx.result available */ }); server.onVerifyFailure(async (ctx) => { /* return { recovered: true, result } */ }); server.onBeforeSettle(async (ctx) => { /* return { abort: true, reason, message? } */ }); server.onAfterSettle(async (ctx) => { /* ctx.result, ctx.transportContext available */ }); server.onSettleFailure(async (ctx) => { /* return { recovered: true, result } */ }); // Core methods await server.initialize(); await server.buildPaymentRequirements(resourceConfig); await server.buildPaymentRequirementsFromOptions(paymentOptions, context); await server.createPaymentRequiredResponse(requirements, resourceInfo, error?, extensions?, transportContext?); await server.verifyPayment(paymentPayload, requirements); await server.settlePayment(paymentPayload, requirements, declaredExtensions?, transportContext?); server.findMatchingRequirements(availableRequirements, paymentPayload); ``` ## Server: x402HTTPResourceServer API ```typescript const httpServer = new x402HTTPResourceServer(resourceServer, routes); await httpServer.initialize(); // Calls resourceServer.initialize() + validates route configs // Paywall httpServer.registerPaywallProvider(provider); // Protected request hook - runs before payment processing httpServer.onProtectedRequest(async (context, routeConfig) => { // return { grantAccess: true } - skip payment // return { abort: true, reason } - deny (403) // return void - continue to payment flow }); // Core processing const result = await httpServer.processHTTPRequest(context, paywallConfig?); // result.type: "no-payment-required" | "payment-verified" | "payment-error" const settleResult = await httpServer.processSettlement(paymentPayload, requirements, declaredExtensions?, transportContext?); // settleResult.success: true => settleResult.headers // settleResult.success: false => settleResult.response (HTTPResponseInstructions) httpServer.requiresPayment(context); // Check if route matches httpServer.server; // Access underlying x402ResourceServer httpServer.routes; // Access RoutesConfig ``` ## MCP Server ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { createPaymentWrapper } from "@x402/mcp"; import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server"; import { ExactEvmScheme } from "@x402/evm/exact/server"; const facilitator = new HTTPFacilitatorClient({ url: facilitatorUrl }); const resourceServer = new x402ResourceServer(facilitator) .register("eip155:84532", new ExactEvmScheme()); const requirements = await resourceServer.buildPaymentRequirements([ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo }, ]); const paidTool = createPaymentWrapper(resourceServer, requirements); const mcpServer = new McpServer({ name: "My Paid MCP", version: "1.0.0" }); mcpServer.tool("weather", "Get weather data", {}, paidTool(async (args) => { return { content: [{ type: "text", text: JSON.stringify({ weather: "sunny" }) }] }; })); ``` ## MCP Client ```typescript import { createx402MCPClient } from "@x402/mcp"; import { x402Client } from "@x402/core"; import { registerExactEvmScheme } from "@x402/evm/exact/client"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; const paymentClient = new x402Client(); registerExactEvmScheme(paymentClient, { signer }); const x402Mcp = await createx402MCPClient({ name: "my-client", transport: new SSEClientTransport(new URL("http://localhost:4022/sse")), paymentClient, }); await x402Mcp.connect(); const tools = await x402Mcp.listTools(); const result = await x402Mcp.callTool("weather", { city: "SF" }); ``` ## Paywall (Browser UI) ```typescript import { createPaywall } from "@x402/paywall"; import { evmPaywall } from "@x402/paywall/evm"; import { svmPaywall } from "@x402/paywall/svm"; const paywall = createPaywall() .withNetwork(evmPaywall) .withNetwork(svmPaywall) .withConfig({ appName: "My App", appLogo: "/logo.png", testnet: true, }) .build(); // Pass to middleware for browser-facing endpoints paymentMiddleware(routes, server, undefined, paywall); ``` ## Self-Facilitation (In-Process) Run the facilitator in the same process as the resource server - no external facilitator URL needed: ```typescript import { x402Facilitator } from "@x402/core/facilitator"; import { paymentMiddleware, x402ResourceServer } from "@x402/express"; import { toFacilitatorEvmSigner } from "@x402/evm"; import { registerExactEvmScheme } from "@x402/evm/exact/facilitator"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { createWalletClient, http, publicActions } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { baseSepolia } from "viem/chains"; const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`); const viemClient = createWalletClient({ account, chain: baseSepolia, transport: http() }).extend(publicActions); const evmSigner = toFacilitatorEvmSigner({ address: account.address, getCode: viemClient.getCode, readContract: viemClient.readContract, verifyTypedData: viemClient.verifyTypedData, writeContract: viemClient.writeContract, sendTransaction: viemClient.sendTransaction, waitForTransactionReceipt: viemClient.waitForTransactionReceipt, }); const facilitator = new x402Facilitator(); registerExactEvmScheme(facilitator, { signer: evmSigner, networks: "eip155:84532" }); const server = new x402ResourceServer({ verify: facilitator.verify.bind(facilitator), settle: facilitator.settle.bind(facilitator), getSupported: async () => facilitator.getSupported(), }).register("eip155:84532", new ExactEvmScheme()); app.use(paymentMiddleware(routes, server)); ``` ## Facilitator: HTTPFacilitatorClient ```typescript import { HTTPFacilitatorClient } from "@x402/core/server"; const facilitator = new HTTPFacilitatorClient({ url: "https://x402.org/facilitator", // default createAuthHeaders: async () => ({ verify: { Authorization: "Bearer ..." }, settle: { Authorization: "Bearer ..." }, supported: { Authorization: "Bearer ..." }, }), }); // `createAuthHeaders` MUST return an object keyed by facilitator path. // Returning a flat `{ Authorization: ... }` now throws - it previously // silently dropped authentication on every request. // FacilitatorClient interface: verify(), settle(), getSupported() // Retries getSupported() on 429 with exponential backoff (3 attempts) ``` ## Facilitator (Self-hosted) ```typescript import { x402Facilitator } from "@x402/core/facilitator"; import { ExactEvmScheme } from "@x402/evm/exact/facilitator"; import { ExactSvmScheme } from "@x402/svm/exact/facilitator"; const facilitator = new x402Facilitator(); facilitator.register("eip155:84532", new ExactEvmScheme({ privateKey: process.env.FACILITATOR_KEY })); facilitator.register( ["solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "solana:devnet"], new ExactSvmScheme({ keypair }), ); // register() accepts single network or array of networks // registerV1() for v1 protocol support // Extensions facilitator.registerExtension(extension); // FacilitatorExtension facilitator.getExtensions(); // string[] of registered keys facilitator.getExtension<T>(key); // typed extension lookup // Lifecycle hooks (chainable) facilitator.onBeforeVerify(async (ctx) => { /* return { abort: true, reason } */ }); facilitator.onAfterVerify(async (ctx) => { /* only on isValid: true */ }); facilitator.onVerifyFailure(async (ctx) => { /* return { recovered: true, result } */ }); facilitator.onBeforeSettle(async (ctx) => { /* return { abort: true, reason } */ }); facilitator.onAfterSettle(async (ctx) => {}); facilitator.onSettleFailure(async (ctx) => { /* return { recovered: true, result } */ }); // Core methods facilitator.getSupported(); // { kinds, extensions, signers } await facilitator.verify(paymentPayload, paymentRequirements); await facilitator.settle(paymentPayload, paymentRequirements); // Expose /verify, /settle, /supported endpoints ``` ## Dynamic Pricing and PayTo ```typescript app.use( paymentMiddleware( { "GET /weather": { accepts: [ { scheme: "exact", price: (context) => calculatePrice(context), // HTTPRequestContext network: "eip155:84532", payTo: (context) => getPayToAddress(context), // HTTPRequestContext }, ], description: "Weather data", mimeType: "application/json", }, }, server, ), ); ``` ## Upto Scheme (Usage-Based Billing) Server advertises max price with `scheme: "upto"`, then calls `setSettlementOverrides` with actual usage: ```typescript import { paymentMiddleware, setSettlementOverrides, x402ResourceServer } from "@x402/express"; import { UptoEvmScheme } from "@x402/evm/upto/server"; const server = new x402ResourceServer(facilitatorClient) .register("eip155:84532", new UptoEvmScheme()); const routes = { "GET /api/generate": { accepts: { scheme: "upto", price: "$0.10", network: "eip155:84532", payTo }, description: "AI text generation - billed by token usage", }, }; app.get("/api/generate", (req, res) => { const actualCost = computeActualCost(); // your billing logic setSettlementOverrides(res, { amount: String(actualCost) }); // raw units, "$0.05", or "50%" res.json({ result: "..." }); }); ``` Client registers both exact and upto schemes: ```typescript import { UptoEvmScheme } from "@x402/evm/upto/client"; client.register("eip155:*", new UptoEvmScheme(signer)); ``` ## Custom Unpaid/Settlement Failure Responses ```typescript "GET /weather": { accepts: [{ scheme: "exact", price: "$0.001", network: "eip155:84532", payTo }], description: "Weather data", unpaidResponseBody: async (context) => ({ contentType: "application/json", body: { preview: "Weather data available", hint: "Pay to access full report" }, }), settlementFailedResponseBody: async (context, settleResult) => ({ contentType: "application/json", body: { error: "Payment settlement failed", reason: settleResult.errorReason }, }), } ``` ## Extensions: Bazaar Discovery ```typescript import { declareDiscoveryExtension, BAZAAR } from "@x402/extensions/bazaar"; "GET /weather": { accepts: [{ scheme: "exact", price: "$0.001", network: "eip155:84532", payTo }], description: "Weather API", mimeType: "application/json", extensions: { ...declareDiscoveryExtension({ output: { example: { weather: "sunny", temperature: 72 } } }), }, } // MCP tool discovery declareDiscoveryExtension({ toolName: "financial_analysis", description: "Analyze financial data for a given ticker", inputSchema: { type: "object", properties: { ticker: { type: "string" } }, required: ["ticker"] }, }); // Bazaar auto-registers as ResourceServerExtension when declared in route extensions // Manual: server.registerExtension(bazaarResourceServerExtension) ``` ## Extensions: Offer/Receipt ```typescript import { createOfferReceiptExtension, declareOfferReceiptExtension } from "@x402/extensions/offer-receipt"; // Server: declare on route extensions: { ...declareOfferReceiptExtension({ issuerDid: "did:key:..." }), } // Server: register extension const offerReceiptExt = createOfferReceiptExtension(issuer); server.registerExtension(offerReceiptExt); // Client: extract import { extractOffersFromPaymentRequired, extractReceiptFromResponse } from "@x402/extensions/offer-receipt"; ``` ## Extensions: Sign-in-with-x ```typescript import { createSIWxResourceServerExtension, declareSIWxExtension } from "@x402/extensions/sign-in-with-x"; // Server: declare on route (`domain` and `resourceUri` were removed) extensions: { ...declareSIWxExtension({ statement: "Sign in", expirationSeconds: 300 }), } // Server: register with a required operator-configured public origin server.registerExtension( createSIWxResourceServerExtension({ origin: "https://api.example.com" }), ); // Client import { createSIWxPayload, wrapFetchWithSIWx } from "@x402/extensions/sign-in-with-x"; ``` ## Extensions: Gas Sponsoring ```typescript // EIP-2612 (for tokens with EIP-2612 permit support) import { declareEip2612GasSponsoringExtension } from "@x402/extensions"; extensions: { ...declareEip2612GasSponsoringExtension(), } // ERC-20 Approval (for generic ERC-20 tokens without EIP-2612) import { declareErc20ApprovalGasSponsoringExtension } from "@x402/extensions"; extensions: { ...declareErc20ApprovalGasSponsoringExtension(), } ``` ## Extension Interfaces ```typescript // Client extension (enriches payment payloads) interface ClientExtension { key: string; enrichPaymentPayload?: (payload: PaymentPayload, required: PaymentRequired) => Promise<PaymentPayload>; } // Resource server extension (enriches 402 response and settlement) interface ResourceServerExtension { key: string; enrichDeclaration?: (declaration: unknown, transportContext: unknown) => unknown; enrichPaymentRequiredResponse?: (declaration: unknown, context: PaymentRequiredContext) => Promise<unknown>; enrichSettlementResponse?: (declaration: unknown, context: SettleResultContext) => Promise<unknown>; } // Facilitator extension (provides capabilities to mechanisms) interface FacilitatorExtension { key: string; // Extended by specific extensions with additional properties } ``` ## Error Types ```typescript // VerifyError - thrown when payment verification fails class VerifyError extends Error { statusCode: number; invalidReason?: string; invalidMessage?: string; payer?: string; } // SettleError - thrown when payment settlement fails class SettleError extends Error { statusCode: number; errorReason?: string; errorMessage?: string; payer?: string; transaction: string; network: Network; } // FacilitatorResponseError - thrown when facilitator returns malformed data class FacilitatorResponseError extends Error {} // RouteConfigurationError - thrown when route config validation fails during initialize() class RouteConfigurationError extends Error { errors: RouteValidationError[]; } // Helper: getFacilitatorResponseError(error) - walks error cause chain ``` ## V1 to V2 Migration | V1 | V2 | |----|----| | `x402` | `@x402/core` | | `x402-express` | `@x402/express` | | `x402-axios` | `@x402/axios` | | `x402-fetch` | `@x402/fetch` | | `withPaymentInterceptor` | `wrapAxiosWithPayment` | | `X-PAYMENT` header | `PAYMENT-SIGNATURE` header | | `X-PAYMENT-RESPONSE` header | `PAYMENT-RESPONSE` header | | `base-sepolia` | `eip155:84532` (CAIP-2) | | Wallet passed directly | `x402Client` + `registerExactEvmScheme` | | `ExactEvmFacilitator` | `ExactEvmScheme` (from `/exact/facilitator`) | -
upto-scheme.md 11.4 KB
# Upto Scheme Reference > **Status: Implemented in TypeScript, Go, and Python SDKs.** Full client/server/facilitator implementations are available via `@x402/evm/upto/*` subpaths (TypeScript), `go/mechanisms/evm/upto/*` (Go), and `x402.mechanisms.evm.upto` (Python). The `x402UptoPermit2Proxy` contract is deployed. > > A **draft** `upto` binding for Solana exists at spec stage (`specs/schemes/upto/scheme_upto_svm.md`), built on the external [Solana payment-channels program](https://github.com/solana-foundation/payment-channels). No SDK implements it yet - the shipping SDKs remain EVM/Permit2 only. The `upto` scheme enables usage-based payments where the client authorizes a **maximum amount** and the server settles for the **actual amount consumed**. Ideal for LLM token generation, bandwidth metering, time-based API access, and dynamic compute pricing. ## Key Differences from Exact Scheme | Property | Exact | Upto | |----------|-------|------| | Amount | Fixed, known upfront | Variable, determined at settlement | | EIP-3009 | Supported (recommended) | NOT supported | | Permit2 | Fallback | Required (only method) | | Settlement amount | Must equal authorized amount | Must be <= authorized maximum | | Zero settlement | Not applicable | Allowed (no charge, no tx needed) | ## Core Properties 1. **Single-Use Authorization** - each authorization settled at most once. After settlement (regardless of amount), the authorization is consumed and cannot be reused. Enforced via Permit2's nonce mechanism. 2. **Time-Bound Authorization** - `validAfter` (not valid before) and `deadline` (expires after). Enforced via Permit2's deadline + witness validAfter. 3. **Recipient Binding** - authorization cryptographically binds recipient address via Permit2 witness pattern (`witness.to`). Prevents facilitator from redirecting funds. 4. **Maximum Amount Enforcement** - settled amount MUST be <= authorized maximum. Settled amount MAY be 0 (no charge if no usage). 5. **Phase-Dependent `amount` Semantics** - at **verification** time, `amount` in PaymentRequirements = maximum the client authorizes. At **settlement** time, `amount` = actual amount to settle (must be <= maximum). This reuses the existing PaymentRequirements type for both phases. ## Out of Scope The following patterns are NOT supported by `upto` and would require different schemes: - **Multi-settlement / streaming**: Settling the same authorization multiple times (e.g., pay-per-chunk streaming) - **Recurring payments**: Automatic periodic charges without new authorizations - **Open-ended allowances**: Authorizations without time bounds or single-use constraints ## EVM Implementation ### Why Permit2 Only EIP-3009 `transferWithAuthorization` requires the exact amount to be known at signature time - the signed `value` is the transfer amount. Upto needs variable settlement, so only Permit2's `permitWitnessTransferFrom` works, where `permitted.amount` is the maximum and `requestedAmount` at settlement can be less. ### One-Time Setup Clients must approve the Permit2 contract. Three options: - **Option A**: Direct user approval (`ERC20.approve(Permit2, amount)`) - **Option B**: Sponsored ERC20 approval (extension) - facilitator pays gas - **Option C**: EIP-2612 permit (extension) - off-chain signature if token supports it ### PaymentPayload Structure ```json { "x402Version": 2, "accepted": { "scheme": "upto", "network": "eip155:84532", "amount": "5000000", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60 }, "payload": { "signature": "0x2d6a7588...", "permit2Authorization": { "permitted": { "token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "amount": "5000000" }, "from": "0x857b06519E91e3A54538791bDbb0E22373e36b66", "spender": "0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002", "nonce": "<EXAMPLE_NONCE>", "deadline": "1740672154", "witness": { "to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "facilitator": "<EXAMPLE_FACILITATOR_ADDRESS>", "validAfter": "1740672089" } } } } ``` The `spender` is the `x402UptoPermit2Proxy` contract at `0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002` (same address across all EVM chains via CREATE2). > **`extra.facilitatorAddress` is required.** The upto client reads `paymentRequirements.extra.facilitatorAddress` and embeds it as `witness.facilitator`. The facilitator supplies it automatically via `getExtra()`, so `<EXAMPLE_FACILITATOR_ADDRESS>` above is whatever your facilitator advertises at `GET /supported` - never a hard-coded constant, and it changes if the operator rotates signers. Without it the client throws `upto scheme requires facilitatorAddress in paymentRequirements.extra`. Only that address can call `settle()` - the contract reverts with `UnauthorizedFacilitator` when `msg.sender != witness.facilitator`. > The `nonce` field is an opaque 32-byte value (64 hex chars). Examples use `<EXAMPLE_NONCE>` as a placeholder; real implementations generate it randomly per authorization. ### Verification Steps 1. Verify `payload.signature` is valid and recovers to `permit2Authorization.from` 2. Verify client has enabled Permit2 approval (`ERC20.allowance >= amount`). If insufficient: check for Sponsored ERC20 Approval or EIP-2612 Permit extensions. If neither: return `412 Precondition Failed` (Error: `permit2_allowance_required`) 3. Verify client has sufficient token balance 4. Verify `permit2Authorization.permitted.amount` equals `amount` from requirements 5. Verify `deadline` not expired and `witness.validAfter` is active 6. Verify token and network match requirements 7. Simulate settlement with full amount (worst case) ### Settlement Settlement amount rules: - MUST be <= authorized maximum - MAY be 0 (no charge if no usage) - Determined by resource server (not client) Settlement process: - **Standard**: Call `x402UptoPermit2Proxy.settle(permit, actualAmount, owner, witness, signature)` where `actualAmount <= permit.permitted.amount` - **With Sponsored ERC20 Approval**: Facilitator batches `ERC20.approve` call before `settle` - **With EIP-2612**: Call `x402UptoPermit2Proxy.settleWithPermit(permit2612, permit, actualAmount, owner, witness, signature)` - **Zero settlement**: No on-chain transaction required. Authorization expires naturally. ### SettlementResponse for Upto | Field | Type | Required | Description | |-------|------|----------|-------------| | `success` | boolean | Yes | Whether settlement succeeded | | `transaction` | string | Yes | Tx hash (empty string if $0 settlement) | | `network` | string | Yes | CAIP-2 network ID | | `payer` | string | No | Payer's wallet address | | `amount` | string | Yes | Actual amount charged (may be "0") | ### x402UptoPermit2Proxy Contract ```solidity contract x402UptoPermit2Proxy is x402BasePermit2Proxy { error UnauthorizedFacilitator(); error AmountExceedsPermitted(); struct Witness { address to; // Destination address (immutable once signed) address facilitator; // Address authorized to settle (must be msg.sender) uint256 validAfter; // Earliest timestamp when payment can be settled } function settle( ISignatureTransfer.PermitTransferFrom calldata permit, uint256 amount, address owner, Witness calldata witness, bytes calldata signature ) external nonReentrant { if (amount > permit.permitted.amount) revert AmountExceedsPermitted(); if (msg.sender != witness.facilitator) revert UnauthorizedFacilitator(); bytes32 witnessHash = keccak256(abi.encode(WITNESS_TYPEHASH, witness.to, witness.facilitator, witness.validAfter)); _settle(permit, amount, owner, witness.to, witness.validAfter, witnessHash, WITNESS_TYPE_STRING, signature); emit Settled(); } function settleWithPermit( EIP2612Permit calldata permit2612, ISignatureTransfer.PermitTransferFrom calldata permit, uint256 amount, address owner, Witness calldata witness, bytes calldata signature ) external nonReentrant { if (amount > permit.permitted.amount) revert AmountExceedsPermitted(); if (msg.sender != witness.facilitator) revert UnauthorizedFacilitator(); _executePermit(permit.permitted.token, owner, permit2612, permit.permitted.amount); // ... same witnessHash + _settle, emits SettledWithPermit() } } ``` > **The upto witness is not the exact witness.** It carries a third field, `facilitator`, which the `exact` witness does not, and it has **no** `extra` field. Signing the wrong struct produces a digest the contract cannot verify, so every payment fails. ### EIP-712 Types ```typescript // exported as `uptoPermit2WitnessTypes` from @x402/evm const uptoPermit2WitnessTypes = { PermitWitnessTransferFrom: [ { name: "permitted", type: "TokenPermissions" }, { name: "spender", type: "address" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, { name: "witness", type: "Witness" }, ], TokenPermissions: [ { name: "token", type: "address" }, { name: "amount", type: "uint256" }, ], Witness: [ { name: "to", type: "address" }, { name: "facilitator", type: "address" }, { name: "validAfter", type: "uint256" }, ], }; ``` Canonical contract constants: ``` WITNESS_TYPE_STRING = "Witness witness)TokenPermissions(address token,uint256 amount)Witness(address to,address facilitator,uint256 validAfter)" WITNESS_TYPEHASH = keccak256("Witness(address to,address facilitator,uint256 validAfter)") ``` For comparison, the `exact` proxy uses `Witness(address to,uint256 validAfter)`. ### Error Codes | Code | Description | |------|-------------| | `invalid_upto_evm_payload_settlement_exceeds_amount` | Attempted to settle for more than authorized maximum | | `AmountExceedsPermitted` | Contract-level revert when `amount > permit.permitted.amount` | | `UnauthorizedFacilitator` | Contract-level revert when `msg.sender != witness.facilitator` | | `permit2_allowance_required` | Client has not approved Permit2 contract (412 status) | ## Security Considerations 1. **Maximum Amount Authorization**: Clients should carefully consider the `amount` they authorize. While servers can only charge up to this amount, clients bear the risk of the full amount being charged. 2. **Server Trust**: The `upto` scheme requires clients to trust that servers will charge fair amounts based on actual usage. Malicious servers could charge up to `amount` regardless of actual usage. 3. **Signature Reuse Prevention**: The Permit2 nonce mechanism prevents signature reuse. Each authorization can only be settled once. 4. **Time Constraints**: Authorizations have explicit valid time windows (`deadline`, `validAfter`) to limit their lifetime and reduce exposure. 5. **Zero Settlement**: Allowing $0 settlements means unused authorizations naturally expire without on-chain transactions, reducing gas costs and blockchain bloat. ## SDK Support | SDK | Upto Scheme Support | |-----|:---:| | TypeScript (`@x402/evm/upto/*`) | Full client/server/facilitator | | Go (`go/mechanisms/evm/upto/*`) | Full client/server/facilitator | | Python (`x402.mechanisms.evm.upto`) | Full client/server/facilitator (since v2.8.0) | The `x402UptoPermit2Proxy` contract is deployed at `0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002` (same address across all EVM chains via CREATE2). See `references/typescript-sdk.md` and `references/go-sdk.md` for SDK usage examples. -
xrpl-scheme.md 7.1 KB
# XRPL Exact Scheme Reference The `exact` scheme on the XRP Ledger uses payer-signed XRPL `Payment` transactions. The payer signs a complete transaction and pays the XRPL network fee themselves; the facilitator only reads ledger state and submits the signed blob. **TypeScript only** - `@x402/xrpl`. Python and Go not implemented. > **Not yet on npm.** The package is tagged `npm-@x402/xrpl@v2.20.0` and ships in-repo at 2.20.0, but `npm install @x402/xrpl` currently 404s - build from source until the publish workflow runs. Every other `@x402/*` package is published at 2.20.0. ## Network Identifiers | Network | CAIP-2 ID | Default WebSocket endpoint | |---------|-----------|----------------------------| | XRPL Mainnet | `xrpl:0` | `wss://s1.ripple.com:51233` | | XRPL Testnet | `xrpl:1` | `wss://s.altnet.rippletest.net:51233` | | XRPL Devnet | `xrpl:2` | `wss://s.devnet.rippletest.net:51233` | | Custom | `xrpl:<networkId>` | Supply via `wsUrlByNetwork` | The default facilitator (`https://x402.org/facilitator`) supports `xrpl:1`. ## No Fee Sponsorship Unlike every other x402 chain, XRPL cannot sponsor fees. The payer signs a complete transaction with the fee embedded, so: ``` extra.areFeesSponsored MUST be present and MUST be false ``` The facilitator needs no funded account - it reads ledger state and submits. This inverts the usual x402 gas-abstraction assumption: the payer must hold XRP for fees regardless of which asset they are paying in. ## No Default Asset - Explicit Pricing Required XRPL has no dollar-string default-asset mapping. A server that writes `price: "$0.001"` for an XRPL route throws: ``` XRPL exact payments require explicit AssetAmount pricing ``` Use an `AssetAmount` instead: | Asset type | `asset` | `amount` | `extra` | |------------|---------|----------|---------| | Native XRP | the literal string `"XRP"` | integer **drops** string (1 XRP = 1,000,000 drops) | - | | Issued currency (IOU) | currency code (3-char or 40-hex) | XRPL decimal `value` string, e.g. `"10.5"` | `extra.issuer` = issuer classic address | There is no `extra.decimals` field - issued-currency amounts are ledger decimal values, so `amount` is used verbatim as the signed `value`. ## Asset Transfer Methods (Sequencing) `extra.assetTransferMethod` selects how the signed transaction is sequenced: | Method | Behavior | Trade-off | |--------|----------|-----------| | `"sequence"` (default) | Consumes the payer account's current `Sequence` | No preflight transaction, no extra reserve, but **one pending payment per account at a time** | | `"ticketSequence"` | Consumes a pre-created XRPL Ticket (`Sequence = 0` plus `TicketSequence`) | Multiple concurrent pending payments; each outstanding ticket locks owner reserve (0.2 XRP on mainnet), max 250 per account | The client follows the method pinned in the payment requirements and defaults to `"sequence"`. Servers offer `"ticketSequence"` by advertising it in `extra.assetTransferMethod`, optionally as a second `accepts` entry so clients can choose. **Sequence-mode footgun:** while a `"sequence"` payment is pending, the payer account must not sign or submit other transactions. Consuming the sequence elsewhere **permanently invalidates** the payment. For `"ticketSequence"`, the client auto-creates one ticket when none is available. `ticketCreateCount` controls this (default `1`; set `0` to require pre-provisioned tickets). ## Payload ```json { "payload": { "signedTxBlob": "1200002280000000240000000161..." } } ``` A single hex-encoded signed XRPL transaction blob. ## TypeScript Usage ### Client ```typescript import { Wallet } from "xrpl"; import { x402Client } from "@x402/core/client"; import { createXrplWalletSigner } from "@x402/xrpl"; import { ExactXrplScheme } from "@x402/xrpl/exact/client"; const wallet = Wallet.fromSeed(process.env.XRPL_SEED); const signer = createXrplWalletSigner(wallet); const client = new x402Client().register("xrpl:*", new ExactXrplScheme(signer)); ``` The client uses `xrpl.Client` to autofill `Sequence` (or `Sequence = 0` plus `TicketSequence`), `Fee`, `LastLedgerSequence`, and `NetworkID` for custom networks before signing. Override the connection with `wsUrlByNetwork` or `clientFactory`; set `feeDrops` only to bypass network fee autofill. Applications that prepare transactions externally pass `preparePaymentTransaction`. ### Server ```typescript import { x402ResourceServer } from "@x402/core/server"; import { ExactXrplScheme } from "@x402/xrpl/exact/server"; const server = new x402ResourceServer(facilitator) .register("xrpl:1", new ExactXrplScheme()); ``` ### Facilitator ```typescript import { ExactXrplScheme } from "@x402/xrpl/exact/facilitator"; facilitator.register("xrpl:1", new ExactXrplScheme()); ``` ## Ticket Provisioning ```typescript import { Wallet } from "xrpl"; import { createTickets, createXrplWalletSigner, getXrplTicketSequences } from "@x402/xrpl"; const signer = createXrplWalletSigner(Wallet.fromSeed(process.env.XRPL_SEED)); const ticketSequences = await createTickets(signer, "xrpl:1", 5); const available = await getXrplTicketSequences(account, "xrpl:1"); ``` ## Duplicate Settlement Mitigation `@x402/xrpl` ships a `SettlementCache` keyed on the signed transaction blob. TTL is sized from the payment's `maxTimeoutSeconds` (which bounds `LastLedgerSequence` expiry) with a **120 second floor** and margin, mirroring the SVM cache. Repeat submissions of the same blob return `duplicate_settlement`. The reasoning: while the transaction can still land, re-submission would pass re-verification (its sequence or ticket is not yet consumed) and resolve to the same validated `tesSUCCESS`. ## Verification Notes - Signing public keys must be canonical: 33-byte compressed secp256k1 (`02`/`03` prefix) or ed25519 (`ED` prefix), 64 hex chars. `rippled` rejects non-canonical keys at preflight, so verification rejects them too rather than accepting an unsettleable payload. - Default facilitator max fee: `10000` drops. - Expiration converts `maxTimeoutSeconds` to ledgers at ~5 seconds per ledger, plus a 2-ledger tolerance. - The `tfPartialPayment` flag (`0x00020000`) is checked - partial payments would deliver less than the signed amount. ## Testnet Setup 1. Fund a payer account with the [XRPL Testnet faucet](https://xrpl.org/resources/dev-tools/xrp-faucets) (`wss://s.altnet.rippletest.net:51233`, network `xrpl:1`). 2. Keep [reserves](https://xrpl.org/docs/concepts/accounts/reserves) funded: base reserve (1 XRP) plus 0.2 XRP owner reserve per outstanding ticket. 3. For IOU payments, the receiving account needs a [trust line](https://xrpl.org/docs/concepts/tokens/fungible-tokens) to the issuer and the payer needs sufficient balance. 4. The facilitator needs no funded account. ## Key Exports | Purpose | Export | |---------|--------| | Client signer from an `xrpl` Wallet | `createXrplWalletSigner(wallet)` | | Create tickets | `createTickets(signer, network, ticketCount)` | | List available tickets | `getXrplTicketSequences(account, network)` | | Invoice id to XRPL `InvoiceID` | `invoiceIdToInvoiceIdField(invoiceId)` | | Network constants | `XRPL_MAINNET`, `XRPL_TESTNET`, `XRPL_DEVNET` | | Scheme (all roles) | `ExactXrplScheme` from `@x402/xrpl/exact/{client,server,facilitator}` |
-
-
CHANGELOG.md 11.9 KB
# Changelog All notable changes to this skill will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), and this skill adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.11.3] - 2026-09-09 ### Changed - Description condensed to fit the repo's 250-character limit. ## [0.11.2] - 2026-08-21 ### Changed - Declared ClawHub browse categories (`finance, development`) and topics in `metadata`, so the release pipeline publishes them instead of leaving the skill in the `other` category. ### Removed - `skill-card.md`. The ClawHub CLI strips a root `skill-card.md` from every publish and the registry generates its own card, so the authored file never reached ClawHub. ## [0.11.1] - 2026-08-07 ### Removed - Git-tracked `.last-refresh` provenance file (stray artifact outside the documented skill layout; now gitignored). ## [0.11.0] - 2026-07-30 ### Added - `@x402/near` (NEAR exact via NEP-366 SignedDelegate + NEP-141 `ft_transfer`, relayer-sponsored gas, `near:mainnet`/`near:testnet`) and `@x402/xrpl` (XRPL exact, `xrpl:0`/`xrpl:1`/`xrpl:2`), each with a per-chain reference doc. NEAR is published on npm; XRPL is tagged 2.20.0 but not yet published. - Network: Igra mainnet (`eip155:38833`, USDC, Permit2 only - the token implements neither EIP-3009 nor EIP-2612). - `extra.facilitatorAddress` - required by the upto client and embedded in the Permit2 witness as `witness.facilitator`. - SVM `extra.recentBlockhash` / `extra.lastValidBlockHeight` transaction-construction hints (TS, Go, Python; non-binding). - `onAfterVerify` can now abort, dispatching `onVerifiedPaymentCanceled` with reason `after_verify_aborted`. - Wallet compatibility: ERC-6492 counterfactual wallets are **not** supported on the Permit2 path; a token whose EIP-3009 implementation only calls `ecrecover` fails every non-EOA wallet type; `payerAuthorizer` must be an EOA. - Spec-stage schemes Starknet and Casper; draft SVM `upto` binding via the Solana payment-channels program. - Error codes: 15 `invalid_siwx_*`, two batch-settlement codes, `invalid_exact_stellar_payload_fee_exceeds_maximum`, `invalid_exact_hedera_payload_signature_invalid`, `UnauthorizedFacilitator`. - Bazaar catalog-visibility troubleshooting; offer-receipt signer *authorization* (`did:web`, DNS TXT `_controllers.<domain>`); `builder-code` service codes capped at 5 and silently truncated. - Third-party SDK and facilitator directories; Slack replaced Discord as the community channel. - Operational guidance from field use: SVM `maxTimeoutSeconds` above ~90s is unenforceable (blockhash lifetime); both payer and `payTo` ATAs must exist; `SettlementCache` rejection is client-visible and only successful settlements should be cached; browser clients must expose the un-prefixed V2 CORS headers; server-side scheme registration does not create a local verify path; a single-EOA facilitator must serialize its own settles because settle-time simulation cannot detect nonce races; set `Content-Type` explicitly on payment-wrapped fetch bodies. ### Changed - SDK versions: TypeScript 2.17.0 -> 2.20.0, Python 2.14.0 -> 2.17.0, Go v2.17.0 -> v2.20.0. - Default facilitator now also covers Algorand Testnet and XRPL Testnet, and advertises `upto` + `batch-settlement` on Base Sepolia alongside three extensions; upstream now documents it as dev/testnet only, not a production default. - NEAR moved from spec-stage to a shipped TypeScript SDK; the spec-only chain list is now Cardano, Sui, Starknet, and Casper. - Python extras: added `evm`, `tvm`, and the `clients` / `servers` / `mechanisms` bundles. ### Fixed - **Breaking for signers:** the `upto` Permit2 witness struct was wrong - it omitted the mandatory `facilitator` field and carried a phantom `bytes extra`. The EIP-712 type list and witness type string were wrong to match. A client signing the documented struct produced a digest the contract cannot verify, so every upto payment would fail. - Algorand CAIP-2 identifiers are the URL-safe base64 genesis hash truncated to the first 32 characters; the previous padded full-hash form no longer matches. - The Permit2 allowance error is `permit2_allowance_required` on the wire, not `PERMIT2_ALLOWANCE_REQUIRED`. - SIWx examples: `siwxResourceServerExtension` does not exist (it is `createSIWxResourceServerExtension`, which requires an operator-configured `origin`), and `domain` / `resourceUri` were removed from the declare options. - Replaced the dead `x402.org/ecosystem` link (hard 404) with the docs facilitator directory. - `builder-code` now ships Python; the support matrix said pending. - `createAuthHeaders` must return an object keyed by facilitator path - a flat object previously dropped authentication silently and now throws. ### Security - SIWx binds to an operator-configured `origin` instead of request-derived values; deriving the domain from the `Host` header allowed a signature made for another site to be replayed. The `uri` origin check tightened from prefix to exact match. - Batch-settlement EVM: unauthenticated path traversal and pre-verification channel mutation fixed across all three SDKs. - SIWx Solana rejects small-order Ed25519 public keys; Hedera facilitators must cryptographically verify the payer signed the frozen transaction body; Aptos verification must not rely on simulation; Stellar facilitators must not use the client's fee bid. - Solana settlement is not settled until the transaction status confirms it: `skipPreflight` submission plus swallowed confirmation errors reports success for transactions that landed with `meta.err` set. Verified against: @x402/core@2.20.0, @x402/evm@2.20.0, x402@2.17.0, github.com/x402-foundation/x402/go/v2@v2.20.0 ## [0.10.2] - 2026-07-22 ### Added - skill-card.md release record following NVIDIA's skill-card format ### Changed - metadata.openclaw audited against the official ClawHub spec ## [0.10.1] - 2026-07-10 ### Changed - CHANGELOG preamble pinned to Keep a Changelog 2.0.0 (format unchanged; KaC 2.0.0 keeps existing changelogs valid). ## [0.10.0] - 2026-07-01 ### Added - Networks: Mezo mainnet (`eip155:31612`, mUSD, 18 decimals, Permit2 + EIP-2612), XDC Network mainnet (`eip155:50`, USDC) and XDC Apothem testnet (`eip155:51`, USDC). - SDK bindings: `@x402/tvm` (TON exact scheme) and `@x402/keeta` (`keeta:21378`/`keeta:1413829460`) in TS 2.15.0; `@x402/concordium` (native CCD, `ccd:*`) in TS 2.17.0. - Core APIs: transport-agnostic `parsePaymentResult` -> `HTTPResourceResponse` (2.15.0); `validateFacilitatorSupport` startup hook (Go `FacilitatorSupportValidator`) that fails fast on facilitator capability mismatch (2.17.0); `dynamicInfoFields` extension capability for per-response nonce/timestamp fields (2.16.0). - Expanded EVM wallet compatibility: plain EOA, ERC-4337/ERC-7579 smart accounts, ERC-6492 counterfactual, and ERC-7702-delegated EOAs; ERC-6492 now covers exact + batch-settlement (2.17.0). - Go gained sign-in-with-x server + client (`go/v2/extensions/signinwithx`, v2.16.0), closing the extensions SDK matrix. - Error codes: `asset_not_deployed_contract` (EVM verify rejects EOA asset addresses) and `invalid_batch_settlement_evm_authorizer_not_configured` (optional batch-settlement `receiverAuthorizer`). - Default facilitator now also supports Hedera Testnet; Hedera HBAR native token usable via asset id `0.0.0` (tinybars, 10^8). ### Changed - SDK versions: TypeScript 2.14.0 -> 2.17.0, Python 2.12.0 -> 2.14.0, Go v2.14.0 -> v2.17.0. - Spec-stage `exact` chains with no SDK narrowed to Cardano, NEAR, Sui (Concordium and Keeta now ship TypeScript SDKs); TON is now TypeScript + Python (was Python-only). - Avalanche marked as runtime-registration only (no pre-configured default asset in v2). - EVM client authorization `validAfter` now set to 0 to reduce onchain timing failures; Go raised the default resource-server `maxTimeoutSeconds` from 60 to 300. - builder-code extension: multiple service codes (`s` as string or array) and EVM `calldataSuffix` plumbing. Verified against: @x402/core@2.17.0, @x402/evm@2.17.0, x402@2.14.0, github.com/x402-foundation/x402/go/v2@v2.17.0 ## [0.9.0] - 2026-06-09 ### Added - Networks: ADI Chain (`eip155:36900`, USDC.e), HPP mainnet (`eip155:190415`) and HPP Sepolia (`eip155:181228`), both defaulting to Bridged USDC (USDC.e). - `@x402/hedera` TypeScript mechanism package (Hedera HBAR + HTS fungible-asset transfers). - Note that x402 now has a fourth official SDK (Java 17+, build from source) and spec-stage `exact` schemes for Concordium, Cardano, NEAR, Sui, and Keeta (no SDK yet), plus a Cloudflare `batch-settlement` variant. - Optimistic-settlement footgun: verify off-chain -> serve -> settle asynchronously, so settlement can fail after the buyer already received the resource. ### Changed - SDK versions: TypeScript 2.12.0 -> 2.14.0, Python 2.10.0 -> 2.12.0, Go v2.11.0 -> v2.14.0. - Go module path now carries a `/v2` suffix (`github.com/x402-foundation/x402/go/v2/...`); all Go imports and `go get` updated - the old bare path no longer resolves tagged releases. - `auth-capture` now ships a TypeScript client scheme (`@x402/evm/auth-capture/client`); `batch-settlement` now ships a full TypeScript SDK (`@x402/evm/batch-settlement/*`); `builder-code` now has TypeScript (`@x402/extensions/builder-code`) and Go (`go/v2/extensions/buildercode`) helpers; `sign-in-with-x` now has a Python implementation. - SVM exact static instruction-count ceiling raised from 6 to 7; added simulation-based smart-wallet verification (`enableSmartWalletVerification`) as a fallback path for allowlisted programs (Squads, Swig, SPL Governance, Metaplex Core, Lighthouse). ### Security - SVM exact facilitator deduplication now keys on the transaction message hash (not the full signed-tx bytes), closing a cache-bypass via fee-payer-signature randomization. - ERC-6492 factory-call-injection hardening: the `eip6492AllowedFactories` allowlist (`eip6492_allowed_factories` in Python) is now the sole gate; an empty/omitted list disables counterfactual deployment and returns `eip6492_factory_not_allowed`. The `DeployERC4337WithEIP6492` config field was removed across all three SDKs. Verified against: @x402/core@2.14.0, @x402/evm@2.14.0, x402@2.12.0, github.com/x402-foundation/x402/go/v2@v2.14.0 ## [0.8.0] - 2026-05-22 ### Added - `batch-settlement` and `auth-capture` payment schemes (`batch-settlement` ships in the Go and Python SDKs; `auth-capture` is spec-defined, SDK support pending). - Networks: TON/TVM (`tvm:-239`/`tvm:-3`), Hedera (`hedera:mainnet`/`testnet`), Algorand, and Radius (`eip155:723487`/`72344`). New `@x402/avm` package (Algorand TS SDK). - Extensions: `builder-code`, `http-message-signatures`, `auth-hints` (spec-defined; no SDK helpers yet). - Protocol fields: `ResourceInfo.serviceName`/`tags`/`iconUrl`, `VerifyResponse.extra`, `/discovery/resources` filters (`payTo`/`scheme`/`network`/`extensions`), `GET /discovery/search`, and the `PERMIT2_ALLOWANCE_REQUIRED` error code. - Solana footgun: the destination USDC ATA must already exist on-chain (x402 SVM transactions carry no ATA-create instruction). ### Changed - SDK versions: TypeScript 2.9.0 -> 2.12.0, Python 2.6.0 -> 2.10.0, Go 2.7.0 -> 2.11.0. - MegaETH default token name corrected to "MegaUSD". - Python now supports the `upto` scheme; gas-sponsoring and `payment-identifier` extensions now span TS + Go + Python. - Default `x402.org` facilitator now also supports Aptos Testnet. - `@x402/fastify` is published on npm (removed the "not yet published" note). - Tightened the SKILL.md description to a concise summary; SDK version numbers are tracked in the body and `metadata.upstream` rather than inline. ### Security - Corrected the `x402UptoPermit2Proxy` contract address to `0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002`. The previous value was a stale pre-redeployment address - a fund-loss hazard if copied into signing code. Verified against: @x402/core@2.12.0, x402@2.10.0, github.com/x402-foundation/x402/go@v2.11.0 ## [0.7.2] - 2026-04-30 - Initial CHANGELOG; upstream tracking established. -
LICENSE.txt 8.9 KB
Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS -
SKILL.md 18.3 KB
--- name: x402 description: Build internet-native payments with x402 - HTTP 402 for on-chain micropayments, no accounts or API keys. Use for paid APIs, paywalled content, agent payment flows, or per-call MCP tools. TypeScript, Python, and Go SDKs across EVM and Solana. metadata: version: "0.11.3" categories: "finance, development" topics: "x402, payments, http-402, micropayments, stablecoins" upstream: "@x402/core@2.20.0, @x402/evm@2.20.0, x402@2.17.0, github.com/x402-foundation/x402/go/v2@v2.20.0" openclaw: homepage: https://github.com/tenequm/skills/tree/main/skills/x402 emoji: "💰" primaryEnv: EVM_PRIVATE_KEY envVars: - name: EVM_PRIVATE_KEY required: false description: EVM signer key for x402 client/server. - name: SVM_PRIVATE_KEY required: false description: Solana signer key for x402 client/server. - name: APTOS_PRIVATE_KEY required: false description: Aptos signer for x402 on Aptos. - name: API_KEY required: false description: Example upstream bearer token used in lifecycle hook examples. - name: FACILITATOR_KEY required: false description: Self-hosted facilitator signing key. - name: FACILITATOR_URL required: false description: Facilitator endpoint URL override. --- # x402 Protocol Development x402 is an open standard (Apache-2.0) that activates the HTTP `402 Payment Required` status code for programmatic, on-chain payments. Originally created by Coinbase, now maintained by the [x402 Foundation](https://github.com/x402-foundation/x402). No accounts, sessions, or API keys required - clients pay with signed crypto transactions directly over HTTP. ## When to Use - Building a **paid API** that accepts crypto micropayments - Adding **paywall** to web content or endpoints - Enabling **AI agents** to autonomously pay for resources - Integrating **MCP tools** that require payment - Building **agent-to-agent** (A2A) payment flows - Working with **EVM** (Base, Ethereum, MegaETH, Monad, Polygon, Stable, Arbitrum), **Solana**, **Stellar**, **Aptos**, **NEAR**, or **XRPL** payment settlement - Implementing **usage-based billing** with the `upto` scheme (LLM tokens, bandwidth, compute) - Running an **in-process facilitator** (self-facilitation) without external facilitator dependency ## Core Architecture Three roles in every x402 payment: 1. **Resource Server** - protects endpoints, returns 402 with payment requirements 2. **Client** - signs payment authorization, retries request with payment header 3. **Facilitator** - verifies signatures, settles transactions on-chain Payment flow (HTTP transport): ``` Client -> GET /resource -> Server returns 402 + PAYMENT-REQUIRED header Client -> signs payment -> retries with PAYMENT-SIGNATURE header Server -> POST /verify to Facilitator -> POST /settle to Facilitator Server -> returns 200 + PAYMENT-RESPONSE header + resource data ``` ## Quick Start: Seller (TypeScript + Express) ```typescript import express from "express"; import { paymentMiddleware, x402ResourceServer } from "@x402/express"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; const app = express(); const payTo = "0xYourWalletAddress"; const facilitator = new HTTPFacilitatorClient({ url: "https://x402.org/facilitator" }); const server = new x402ResourceServer(facilitator) .register("eip155:84532", new ExactEvmScheme()); app.use( paymentMiddleware( { "GET /weather": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo }, ], description: "Weather data", mimeType: "application/json", }, }, server, ), ); app.get("/weather", (req, res) => { res.json({ weather: "sunny", temperature: 70 }); }); app.listen(4021); ``` Install: `npm install @x402/express @x402/core @x402/evm` ## Quick Start: Buyer (TypeScript + Axios) ```typescript import { x402Client, wrapAxiosWithPayment } from "@x402/axios"; import { registerExactEvmScheme } from "@x402/evm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; import axios from "axios"; const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`); const client = new x402Client(); registerExactEvmScheme(client, { signer }); const api = wrapAxiosWithPayment(axios.create(), client); const response = await api.get("http://localhost:4021/weather"); // Payment handled automatically on 402 response ``` Install: `npm install @x402/axios @x402/evm viem` ## Quick Start: Seller (Python + FastAPI) ```python from fastapi import FastAPI from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption from x402.http.middleware.fastapi import PaymentMiddlewareASGI from x402.http.types import RouteConfig from x402.mechanisms.evm.exact import ExactEvmServerScheme from x402.server import x402ResourceServer app = FastAPI() facilitator = HTTPFacilitatorClient(FacilitatorConfig(url="https://x402.org/facilitator")) server = x402ResourceServer(facilitator) server.register("eip155:84532", ExactEvmServerScheme()) routes = { "GET /weather": RouteConfig( accepts=[PaymentOption(scheme="exact", pay_to="0xYourAddress", price="$0.001", network="eip155:84532")], mime_type="application/json", description="Weather data", ), } app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server) @app.get("/weather") async def get_weather(): return {"weather": "sunny", "temperature": 70} ``` Install: `pip install "x402[fastapi,evm]"` ## Quick Start: Seller (Go + Gin) ```go import ( x402http "github.com/x402-foundation/x402/go/v2/http" ginmw "github.com/x402-foundation/x402/go/v2/http/gin" evm "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/server" ) facilitator := x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{URL: facilitatorURL}) routes := x402http.RoutesConfig{ "GET /weather": { Accepts: x402http.PaymentOptions{ {Scheme: "exact", Price: "$0.001", Network: "eip155:84532", PayTo: evmAddress}, }, Description: "Weather data", MimeType: "application/json", }, } r.Use(ginmw.X402Payment(ginmw.Config{ Routes: routes, Facilitator: facilitator, Schemes: []ginmw.SchemeConfig{{Network: "eip155:84532", Server: evm.NewExactEvmScheme()}}, })) ``` Install: `go get github.com/x402-foundation/x402/go/v2` ## Multi-Network Support (EVM + Solana) Servers can accept payment on multiple networks simultaneously: ```typescript import { ExactEvmScheme } from "@x402/evm/exact/server"; import { ExactSvmScheme } from "@x402/svm/exact/server"; const server = new x402ResourceServer(facilitator) .register("eip155:84532", new ExactEvmScheme()) .register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()); // Route config with both networks "GET /weather": { accepts: [ { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: evmAddress }, { scheme: "exact", price: "$0.001", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: svmAddress }, ], } ``` Clients register both schemes and auto-select based on server requirements: ```typescript const client = new x402Client(); registerExactEvmScheme(client, { signer: evmSigner }); registerExactSvmScheme(client, { signer: svmSigner }); ``` ## Supported Networks | Network | CAIP-2 ID | Status | |---------|-----------|--------| | Base Mainnet | `eip155:8453` | Mainnet | | Base Sepolia | `eip155:84532` | Testnet | | MegaETH Mainnet | `eip155:4326` | Mainnet (MegaUSD default, 18 decimals) | | Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Mainnet | | Solana Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | Testnet | | Stellar Mainnet | `stellar:pubnet` | Mainnet (TypeScript SDK only) | | Stellar Testnet | `stellar:testnet` | Testnet (TypeScript SDK only) | | Aptos Mainnet | `aptos:1` | Mainnet (TypeScript SDK only) | | Aptos Testnet | `aptos:2` | Testnet (TypeScript SDK only) | | Monad Mainnet | `eip155:143` | Mainnet | | Polygon Mainnet | `eip155:137` | Mainnet | | Polygon Amoy | `eip155:80002` | Testnet | | Stable Mainnet | `eip155:988` | Mainnet | | Stable Testnet | `eip155:2201` | Testnet | | Arbitrum One | `eip155:42161` | Mainnet | | Arbitrum Sepolia | `eip155:421614` | Testnet | | XDC Network Mainnet | `eip155:50` | Mainnet (USDC) | | XDC Apothem Testnet | `eip155:51` | Testnet (USDC) | | Mezo Mainnet | `eip155:31612` | Mainnet (mUSD, 18 decimals, Permit2 + EIP-2612) | | Mezo Testnet | `eip155:31611` | Testnet (mUSD, Permit2 + EIP-2612) | | Avalanche | `eip155:43114` | Runtime registration only (no default asset; community facilitators) | | Radius Mainnet | `eip155:723487` | Mainnet (SBC default) | | Radius Testnet | `eip155:72344` | Testnet (SBC default) | | ADI Chain | `eip155:36900` | Mainnet (USDC.e default) | | HPP Mainnet | `eip155:190415` | Mainnet (Bridged USDC default) | | HPP Sepolia | `eip155:181228` | Testnet (Bridged USDC default) | | TON Mainnet | `tvm:-239` | Mainnet (jetton transfers; Python + TypeScript SDK) | | TON Testnet | `tvm:-3` | Testnet | | Hedera Mainnet | `hedera:mainnet` | Mainnet (HBAR + HTS tokens) | | Hedera Testnet | `hedera:testnet` | Testnet | | Algorand Mainnet | `algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k` | Mainnet (USDC ASA) | | Algorand Testnet | `algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDe` | Testnet (USDC ASA) | | Keeta Mainnet | `keeta:21378` | Mainnet (TypeScript SDK) | | Keeta Testnet | `keeta:1413829460` | Testnet (TypeScript SDK) | | Concordium Mainnet | `ccd:9dd9ca4d19e9393877d2c44b70f89acb` | Mainnet (native CCD, 6 decimals; TypeScript SDK) | | Concordium Testnet | `ccd:4221332d34e1694168c2a0c0b3fd0f27` | Testnet (native CCD; TypeScript SDK) | | Igra Mainnet | `eip155:38833` | Mainnet (USDC, Permit2 only - no EIP-3009, no EIP-2612) | | NEAR Mainnet | `near:mainnet` | Mainnet (NEP-141 USDC, relayer-sponsored; TypeScript SDK) | | NEAR Testnet | `near:testnet` | Testnet (TypeScript SDK) | | XRPL Mainnet | `xrpl:0` | Mainnet (XRP + IOUs, no fee sponsorship; TypeScript SDK) | | XRPL Testnet | `xrpl:1` | Testnet | | XRPL Devnet | `xrpl:2` | Devnet | > **Algorand CAIP-2 ids changed.** The reference is the URL-safe base64 genesis hash **truncated to the first 32 characters**. The older padded full-hash form (`algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=`) no longer matches; SDKs normalize legacy ids on input, but emit the truncated form. Default facilitator (`https://x402.org/facilitator`) supports Base Sepolia, Solana Devnet, Algorand Testnet, Stellar Testnet, Aptos Testnet, Hedera Testnet, and XRPL Testnet. On Base Sepolia it advertises `exact`, `upto`, **and** `batch-settlement`, plus the `builder-code`, `eip2612GasSponsoring`, and `erc20ApprovalGasSponsoring` extensions. > **Not a production default.** Upstream now states explicitly that the public `x402.org` facilitator is intended for development and testnet workflows - do not assume it is the default path for production mainnet routes. See the [facilitator directory](https://docs.x402.org/dev-tools/facilitators) for production options. ## SDK Packages ### TypeScript v2.20.0 ([npm](https://www.npmjs.com/org/x402), [GitHub](https://github.com/x402-foundation/x402/tree/main/typescript)) | Package | Purpose | |---------|---------| | `@x402/core` | Core types, client, server, facilitator | | `@x402/evm` | EVM exact + upto schemes (EIP-3009, Permit2). Upto via `@x402/evm/upto/*` subpaths | | `@x402/svm` | Solana scheme (SPL TransferChecked) | | `@x402/stellar` | Stellar scheme (SEP-41 Soroban token transfers) | | `@x402/aptos` | Aptos scheme (Fungible Asset transfers) | | `@x402/avm` | Algorand (AVM) scheme | | `@x402/hedera` | Hedera scheme (HBAR + HTS fungible-asset transfers) | | `@x402/tvm` | TON scheme (jetton transfers) | | `@x402/keeta` | Keeta scheme (exact) | | `@x402/concordium` | Concordium scheme (native CCD, exact) | | `@x402/near` | NEAR scheme (NEP-366 SignedDelegate + NEP-141 `ft_transfer`, relayer-sponsored) | | `@x402/xrpl` | XRPL scheme (payer-signed `Payment`, no fee sponsorship). Tagged 2.20.0 but **not yet on npm** - build from source | | `@x402/express` | Express middleware | | `@x402/fastify` | Fastify middleware | | `@x402/hono` | Hono edge middleware | | `@x402/next` | Next.js middleware | | `@x402/axios` | Axios interceptor | | `@x402/fetch` | Fetch wrapper | | `@x402/paywall` | Browser paywall UI | | `@x402/mcp` | MCP client + server | | `@x402/extensions` | Bazaar, offer-receipt, payment-identifier, sign-in-with-x, gas sponsoring | ### Python v2.17.0 ([PyPI](https://pypi.org/project/x402/), [GitHub](https://github.com/x402-foundation/x402/tree/main/python)) ```bash pip install "x402[httpx]" # Async HTTP client pip install "x402[requests]" # Sync HTTP client pip install "x402[fastapi]" # FastAPI server pip install "x402[flask]" # Flask server pip install "x402[evm]" # EVM support pip install "x402[svm]" # Solana support pip install "x402[tvm]" # TON support pip install "x402[mcp]" # MCP integration pip install "x402[extensions]" # Extensions (bazaar, gas sponsoring, etc.) pip install "x402[all]" # Everything ``` Convenience bundles: `clients` (httpx + requests), `servers` (flask + fastapi), `mechanisms` (evm + svm + tvm). ### Go v2.20.0 ([GitHub](https://github.com/x402-foundation/x402/tree/main/go)) The Go module path carries a `/v2` suffix - the bare `.../x402/go` path no longer resolves tagged releases. ```bash go get github.com/x402-foundation/x402/go/v2 ``` ### Java (Java 17+, [GitHub](https://github.com/x402-foundation/x402/tree/main/java)) A fourth official binding is in the repo (`PaymentFilter`, `FacilitatorClient`, `X402HttpClient`). Not published to a package registry yet - build from source. ## Key Concepts - **Client/Server/Facilitator**: The three roles in every payment. Client signs, server enforces, facilitator settles on-chain. See `references/core-concepts.md` - **Wallet**: Both payment mechanism and identity for buyers/sellers. See `references/core-concepts.md` - **Networks & Tokens**: CAIP-2 identifiers, EIP-3009 tokens on EVM, SPL on Solana, custom token config. See `references/core-concepts.md` - **Scheme**: Payment method. `exact` = transfer exact amount; `upto` = authorize max, settle actual usage (shipping SDKs are EVM Permit2 only; a draft SVM binding via Solana payment channels is spec-stage); `batch-settlement` = commit at request time, settle asynchronously; `auth-capture` = escrow / authorize-then-capture with void, refund, reclaim. See `references/evm-scheme.md`, `references/svm-scheme.md`, `references/stellar-scheme.md`, `references/upto-scheme.md`, `references/aptos-scheme.md`, `references/near-scheme.md`, `references/xrpl-scheme.md`, `references/protocol-spec.md` - **Self-facilitation**: Run an in-process facilitator instead of calling an external URL. See `references/typescript-sdk.md`, `references/go-sdk.md` - **Transport**: How payment data is transmitted (HTTP headers, MCP `_meta`, A2A metadata). See `references/transports.md` - **Extensions**: Optional features (bazaar discovery, offer-receipt attestations, payment-identifier idempotency, sign-in-with-x auth, gas sponsoring, builder-code attribution, http-message-signatures, auth-hints). See `references/extensions.md` - **Hooks**: Lifecycle callbacks on client/server/facilitator (TS, Python, Go). See `references/lifecycle-hooks.md` - **Protocol types**: `PaymentRequired`, `PaymentPayload`, `SettlementResponse`. See `references/protocol-spec.md` - **Custom tokens**: Use `registerMoneyParser` for non-USDC tokens, Permit2 for non-EIP-3009 tokens. See `references/evm-scheme.md` - **Mainnet deployment**: Switch facilitator URL, network IDs, and wallet addresses. See `references/core-concepts.md` ## References | File | Content | |------|---------| | `references/core-concepts.md` | HTTP 402 foundation, client/server/facilitator roles, wallet identity, networks, tokens, custom token config, dynamic registration, self-hosted facilitator, mainnet deployment | | `references/protocol-spec.md` | v2 protocol types, payment flow, facilitator API, error codes | | `references/typescript-sdk.md` | TypeScript SDK patterns for server, client, MCP, paywall, facilitator | | `references/python-sdk.md` | Python SDK patterns for server, client, MCP (server + client), facilitator | | `references/go-sdk.md` | Go SDK patterns for server, client, MCP, facilitator, signers, custom money parser | | `references/evm-scheme.md` | EVM exact scheme: EIP-3009, Permit2, default asset resolution, registerMoneyParser, custom tokens | | `references/svm-scheme.md` | Solana exact scheme: SPL TransferChecked, verification rules, duplicate settlement mitigation | | `references/stellar-scheme.md` | Stellar exact scheme: SEP-41 Soroban token transfers, ledger-based expiration, fee sponsorship, TypeScript SDK only | | `references/upto-scheme.md` | Upto (usage-based) scheme: authorize max amount, settle actual usage. EVM via Permit2 only | | `references/aptos-scheme.md` | Aptos exact scheme: fungible asset transfers, fee payer sponsorship, TypeScript SDK only | | `references/near-scheme.md` | NEAR exact scheme: NEP-366 SignedDelegate, NEP-141 `ft_transfer`, relayer gas sponsorship, full-access-key requirement, NEP-145 storage registration | | `references/xrpl-scheme.md` | XRPL exact scheme: payer-signed `Payment`, no fee sponsorship, explicit `AssetAmount` pricing, sequence vs ticketSequence | | `references/transports.md` | HTTP, MCP, A2A transport implementations | | `references/extensions.md` | Bazaar, payment-identifier, sign-in-with-x, gas sponsoring (eip2612 + erc20) extensions | | `references/lifecycle-hooks.md` | Client/server/facilitator hooks (TypeScript, Python, Go), hook chaining, MCP hooks | ## Official Resources - GitHub: https://github.com/x402-foundation/x402 - Spec: https://github.com/x402-foundation/x402/tree/main/specs - Docs: https://docs.x402.org - Website: https://x402.org - [Facilitator directory](https://docs.x402.org/dev-tools/facilitators) - named production and community facilitators - [Third-party SDKs](https://docs.x402.org/dev-tools/third-party-sdks) - Rust (`x402-rs`), Java (Mogami), Ruby (x402-rails), and others beyond the three official bindings - [Third-party extensions](https://docs.x402.org/dev-tools/third-party-extensions) - ecosystem extension packages - Community: Slack at http://slack.x402.org/ (the project moved off Discord) - Foundation Charter: https://github.com/x402-foundation/x402/tree/main/foundation
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.