yeetful 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yeetful
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ # yeetful
2
+
3
+ **Drop-in [x402](https://www.x402.org) payments for your APIs and clients.** Gate any HTTP route behind a stablecoin micropayment in a few lines — no accounts, no API keys, no webhooks. Built for [Yeetful](https://yeetful.com), MIT-licensed, works anywhere TypeScript does.
4
+
5
+ ```bash
6
+ npm install yeetful viem
7
+ ```
8
+
9
+ ```ts
10
+ // Server — gate a route for 1¢ USDC
11
+ import { withPayment } from 'yeetful/next'
12
+
13
+ export const GET = withPayment(
14
+ { price: '0.01', recipient: '0xYourAddress', network: 'base' },
15
+ async () => Response.json({ secret: 'gm' })
16
+ )
17
+ ```
18
+
19
+ ```ts
20
+ // Client — auto-pay when a server returns 402
21
+ import { createPaymentClient } from 'yeetful/client'
22
+ import { createWalletClient, http } from 'viem'
23
+ import { base } from 'viem/chains'
24
+ import { privateKeyToAccount } from 'viem/accounts'
25
+
26
+ const wallet = createWalletClient({
27
+ account: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`),
28
+ chain: base,
29
+ transport: http(),
30
+ })
31
+
32
+ const pay = createPaymentClient({ wallet })
33
+ const res = await pay('https://api.example.com/premium')
34
+ console.log(await res.json()) // → { secret: 'gm' }
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Why x402?
40
+
41
+ x402 is a reborn HTTP `402 Payment Required` — a protocol where servers quote a price, clients sign a stablecoin authorization, and a facilitator settles on-chain. No accounts, no Stripe dashboards, no webhook retries. Works on EVM chains today (USDC on Base, Optimism, Arbitrum, Polygon, Ethereum).
42
+
43
+ **You get:**
44
+ - Per-request pricing for any API — LLM calls, data feeds, premium endpoints, MCP tools.
45
+ - One-sentence paywalls for agents: an LLM with a wallet can now pay for what it uses.
46
+ - Instant settlement on L2 — no chargebacks, no holds, no 30-day payout delay.
47
+
48
+ ---
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ npm install yeetful viem
54
+ # or
55
+ pnpm add yeetful viem
56
+ # or
57
+ yarn add yeetful viem
58
+ ```
59
+
60
+ `viem` is a peer dependency so the SDK stays light and stays in sync with whatever viem version your app already uses.
61
+
62
+ ---
63
+
64
+ ## Quickstart
65
+
66
+ ### Server: gate a route
67
+
68
+ #### Next.js (App Router)
69
+
70
+ ```ts
71
+ // app/api/premium/route.ts
72
+ import { withPayment } from 'yeetful/next'
73
+
74
+ export const GET = withPayment(
75
+ {
76
+ price: '0.01', // USD
77
+ recipient: '0xYourWalletAddress', // gets paid
78
+ network: 'base', // or ['base', 'optimism']
79
+ description: 'Premium GM endpoint',
80
+ },
81
+ async (req) => {
82
+ return Response.json({ message: 'gm, thanks for the cent' })
83
+ }
84
+ )
85
+ ```
86
+
87
+ #### Express
88
+
89
+ ```ts
90
+ import express from 'express'
91
+ import { paymentRequired } from 'yeetful/express'
92
+
93
+ const app = express()
94
+
95
+ app.get(
96
+ '/premium',
97
+ paymentRequired({
98
+ price: '0.01',
99
+ recipient: '0xYourWalletAddress',
100
+ network: 'base',
101
+ }),
102
+ (req, res) => {
103
+ res.json({ message: 'gm', payer: req.x402?.payer })
104
+ }
105
+ )
106
+
107
+ app.listen(3000)
108
+ ```
109
+
110
+ #### Anywhere else (Hono, Bun, Cloudflare Workers, raw Node)
111
+
112
+ Use the runtime-agnostic `gate()` helper. Give it a standard `Request`, get back either a 402 `Response` or a `settle()` handle.
113
+
114
+ ```ts
115
+ import { gate } from 'yeetful/server'
116
+
117
+ export default {
118
+ async fetch(request: Request) {
119
+ const result = await gate(request, {
120
+ price: '0.01',
121
+ recipient: '0xYourWalletAddress',
122
+ network: 'base',
123
+ })
124
+
125
+ if (result.type === 'paymentRequired') return result.response
126
+
127
+ // …do the paid work…
128
+ const body = Response.json({ message: 'gm' })
129
+
130
+ const { header } = await result.settle()
131
+ body.headers.set('X-PAYMENT-RESPONSE', header)
132
+ return body
133
+ },
134
+ }
135
+ ```
136
+
137
+ ### Client: auto-pay
138
+
139
+ ```ts
140
+ import { createPaymentClient } from 'yeetful/client'
141
+
142
+ const pay = createPaymentClient({
143
+ wallet, // any viem WalletClient
144
+ maxAmountAtomic: 1_000_000n, // cap: 1 USDC per call
145
+ allowedNetworks: ['base'], // only pay on Base
146
+ onPaymentRequired: async (req) => {
147
+ console.log(`Pay ${req.maxAmountRequired} to ${req.payTo}?`)
148
+ return true // return false to cancel
149
+ },
150
+ })
151
+
152
+ // Use exactly like fetch.
153
+ const res = await pay('https://api.example.com/premium')
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Configuration
159
+
160
+ ### `RouteGateOptions` — server
161
+
162
+ | Option | Type | Default | Notes |
163
+ | --- | --- | --- | --- |
164
+ | `price` | `string \| number` | **required** | USD amount, e.g. `'0.01'`. Converted to USDC atomic units. |
165
+ | `recipient` | `Address` | **required** | Address that receives the payment. |
166
+ | `network` | `X402Network \| X402Network[]` | `'base'` | Networks you'll accept. Multi-chain = multi-item discovery. |
167
+ | `asset` | `Address` | USDC for network | Override to use a different ERC-20. |
168
+ | `description` | `string` | — | Shown to the paying client. |
169
+ | `maxTimeoutSeconds` | `number` | `600` | Validity window of the signed authorization. |
170
+ | `facilitator` | `FacilitatorConfig \| false` | hosted facilitator | Pass `false` to skip on-chain settlement (testing only). |
171
+
172
+ Supported networks: `base`, `base-sepolia`, `ethereum`, `optimism`, `arbitrum`, `polygon`.
173
+
174
+ ### `ClientOptions` — client
175
+
176
+ | Option | Type | Notes |
177
+ | --- | --- | --- |
178
+ | `wallet` | `WalletClient` | Any viem wallet capable of signing EIP-712 typed data. |
179
+ | `maxAmountAtomic` | `bigint` | Reject requirements above this cap — safety belt. |
180
+ | `allowedNetworks` | `X402Network[]` | Only pay on these networks. |
181
+ | `onPaymentRequired` | `(req) => boolean \| Promise<boolean>` | Approval hook; return `false` to cancel. |
182
+ | `fetch` | `typeof fetch` | Override the underlying fetch (e.g. for timeouts). |
183
+
184
+ ---
185
+
186
+ ## How it works
187
+
188
+ 1. **Client requests** a paid resource normally.
189
+ 2. **Server** responds with `402 Payment Required` and a JSON body listing acceptable requirements (network, asset, amount, recipient).
190
+ 3. **Client** picks the cheapest requirement, signs an [EIP-3009 `TransferWithAuthorization`](https://eips.ethereum.org/EIPS/eip-3009) with the user's wallet, and retries the request with an `X-PAYMENT` header (base64 JSON).
191
+ 4. **Server** hands the signed payload to a facilitator which `verify`s the signature and `settle`s the transfer on-chain.
192
+ 5. **Server** runs the handler and returns the response with an `X-PAYMENT-RESPONSE` header containing the transaction hash.
193
+
194
+ The signing is gasless for the payer — the facilitator broadcasts the transfer and picks up gas.
195
+
196
+ ---
197
+
198
+ ## Facilitators
199
+
200
+ By default the SDK uses the hosted facilitator at `https://facilitator.yeetful.com`. Override it anywhere you configure the server:
201
+
202
+ ```ts
203
+ withPayment(
204
+ {
205
+ price: '0.01',
206
+ recipient: '0xYourAddress',
207
+ facilitator: {
208
+ url: 'https://your-facilitator.example.com',
209
+ authHeader: 'Bearer your-token',
210
+ },
211
+ },
212
+ handler,
213
+ )
214
+ ```
215
+
216
+ Pass `facilitator: false` to skip verification and settlement entirely — only useful for local testing.
217
+
218
+ ---
219
+
220
+ ## Advanced
221
+
222
+ ### Accept multiple networks
223
+
224
+ ```ts
225
+ withPayment(
226
+ {
227
+ price: '0.01',
228
+ recipient: '0xYourAddress',
229
+ network: ['base', 'optimism', 'arbitrum'],
230
+ },
231
+ handler,
232
+ )
233
+ ```
234
+
235
+ Clients automatically pick the cheapest network they're configured to use.
236
+
237
+ ### Sign a payment manually
238
+
239
+ ```ts
240
+ import { signPayment } from 'yeetful/client'
241
+
242
+ const payment = await signPayment(wallet, {
243
+ scheme: 'exact',
244
+ network: 'base',
245
+ asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
246
+ maxAmountRequired: '10000', // 0.01 USDC
247
+ payTo: '0xRecipient',
248
+ })
249
+ ```
250
+
251
+ ### Use with AI agents / MCP tools
252
+
253
+ x402 is a natural fit for agent tooling — drop `withPayment` in front of any MCP tool endpoint and agents with wallets can pay per-call. This SDK is what powers paid tools on [Yeetful](https://yeetful.com).
254
+
255
+ ---
256
+
257
+ ## API reference
258
+
259
+ ### `yeetful/server`
260
+
261
+ - `gate(request, options)` — runtime-agnostic. Returns `{ type: 'paymentRequired', response }` or `{ type: 'ok', payer, settle }`.
262
+ - `Facilitator` — thin wrapper around verify/settle HTTP endpoints.
263
+ - `DEFAULT_FACILITATOR_URL` — the hosted facilitator URL.
264
+
265
+ ### `yeetful/next`
266
+
267
+ - `withPayment(options, handler)` — wraps a Next.js route handler.
268
+
269
+ ### `yeetful/express`
270
+
271
+ - `paymentRequired(options)` — returns an Express `RequestHandler`. Sets `req.x402.payer` after successful verification.
272
+
273
+ ### `yeetful/client`
274
+
275
+ - `createPaymentClient(options)` — returns a `fetch`-compatible function that handles 402s automatically.
276
+ - `signPayment(wallet, requirement)` — sign a payment payload by hand.
277
+ - `PaymentError` — thrown when the client declines to pay.
278
+
279
+ ### Helpers
280
+
281
+ - `usdcAddress(network)` — canonical USDC contract for a supported network.
282
+ - `usdToAtomic(amount, decimals?)` — safe USD → atomic-units conversion.
283
+ - `encodePayment` / `decodePayment` — base64 JSON codec for headers.
284
+
285
+ ---
286
+
287
+ ## Development
288
+
289
+ ```bash
290
+ npm install
291
+ npm run build # bundles ESM + CJS + d.ts via tsup
292
+ npm run typecheck
293
+ npm test
294
+ ```
295
+
296
+ To publish:
297
+
298
+ ```bash
299
+ npm run build
300
+ npm publish
301
+ ```
302
+
303
+ ---
304
+
305
+ ## License
306
+
307
+ MIT © Yeetful
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ // src/utils.ts
4
+ var utf8Encoder = new TextEncoder();
5
+ new TextDecoder();
6
+ function encodePayment(value) {
7
+ const bytes = utf8Encoder.encode(JSON.stringify(value));
8
+ if (typeof Buffer !== "undefined") {
9
+ return Buffer.from(bytes).toString("base64");
10
+ }
11
+ let binary = "";
12
+ for (const byte of bytes) binary += String.fromCharCode(byte);
13
+ return btoa(binary);
14
+ }
15
+ function randomNonce() {
16
+ const bytes = new Uint8Array(32);
17
+ globalThis.crypto.getRandomValues(bytes);
18
+ return "0x" + Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
19
+ }
20
+
21
+ // src/client.ts
22
+ function createPaymentClient(options) {
23
+ const baseFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
24
+ return async function payFetch(input, init = {}) {
25
+ const first = await baseFetch(input, init);
26
+ if (first.status !== 402) return first;
27
+ const requirements = await parsePaymentRequired(first);
28
+ const requirement = selectRequirement(requirements.accepts, options);
29
+ if (!requirement) {
30
+ throw new PaymentError("No acceptable payment requirement matched client constraints", requirements);
31
+ }
32
+ if (options.onPaymentRequired) {
33
+ const approved = await options.onPaymentRequired(requirement);
34
+ if (!approved) throw new PaymentError("Payment rejected by user", requirements);
35
+ }
36
+ const payment = await signPayment(options.wallet, requirement);
37
+ const headers = new Headers(init.headers);
38
+ headers.set("X-PAYMENT", encodePayment(payment));
39
+ return baseFetch(input, { ...init, headers });
40
+ };
41
+ }
42
+ var PaymentError = class extends Error {
43
+ requirements;
44
+ constructor(message, requirements) {
45
+ super(message);
46
+ this.name = "PaymentError";
47
+ this.requirements = requirements;
48
+ }
49
+ };
50
+ async function parsePaymentRequired(res) {
51
+ try {
52
+ return await res.clone().json();
53
+ } catch {
54
+ throw new PaymentError("402 response did not contain a valid x402 discovery body");
55
+ }
56
+ }
57
+ function selectRequirement(accepts, opts) {
58
+ const filtered = accepts.filter((a) => a.scheme === "exact").filter((a) => !opts.allowedNetworks || opts.allowedNetworks.includes(a.network)).filter((a) => !opts.maxAmountAtomic || BigInt(a.maxAmountRequired) <= opts.maxAmountAtomic);
59
+ return filtered.sort(
60
+ (a, b) => BigInt(a.maxAmountRequired) < BigInt(b.maxAmountRequired) ? -1 : 1
61
+ )[0];
62
+ }
63
+ async function signPayment(wallet, requirement) {
64
+ const account = wallet.account;
65
+ if (!account) throw new PaymentError("Wallet has no account attached");
66
+ const now = Math.floor(Date.now() / 1e3);
67
+ const validAfter = BigInt(now - 60);
68
+ const validBefore = BigInt(now + (requirement.maxTimeoutSeconds ?? 600));
69
+ const nonce = randomNonce();
70
+ const tokenName = requirement.extra?.name ?? "USD Coin";
71
+ const tokenVersion = requirement.extra?.version ?? "2";
72
+ const signature = await wallet.signTypedData({
73
+ account,
74
+ domain: {
75
+ name: tokenName,
76
+ version: tokenVersion,
77
+ chainId: chainIdForNetwork(requirement.network),
78
+ verifyingContract: requirement.asset
79
+ },
80
+ types: {
81
+ TransferWithAuthorization: [
82
+ { name: "from", type: "address" },
83
+ { name: "to", type: "address" },
84
+ { name: "value", type: "uint256" },
85
+ { name: "validAfter", type: "uint256" },
86
+ { name: "validBefore", type: "uint256" },
87
+ { name: "nonce", type: "bytes32" }
88
+ ]
89
+ },
90
+ primaryType: "TransferWithAuthorization",
91
+ message: {
92
+ from: account.address,
93
+ to: requirement.payTo,
94
+ value: BigInt(requirement.maxAmountRequired),
95
+ validAfter,
96
+ validBefore,
97
+ nonce
98
+ }
99
+ });
100
+ return {
101
+ x402Version: 1,
102
+ scheme: "exact",
103
+ network: requirement.network,
104
+ payload: {
105
+ signature,
106
+ authorization: {
107
+ from: account.address,
108
+ to: requirement.payTo,
109
+ value: requirement.maxAmountRequired,
110
+ validAfter: validAfter.toString(),
111
+ validBefore: validBefore.toString(),
112
+ nonce
113
+ }
114
+ }
115
+ };
116
+ }
117
+ function chainIdForNetwork(network) {
118
+ switch (network) {
119
+ case "base":
120
+ return 8453;
121
+ case "base-sepolia":
122
+ return 84532;
123
+ case "ethereum":
124
+ return 1;
125
+ case "optimism":
126
+ return 10;
127
+ case "arbitrum":
128
+ return 42161;
129
+ case "polygon":
130
+ return 137;
131
+ }
132
+ }
133
+
134
+ exports.PaymentError = PaymentError;
135
+ exports.createPaymentClient = createPaymentClient;
136
+ exports.signPayment = signPayment;
137
+ //# sourceMappingURL=client.cjs.map
138
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/client.ts"],"names":[],"mappings":"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","file":"client.cjs","sourcesContent":["import type { X402Network } from './types.js'\n\nconst USDC_BY_NETWORK: Record<X402Network, `0x${string}`> = {\n base: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'base-sepolia': '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n ethereum: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n optimism: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',\n arbitrum: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n polygon: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n}\n\nexport const USDC_DECIMALS = 6\n\n/** Returns the canonical USDC contract address for a supported network. */\nexport function usdcAddress(network: X402Network): `0x${string}` {\n return USDC_BY_NETWORK[network]\n}\n\n/**\n * Convert a human-friendly USD amount (e.g. \"0.01\") into atomic USDC units.\n * Fixed-point to avoid float drift — safer than Number math for payments.\n */\nexport function usdToAtomic(amount: string | number, decimals = USDC_DECIMALS): string {\n const str = typeof amount === 'number' ? amount.toString() : amount\n if (!/^\\d+(\\.\\d+)?$/.test(str)) {\n throw new Error(`Invalid amount: ${str}`)\n }\n const [whole, frac = ''] = str.split('.')\n const padded = frac.slice(0, decimals).padEnd(decimals, '0')\n return (BigInt(whole ?? '0') * 10n ** BigInt(decimals) + BigInt(padded || '0')).toString()\n}\n\nconst utf8Encoder = new TextEncoder()\nconst utf8Decoder = new TextDecoder()\n\n/** Base64 encode a JSON value — works in Node 18+ and browsers. */\nexport function encodePayment(value: unknown): string {\n const bytes = utf8Encoder.encode(JSON.stringify(value))\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(bytes).toString('base64')\n }\n let binary = ''\n for (const byte of bytes) binary += String.fromCharCode(byte)\n return btoa(binary)\n}\n\n/** Base64 decode a payment header value back into JSON. */\nexport function decodePayment<T = unknown>(b64: string): T {\n let bytes: Uint8Array\n if (typeof Buffer !== 'undefined') {\n bytes = new Uint8Array(Buffer.from(b64, 'base64'))\n } else {\n const binary = atob(b64)\n bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n }\n return JSON.parse(utf8Decoder.decode(bytes)) as T\n}\n\n/** Generate a random 32-byte nonce as a 0x-prefixed hex string. */\nexport function randomNonce(): `0x${string}` {\n const bytes = new Uint8Array(32)\n globalThis.crypto.getRandomValues(bytes)\n return ('0x' + Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')) as `0x${string}`\n}\n","import type { Address, Hex, WalletClient } from 'viem'\nimport type {\n PaymentPayload,\n PaymentRequiredResponse,\n PaymentRequirement,\n X402Network,\n} from './types.js'\nimport { encodePayment, randomNonce } from './utils.js'\n\nexport interface ClientOptions {\n /** A viem WalletClient able to sign EIP-712 typed data. */\n wallet: WalletClient\n /** Underlying fetch to wrap (defaults to global fetch). */\n fetch?: typeof fetch\n /**\n * Hook called before signing. Return `false` to reject the payment.\n * Useful for showing a confirmation UI to the user.\n */\n onPaymentRequired?: (requirement: PaymentRequirement) => boolean | Promise<boolean>\n /** Only allow payments up to this atomic-units cap. Safety belt. */\n maxAmountAtomic?: bigint\n /** Restrict to specific networks (defaults to all). */\n allowedNetworks?: X402Network[]\n}\n\n/**\n * Create a fetch wrapper that transparently handles x402 payments.\n *\n * On a 402 response, it picks the cheapest acceptable requirement,\n * signs an EIP-3009 authorization with the provided wallet, and retries\n * the request with an `X-PAYMENT` header.\n *\n * @example\n * ```ts\n * const pay = createPaymentClient({ wallet })\n * const res = await pay('https://api.example.com/premium')\n * ```\n */\nexport function createPaymentClient(options: ClientOptions) {\n const baseFetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n\n return async function payFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n ): Promise<Response> {\n const first = await baseFetch(input, init)\n if (first.status !== 402) return first\n\n const requirements = await parsePaymentRequired(first)\n const requirement = selectRequirement(requirements.accepts, options)\n if (!requirement) {\n throw new PaymentError('No acceptable payment requirement matched client constraints', requirements)\n }\n\n if (options.onPaymentRequired) {\n const approved = await options.onPaymentRequired(requirement)\n if (!approved) throw new PaymentError('Payment rejected by user', requirements)\n }\n\n const payment = await signPayment(options.wallet, requirement)\n const headers = new Headers(init.headers)\n headers.set('X-PAYMENT', encodePayment(payment))\n\n return baseFetch(input, { ...init, headers })\n }\n}\n\nexport class PaymentError extends Error {\n readonly requirements?: PaymentRequiredResponse\n constructor(message: string, requirements?: PaymentRequiredResponse) {\n super(message)\n this.name = 'PaymentError'\n this.requirements = requirements\n }\n}\n\nasync function parsePaymentRequired(res: Response): Promise<PaymentRequiredResponse> {\n try {\n return (await res.clone().json()) as PaymentRequiredResponse\n } catch {\n throw new PaymentError('402 response did not contain a valid x402 discovery body')\n }\n}\n\nfunction selectRequirement(\n accepts: PaymentRequirement[],\n opts: ClientOptions,\n): PaymentRequirement | undefined {\n const filtered = accepts\n .filter((a) => a.scheme === 'exact')\n .filter((a) => !opts.allowedNetworks || opts.allowedNetworks.includes(a.network))\n .filter((a) => !opts.maxAmountAtomic || BigInt(a.maxAmountRequired) <= opts.maxAmountAtomic)\n\n return filtered.sort((a, b) =>\n BigInt(a.maxAmountRequired) < BigInt(b.maxAmountRequired) ? -1 : 1,\n )[0]\n}\n\n/** Sign an EIP-3009 `TransferWithAuthorization` for the given requirement. */\nexport async function signPayment(\n wallet: WalletClient,\n requirement: PaymentRequirement,\n): Promise<PaymentPayload> {\n const account = wallet.account\n if (!account) throw new PaymentError('Wallet has no account attached')\n\n const now = Math.floor(Date.now() / 1000)\n const validAfter = BigInt(now - 60)\n const validBefore = BigInt(now + (requirement.maxTimeoutSeconds ?? 600))\n const nonce = randomNonce()\n\n const tokenName =\n (requirement.extra?.name as string | undefined) ?? 'USD Coin'\n const tokenVersion =\n (requirement.extra?.version as string | undefined) ?? '2'\n\n const signature = (await wallet.signTypedData({\n account,\n domain: {\n name: tokenName,\n version: tokenVersion,\n chainId: chainIdForNetwork(requirement.network),\n verifyingContract: requirement.asset,\n },\n types: {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n },\n primaryType: 'TransferWithAuthorization',\n message: {\n from: account.address as Address,\n to: requirement.payTo,\n value: BigInt(requirement.maxAmountRequired),\n validAfter,\n validBefore,\n nonce,\n },\n })) as Hex\n\n return {\n x402Version: 1,\n scheme: 'exact',\n network: requirement.network,\n payload: {\n signature,\n authorization: {\n from: account.address as Address,\n to: requirement.payTo,\n value: requirement.maxAmountRequired,\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n },\n },\n }\n}\n\nfunction chainIdForNetwork(network: X402Network): number {\n switch (network) {\n case 'base':\n return 8453\n case 'base-sepolia':\n return 84532\n case 'ethereum':\n return 1\n case 'optimism':\n return 10\n case 'arbitrum':\n return 42161\n case 'polygon':\n return 137\n }\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { WalletClient } from 'viem';
2
+ import { b as PaymentRequirement, X as X402Network, a as PaymentRequiredResponse, P as PaymentPayload } from './types-DzGpKiV3.cjs';
3
+
4
+ interface ClientOptions {
5
+ /** A viem WalletClient able to sign EIP-712 typed data. */
6
+ wallet: WalletClient;
7
+ /** Underlying fetch to wrap (defaults to global fetch). */
8
+ fetch?: typeof fetch;
9
+ /**
10
+ * Hook called before signing. Return `false` to reject the payment.
11
+ * Useful for showing a confirmation UI to the user.
12
+ */
13
+ onPaymentRequired?: (requirement: PaymentRequirement) => boolean | Promise<boolean>;
14
+ /** Only allow payments up to this atomic-units cap. Safety belt. */
15
+ maxAmountAtomic?: bigint;
16
+ /** Restrict to specific networks (defaults to all). */
17
+ allowedNetworks?: X402Network[];
18
+ }
19
+ /**
20
+ * Create a fetch wrapper that transparently handles x402 payments.
21
+ *
22
+ * On a 402 response, it picks the cheapest acceptable requirement,
23
+ * signs an EIP-3009 authorization with the provided wallet, and retries
24
+ * the request with an `X-PAYMENT` header.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const pay = createPaymentClient({ wallet })
29
+ * const res = await pay('https://api.example.com/premium')
30
+ * ```
31
+ */
32
+ declare function createPaymentClient(options: ClientOptions): (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
33
+ declare class PaymentError extends Error {
34
+ readonly requirements?: PaymentRequiredResponse;
35
+ constructor(message: string, requirements?: PaymentRequiredResponse);
36
+ }
37
+ /** Sign an EIP-3009 `TransferWithAuthorization` for the given requirement. */
38
+ declare function signPayment(wallet: WalletClient, requirement: PaymentRequirement): Promise<PaymentPayload>;
39
+
40
+ export { type ClientOptions, PaymentError, createPaymentClient, signPayment };
@@ -0,0 +1,40 @@
1
+ import { WalletClient } from 'viem';
2
+ import { b as PaymentRequirement, X as X402Network, a as PaymentRequiredResponse, P as PaymentPayload } from './types-DzGpKiV3.js';
3
+
4
+ interface ClientOptions {
5
+ /** A viem WalletClient able to sign EIP-712 typed data. */
6
+ wallet: WalletClient;
7
+ /** Underlying fetch to wrap (defaults to global fetch). */
8
+ fetch?: typeof fetch;
9
+ /**
10
+ * Hook called before signing. Return `false` to reject the payment.
11
+ * Useful for showing a confirmation UI to the user.
12
+ */
13
+ onPaymentRequired?: (requirement: PaymentRequirement) => boolean | Promise<boolean>;
14
+ /** Only allow payments up to this atomic-units cap. Safety belt. */
15
+ maxAmountAtomic?: bigint;
16
+ /** Restrict to specific networks (defaults to all). */
17
+ allowedNetworks?: X402Network[];
18
+ }
19
+ /**
20
+ * Create a fetch wrapper that transparently handles x402 payments.
21
+ *
22
+ * On a 402 response, it picks the cheapest acceptable requirement,
23
+ * signs an EIP-3009 authorization with the provided wallet, and retries
24
+ * the request with an `X-PAYMENT` header.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const pay = createPaymentClient({ wallet })
29
+ * const res = await pay('https://api.example.com/premium')
30
+ * ```
31
+ */
32
+ declare function createPaymentClient(options: ClientOptions): (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
33
+ declare class PaymentError extends Error {
34
+ readonly requirements?: PaymentRequiredResponse;
35
+ constructor(message: string, requirements?: PaymentRequiredResponse);
36
+ }
37
+ /** Sign an EIP-3009 `TransferWithAuthorization` for the given requirement. */
38
+ declare function signPayment(wallet: WalletClient, requirement: PaymentRequirement): Promise<PaymentPayload>;
39
+
40
+ export { type ClientOptions, PaymentError, createPaymentClient, signPayment };