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/dist/client.js ADDED
@@ -0,0 +1,134 @@
1
+ // src/utils.ts
2
+ var utf8Encoder = new TextEncoder();
3
+ new TextDecoder();
4
+ function encodePayment(value) {
5
+ const bytes = utf8Encoder.encode(JSON.stringify(value));
6
+ if (typeof Buffer !== "undefined") {
7
+ return Buffer.from(bytes).toString("base64");
8
+ }
9
+ let binary = "";
10
+ for (const byte of bytes) binary += String.fromCharCode(byte);
11
+ return btoa(binary);
12
+ }
13
+ function randomNonce() {
14
+ const bytes = new Uint8Array(32);
15
+ globalThis.crypto.getRandomValues(bytes);
16
+ return "0x" + Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
17
+ }
18
+
19
+ // src/client.ts
20
+ function createPaymentClient(options) {
21
+ const baseFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
22
+ return async function payFetch(input, init = {}) {
23
+ const first = await baseFetch(input, init);
24
+ if (first.status !== 402) return first;
25
+ const requirements = await parsePaymentRequired(first);
26
+ const requirement = selectRequirement(requirements.accepts, options);
27
+ if (!requirement) {
28
+ throw new PaymentError("No acceptable payment requirement matched client constraints", requirements);
29
+ }
30
+ if (options.onPaymentRequired) {
31
+ const approved = await options.onPaymentRequired(requirement);
32
+ if (!approved) throw new PaymentError("Payment rejected by user", requirements);
33
+ }
34
+ const payment = await signPayment(options.wallet, requirement);
35
+ const headers = new Headers(init.headers);
36
+ headers.set("X-PAYMENT", encodePayment(payment));
37
+ return baseFetch(input, { ...init, headers });
38
+ };
39
+ }
40
+ var PaymentError = class extends Error {
41
+ requirements;
42
+ constructor(message, requirements) {
43
+ super(message);
44
+ this.name = "PaymentError";
45
+ this.requirements = requirements;
46
+ }
47
+ };
48
+ async function parsePaymentRequired(res) {
49
+ try {
50
+ return await res.clone().json();
51
+ } catch {
52
+ throw new PaymentError("402 response did not contain a valid x402 discovery body");
53
+ }
54
+ }
55
+ function selectRequirement(accepts, opts) {
56
+ 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);
57
+ return filtered.sort(
58
+ (a, b) => BigInt(a.maxAmountRequired) < BigInt(b.maxAmountRequired) ? -1 : 1
59
+ )[0];
60
+ }
61
+ async function signPayment(wallet, requirement) {
62
+ const account = wallet.account;
63
+ if (!account) throw new PaymentError("Wallet has no account attached");
64
+ const now = Math.floor(Date.now() / 1e3);
65
+ const validAfter = BigInt(now - 60);
66
+ const validBefore = BigInt(now + (requirement.maxTimeoutSeconds ?? 600));
67
+ const nonce = randomNonce();
68
+ const tokenName = requirement.extra?.name ?? "USD Coin";
69
+ const tokenVersion = requirement.extra?.version ?? "2";
70
+ const signature = await wallet.signTypedData({
71
+ account,
72
+ domain: {
73
+ name: tokenName,
74
+ version: tokenVersion,
75
+ chainId: chainIdForNetwork(requirement.network),
76
+ verifyingContract: requirement.asset
77
+ },
78
+ types: {
79
+ TransferWithAuthorization: [
80
+ { name: "from", type: "address" },
81
+ { name: "to", type: "address" },
82
+ { name: "value", type: "uint256" },
83
+ { name: "validAfter", type: "uint256" },
84
+ { name: "validBefore", type: "uint256" },
85
+ { name: "nonce", type: "bytes32" }
86
+ ]
87
+ },
88
+ primaryType: "TransferWithAuthorization",
89
+ message: {
90
+ from: account.address,
91
+ to: requirement.payTo,
92
+ value: BigInt(requirement.maxAmountRequired),
93
+ validAfter,
94
+ validBefore,
95
+ nonce
96
+ }
97
+ });
98
+ return {
99
+ x402Version: 1,
100
+ scheme: "exact",
101
+ network: requirement.network,
102
+ payload: {
103
+ signature,
104
+ authorization: {
105
+ from: account.address,
106
+ to: requirement.payTo,
107
+ value: requirement.maxAmountRequired,
108
+ validAfter: validAfter.toString(),
109
+ validBefore: validBefore.toString(),
110
+ nonce
111
+ }
112
+ }
113
+ };
114
+ }
115
+ function chainIdForNetwork(network) {
116
+ switch (network) {
117
+ case "base":
118
+ return 8453;
119
+ case "base-sepolia":
120
+ return 84532;
121
+ case "ethereum":
122
+ return 1;
123
+ case "optimism":
124
+ return 10;
125
+ case "arbitrum":
126
+ return 42161;
127
+ case "polygon":
128
+ return 137;
129
+ }
130
+ }
131
+
132
+ export { PaymentError, createPaymentClient, signPayment };
133
+ //# sourceMappingURL=client.js.map
134
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/client.ts"],"names":[],"mappings":"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","file":"client.js","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,224 @@
1
+ 'use strict';
2
+
3
+ // src/facilitator.ts
4
+ var Facilitator = class {
5
+ url;
6
+ authHeader;
7
+ fetcher;
8
+ constructor(config) {
9
+ this.url = config.url.replace(/\/$/, "");
10
+ this.authHeader = config.authHeader;
11
+ this.fetcher = config.fetch ?? globalThis.fetch.bind(globalThis);
12
+ }
13
+ async verify(payment, requirement) {
14
+ return this.post("/verify", { paymentPayload: payment, paymentRequirements: requirement });
15
+ }
16
+ async settle(payment, requirement) {
17
+ return this.post("/settle", { paymentPayload: payment, paymentRequirements: requirement });
18
+ }
19
+ async post(path, body) {
20
+ const res = await this.fetcher(this.url + path, {
21
+ method: "POST",
22
+ headers: {
23
+ "content-type": "application/json",
24
+ ...this.authHeader ? { authorization: this.authHeader } : {}
25
+ },
26
+ body: JSON.stringify(body)
27
+ });
28
+ if (!res.ok) {
29
+ const text = await res.text().catch(() => "");
30
+ throw new Error(`Facilitator ${path} failed: ${res.status} ${text}`);
31
+ }
32
+ return await res.json();
33
+ }
34
+ };
35
+ var DEFAULT_FACILITATOR_URL = "https://facilitator.yeetful.com";
36
+
37
+ // src/utils.ts
38
+ var USDC_BY_NETWORK = {
39
+ base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
40
+ "base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
41
+ ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
42
+ optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
43
+ arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
44
+ polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
45
+ };
46
+ var USDC_DECIMALS = 6;
47
+ function usdcAddress(network) {
48
+ return USDC_BY_NETWORK[network];
49
+ }
50
+ function usdToAtomic(amount, decimals = USDC_DECIMALS) {
51
+ const str = typeof amount === "number" ? amount.toString() : amount;
52
+ if (!/^\d+(\.\d+)?$/.test(str)) {
53
+ throw new Error(`Invalid amount: ${str}`);
54
+ }
55
+ const [whole, frac = ""] = str.split(".");
56
+ const padded = frac.slice(0, decimals).padEnd(decimals, "0");
57
+ return (BigInt(whole ?? "0") * 10n ** BigInt(decimals) + BigInt(padded || "0")).toString();
58
+ }
59
+ var utf8Encoder = new TextEncoder();
60
+ var utf8Decoder = new TextDecoder();
61
+ function encodePayment(value) {
62
+ const bytes = utf8Encoder.encode(JSON.stringify(value));
63
+ if (typeof Buffer !== "undefined") {
64
+ return Buffer.from(bytes).toString("base64");
65
+ }
66
+ let binary = "";
67
+ for (const byte of bytes) binary += String.fromCharCode(byte);
68
+ return btoa(binary);
69
+ }
70
+ function decodePayment(b64) {
71
+ let bytes;
72
+ if (typeof Buffer !== "undefined") {
73
+ bytes = new Uint8Array(Buffer.from(b64, "base64"));
74
+ } else {
75
+ const binary = atob(b64);
76
+ bytes = new Uint8Array(binary.length);
77
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
78
+ }
79
+ return JSON.parse(utf8Decoder.decode(bytes));
80
+ }
81
+
82
+ // src/server.ts
83
+ async function gate(request, opts) {
84
+ const requirements = buildRequirements(request, opts);
85
+ const header = request.headers.get("x-payment") ?? request.headers.get("X-PAYMENT");
86
+ if (!header) {
87
+ return { type: "paymentRequired", response: paymentRequiredResponse(requirements) };
88
+ }
89
+ let payment;
90
+ try {
91
+ payment = decodePayment(header);
92
+ } catch {
93
+ return {
94
+ type: "paymentRequired",
95
+ response: paymentRequiredResponse(requirements, "Invalid X-PAYMENT header")
96
+ };
97
+ }
98
+ const matched = requirements.find(
99
+ (r) => r.network === payment.network && r.scheme === payment.scheme
100
+ );
101
+ if (!matched) {
102
+ return {
103
+ type: "paymentRequired",
104
+ response: paymentRequiredResponse(requirements, "Payment does not match any accepted requirement")
105
+ };
106
+ }
107
+ if (opts.facilitator === false) {
108
+ const payer2 = payment.payload.authorization.from;
109
+ return {
110
+ type: "ok",
111
+ payer: payer2,
112
+ settle: async () => ({
113
+ header: encodePayment({ success: true, network: matched.network }),
114
+ result: { success: true }
115
+ })
116
+ };
117
+ }
118
+ const facilitator = new Facilitator(opts.facilitator ?? { url: DEFAULT_FACILITATOR_URL });
119
+ const verified = await facilitator.verify(payment, matched);
120
+ if (!verified.isValid) {
121
+ return {
122
+ type: "paymentRequired",
123
+ response: paymentRequiredResponse(
124
+ requirements,
125
+ verified.invalidReason ?? "Payment verification failed"
126
+ )
127
+ };
128
+ }
129
+ const payer = verified.payer ?? payment.payload.authorization.from;
130
+ return {
131
+ type: "ok",
132
+ payer,
133
+ settle: async () => {
134
+ const result = await facilitator.settle(payment, matched);
135
+ return {
136
+ header: encodePayment({
137
+ success: result.success,
138
+ transaction: result.transaction,
139
+ network: result.network ?? matched.network,
140
+ errorReason: result.errorReason,
141
+ payer
142
+ }),
143
+ result
144
+ };
145
+ }
146
+ };
147
+ }
148
+ function buildRequirements(request, opts) {
149
+ const networks = Array.isArray(opts.network) ? opts.network : [opts.network ?? "base"];
150
+ const atomic = usdToAtomic(opts.price);
151
+ const resource = opts.resource ?? request.url;
152
+ return networks.map((network) => ({
153
+ scheme: "exact",
154
+ network,
155
+ asset: opts.asset ?? usdcAddress(network),
156
+ maxAmountRequired: atomic,
157
+ payTo: opts.recipient,
158
+ description: opts.description,
159
+ resource,
160
+ maxTimeoutSeconds: opts.maxTimeoutSeconds ?? 600,
161
+ extra: { name: "USD Coin", version: "2" }
162
+ }));
163
+ }
164
+ function paymentRequiredResponse(accepts, error) {
165
+ const body = { x402Version: 1, accepts, ...error ? { error } : {} };
166
+ return new Response(JSON.stringify(body), {
167
+ status: 402,
168
+ headers: { "content-type": "application/json" }
169
+ });
170
+ }
171
+
172
+ // src/express.ts
173
+ function paymentRequired(options) {
174
+ return async function middleware(req, res, next) {
175
+ try {
176
+ const result = await gate(toFetchRequest(req), options);
177
+ if (result.type === "paymentRequired") {
178
+ const body = await result.response.text();
179
+ res.status(402);
180
+ result.response.headers.forEach((v, k) => res.setHeader(k, v));
181
+ res.send(body);
182
+ return;
183
+ }
184
+ req.x402 = { payer: result.payer };
185
+ const end = res.end.bind(res);
186
+ let settled = false;
187
+ const settle = async () => {
188
+ if (settled) return;
189
+ settled = true;
190
+ try {
191
+ const { header } = await result.settle();
192
+ if (!res.headersSent) res.setHeader("X-PAYMENT-RESPONSE", header);
193
+ } catch (err) {
194
+ if (!res.headersSent) {
195
+ res.status(502).json({ error: "Payment settlement failed", detail: String(err) });
196
+ }
197
+ }
198
+ };
199
+ res.end = ((...args) => {
200
+ void settle().finally(() => end(...args));
201
+ return res;
202
+ });
203
+ next();
204
+ } catch (err) {
205
+ next(err);
206
+ }
207
+ };
208
+ }
209
+ function toFetchRequest(req) {
210
+ const protocol = req.protocol || "http";
211
+ const host = req.headers.host ?? "localhost";
212
+ const url = `${protocol}://${host}${req.originalUrl}`;
213
+ const headers = new Headers();
214
+ for (const [k, v] of Object.entries(req.headers)) {
215
+ if (Array.isArray(v)) headers.set(k, v.join(","));
216
+ else if (typeof v === "string") headers.set(k, v);
217
+ }
218
+ return new Request(url, { method: req.method, headers });
219
+ }
220
+
221
+ exports.gate = gate;
222
+ exports.paymentRequired = paymentRequired;
223
+ //# sourceMappingURL=express.cjs.map
224
+ //# sourceMappingURL=express.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/facilitator.ts","../src/utils.ts","../src/server.ts","../src/express.ts"],"names":["payer"],"mappings":"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","file":"express.cjs","sourcesContent":["import type {\n FacilitatorConfig,\n PaymentPayload,\n PaymentRequirement,\n SettleResult,\n VerifyResult,\n} from './types.js'\n\n/**\n * Thin wrapper around an x402 facilitator HTTP service.\n * The facilitator verifies signed payment authorizations and settles them on-chain.\n */\nexport class Facilitator {\n private readonly url: string\n private readonly authHeader?: string\n private readonly fetcher: typeof fetch\n\n constructor(config: FacilitatorConfig) {\n this.url = config.url.replace(/\\/$/, '')\n this.authHeader = config.authHeader\n this.fetcher = config.fetch ?? globalThis.fetch.bind(globalThis)\n }\n\n async verify(\n payment: PaymentPayload,\n requirement: PaymentRequirement,\n ): Promise<VerifyResult> {\n return this.post<VerifyResult>('/verify', { paymentPayload: payment, paymentRequirements: requirement })\n }\n\n async settle(\n payment: PaymentPayload,\n requirement: PaymentRequirement,\n ): Promise<SettleResult> {\n return this.post<SettleResult>('/settle', { paymentPayload: payment, paymentRequirements: requirement })\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await this.fetcher(this.url + path, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(this.authHeader ? { authorization: this.authHeader } : {}),\n },\n body: JSON.stringify(body),\n })\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`Facilitator ${path} failed: ${res.status} ${text}`)\n }\n return (await res.json()) as T\n }\n}\n\n/** Default hosted facilitator. Override for self-hosted deployments. */\nexport const DEFAULT_FACILITATOR_URL = 'https://facilitator.yeetful.com'\n","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 } from 'viem'\nimport { Facilitator, DEFAULT_FACILITATOR_URL } from './facilitator.js'\nimport type {\n FacilitatorConfig,\n PaymentPayload,\n PaymentRequirement,\n PaymentRequiredResponse,\n SettleResult,\n X402Network,\n} from './types.js'\nimport { decodePayment, encodePayment, usdcAddress, usdToAtomic } from './utils.js'\n\nexport interface RouteGateOptions {\n /** Price in USD, e.g. \"0.01\". Converted to USDC atomic units. */\n price: string | number\n /** Recipient address that receives the funds. */\n recipient: Address\n /** Network(s) to accept. Defaults to `['base']`. Pass an array for multi-chain. */\n network?: X402Network | X402Network[]\n /** Override the payment asset (defaults to USDC for the given network). */\n asset?: Address\n /** Human-readable description of the resource. */\n description?: string\n /** Seconds the signed authorization is valid. Default: 600. */\n maxTimeoutSeconds?: number\n /** Facilitator config, or `false` to skip verification/settlement (trust-mode). */\n facilitator?: FacilitatorConfig | false\n /**\n * Optional resource identifier (defaults to the request URL). Useful for\n * logging / receipts.\n */\n resource?: string\n}\n\n/**\n * Runtime-agnostic gate: given a Fetch-API `Request`, either returns a 402\n * `Response` or a `{ settle }` handle. After your handler runs, call `settle()`\n * to finalize the payment and get a `X-PAYMENT-RESPONSE` header to attach.\n *\n * This is the building block used by the framework adapters (`./next`, `./express`).\n */\nexport async function gate(\n request: Request,\n opts: RouteGateOptions,\n): Promise<\n | { type: 'paymentRequired'; response: Response }\n | { type: 'ok'; payer: Address; settle: () => Promise<{ header: string; result: SettleResult }> }\n> {\n const requirements = buildRequirements(request, opts)\n\n const header = request.headers.get('x-payment') ?? request.headers.get('X-PAYMENT')\n if (!header) {\n return { type: 'paymentRequired', response: paymentRequiredResponse(requirements) }\n }\n\n let payment: PaymentPayload\n try {\n payment = decodePayment<PaymentPayload>(header)\n } catch {\n return {\n type: 'paymentRequired',\n response: paymentRequiredResponse(requirements, 'Invalid X-PAYMENT header'),\n }\n }\n\n const matched = requirements.find(\n (r) => r.network === payment.network && r.scheme === payment.scheme,\n )\n if (!matched) {\n return {\n type: 'paymentRequired',\n response: paymentRequiredResponse(requirements, 'Payment does not match any accepted requirement'),\n }\n }\n\n if (opts.facilitator === false) {\n const payer = payment.payload.authorization.from as Address\n return {\n type: 'ok',\n payer,\n settle: async () => ({\n header: encodePayment({ success: true, network: matched.network }),\n result: { success: true },\n }),\n }\n }\n\n const facilitator = new Facilitator(opts.facilitator ?? { url: DEFAULT_FACILITATOR_URL })\n const verified = await facilitator.verify(payment, matched)\n if (!verified.isValid) {\n return {\n type: 'paymentRequired',\n response: paymentRequiredResponse(\n requirements,\n verified.invalidReason ?? 'Payment verification failed',\n ),\n }\n }\n\n const payer = (verified.payer ?? payment.payload.authorization.from) as Address\n\n return {\n type: 'ok',\n payer,\n settle: async () => {\n const result = await facilitator.settle(payment, matched)\n return {\n header: encodePayment({\n success: result.success,\n transaction: result.transaction,\n network: result.network ?? matched.network,\n errorReason: result.errorReason,\n payer,\n }),\n result,\n }\n },\n }\n}\n\nfunction buildRequirements(request: Request, opts: RouteGateOptions): PaymentRequirement[] {\n const networks = Array.isArray(opts.network) ? opts.network : [opts.network ?? 'base']\n const atomic = usdToAtomic(opts.price)\n const resource = opts.resource ?? request.url\n\n return networks.map((network) => ({\n scheme: 'exact',\n network,\n asset: opts.asset ?? usdcAddress(network),\n maxAmountRequired: atomic,\n payTo: opts.recipient,\n description: opts.description,\n resource,\n maxTimeoutSeconds: opts.maxTimeoutSeconds ?? 600,\n extra: { name: 'USD Coin', version: '2' },\n }))\n}\n\nfunction paymentRequiredResponse(\n accepts: PaymentRequirement[],\n error?: string,\n): Response {\n const body: PaymentRequiredResponse = { x402Version: 1, accepts, ...(error ? { error } : {}) }\n return new Response(JSON.stringify(body), {\n status: 402,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nexport { Facilitator, DEFAULT_FACILITATOR_URL } from './facilitator.js'\n","import type { Request as ExpressRequest, Response as ExpressResponse, NextFunction, RequestHandler } from 'express'\nimport { gate, type RouteGateOptions } from './server.js'\n\ndeclare module 'express-serve-static-core' {\n interface Request {\n x402?: { payer: string }\n }\n}\n\n/**\n * Express middleware that gates a route behind an x402 payment.\n *\n * @example\n * ```ts\n * import express from 'express'\n * import { paymentRequired } from 'yeetful/express'\n *\n * const app = express()\n * app.get(\n * '/premium',\n * paymentRequired({ price: '0.01', recipient: '0xYourAddress' }),\n * (req, res) => res.json({ secret: 'gm', payer: req.x402?.payer })\n * )\n * ```\n */\nexport function paymentRequired(options: RouteGateOptions): RequestHandler {\n return async function middleware(\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction,\n ) {\n try {\n const result = await gate(toFetchRequest(req), options)\n\n if (result.type === 'paymentRequired') {\n const body = await result.response.text()\n res.status(402)\n result.response.headers.forEach((v, k) => res.setHeader(k, v))\n res.send(body)\n return\n }\n\n req.x402 = { payer: result.payer }\n\n // Settle once the response is being sent. We hook into `res.end` so that\n // userland handlers don't need to know about settlement.\n const end = res.end.bind(res) as typeof res.end\n let settled = false\n const settle = async () => {\n if (settled) return\n settled = true\n try {\n const { header } = await result.settle()\n if (!res.headersSent) res.setHeader('X-PAYMENT-RESPONSE', header)\n } catch (err) {\n if (!res.headersSent) {\n res.status(502).json({ error: 'Payment settlement failed', detail: String(err) })\n }\n }\n }\n ;(res as unknown as { end: typeof res.end }).end = ((...args: unknown[]) => {\n void settle().finally(() => end(...(args as Parameters<typeof res.end>)))\n return res\n }) as typeof res.end\n\n next()\n } catch (err) {\n next(err)\n }\n }\n}\n\nfunction toFetchRequest(req: ExpressRequest): Request {\n const protocol = req.protocol || 'http'\n const host = req.headers.host ?? 'localhost'\n const url = `${protocol}://${host}${req.originalUrl}`\n const headers = new Headers()\n for (const [k, v] of Object.entries(req.headers)) {\n if (Array.isArray(v)) headers.set(k, v.join(','))\n else if (typeof v === 'string') headers.set(k, v)\n }\n return new Request(url, { method: req.method, headers })\n}\n\nexport { gate } from './server.js'\nexport type { RouteGateOptions } from './server.js'\n"]}
@@ -0,0 +1,32 @@
1
+ import { RequestHandler } from 'express';
2
+ import { RouteGateOptions } from './server.cjs';
3
+ export { gate } from './server.cjs';
4
+ import 'viem';
5
+ import './types-DzGpKiV3.cjs';
6
+
7
+ declare module 'express-serve-static-core' {
8
+ interface Request {
9
+ x402?: {
10
+ payer: string;
11
+ };
12
+ }
13
+ }
14
+ /**
15
+ * Express middleware that gates a route behind an x402 payment.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import express from 'express'
20
+ * import { paymentRequired } from 'yeetful/express'
21
+ *
22
+ * const app = express()
23
+ * app.get(
24
+ * '/premium',
25
+ * paymentRequired({ price: '0.01', recipient: '0xYourAddress' }),
26
+ * (req, res) => res.json({ secret: 'gm', payer: req.x402?.payer })
27
+ * )
28
+ * ```
29
+ */
30
+ declare function paymentRequired(options: RouteGateOptions): RequestHandler;
31
+
32
+ export { RouteGateOptions, paymentRequired };
@@ -0,0 +1,32 @@
1
+ import { RequestHandler } from 'express';
2
+ import { RouteGateOptions } from './server.js';
3
+ export { gate } from './server.js';
4
+ import 'viem';
5
+ import './types-DzGpKiV3.js';
6
+
7
+ declare module 'express-serve-static-core' {
8
+ interface Request {
9
+ x402?: {
10
+ payer: string;
11
+ };
12
+ }
13
+ }
14
+ /**
15
+ * Express middleware that gates a route behind an x402 payment.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import express from 'express'
20
+ * import { paymentRequired } from 'yeetful/express'
21
+ *
22
+ * const app = express()
23
+ * app.get(
24
+ * '/premium',
25
+ * paymentRequired({ price: '0.01', recipient: '0xYourAddress' }),
26
+ * (req, res) => res.json({ secret: 'gm', payer: req.x402?.payer })
27
+ * )
28
+ * ```
29
+ */
30
+ declare function paymentRequired(options: RouteGateOptions): RequestHandler;
31
+
32
+ export { RouteGateOptions, paymentRequired };