{"_id":"@ag402/fetch","_rev":"3-369a2b9a8e9102d3ccf02303152d7124","name":"@ag402/fetch","dist-tags":{"latest":"0.1.20"},"versions":{"0.1.0":{"name":"@ag402/fetch","version":"0.1.0","keywords":["x402","payment","http","solana","usdc","ai-agent","ag402","fetch","402"],"author":{"url":"https://github.com/agentpayments/ag402","name":"ag402"},"license":"MIT","_id":"@ag402/fetch@0.1.0","maintainers":[{"name":"aethercore-dev","email":"aethercore.dev@proton.me"}],"homepage":"https://github.com/agentpayments/ag402/tree/main/sdk/typescript#readme","bugs":{"url":"https://github.com/agentpayments/ag402/issues"},"dist":{"shasum":"0470a1feec591c01f42706ae96af1f89c599b633","tarball":"https://registry.npmjs.org/@ag402/fetch/-/fetch-0.1.0.tgz","fileCount":8,"integrity":"sha512-bkEMMz6F/xvINTXlOznAr4wPADPz+MViBwl+6i+OKd7o8Yr/UCR59AoOe0YiEM8x8Qf8JbpD3qeqbor+jsBwQg==","signatures":[{"sig":"MEUCICrw4BnUV5X9qsFxrnDDeUJNOsWCRj/wJ7XdhKWyTGpoAiEAlOvFwt0WCSh9e+gKVxXUF6Pn9yVYY3fQh3b6vM0YhHE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":62475},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"fe0b4e1d5bab8ea5f3e995e1d0507a7117f29155","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","test:watch":"vitest","prepublishOnly":"npm run lint && npm test && npm run build"},"_npmUser":{"name":"aethercore-dev","email":"aethercore.dev@proton.me"},"repository":{"url":"git+https://github.com/agentpayments/ag402.git","type":"git","directory":"sdk/typescript"},"_npmVersion":"10.8.2","description":"TypeScript SDK for x402 auto-payment — wraps fetch() to handle HTTP 402 Payment Required","directories":{},"_nodeVersion":"20.20.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^25.4.0"},"_npmOperationalInternal":{"tmp":"tmp/fetch_0.1.0_1773214316146_0.6599144782628639","host":"s3://npm-registry-packages-npm-production"}},"0.1.19":{"name":"@ag402/fetch","version":"0.1.19","keywords":["x402","payment","http","solana","usdc","ai-agent","ag402","fetch","402"],"author":{"url":"https://github.com/agentpayments/ag402","name":"ag402"},"license":"MIT","_id":"@ag402/fetch@0.1.19","maintainers":[{"name":"aethercore-dev","email":"aethercore.dev@proton.me"}],"homepage":"https://github.com/agentpayments/ag402/tree/main/sdk/typescript#readme","bugs":{"url":"https://github.com/agentpayments/ag402/issues"},"dist":{"shasum":"351d8fb22cf979712eead83ff7fcd8a26e88a699","tarball":"https://registry.npmjs.org/@ag402/fetch/-/fetch-0.1.19.tgz","fileCount":8,"integrity":"sha512-ZzJrpKd6Y4jCMEDoqMKqdqq2P2LZubZUb3ZBTDifi6AmYQBJzl9ieiKlKus25Ii8o+3U78MvUrRMzYoU5npj9Q==","signatures":[{"sig":"MEUCIF7vZJmz3cKHWWtTI9Ewcucq35a4CAwzCXyhY5XxlIb5AiEAh6dlA+NvcO5Y90iTXA7DIZfxfD2v9k09W85kmvQGyKs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":62854},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"09676561fbce33845b351d6862bcb4725e0fc831","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","test:watch":"vitest","prepublishOnly":"npm run lint && npm test && npm run build"},"_npmUser":{"name":"aethercore-dev","email":"aethercore.dev@proton.me"},"repository":{"url":"git+https://github.com/agentpayments/ag402.git","type":"git","directory":"sdk/typescript"},"_npmVersion":"10.8.2","description":"TypeScript SDK for x402 auto-payment — wraps fetch() to handle HTTP 402 Payment Required","directories":{},"_nodeVersion":"20.20.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^25.4.0"},"_npmOperationalInternal":{"tmp":"tmp/fetch_0.1.19_1773479861133_0.14886497763746998","host":"s3://npm-registry-packages-npm-production"}},"0.1.20":{"name":"@ag402/fetch","version":"0.1.20","description":"TypeScript SDK for x402 auto-payment — wraps fetch() to handle HTTP 402 Payment Required","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"tsup src/index.ts --format esm,cjs --dts --clean","test":"vitest run","test:watch":"vitest","lint":"tsc --noEmit","prepublishOnly":"npm run lint && npm test && npm run build"},"keywords":["x402","payment","http","solana","usdc","ai-agent","ag402","fetch","402"],"author":{"name":"ag402","url":"https://github.com/agentpayments/ag402"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/agentpayments/ag402.git","directory":"sdk/typescript"},"homepage":"https://github.com/agentpayments/ag402/tree/main/sdk/typescript#readme","bugs":{"url":"https://github.com/agentpayments/ag402/issues"},"engines":{"node":">=18"},"devDependencies":{"@types/node":"^25.4.0","tsup":"^8.3.0","typescript":"^5.7.0","vitest":"^2.1.0"},"_id":"@ag402/fetch@0.1.20","gitHead":"7c300d88445e9aebb668a9a5c52f3c738af1dc3d","_nodeVersion":"20.20.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-uIKOKePeRN7GEx0JPdVeOWboZbBiL8/jyVM3zjkDddQHE7xIq6yphil+3Z00sY/mfdeeuVm5nu+HERldKwOy/A==","shasum":"56622f35f8ef232b9620398f43e0d546afaffc11","tarball":"https://registry.npmjs.org/@ag402/fetch/-/fetch-0.1.20.tgz","fileCount":8,"unpackedSize":62854,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBchjDAYSeVgZs7lXNjqsEG8hXX4LSPTVR7TEDkeWKRjAiEA5ltSWxTlyXlXdbiQSGyTHFCmLaEv2d25mfFZosWUU+E="}]},"_npmUser":{"name":"aethercore-dev","email":"aethercore.dev@proton.me"},"directories":{},"maintainers":[{"name":"aethercore-dev","email":"aethercore.dev@proton.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/fetch_0.1.20_1773574730037_0.4887171988971286"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-11T07:31:56.009Z","modified":"2026-03-15T11:38:50.321Z","0.1.0":"2026-03-11T07:31:56.315Z","0.1.19":"2026-03-14T09:17:41.267Z","0.1.20":"2026-03-15T11:38:50.182Z"},"bugs":{"url":"https://github.com/agentpayments/ag402/issues"},"author":{"name":"ag402","url":"https://github.com/agentpayments/ag402"},"license":"MIT","homepage":"https://github.com/agentpayments/ag402/tree/main/sdk/typescript#readme","keywords":["x402","payment","http","solana","usdc","ai-agent","ag402","fetch","402"],"repository":{"type":"git","url":"git+https://github.com/agentpayments/ag402.git","directory":"sdk/typescript"},"description":"TypeScript SDK for x402 auto-payment — wraps fetch() to handle HTTP 402 Payment Required","maintainers":[{"name":"aethercore-dev","email":"aethercore.dev@proton.me"}],"readme":"# @ag402/fetch\n\n[![npm version](https://img.shields.io/npm/v/@ag402/fetch)](https://www.npmjs.com/package/@ag402/fetch)\n[![license](https://img.shields.io/npm/l/@ag402/fetch)](./LICENSE)\n[![CI](https://img.shields.io/badge/build-passing-brightgreen)](./src/__tests__)\n[![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](./package.json)\n[![Node.js](https://img.shields.io/node/v/@ag402/fetch)](./package.json)\n\nTypeScript SDK for **x402 auto-payment** — wraps the native `fetch()` to automatically handle HTTP 402 Payment Required responses.\n\n**Zero runtime dependencies. Node.js 18+. ESM + CJS.**\n\n---\n\n## The Problem\n\nWhen your AI agent hits a paid API, it gets back a `402 Payment Required` response. Without this library, handling it requires:\n\n```typescript\n// Without @ag402/fetch — you write this every time:\nconst res = await fetch(\"https://paid-api.example.com/data\");\n\nif (res.status === 402) {\n  const wwwAuth = res.headers.get(\"www-authenticate\");\n  const challenge = parseChallenge(wwwAuth);           // parse the header\n  if (challenge.chain !== \"solana\") throw ...;         // validate chain\n  if (parseFloat(challenge.amount) > limit) throw ...; // validate amount\n  const txId = await wallet.deduct(...);               // deduct from wallet\n  await provider.pay(challenge);                       // broadcast on-chain\n  const retryRes = await fetch(\"https://paid-api.example.com/data\", {\n    headers: { Authorization: buildProof(txHash) },    // retry with proof\n  });\n  // handle retryRes...\n}\n```\n\n## The Solution\n\n```typescript\nimport { createX402Fetch, InMemoryWallet } from \"@ag402/fetch\";\n\nconst wallet = new InMemoryWallet(100); // start with $100 test USDC\nconst apiFetch = createX402Fetch({ wallet });\n// ⚠️  No `provider` supplied → uses MockPaymentProvider (fake tx hashes, test only).\n//     For production, pass a real `provider`. See \"Custom Payment Provider\" below.\n\nconst res = await apiFetch(\"https://paid-api.example.com/data\");\n// That's it. Payment happened automatically.\nconsole.log(res.x402.paymentMade);  // true\nconsole.log(res.x402.amountPaid);   // 0.05\nconsole.log(res.x402.txHash);       // \"mock_tx_...\"\n```\n\n---\n\n## Install\n\n```bash\nnpm install @ag402/fetch\n```\n\n## How It Works\n\n1. Forward the original request as-is\n2. If the server returns `402` with `WWW-Authenticate: x402 ...`:\n   - Validate the challenge (chain, token, amount, budget limits)\n   - Deduct from wallet\n   - Call the payment provider → receive `tx_hash`\n   - Retry with `Authorization: x402 tx_hash=\"...\" chain=\"...\" payer_address=\"...\" request_id=\"...\"`\n3. Return the final response with `.x402` metadata attached\n\nNon-x402 responses (including plain `402` without the x402 header) are passed through unchanged.\n\n---\n\n## Response Metadata\n\nEvery response has a `.x402` property:\n\n```typescript\ninterface X402FetchMeta {\n  paymentMade: boolean;  // true if payment was submitted on-chain (even if retry failed)\n  txHash: string;        // on-chain tx hash, or \"mock_tx_...\" in test mode; \"\" if no payment\n  amountPaid: number;    // USD amount paid; 0 if no payment\n  blocked: boolean;      // true if rejected by a local budget rule before attempting payment\n  error?: string;        // set when something went wrong — including when paymentMade=true\n}\n```\n\n> **Important:** `error` can be set even when `paymentMade=true` — this means the on-chain payment\n> was sent but the service's retry request failed. The funds are gone. Check your transaction log.\n\n### Error Handling Pattern\n\n```typescript\nconst res = await apiFetch(\"https://api.example.com/data\");\n\nif (res.x402.blocked) {\n  // Budget rule rejected the payment — no funds were spent\n  console.error(\"Payment blocked:\", res.x402.error);\n} else if (!res.ok) {\n  if (res.x402.paymentMade) {\n    // Payment sent on-chain but service failed — contact the provider\n    console.error(\"Paid but service failed:\", res.x402.txHash, res.x402.error);\n  } else {\n    // Either non-x402 error, or payment failed before broadcast (funds not spent)\n    console.error(\"Request failed:\", res.x402.error ?? res.status);\n  }\n} else {\n  // Success\n  const data = await res.json();\n}\n```\n\n---\n\n## Configuration\n\n```typescript\nconst apiFetch = createX402Fetch({\n  wallet,\n  config: {\n    maxAmountPerCall: 1.00,     // Reject any challenge > $1 per call (default: $1.00)\n    maxTotalSpend: 50.00,       // Stop paying after $50 total (default: Infinity)\n    acceptedChains: [\"solana\"], // Only pay on Solana (default: [\"solana\"])\n    acceptedTokens: [\"USDC\"],   // Only accept USDC (default: [\"USDC\"])\n    debug: true,                // Log payment activity to console (default: false)\n  },\n  paymentTimeoutMs: 30_000,     // Timeout for provider.pay() — rollback on timeout (default: 30s)\n});\n\n// Track spend across all calls on this instance\nconsole.log(apiFetch.getTotalSpent()); // e.g. 1.35\n```\n\nConfig and construction options are validated at construction time — invalid values (negative limits, empty arrays, NaN, zero/negative `paymentTimeoutMs`) throw immediately before any request is made.\n\n---\n\n## Wallet Interface\n\n`createX402Fetch` accepts any object implementing the `Wallet` interface, not just `InMemoryWallet`:\n\n```typescript\nimport type { Wallet } from \"@ag402/fetch\";\n\nconst myWallet: Wallet = {\n  getBalance(): number { /* ... */ },\n  deduct(amount: number, toAddress: string): string { /* return tx id */ },\n  rollback(txId: string): boolean { /* undo deduction */ },\n};\n\nconst apiFetch = createX402Fetch({ wallet: myWallet });\n```\n\n`InMemoryWallet` stores amounts as integer micro-units internally ($0.000001 precision) to avoid IEEE 754 float drift. It rejects `NaN` and `Infinity` as initial balance. It resets on process restart — use a custom `Wallet` backed by SQLite for persistence.\n\n`InMemoryWallet` also exposes a `deposit(amount: number): void` method for adding funds after construction (useful in tests and REPL sessions).\n\n### Concurrency Warning\n\n> ⚠️ **`createX402Fetch` is NOT safe for concurrent calls on the same instance.**\n\nTwo simultaneous calls (e.g. `Promise.all`) may both pass the budget check before either deducts, causing over-spend:\n\n```typescript\n// UNSAFE — may over-spend:\nconst [r1, r2] = await Promise.all([apiFetch(urlA), apiFetch(urlB)]);\n\n// SAFE — sequential:\nconst r1 = await apiFetch(urlA);\nconst r2 = await apiFetch(urlB);\n```\n\nIf you need concurrent calls, create a separate `createX402Fetch` instance per call chain, or guard with an external mutex.\n\n---\n\n## Custom Payment Provider\n\nBy default, `MockPaymentProvider` is used — it returns a fake `mock_tx_...` hash and **never touches a real blockchain**. A `console.warn` is emitted when `MockPaymentProvider` is auto-selected outside of test environments (`NODE_ENV=test`, `VITEST`, `JEST_WORKER_ID`).\n\nFor production, implement `PaymentProvider`:\n\n```typescript\nimport type { PaymentProvider, X402PaymentChallenge } from \"@ag402/fetch\";\n\nconst myProvider: PaymentProvider = {\n  async pay(challenge: X402PaymentChallenge, requestId: string): Promise<string> {\n    // Broadcast real USDC transfer on-chain; requestId is the idempotency key\n    return \"real_on_chain_tx_hash\";\n  },\n  getAddress(): string {\n    return \"YourWalletPublicKey\";\n  },\n};\n\nconst apiFetch = createX402Fetch({ wallet, provider: myProvider });\n```\n\n> **Use `@ag402/solana` for real on-chain payments:**\n>\n> ```bash\n> npm install @ag402/fetch @ag402/solana\n> ```\n> ```typescript\n> import { SolanaPaymentProvider, fromEnv } from \"@ag402/solana\";\n>\n> // Reads SOLANA_PRIVATE_KEY from environment\n> const provider = fromEnv();\n> const apiFetch = createX402Fetch({ wallet, provider });\n> ```\n>\n> See the [`@ag402/solana` README](https://www.npmjs.com/package/@ag402/solana) for full setup, mainnet config, and confirmationLevel options.\n\n---\n\n## Protocol Utilities\n\nAll x402 header parsing/building is exported independently:\n\n```typescript\nimport {\n  parseWwwAuthenticate,   // string → X402PaymentChallenge | null\n  buildWwwAuthenticate,   // X402PaymentChallenge → string\n  parseAuthorization,     // string → X402PaymentProof | null\n  buildAuthorization,     // X402PaymentProof → string\n  parseAmount,            // X402PaymentChallenge → number (validates: positive, decimal-only)\n  descriptorToChallenge,  // X402ServiceDescriptor → X402PaymentChallenge\n} from \"@ag402/fetch\";\n```\n\n`parseAmount` strictly rejects hex (`0x10`), scientific notation (`1e5`), multi-token strings (`\"1 extra\"`), and non-positive values — safe to use on untrusted server input.\n\n`buildAuthorization` and `buildWwwAuthenticate` throw on CR/LF/double-quote in field values to prevent HTTP header injection.\n\n`parseWwwAuthenticate` rejects headers larger than 8 KB to prevent regex exhaustion from malicious servers.\n\n---\n\n## Examples\n\n| File | Description |\n|------|-------------|\n| [`examples/basic-usage.ts`](./examples/basic-usage.ts) | Auto-pay a 402-protected endpoint with `MockPaymentProvider` |\n| [`examples/custom-provider.ts`](./examples/custom-provider.ts) | Implement a real `PaymentProvider` for on-chain payments |\n| [`examples/server-side-challenge.ts`](./examples/server-side-challenge.ts) | Emit a 402 challenge from a Node.js HTTP server (seller side) |\n\nFor a real Solana on-chain example, see the [`@ag402/solana` README](https://www.npmjs.com/package/@ag402/solana).\n\nRun any example with:\n```bash\nnpx tsx examples/basic-usage.ts\n```\n\n---\n\n## Compatibility\n\n| Runtime | Minimum version | Notes |\n|---------|----------------|-------|\n| Node.js | 18 | Native `fetch` + `crypto.randomUUID()` |\n| Bun | 1.0 | Fully supported; frozen Response handled via Proxy |\n| Deno | 1.28 | Native `fetch` available |\n| Browser | — | Not officially supported (no Solana wallet integration yet) |\n\n---\n\n## Limitations\n\n- **No wallet persistence** — `InMemoryWallet` resets on restart; implement `Wallet` for persistence\n- **No concurrent call safety** — do not use `Promise.all` on the same instance; see [Concurrency Warning](#concurrency-warning)\n- **No TypeScript gateway/seller side** — buyer only in this package\n\n## Deferred / Roadmap\n\n- SQLite-backed persistent wallet (`SqliteWallet`)\n- TypeScript gateway/seller side\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md"}