{"_id":"@402md/x402","_rev":"2-5a0b58aa897f911f20a1976e6fdf936a","name":"@402md/x402","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@402md/x402","version":"0.1.0","keywords":["x402","402","payment","paywall","usdc","base","stellar","ai-agents","middleware","express","hono","nextjs"],"license":"MIT","_id":"@402md/x402@0.1.0","maintainers":[{"name":"henriquebreim","email":"developers@402.md"}],"homepage":"https://github.com/402md/x402#readme","bugs":{"url":"https://github.com/402md/x402/issues"},"dist":{"shasum":"0bfbaa4b0ff79defe3f1a5facb57884a7e8f9075","tarball":"https://registry.npmjs.org/@402md/x402/-/x402-0.1.0.tgz","fileCount":8,"integrity":"sha512-QycxuCnwKTwpRnDsE9jF7ht/78QGfTYLChUjVFwNSg8VQ8FgK5TyUHeL4aQm/oAFS1GthbgR4VOhOAdogV5n7A==","signatures":[{"sig":"MEQCICGHePxoaOzg7jaEVWNjw5EAkQpVxE/QY1R34T7KeX60AiAiSgd4A4QbE5WaovR+bFh2/DwAS4ID6V4LYWvT7yLfqQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":26577200},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"9586dc6d2a2a8613fdb0c15dc52d6e409aa8460e","scripts":{"dev":"tsup --watch","lint":"eslint src/","test":"vitest run","build":"tsup","format":"prettier --write 'src/**/*.ts' '__tests__/**/*.ts'","lint:fix":"eslint src/ --fix","test:cov":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check 'src/**/*.ts' '__tests__/**/*.ts'","prepublishOnly":"npm run build"},"_npmUser":{"name":"henriquebreim","email":"developers@402.md"},"repository":{"url":"git+https://github.com/402md/x402.git","type":"git"},"_npmVersion":"11.2.0","description":"x402 protocol implementation — one-liner paywalls, auto-paying fetch, Base + Stellar support","directories":{},"lint-staged":{"*.ts":["eslint --fix","prettier --write"]},"_nodeVersion":"22.13.1","dependencies":{"@x402/core":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","eslint":"^9.28.0","vitest":"^3.2.1","globals":"^16.1.0","prettier":"^3.5.3","@eslint/js":"^9.28.0","typescript":"^5.8.3","@types/node":"^22.15.21","lint-staged":"^16.1.0","typescript-eslint":"^8.34.0","eslint-config-prettier":"^10.1.5","eslint-plugin-prettier":"^5.4.0"},"optionalDependencies":{"viem":"^2.0.0","@stellar/stellar-sdk":"^12.0.0"},"_npmOperationalInternal":{"tmp":"tmp/x402_0.1.0_1773448936246_0.536701600278767","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@402md/x402","version":"0.1.1","description":"x402 protocol implementation — one-liner paywalls, auto-paying fetch, Base + Stellar support","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","dev":"tsup --watch","lint":"eslint src/","lint:fix":"eslint src/ --fix","format":"prettier --write 'src/**/*.ts' '__tests__/**/*.ts'","format:check":"prettier --check 'src/**/*.ts' '__tests__/**/*.ts'","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:cov":"vitest run --coverage","prepublishOnly":"npm run build"},"dependencies":{"@x402/core":"^2.3.0"},"optionalDependencies":{"viem":"^2.0.0","@stellar/stellar-sdk":"^12.0.0"},"devDependencies":{"@eslint/js":"^9.28.0","@types/node":"^22.15.21","eslint":"^9.28.0","eslint-config-prettier":"^10.1.5","eslint-plugin-prettier":"^5.4.0","globals":"^16.1.0","lint-staged":"^16.1.0","prettier":"^3.5.3","tsup":"^8.5.0","typescript":"^5.8.3","typescript-eslint":"^8.34.0","vitest":"^3.2.1"},"lint-staged":{"*.ts":["eslint --fix","prettier --write"]},"keywords":["x402","402","payment","paywall","usdc","base","stellar","ai-agents","middleware","express","hono","nextjs"],"license":"MIT","repository":{"type":"git","url":"git+https://github.com/402md/x402.git"},"engines":{"node":">=18"},"_id":"@402md/x402@0.1.1","gitHead":"b261eb1628e4e82e9382db31f363012032a73638","bugs":{"url":"https://github.com/402md/x402/issues"},"homepage":"https://github.com/402md/x402#readme","_nodeVersion":"22.13.1","_npmVersion":"11.2.0","dist":{"integrity":"sha512-w8vP9g/yBzxT8045Ds+2MaiJ/WqmybJn9jjMfCiZzrm7eZoOvHgeKszcYPoeP5AbWT/OOVg88YwSfM7W30HXgA==","shasum":"403968204197a00c6be48c44300d8d7c5b84b542","tarball":"https://registry.npmjs.org/@402md/x402/-/x402-0.1.1.tgz","fileCount":8,"unpackedSize":26607026,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCOlfTOBx/VU6V6iortX33bR3rsp2EHeNN6sff0tP0hcgIhANm5FHU0nJO+NEIk5MQL96jPTZfV6yqTH1n2f3DS94xw"}]},"_npmUser":{"name":"henriquebreim","email":"developers@402.md"},"directories":{},"maintainers":[{"name":"henriquebreim","email":"developers@402.md"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/x402_0.1.1_1773963782735_0.3022686464340183"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-14T00:42:16.138Z","modified":"2026-03-19T23:43:03.150Z","0.1.0":"2026-03-14T00:42:16.663Z","0.1.1":"2026-03-19T23:43:03.015Z"},"bugs":{"url":"https://github.com/402md/x402/issues"},"license":"MIT","homepage":"https://github.com/402md/x402#readme","keywords":["x402","402","payment","paywall","usdc","base","stellar","ai-agents","middleware","express","hono","nextjs"],"repository":{"type":"git","url":"git+https://github.com/402md/x402.git"},"description":"x402 protocol implementation — one-liner paywalls, auto-paying fetch, Base + Stellar support","maintainers":[{"name":"henriquebreim","email":"developers@402.md"}],"readme":"# @402md/x402\n\n[![npm version](https://img.shields.io/npm/v/@402md/x402)](https://www.npmjs.com/package/@402md/x402)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![x402](https://img.shields.io/badge/x402-v2-green)](https://x402.org)\n[![Base](https://img.shields.io/badge/Base-EVM-3245FF)](https://base.org)\n[![Stellar](https://img.shields.io/badge/Stellar-Soroban-blueviolet)](https://stellar.org)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6)](https://www.typescriptlang.org)\n\nOne-liner paywalls and auto-paying fetch for the [x402 protocol](https://www.x402.org/). Supports **Base** (EVM) and **Stellar** (Soroban) with built-in budget controls for AI agents.\n\n```ts\n// Server — one line to paywall any route\napp.get('/api/premium', paywall({ price: '0.01', payTo: '0x...', network: 'base' }), handler)\n\n// Client — auto-pays 402 responses transparently\nconst res = await client.fetch('https://api.example.com/premium')\n```\n\n## Table of Contents\n\n- [Install](#install)\n- [Quick Start](#quick-start)\n- [Server Middleware](#server-middleware)\n  - [Express / Connect](#express--connect)\n  - [Hono](#hono)\n  - [Next.js App Router](#nextjs-app-router)\n- [Client](#client)\n  - [Persistent Client](#persistent-client)\n  - [One-shot Fetch](#one-shot-fetch)\n- [How Payment Validation Works](#how-payment-validation-works)\n  - [The 402 Flow](#the-402-flow)\n  - [EVM (Base) — EIP-712 Gasless Signatures](#evm-base--eip-712-gasless-signatures)\n  - [Stellar — Soroban Auth Entries (Gasless)](#stellar--soroban-auth-entries-gasless)\n  - [Facilitator Verify + Settle](#facilitator-verify--settle)\n- [Supported Networks](#supported-networks)\n  - [Base (Mainnet)](#base-mainnet)\n  - [Base Sepolia (Testnet)](#base-sepolia-testnet)\n  - [Stellar (Mainnet)](#stellar-mainnet)\n  - [Stellar Testnet](#stellar-testnet)\n- [Budget System](#budget-system)\n- [Dynamic Pricing](#dynamic-pricing)\n- [Cart / E-commerce Example](#cart--e-commerce-example)\n- [Subscription with Wallet Auth](#subscription-with-wallet-auth)\n- [USDC Amount Handling](#usdc-amount-handling)\n- [API Reference](#api-reference)\n  - [Server Exports](#server-exports)\n  - [Client Exports](#client-exports)\n  - [Constants](#constants)\n  - [Types](#types)\n- [Optional Dependencies](#optional-dependencies)\n\n---\n\n## Install\n\n```bash\nnpm install @402md/x402\n```\n\nChain SDKs are **optional** — install only what you need:\n\n```bash\n# For EVM networks (Base, Base Sepolia)\nnpm install viem\n\n# For Stellar networks (Stellar, Stellar Testnet)\nnpm install @stellar/stellar-sdk\n```\n\nIf you try to use a network without its SDK installed, you get a clear error:\n\n```\nError: viem is required for EVM networks. Install it: npm install viem\n```\n\n---\n\n## Quick Start\n\n### Paywall a route (server)\n\n```ts\nimport express from 'express'\nimport { paywall } from '@402md/x402'\n\nconst app = express()\n\napp.get(\n  '/api/weather',\n  paywall({\n    price: '0.001',          // $0.001 USDC per request\n    payTo: '0xYourAddress',  // your wallet\n    network: 'base'          // Base mainnet\n  }),\n  (req, res) => {\n    // req.x402.payment contains { settled, txHash, payer, network }\n    res.json({ temperature: 22, unit: 'celsius' })\n  }\n)\n\napp.listen(3000)\n```\n\n### Consume a paywalled API (client)\n\n```ts\nimport { createPaymentClient } from '@402md/x402'\n\nconst client = await createPaymentClient({\n  evmPrivateKey: process.env.PRIVATE_KEY,\n  network: 'base',\n  budget: { maxPerCall: '0.01', maxPerDay: '1.00' }\n})\n\nconst res = await client.fetch('https://api.example.com/api/weather')\nconst data = await res.json()\nconsole.log(data) // { temperature: 22, unit: 'celsius' }\n```\n\n---\n\n## Server Middleware\n\nAll middleware functions accept a `PaywallConfig` object:\n\n```ts\ninterface PaywallConfig {\n  price: string              // USDC amount, e.g. '0.001'\n  payTo: string              // recipient address (EVM or Stellar)\n  network: PaymentNetwork    // 'base' | 'base-sepolia' | 'stellar' | 'stellar-testnet'\n  facilitatorUrl?: string    // override default facilitator\n  description?: string       // human-readable resource description\n  maxTimeoutSeconds?: number // payment validity window (default: 300)\n  onPayment?: (payment: VerifiedPayment) => void  // post-payment callback\n}\n```\n\n### Express / Connect\n\n```ts\nimport express from 'express'\nimport { paywall } from '@402md/x402'\n\nconst app = express()\n\n// Simple — one config object\napp.get(\n  '/api/data',\n  paywall({ price: '0.01', payTo: '0xABC...', network: 'base' }),\n  (req, res) => {\n    // Payment verified and settled. Access details:\n    const { settled, txHash, payer, network } = req.x402.payment\n    res.json({ data: 'premium content', payer })\n  }\n)\n\n// With callback — log every payment\napp.post(\n  '/api/generate',\n  paywall({\n    price: '0.05',\n    payTo: '0xABC...',\n    network: 'base',\n    description: 'AI text generation',\n    onPayment: (payment) => {\n      console.log(`Received payment from ${payment.payer}: ${payment.txHash}`)\n    }\n  }),\n  (req, res) => {\n    res.json({ text: 'generated content' })\n  }\n)\n\n// Testnet — same API, just change network\napp.get(\n  '/api/test',\n  paywall({ price: '0.001', payTo: '0xABC...', network: 'base-sepolia' }),\n  handler\n)\n```\n\nWorks with any Express-compatible framework (Connect, Polka, etc.).\n\n### Hono\n\n```ts\nimport { Hono } from 'hono'\nimport { paywallHono, getPayment } from '@402md/x402'\n\nconst app = new Hono()\n\napp.get(\n  '/api/data',\n  paywallHono({ price: '0.01', payTo: '0xABC...', network: 'base' }),\n  (c) => {\n    const payment = getPayment(c)\n    return c.json({ data: 'premium content', payer: payment?.payer })\n  }\n)\n\nexport default app\n```\n\n`getPayment(c)` is a typed helper that retrieves the `VerifiedPayment` from Hono's context.\n\n### Next.js App Router\n\n```ts\n// app/api/premium/route.ts\nimport { paywallNextjs } from '@402md/x402'\n\nexport const GET = paywallNextjs(\n  { price: '0.01', payTo: '0xABC...', network: 'base' },\n  async (req) => {\n    // req.x402.payment is available here\n    return Response.json({ data: 'premium content' })\n  }\n)\n\nexport const POST = paywallNextjs(\n  { price: '0.05', payTo: '0xABC...', network: 'base' },\n  async (req) => {\n    const body = await req.json()\n    return Response.json({ result: 'processed', input: body })\n  }\n)\n```\n\nWraps your route handler — if no valid payment is present, returns 402 before your handler runs.\n\n---\n\n## Client\n\n### Persistent Client\n\nBest for agents or services that make multiple paid requests:\n\n```ts\nimport { createPaymentClient } from '@402md/x402'\n\nconst client = await createPaymentClient({\n  evmPrivateKey: process.env.PRIVATE_KEY,\n  network: 'base',\n  budget: {\n    maxPerCall: '0.10',\n    maxPerDay: '5.00',\n    maxPerSession: '2.00'\n  }\n})\n\n// Auto-paying fetch — handles 402 transparently\nconst res = await client.fetch('https://api.example.com/premium')\n\n// Check balance\nconst balance = await client.getBalance()  // '42.50'\n\n// Get wallet address\nconst address = client.getAddress()  // '0x...'\n\n// Manual payment (advanced)\nconst token = await client.pay(paymentRequirement)\n```\n\n**What `client.fetch()` does internally:**\n\n1. Makes the HTTP request normally\n2. If server returns `200` — returns the response as-is\n3. If server returns `402` — parses the payment requirements, checks budget, signs payment, retries with `X-PAYMENT` header\n4. Returns the paid response\n\n### One-shot Fetch\n\nFor single requests where you don't need to reuse the client:\n\n```ts\nimport { x402Fetch } from '@402md/x402'\n\nconst res = await x402Fetch('https://api.example.com/premium', {\n  method: 'POST',\n  body: JSON.stringify({ prompt: 'hello' }),\n  headers: { 'Content-Type': 'application/json' },\n  paymentConfig: {\n    evmPrivateKey: process.env.PRIVATE_KEY,\n    network: 'base',\n    budget: { maxPerCall: '0.10' }\n  }\n})\n```\n\nPass `skipPayment: true` to get the raw 402 response without auto-paying:\n\n```ts\nconst res = await x402Fetch('https://api.example.com/premium', {\n  skipPayment: true\n})\n// res.status === 402\n```\n\n---\n\n## How Payment Validation Works\n\n### The 402 Flow\n\nThe x402 protocol uses HTTP status `402 Payment Required` to create a challenge-response payment flow. Every payment is **gasless for the payer** — the facilitator pays on-chain gas fees.\n\n```\nAgent                          Server                      Facilitator\n  |                              |                              |\n  |  GET /api/data               |                              |\n  |----------------------------->|                              |\n  |                              |                              |\n  |  402 + PaymentRequired JSON  |                              |\n  |<-----------------------------|                              |\n  |                              |                              |\n  |  [sign authorization]        |                              |\n  |                              |                              |\n  |  GET /api/data               |                              |\n  |  X-PAYMENT: <base64 token>   |                              |\n  |----------------------------->|                              |\n  |                              |  POST /verify                |\n  |                              |  {paymentPayload, reqs}      |\n  |                              |----------------------------->|\n  |                              |  { isValid: true }           |\n  |                              |<-----------------------------|\n  |                              |                              |\n  |                              |  POST /settle                |\n  |                              |  {paymentPayload, reqs}      |\n  |                              |----------------------------->|\n  |                              |  { success, txHash }         |\n  |                              |<-----------------------------|\n  |                              |                              |\n  |  200 + response data         |                              |\n  |<-----------------------------|                              |\n```\n\n#### Step 1: Server returns 402\n\nWhen a request arrives without a valid `X-PAYMENT` header, the middleware returns:\n\n```json\n{\n  \"x402Version\": 2,\n  \"accepts\": [\n    {\n      \"scheme\": \"exact\",\n      \"network\": \"eip155:8453\",\n      \"amount\": \"10000\",\n      \"payTo\": \"0xRecipientAddress\",\n      \"maxTimeoutSeconds\": 300,\n      \"asset\": \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n      \"extra\": {\n        \"facilitator\": \"https://facilitator.x402.org\",\n        \"name\": \"USD Coin\",\n        \"version\": \"2\"\n      }\n    }\n  ],\n  \"resource\": {\n    \"url\": \"/api/data\",\n    \"description\": \"Premium data access\",\n    \"mimeType\": \"application/json\"\n  }\n}\n```\n\nKey fields:\n- `amount` — USDC in **atomic units** (6 decimals). `\"10000\"` = $0.01\n- `network` — [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain identifier\n- `asset` — USDC contract address on that network\n- `extra.facilitator` — URL of the service that will verify and settle\n\n#### Step 2: Client signs authorization\n\nThe client signs a **gasless authorization** (not a transaction). The signing mechanism differs by chain:\n\n- **EVM**: EIP-712 `TransferWithAuthorization` (ERC-3009)\n- **Stellar**: Soroban `SorobanAuthorizationEntry`\n\nThe signed proof is base64-encoded and sent as the `X-PAYMENT` header.\n\n#### Step 3: Server verifies and settles\n\nThe server middleware:\n\n1. Decodes the `X-PAYMENT` header (base64 → JSON)\n2. Sends the payment payload to the **facilitator** for verification (`POST /verify`)\n3. If valid, sends it for **settlement** (`POST /settle`)\n4. The facilitator submits the on-chain transaction and pays gas\n5. Returns `{ settled: true, txHash, payer, network }` to your handler\n\n### EVM (Base) — EIP-712 Gasless Signatures\n\nOn Base networks, the client signs an [ERC-3009](https://eips.ethereum.org/EIPS/eip-3009) `TransferWithAuthorization` using EIP-712 typed data:\n\n```\nEIP-712 Domain:\n  name:              \"USD Coin\" (mainnet) / \"USDC\" (testnet)\n  version:           \"2\"\n  chainId:           8453 (mainnet) / 84532 (testnet)\n  verifyingContract: USDC contract address\n\nMessage (TransferWithAuthorization):\n  from:        agent's address\n  to:          payTo (recipient)\n  value:       amount in atomic units\n  validAfter:  0\n  validBefore: now + maxTimeoutSeconds\n  nonce:       random 32 bytes (ERC-3009 uses random nonces)\n```\n\nThis signature **authorizes a USDC transfer without submitting a transaction**. The facilitator takes this signature and calls `transferWithAuthorization()` on the USDC contract, paying the gas itself.\n\n**Why this is gasless**: The agent only signs data (free). The facilitator submits the on-chain transaction and covers gas fees.\n\n**Dependencies**: Requires `viem` for private key management and EIP-712 signing.\n\n### Stellar — Soroban Auth Entries (Gasless)\n\nOn Stellar networks, the client signs a **Soroban authorization entry** — not a full transaction:\n\n```\nSorobanAuthorizedInvocation:\n  function:  USDC contract \"transfer\"\n  args:      [from (agent), to (recipient), amount (i128)]\n\nSorobanAuthorizationEntry:\n  credentials:\n    address:                  agent's public key\n    nonce:                    random i64 (positive)\n    signatureExpirationLedger: current ledger + timeout/5\n    signature:                ed25519 signature\n  rootInvocation:             the invocation above\n```\n\nThe signed auth entry is serialized to XDR (base64) and sent to the facilitator.\n\n**What the facilitator does**:\n1. Receives the signed auth entry\n2. Builds a Stellar transaction with its own source account (pays gas ~$0.00001)\n3. Attaches the agent's auth entry to the transaction\n4. Submits to the Soroban network\n\n**Why this is gasless**: The agent signs only the authorization to move USDC. The facilitator wraps it in a transaction and pays all fees.\n\n**Ledger-based expiration**: Stellar doesn't use wall-clock time for auth expiration. Instead, it uses ledger numbers. With ~5 seconds per ledger, `maxTimeoutSeconds: 300` translates to ~60 ledgers from the current sequence.\n\n**Dependencies**: Requires `@stellar/stellar-sdk` for Keypair management, XDR encoding, and `authorizeEntry()`.\n\n### Facilitator Verify + Settle\n\nThe facilitator is a third-party service that acts as the on-chain settlement layer:\n\n| Network | Facilitator | Operator |\n|---------|-------------|----------|\n| Base, Base Sepolia | `https://facilitator.x402.org` | Coinbase |\n| Stellar | `https://channels.openzeppelin.com/x402` | OpenZeppelin |\n| Stellar Testnet | `https://channels.openzeppelin.com/x402/testnet` | OpenZeppelin |\n\n**Verify** (`POST /verify`):\n- Validates the cryptographic signature\n- Checks the authorization hasn't expired\n- Checks the payer has sufficient USDC balance\n- Returns `{ isValid: true, payer: '0x...' }` or `{ isValid: false, invalidReason: '...' }`\n\n**Settle** (`POST /settle`):\n- Submits the on-chain transaction (calling `transferWithAuthorization` on EVM or the Soroban USDC contract on Stellar)\n- Pays all gas fees\n- Returns `{ success: true, transaction: '0x...', network: 'eip155:8453' }`\n\nYou can override the facilitator URL per route:\n\n```ts\npaywall({\n  price: '0.01',\n  payTo: '0x...',\n  network: 'base',\n  facilitatorUrl: 'https://my-custom-facilitator.com'\n})\n```\n\nOr use the `FacilitatorClient` directly:\n\n```ts\nimport { FacilitatorClient } from '@402md/x402'\n\nconst facilitator = new FacilitatorClient('https://facilitator.x402.org')\nconst verifyResult = await facilitator.verify(paymentPayload, requirements)\nconst settleResult = await facilitator.settle(paymentPayload, requirements)\n```\n\n---\n\n## Supported Networks\n\n### Base (Mainnet)\n\n| Property | Value |\n|----------|-------|\n| Network key | `'base'` |\n| CAIP-2 | `eip155:8453` |\n| Chain ID | `8453` |\n| USDC address | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |\n| Facilitator | `https://facilitator.x402.org` |\n| EIP-712 name | `USD Coin` |\n| SDK | `viem` |\n\n```ts\npaywall({ price: '0.01', payTo: '0x...', network: 'base' })\n```\n\n### Base Sepolia (Testnet)\n\n| Property | Value |\n|----------|-------|\n| Network key | `'base-sepolia'` |\n| CAIP-2 | `eip155:84532` |\n| Chain ID | `84532` |\n| USDC address | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |\n| Facilitator | `https://facilitator.x402.org` |\n| EIP-712 name | `USDC` |\n| SDK | `viem` |\n\n```ts\n// Use for development and testing — faucet USDC available\npaywall({ price: '0.01', payTo: '0x...', network: 'base-sepolia' })\n```\n\n### Stellar (Mainnet)\n\n| Property | Value |\n|----------|-------|\n| Network key | `'stellar'` |\n| CAIP-2 | `stellar:pubnet` |\n| USDC contract | `CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA` |\n| Facilitator | `https://channels.openzeppelin.com/x402` |\n| Signing | Soroban auth entry (gasless) |\n| SDK | `@stellar/stellar-sdk` |\n\n```ts\npaywall({ price: '0.01', payTo: 'GABC...XYZ', network: 'stellar' })\n```\n\n### Stellar Testnet\n\n| Property | Value |\n|----------|-------|\n| Network key | `'stellar-testnet'` |\n| CAIP-2 | `stellar:testnet` |\n| USDC contract | `CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA` |\n| Facilitator | `https://channels.openzeppelin.com/x402/testnet` |\n| Signing | Soroban auth entry (gasless) |\n| SDK | `@stellar/stellar-sdk` |\n\n```ts\npaywall({ price: '0.01', payTo: 'GABC...XYZ', network: 'stellar-testnet' })\n```\n\n### Mixing networks\n\nServer and client networks are independent. A server on Base can coexist with a server on Stellar:\n\n```ts\napp.get('/api/base', paywall({ price: '0.01', payTo: '0x...', network: 'base' }), handler)\napp.get('/api/stellar', paywall({ price: '0.01', payTo: 'GABC...', network: 'stellar' }), handler)\n```\n\n---\n\n## Budget System\n\nAI agents need spending limits. The `BudgetTracker` enforces three types of caps:\n\n```ts\nconst client = await createPaymentClient({\n  evmPrivateKey: '0x...',\n  network: 'base',\n  budget: {\n    maxPerCall: '0.10',    // rejects any single payment > $0.10\n    maxPerDay: '5.00',     // rejects if cumulative daily spend would exceed $5.00\n    maxPerSession: '2.00'  // rejects if cumulative session spend would exceed $2.00\n  }\n})\n```\n\n| Limit | Scope | Resets |\n|-------|-------|--------|\n| `maxPerCall` | Single payment | Per request |\n| `maxPerDay` | Calendar day (midnight local time) | Daily at 00:00 |\n| `maxPerSession` | Lifetime of this `PaymentClient` instance | Never (create a new client) |\n\nAll limits are optional. If no budget is configured, there are no spending restrictions.\n\n**Budget is checked before signing** — if a payment would exceed any limit, an error is thrown and no signature is created. After successful signing, the amount is recorded.\n\n```ts\ntry {\n  const res = await client.fetch('https://expensive-api.com/data')\n} catch (e) {\n  // \"Would exceed daily budget. Spent: $4.50, requested: $1.00, limit: $5.00\"\n  console.error(e.message)\n}\n```\n\n---\n\n## Dynamic Pricing\n\n`paywall()` already supports dynamic prices — just compute the amount at request time:\n\n```ts\nimport express from 'express'\nimport { paywall } from '@402md/x402'\n\nconst app = express()\napp.use(express.json())\n\napp.post('/v1/checkout', async (req, res, next) => {\n  const cart = await getCart(req.body.cartId)\n  const shipping = await calculateShipping(req.body.address)\n  const total = (cart.subtotal + shipping).toFixed(6)\n\n  paywall({\n    price: total,\n    payTo: '0x...',\n    network: 'base',\n    description: `Order: $${total} USDC`\n  })(req, res, next)\n}, async (req, res) => {\n  const order = await processOrder(req.body)\n  res.json({ orderId: order.id, status: 'confirmed' })\n})\n```\n\nThe client doesn't need any changes — `client.fetch()` reads the actual price from the 402 response and pays whatever the server requires. Budget limits still apply.\n\n---\n\n## Cart / E-commerce Example\n\nA full e-commerce flow with free endpoints (search, cart, shipping) and a dynamic-priced checkout:\n\n### Server\n\n```ts\nimport express from 'express'\nimport { paywall } from '@402md/x402'\n\nconst app = express()\napp.use(express.json())\n\n// Free — product search\napp.get('/v1/products', async (req, res) => {\n  const results = await searchProducts(req.query.q as string)\n  res.json(results)\n})\n\n// Free — shipping quote\napp.post('/v1/shipping/quote', async (req, res) => {\n  const quote = await calculateShipping(req.body.address, req.body.items)\n  res.json({ shipping: quote.toFixed(6), estimatedDays: 3 })\n})\n\n// Dynamic price — checkout\napp.post('/v1/orders', async (req, res, next) => {\n  const { items, shipping, address } = req.body\n  const subtotal = items.reduce((sum, i) => sum + i.price * i.qty, 0)\n  const total = (subtotal + shipping).toFixed(6)\n\n  paywall({\n    price: total,\n    payTo: '0xYourAddress',\n    network: 'base',\n    description: `Order: ${items.length} items, $${total} USDC`,\n    onPayment: (payment) => {\n      console.log(`Order paid: ${payment.txHash} from ${payment.payer}`)\n    }\n  })(req, res, next)\n}, async (req, res) => {\n  const order = await createOrder(req.body, req.x402.payment)\n  res.json({ orderId: order.id, status: 'confirmed' })\n})\n```\n\n### Client (AI Agent)\n\n```ts\nimport { createPaymentClient } from '@402md/x402'\n\nconst client = await createPaymentClient({\n  evmPrivateKey: process.env.PRIVATE_KEY,\n  network: 'base',\n  budget: { maxPerCall: '200.00', maxPerDay: '500.00' }\n})\n\n// 1. Search products (free)\nconst products = await client.fetch('https://shop.example.com/v1/products?q=keyboard')\nconst items = [\n  { id: 'kb-01', name: 'Mechanical Keyboard', price: 89.99, qty: 1 },\n  { id: 'usbc-01', name: 'USB-C Cable', price: 12.99, qty: 1 }\n]\n\n// 2. Get shipping quote (free)\nconst quoteRes = await client.fetch('https://shop.example.com/v1/shipping/quote', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ address: '123 Main St', items })\n})\nconst { shipping } = await quoteRes.json() // \"7.500000\"\n\n// 3. Checkout (auto-pays $110.48 via x402)\nconst orderRes = await client.fetch('https://shop.example.com/v1/orders', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ items, shipping: 7.50, address: '123 Main St' })\n})\nconst order = await orderRes.json() // { orderId: '...', status: 'confirmed' }\n```\n\nThe agent never calculates the total — the server computes it, returns a 402 with the exact price, and `client.fetch()` pays it automatically.\n\n---\n\n## Subscription with Wallet Auth\n\nFor subscription-based services, combine x402 payment with wallet-signature authentication:\n\n1. **Subscribe** — agent pays via x402, server records the wallet\n2. **Login** — agent proves wallet ownership with a signed message\n3. **Access** — protected routes check the subscription via `walletAuth()` middleware\n\n### Server\n\n```ts\nimport express from 'express'\nimport { paywall, verifyWalletSignature, walletAuth } from '@402md/x402'\n\nconst app = express()\napp.use(express.json())\n\n// 1. Subscription payment (x402)\napp.post('/v1/subscribe',\n  paywall({\n    price: '10.00',\n    payTo: '0xYourAddress',\n    network: 'base',\n    description: '30-day subscription'\n  }),\n  async (req, res) => {\n    const { payer } = req.x402.payment\n    const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)\n    await db.subscriptions.upsert({ wallet: payer, expiresAt })\n    res.json({ subscribedUntil: expiresAt.toISOString(), wallet: payer })\n  }\n)\n\n// 2. Wallet auth login (free)\napp.post('/v1/auth', async (req, res) => {\n  const { message, signature, address } = req.body\n  const valid = await verifyWalletSignature({ message, signature, address })\n  if (!valid) return res.status(401).json({ error: 'Invalid signature' })\n\n  const sub = await db.subscriptions.findByWallet(address)\n  if (!sub || sub.expiresAt < new Date())\n    return res.status(403).json({ error: 'No active subscription' })\n\n  const token = signJwt({ wallet: address, exp: sub.expiresAt })\n  res.json({ token })\n})\n\n// 3. Protected route with walletAuth middleware\napp.get('/v1/data',\n  walletAuth({\n    verifyAccess: async (addr) => {\n      const sub = await db.subscriptions.findByWallet(addr)\n      return !!sub && sub.expiresAt > new Date()\n    }\n  }),\n  (req, res) => {\n    res.json({ data: 'premium content', wallet: req.x402.wallet.address })\n  }\n)\n```\n\n### Client (AI Agent)\n\n```ts\nimport { createPaymentClient } from '@402md/x402'\n\nconst client = await createPaymentClient({\n  evmPrivateKey: process.env.PRIVATE_KEY,\n  network: 'base',\n  budget: { maxPerCall: '10.00' }\n})\n\n// Step 1: Subscribe (auto-pays $10.00 via x402)\nawait client.fetch('https://api.example.com/v1/subscribe', { method: 'POST' })\n\n// Step 2: Login with wallet signature\nconst message = `Login: ${Date.now()}`\nconst signature = await client.signMessage(message)\nconst loginRes = await fetch('https://api.example.com/v1/auth', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ message, signature, address: client.getAddress() })\n})\nconst { token } = await loginRes.json()\n\n// Step 3: Use protected endpoints with JWT\nconst data = await fetch('https://api.example.com/v1/data', {\n  headers: { Authorization: `Bearer ${token}` }\n})\n```\n\nThe `walletAuth()` middleware can also be used directly (without JWT) by reading the `Authorization: WalletAuth <base64>` header. The base64 payload is a JSON `WalletSignature` object:\n\n```ts\n// Client sends WalletAuth header directly\nconst sig = await client.signMessage(`Login: ${Date.now()}`)\nconst payload = btoa(JSON.stringify({\n  message: `Login: ${Date.now()}`,\n  signature: sig,\n  address: client.getAddress()\n}))\nconst res = await fetch('https://api.example.com/v1/data', {\n  headers: { Authorization: `WalletAuth ${payload}` }\n})\n```\n\n---\n\n## USDC Amount Handling\n\nUSDC has **6 decimals**. The x402 protocol uses **atomic units** (integers) internally, but this package lets you use readable decimal strings everywhere:\n\n| You write | Internal atomic value | USDC amount |\n|-----------|----------------------|-------------|\n| `'1'` | `1000000` | $1.00 |\n| `'0.01'` | `10000` | $0.01 |\n| `'0.001'` | `1000` | $0.001 |\n| `'0.000001'` | `1` | $0.000001 |\n\nConversion uses **string math only** — no floating-point arithmetic:\n\n```ts\nimport { usdcToAtomic, atomicToUsdc } from '@402md/x402'\n\nusdcToAtomic('0.01')    // '10000'\nusdcToAtomic('1')       // '1000000'\natomicToUsdc('10000')   // '0.01'\natomicToUsdc('1000000') // '1'\n```\n\n---\n\n## API Reference\n\n### Server Exports\n\n#### `paywall(config: PaywallConfig)`\n\nExpress/Connect middleware. Returns a 402 response with payment requirements if no valid `X-PAYMENT` header is present. On valid payment, sets `req.x402.payment` and calls `next()`.\n\n#### `paywallHono(config: PaywallConfig)`\n\nHono middleware. Same behavior as `paywall`, but uses Hono's context API. Sets `x402Payment` in context.\n\n#### `getPayment(c: HonoContext): VerifiedPayment | undefined`\n\nRetrieves the verified payment from a Hono context after `paywallHono` runs.\n\n#### `paywallNextjs(config: PaywallConfig, handler: NextjsHandler)`\n\nNext.js App Router wrapper. Returns a route handler that validates payment before calling your handler. Sets `req.x402.payment` on the request object.\n\n#### `createPaymentRequired(config: PaywallConfig, resourceUrl: string): PaymentRequired`\n\nBuilds a 402 response body manually. Useful if you need custom middleware logic.\n\n#### `verifyPaymentHeader(header: string, network: PaymentNetwork, facilitatorUrl?: string): Promise<VerifiedPayment>`\n\nVerifies and settles a payment header against the facilitator. Throws on failure.\n\n#### `decodePaymentHeader(header: string): Record<string, unknown>`\n\nDecodes a base64 `X-PAYMENT` header into a JSON object.\n\n#### `verifyWalletSignature(sig: WalletSignature): Promise<boolean>`\n\nVerifies a wallet signature. Detects network from address format (`0x` → EVM via EIP-191, `G` → Stellar via ed25519). Returns `false` on invalid signature (never throws).\n\n#### `walletAuth(config: WalletAuthConfig)`\n\nExpress middleware for wallet-signature authentication. Reads `Authorization: WalletAuth <base64>` header, verifies the signature, and checks access via `config.verifyAccess()`. Sets `req.x402.wallet.address` on success. Returns 401 on missing/invalid auth, 403 on access denied.\n\n#### `usdcToAtomic(usdc: string): string`\n\nConverts a USDC decimal string to atomic units (string math, no floats).\n\n#### `atomicToUsdc(atomic: string): string`\n\nConverts atomic USDC units to a decimal string.\n\n### Client Exports\n\n#### `createPaymentClient(config: PaymentClientConfig): Promise<PaymentClient>`\n\nCreates a payment client with auto-paying fetch, budget tracking, and balance queries.\n\n#### `x402Fetch(url: string, options?: X402FetchOptions): Promise<Response>`\n\nOne-shot auto-paying fetch. Creates an ephemeral client per request.\n\n#### `BudgetTracker`\n\nClass for tracking spending limits. Used internally by `createPaymentClient`, but can be used standalone:\n\n```ts\nimport { BudgetTracker } from '@402md/x402'\n\nconst budget = new BudgetTracker({ maxPerCall: '0.10', maxPerDay: '5.00' })\nbudget.check('0.05')   // ok\nbudget.record('0.05')  // records the spend\nbudget.check('4.96')   // throws: would exceed daily budget\n```\n\n#### `FacilitatorClient`\n\nLow-level client for communicating with x402 facilitator services:\n\n```ts\nimport { FacilitatorClient } from '@402md/x402'\n\n// Auto-resolve URL from network name\nconst client = new FacilitatorClient('base')\n\n// Or use a custom URL\nconst custom = new FacilitatorClient('https://my-facilitator.com')\n\nconst { isValid, payer } = await client.verify(payload, requirements)\nconst { success, transaction } = await client.settle(payload, requirements)\n```\n\n#### `getProvider(network: PaymentNetwork, config): Promise<ChainProvider>`\n\nReturns the chain-specific provider for signing payments and checking balances. Automatically selects Base or Stellar based on the network.\n\n### Constants\n\n```ts\nimport {\n  USDC_DECIMALS,           // 6\n  USDC_ADDRESSES,          // { base: '0x833...', 'base-sepolia': '0x036...', ... }\n  CHAIN_IDS,               // { base: 8453, 'base-sepolia': 84532, stellar: null, ... }\n  CAIP2_NETWORKS,          // { base: 'eip155:8453', stellar: 'stellar:pubnet', ... }\n  FACILITATOR_URLS,        // { base: 'https://facilitator.x402.org', ... }\n  X402_SCHEME,             // 'exact'\n  X402_VERSION,            // 2\n  X402_PAYMENT_HEADER,     // 'X-PAYMENT'\n  X402_DEFAULT_TIMEOUT_SECONDS, // 300\n  isEvmNetwork,            // (network) => boolean\n  isStellarNetwork         // (network) => boolean\n} from '@402md/x402'\n```\n\n### Types\n\n```ts\nimport type {\n  PaymentNetwork,       // 'base' | 'base-sepolia' | 'stellar' | 'stellar-testnet'\n  PaywallConfig,        // server middleware config\n  PaymentClientConfig,  // client config\n  BudgetConfig,         // { maxPerCall?, maxPerDay?, maxPerSession? }\n  PaymentClient,        // { pay, signMessage, getBalance, getAddress, fetch }\n  VerifiedPayment,      // { settled, txHash?, payer?, network }\n  X402FetchOptions,     // RequestInit + paymentConfig + skipPayment\n  WalletAuthConfig,     // { verifyAccess, messageFormat? }\n  WalletSignature,      // { message, signature, address }\n  ChainProvider,        // { signPayment, signMessage, getBalance, getAddress }\n\n  // Re-exported from @x402/core\n  PaymentPayload,\n  PaymentRequired,\n  PaymentRequirements,\n  SettleResponse,\n  VerifyResponse\n} from '@402md/x402'\n```\n\n---\n\n## Optional Dependencies\n\n| Dependency | Required for | What it does |\n|-----------|--------------|--------------|\n| `viem` | `base`, `base-sepolia` | EIP-712 signing, balance queries via JSON-RPC |\n| `@stellar/stellar-sdk` | `stellar`, `stellar-testnet` | Keypair management, Soroban auth entry signing, XDR encoding |\n\nBoth are loaded **dynamically** (`await import(...)`) on first use. If you only use Base, you never load the Stellar SDK and vice versa. Bundle size stays minimal.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}