yeetful 0.2.0 → 0.3.1
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/CHANGELOG.md +32 -0
- package/README.md +16 -0
- package/dist/agent.cjs +101 -25
- package/dist/agent.cjs.map +1 -1
- package/dist/agent.d.cts +19 -1
- package/dist/agent.d.ts +19 -1
- package/dist/agent.js +101 -25
- package/dist/agent.js.map +1 -1
- package/dist/client.cjs +89 -22
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +17 -4
- package/dist/client.d.ts +17 -4
- package/dist/client.js +88 -23
- package/dist/client.js.map +1 -1
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.cjs +109 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +108 -24
- package/dist/index.js.map +1 -1
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-DzGpKiV3.d.cts → types-DgG0iIOB.d.cts} +34 -10
- package/dist/{types-DzGpKiV3.d.ts → types-DgG0iIOB.d.ts} +34 -10
- package/package.json +1 -1
package/dist/client.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WalletClient } from 'viem';
|
|
2
|
-
import {
|
|
2
|
+
import { c as PaymentRequirement, X as X402Network, b as PaymentRequiredResponse, E as ExactEvmPayload, a as PaymentPayload } from './types-DgG0iIOB.cjs';
|
|
3
3
|
|
|
4
4
|
interface ClientOptions {
|
|
5
5
|
/** A viem WalletClient able to sign EIP-712 typed data. */
|
|
@@ -17,11 +17,13 @@ interface ClientOptions {
|
|
|
17
17
|
allowedNetworks?: X402Network[];
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Create a fetch wrapper that transparently handles x402 payments
|
|
20
|
+
* Create a fetch wrapper that transparently handles x402 payments —
|
|
21
|
+
* protocol v1 ("base" networks, `maxAmountRequired`, `X-PAYMENT` header)
|
|
22
|
+
* and v2 (CAIP-2 networks, `amount`, `PAYMENT-SIGNATURE` envelope) alike.
|
|
21
23
|
*
|
|
22
24
|
* On a 402 response, it picks the cheapest acceptable requirement,
|
|
23
25
|
* signs an EIP-3009 authorization with the provided wallet, and retries
|
|
24
|
-
* the request with
|
|
26
|
+
* the request with the version-appropriate payment header.
|
|
25
27
|
*
|
|
26
28
|
* @example
|
|
27
29
|
* ```ts
|
|
@@ -34,7 +36,18 @@ declare class PaymentError extends Error {
|
|
|
34
36
|
readonly requirements?: PaymentRequiredResponse;
|
|
35
37
|
constructor(message: string, requirements?: PaymentRequiredResponse);
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Atomic units owed for a requirement, version-agnostic: x402 v2 prices in
|
|
41
|
+
* `amount`, v1 in `maxAmountRequired`. Returns null when absent or
|
|
42
|
+
* unparseable (never throws — selection must be able to skip bad entries).
|
|
43
|
+
*/
|
|
44
|
+
declare function requirementAtomicAmount(req: PaymentRequirement): bigint | null;
|
|
37
45
|
/** Sign an EIP-3009 `TransferWithAuthorization` for the given requirement. */
|
|
46
|
+
declare function signExactAuthorization(wallet: WalletClient, requirement: PaymentRequirement): Promise<ExactEvmPayload>;
|
|
47
|
+
/**
|
|
48
|
+
* Sign a requirement into the x402 **v1** `X-PAYMENT` payload shape.
|
|
49
|
+
* Kept for back-compat; `createPaymentClient` is version-aware internally.
|
|
50
|
+
*/
|
|
38
51
|
declare function signPayment(wallet: WalletClient, requirement: PaymentRequirement): Promise<PaymentPayload>;
|
|
39
52
|
|
|
40
|
-
export { type ClientOptions, PaymentError, createPaymentClient, signPayment };
|
|
53
|
+
export { type ClientOptions, PaymentError, createPaymentClient, requirementAtomicAmount, signExactAuthorization, signPayment };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WalletClient } from 'viem';
|
|
2
|
-
import {
|
|
2
|
+
import { c as PaymentRequirement, X as X402Network, b as PaymentRequiredResponse, E as ExactEvmPayload, a as PaymentPayload } from './types-DgG0iIOB.js';
|
|
3
3
|
|
|
4
4
|
interface ClientOptions {
|
|
5
5
|
/** A viem WalletClient able to sign EIP-712 typed data. */
|
|
@@ -17,11 +17,13 @@ interface ClientOptions {
|
|
|
17
17
|
allowedNetworks?: X402Network[];
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Create a fetch wrapper that transparently handles x402 payments
|
|
20
|
+
* Create a fetch wrapper that transparently handles x402 payments —
|
|
21
|
+
* protocol v1 ("base" networks, `maxAmountRequired`, `X-PAYMENT` header)
|
|
22
|
+
* and v2 (CAIP-2 networks, `amount`, `PAYMENT-SIGNATURE` envelope) alike.
|
|
21
23
|
*
|
|
22
24
|
* On a 402 response, it picks the cheapest acceptable requirement,
|
|
23
25
|
* signs an EIP-3009 authorization with the provided wallet, and retries
|
|
24
|
-
* the request with
|
|
26
|
+
* the request with the version-appropriate payment header.
|
|
25
27
|
*
|
|
26
28
|
* @example
|
|
27
29
|
* ```ts
|
|
@@ -34,7 +36,18 @@ declare class PaymentError extends Error {
|
|
|
34
36
|
readonly requirements?: PaymentRequiredResponse;
|
|
35
37
|
constructor(message: string, requirements?: PaymentRequiredResponse);
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Atomic units owed for a requirement, version-agnostic: x402 v2 prices in
|
|
41
|
+
* `amount`, v1 in `maxAmountRequired`. Returns null when absent or
|
|
42
|
+
* unparseable (never throws — selection must be able to skip bad entries).
|
|
43
|
+
*/
|
|
44
|
+
declare function requirementAtomicAmount(req: PaymentRequirement): bigint | null;
|
|
37
45
|
/** Sign an EIP-3009 `TransferWithAuthorization` for the given requirement. */
|
|
46
|
+
declare function signExactAuthorization(wallet: WalletClient, requirement: PaymentRequirement): Promise<ExactEvmPayload>;
|
|
47
|
+
/**
|
|
48
|
+
* Sign a requirement into the x402 **v1** `X-PAYMENT` payload shape.
|
|
49
|
+
* Kept for back-compat; `createPaymentClient` is version-aware internally.
|
|
50
|
+
*/
|
|
38
51
|
declare function signPayment(wallet: WalletClient, requirement: PaymentRequirement): Promise<PaymentPayload>;
|
|
39
52
|
|
|
40
|
-
export { type ClientOptions, PaymentError, createPaymentClient, signPayment };
|
|
53
|
+
export { type ClientOptions, PaymentError, createPaymentClient, requirementAtomicAmount, signExactAuthorization, signPayment };
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/utils.ts
|
|
2
2
|
var utf8Encoder = new TextEncoder();
|
|
3
|
-
new TextDecoder();
|
|
3
|
+
var utf8Decoder = new TextDecoder();
|
|
4
4
|
function encodePayment(value) {
|
|
5
5
|
const bytes = utf8Encoder.encode(JSON.stringify(value));
|
|
6
6
|
if (typeof Buffer !== "undefined") {
|
|
@@ -10,6 +10,17 @@ function encodePayment(value) {
|
|
|
10
10
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
11
11
|
return btoa(binary);
|
|
12
12
|
}
|
|
13
|
+
function decodePayment(b64) {
|
|
14
|
+
let bytes;
|
|
15
|
+
if (typeof Buffer !== "undefined") {
|
|
16
|
+
bytes = new Uint8Array(Buffer.from(b64, "base64"));
|
|
17
|
+
} else {
|
|
18
|
+
const binary = atob(b64);
|
|
19
|
+
bytes = new Uint8Array(binary.length);
|
|
20
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
21
|
+
}
|
|
22
|
+
return JSON.parse(utf8Decoder.decode(bytes));
|
|
23
|
+
}
|
|
13
24
|
function randomNonce() {
|
|
14
25
|
const bytes = new Uint8Array(32);
|
|
15
26
|
globalThis.crypto.getRandomValues(bytes);
|
|
@@ -23,7 +34,7 @@ function createPaymentClient(options) {
|
|
|
23
34
|
const first = await baseFetch(input, init);
|
|
24
35
|
if (first.status !== 402) return first;
|
|
25
36
|
const requirements = await parsePaymentRequired(first);
|
|
26
|
-
const requirement = selectRequirement(requirements.accepts, options);
|
|
37
|
+
const requirement = selectRequirement(requirements.accepts ?? [], options);
|
|
27
38
|
if (!requirement) {
|
|
28
39
|
throw new PaymentError("No acceptable payment requirement matched client constraints", requirements);
|
|
29
40
|
}
|
|
@@ -31,9 +42,10 @@ function createPaymentClient(options) {
|
|
|
31
42
|
const approved = await options.onPaymentRequired(requirement);
|
|
32
43
|
if (!approved) throw new PaymentError("Payment rejected by user", requirements);
|
|
33
44
|
}
|
|
34
|
-
const
|
|
45
|
+
const payload = await signExactAuthorization(options.wallet, requirement);
|
|
46
|
+
const header = buildPaymentHeader(requirements, requirement, payload);
|
|
35
47
|
const headers = new Headers(init.headers);
|
|
36
|
-
headers.set(
|
|
48
|
+
headers.set(header.name, header.value);
|
|
37
49
|
return baseFetch(input, { ...init, headers });
|
|
38
50
|
};
|
|
39
51
|
}
|
|
@@ -45,22 +57,66 @@ var PaymentError = class extends Error {
|
|
|
45
57
|
this.requirements = requirements;
|
|
46
58
|
}
|
|
47
59
|
};
|
|
60
|
+
function requirementAtomicAmount(req) {
|
|
61
|
+
const raw = req.amount ?? req.maxAmountRequired;
|
|
62
|
+
if (raw == null) return null;
|
|
63
|
+
try {
|
|
64
|
+
return BigInt(raw);
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
48
69
|
async function parsePaymentRequired(res) {
|
|
49
70
|
try {
|
|
50
71
|
return await res.clone().json();
|
|
51
72
|
} catch {
|
|
73
|
+
const header = res.headers.get("payment-required");
|
|
74
|
+
if (header) {
|
|
75
|
+
try {
|
|
76
|
+
return decodePayment(header);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
52
80
|
throw new PaymentError("402 response did not contain a valid x402 discovery body");
|
|
53
81
|
}
|
|
54
82
|
}
|
|
55
83
|
function selectRequirement(accepts, opts) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)[0];
|
|
84
|
+
const priced = accepts.filter((a) => a.scheme === "exact").filter((a) => chainIdForNetwork(a.network) !== null).filter(
|
|
85
|
+
(a) => !opts.allowedNetworks || opts.allowedNetworks.some((n) => chainIdForNetwork(n) === chainIdForNetwork(a.network))
|
|
86
|
+
).map((a) => ({ req: a, amount: requirementAtomicAmount(a) })).filter((e) => e.amount !== null).filter((e) => !opts.maxAmountAtomic || e.amount <= opts.maxAmountAtomic);
|
|
87
|
+
return priced.sort((a, b) => a.amount < b.amount ? -1 : 1)[0]?.req;
|
|
60
88
|
}
|
|
61
|
-
|
|
89
|
+
function buildPaymentHeader(challenge, requirement, payload) {
|
|
90
|
+
const version = challenge.x402Version ?? 1;
|
|
91
|
+
if (version >= 2) {
|
|
92
|
+
const envelope = {
|
|
93
|
+
x402Version: version,
|
|
94
|
+
resource: challenge.resource,
|
|
95
|
+
accepted: requirement,
|
|
96
|
+
payload,
|
|
97
|
+
extensions: challenge.extensions ?? {}
|
|
98
|
+
};
|
|
99
|
+
return { name: "PAYMENT-SIGNATURE", value: encodePayment(envelope) };
|
|
100
|
+
}
|
|
101
|
+
const v1 = {
|
|
102
|
+
x402Version: 1,
|
|
103
|
+
scheme: "exact",
|
|
104
|
+
network: requirement.network,
|
|
105
|
+
payload
|
|
106
|
+
};
|
|
107
|
+
return { name: "X-PAYMENT", value: encodePayment(v1) };
|
|
108
|
+
}
|
|
109
|
+
async function signExactAuthorization(wallet, requirement) {
|
|
62
110
|
const account = wallet.account;
|
|
63
111
|
if (!account) throw new PaymentError("Wallet has no account attached");
|
|
112
|
+
const value = requirementAtomicAmount(requirement);
|
|
113
|
+
if (value === null) {
|
|
114
|
+
throw new PaymentError("x402 requirement is missing a payment amount");
|
|
115
|
+
}
|
|
116
|
+
const chainId = chainIdForNetwork(requirement.network);
|
|
117
|
+
if (chainId === null) {
|
|
118
|
+
throw new PaymentError(`Unsupported x402 network: ${requirement.network}`);
|
|
119
|
+
}
|
|
64
120
|
const now = Math.floor(Date.now() / 1e3);
|
|
65
121
|
const validAfter = BigInt(now - 60);
|
|
66
122
|
const validBefore = BigInt(now + (requirement.maxTimeoutSeconds ?? 600));
|
|
@@ -72,7 +128,7 @@ async function signPayment(wallet, requirement) {
|
|
|
72
128
|
domain: {
|
|
73
129
|
name: tokenName,
|
|
74
130
|
version: tokenVersion,
|
|
75
|
-
chainId
|
|
131
|
+
chainId,
|
|
76
132
|
verifyingContract: requirement.asset
|
|
77
133
|
},
|
|
78
134
|
types: {
|
|
@@ -89,30 +145,37 @@ async function signPayment(wallet, requirement) {
|
|
|
89
145
|
message: {
|
|
90
146
|
from: account.address,
|
|
91
147
|
to: requirement.payTo,
|
|
92
|
-
value
|
|
148
|
+
value,
|
|
93
149
|
validAfter,
|
|
94
150
|
validBefore,
|
|
95
151
|
nonce
|
|
96
152
|
}
|
|
97
153
|
});
|
|
154
|
+
return {
|
|
155
|
+
signature,
|
|
156
|
+
authorization: {
|
|
157
|
+
from: account.address,
|
|
158
|
+
to: requirement.payTo,
|
|
159
|
+
value: value.toString(),
|
|
160
|
+
validAfter: validAfter.toString(),
|
|
161
|
+
validBefore: validBefore.toString(),
|
|
162
|
+
nonce
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function signPayment(wallet, requirement) {
|
|
98
167
|
return {
|
|
99
168
|
x402Version: 1,
|
|
100
169
|
scheme: "exact",
|
|
101
170
|
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
|
-
}
|
|
171
|
+
payload: await signExactAuthorization(wallet, requirement)
|
|
113
172
|
};
|
|
114
173
|
}
|
|
115
174
|
function chainIdForNetwork(network) {
|
|
175
|
+
if (network.startsWith("eip155:")) {
|
|
176
|
+
const id = Number(network.slice("eip155:".length));
|
|
177
|
+
return Number.isInteger(id) && id > 0 ? id : null;
|
|
178
|
+
}
|
|
116
179
|
switch (network) {
|
|
117
180
|
case "base":
|
|
118
181
|
return 8453;
|
|
@@ -126,9 +189,11 @@ function chainIdForNetwork(network) {
|
|
|
126
189
|
return 42161;
|
|
127
190
|
case "polygon":
|
|
128
191
|
return 137;
|
|
192
|
+
default:
|
|
193
|
+
return null;
|
|
129
194
|
}
|
|
130
195
|
}
|
|
131
196
|
|
|
132
|
-
export { PaymentError, createPaymentClient, signPayment };
|
|
197
|
+
export { PaymentError, createPaymentClient, requirementAtomicAmount, signExactAuthorization, signPayment };
|
|
133
198
|
//# sourceMappingURL=client.js.map
|
|
134
199
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 ExactEvmPayload,\n PaymentEnvelopeV2,\n PaymentPayload,\n PaymentRequiredResponse,\n PaymentRequirement,\n X402Network,\n} from './types.js'\nimport { decodePayment, 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 * protocol v1 (\"base\" networks, `maxAmountRequired`, `X-PAYMENT` header)\n * and v2 (CAIP-2 networks, `amount`, `PAYMENT-SIGNATURE` envelope) alike.\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 the version-appropriate 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 payload = await signExactAuthorization(options.wallet, requirement)\n const header = buildPaymentHeader(requirements, requirement, payload)\n const headers = new Headers(init.headers)\n headers.set(header.name, header.value)\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\n/**\n * Atomic units owed for a requirement, version-agnostic: x402 v2 prices in\n * `amount`, v1 in `maxAmountRequired`. Returns null when absent or\n * unparseable (never throws — selection must be able to skip bad entries).\n */\nexport function requirementAtomicAmount(req: PaymentRequirement): bigint | null {\n const raw = req.amount ?? req.maxAmountRequired\n if (raw == null) return null\n try {\n return BigInt(raw)\n } catch {\n return null\n }\n}\n\nasync function parsePaymentRequired(res: Response): Promise<PaymentRequiredResponse> {\n try {\n return (await res.clone().json()) as PaymentRequiredResponse\n } catch {\n // v2 servers mirror the discovery document base64-encoded in the\n // `payment-required` response header — fall back to it for non-JSON bodies.\n const header = res.headers.get('payment-required')\n if (header) {\n try {\n return decodePayment<PaymentRequiredResponse>(header)\n } catch {\n /* fall through to the error below */\n }\n }\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 priced = accepts\n .filter((a) => a.scheme === 'exact')\n // Only EVM networks this client can sign for (drops e.g. \"solana:…\").\n .filter((a) => chainIdForNetwork(a.network) !== null)\n .filter(\n (a) =>\n !opts.allowedNetworks ||\n opts.allowedNetworks.some((n) => chainIdForNetwork(n) === chainIdForNetwork(a.network)),\n )\n .map((a) => ({ req: a, amount: requirementAtomicAmount(a) }))\n .filter((e): e is { req: PaymentRequirement; amount: bigint } => e.amount !== null)\n .filter((e) => !opts.maxAmountAtomic || e.amount <= opts.maxAmountAtomic)\n\n return priced.sort((a, b) => (a.amount < b.amount ? -1 : 1))[0]?.req\n}\n\n/** Build the version-appropriate payment header for a signed authorization. */\nfunction buildPaymentHeader(\n challenge: PaymentRequiredResponse,\n requirement: PaymentRequirement,\n payload: ExactEvmPayload,\n): { name: string; value: string } {\n const version = challenge.x402Version ?? 1\n if (version >= 2) {\n const envelope: PaymentEnvelopeV2 = {\n x402Version: version,\n resource: challenge.resource,\n accepted: requirement,\n payload,\n extensions: challenge.extensions ?? {},\n }\n return { name: 'PAYMENT-SIGNATURE', value: encodePayment(envelope) }\n }\n const v1: PaymentPayload = {\n x402Version: 1,\n scheme: 'exact',\n network: requirement.network,\n payload,\n }\n return { name: 'X-PAYMENT', value: encodePayment(v1) }\n}\n\n/** Sign an EIP-3009 `TransferWithAuthorization` for the given requirement. */\nexport async function signExactAuthorization(\n wallet: WalletClient,\n requirement: PaymentRequirement,\n): Promise<ExactEvmPayload> {\n const account = wallet.account\n if (!account) throw new PaymentError('Wallet has no account attached')\n\n const value = requirementAtomicAmount(requirement)\n if (value === null) {\n throw new PaymentError('x402 requirement is missing a payment amount')\n }\n const chainId = chainIdForNetwork(requirement.network)\n if (chainId === null) {\n throw new PaymentError(`Unsupported x402 network: ${requirement.network}`)\n }\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,\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,\n validAfter,\n validBefore,\n nonce,\n },\n })) as Hex\n\n return {\n signature,\n authorization: {\n from: account.address as Address,\n to: requirement.payTo,\n value: value.toString(),\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n },\n }\n}\n\n/**\n * Sign a requirement into the x402 **v1** `X-PAYMENT` payload shape.\n * Kept for back-compat; `createPaymentClient` is version-aware internally.\n */\nexport async function signPayment(\n wallet: WalletClient,\n requirement: PaymentRequirement,\n): Promise<PaymentPayload> {\n return {\n x402Version: 1,\n scheme: 'exact',\n network: requirement.network,\n payload: await signExactAuthorization(wallet, requirement),\n }\n}\n\n/** Resolve a network to an EVM chain id — v1 friendly names and CAIP-2 ids. */\nfunction chainIdForNetwork(network: string): number | null {\n if (network.startsWith('eip155:')) {\n const id = Number(network.slice('eip155:'.length))\n return Number.isInteger(id) && id > 0 ? id : null\n }\n switch (network as X402Network) {\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 default:\n return null\n }\n}\n"]}
|
package/dist/express.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import { RequestHandler } from 'express';
|
|
|
2
2
|
import { RouteGateOptions } from './server.cjs';
|
|
3
3
|
export { gate } from './server.cjs';
|
|
4
4
|
import 'viem';
|
|
5
|
-
import './types-
|
|
5
|
+
import './types-DgG0iIOB.cjs';
|
|
6
6
|
|
|
7
7
|
declare module 'express-serve-static-core' {
|
|
8
8
|
interface Request {
|
package/dist/express.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { RequestHandler } from 'express';
|
|
|
2
2
|
import { RouteGateOptions } from './server.js';
|
|
3
3
|
export { gate } from './server.js';
|
|
4
4
|
import 'viem';
|
|
5
|
-
import './types-
|
|
5
|
+
import './types-DgG0iIOB.js';
|
|
6
6
|
|
|
7
7
|
declare module 'express-serve-static-core' {
|
|
8
8
|
interface Request {
|
package/dist/index.cjs
CHANGED
|
@@ -57,7 +57,7 @@ function createPaymentClient(options) {
|
|
|
57
57
|
const first = await baseFetch(input, init);
|
|
58
58
|
if (first.status !== 402) return first;
|
|
59
59
|
const requirements = await parsePaymentRequired(first);
|
|
60
|
-
const requirement = selectRequirement(requirements.accepts, options);
|
|
60
|
+
const requirement = selectRequirement(requirements.accepts ?? [], options);
|
|
61
61
|
if (!requirement) {
|
|
62
62
|
throw new PaymentError("No acceptable payment requirement matched client constraints", requirements);
|
|
63
63
|
}
|
|
@@ -65,9 +65,10 @@ function createPaymentClient(options) {
|
|
|
65
65
|
const approved = await options.onPaymentRequired(requirement);
|
|
66
66
|
if (!approved) throw new PaymentError("Payment rejected by user", requirements);
|
|
67
67
|
}
|
|
68
|
-
const
|
|
68
|
+
const payload = await signExactAuthorization(options.wallet, requirement);
|
|
69
|
+
const header = buildPaymentHeader(requirements, requirement, payload);
|
|
69
70
|
const headers = new Headers(init.headers);
|
|
70
|
-
headers.set(
|
|
71
|
+
headers.set(header.name, header.value);
|
|
71
72
|
return baseFetch(input, { ...init, headers });
|
|
72
73
|
};
|
|
73
74
|
}
|
|
@@ -79,22 +80,66 @@ var PaymentError = class extends Error {
|
|
|
79
80
|
this.requirements = requirements;
|
|
80
81
|
}
|
|
81
82
|
};
|
|
83
|
+
function requirementAtomicAmount(req) {
|
|
84
|
+
const raw = req.amount ?? req.maxAmountRequired;
|
|
85
|
+
if (raw == null) return null;
|
|
86
|
+
try {
|
|
87
|
+
return BigInt(raw);
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
82
92
|
async function parsePaymentRequired(res) {
|
|
83
93
|
try {
|
|
84
94
|
return await res.clone().json();
|
|
85
95
|
} catch {
|
|
96
|
+
const header = res.headers.get("payment-required");
|
|
97
|
+
if (header) {
|
|
98
|
+
try {
|
|
99
|
+
return decodePayment(header);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
86
103
|
throw new PaymentError("402 response did not contain a valid x402 discovery body");
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
106
|
function selectRequirement(accepts, opts) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)[0];
|
|
107
|
+
const priced = accepts.filter((a) => a.scheme === "exact").filter((a) => chainIdForNetwork(a.network) !== null).filter(
|
|
108
|
+
(a) => !opts.allowedNetworks || opts.allowedNetworks.some((n) => chainIdForNetwork(n) === chainIdForNetwork(a.network))
|
|
109
|
+
).map((a) => ({ req: a, amount: requirementAtomicAmount(a) })).filter((e) => e.amount !== null).filter((e) => !opts.maxAmountAtomic || e.amount <= opts.maxAmountAtomic);
|
|
110
|
+
return priced.sort((a, b) => a.amount < b.amount ? -1 : 1)[0]?.req;
|
|
94
111
|
}
|
|
95
|
-
|
|
112
|
+
function buildPaymentHeader(challenge, requirement, payload) {
|
|
113
|
+
const version = challenge.x402Version ?? 1;
|
|
114
|
+
if (version >= 2) {
|
|
115
|
+
const envelope = {
|
|
116
|
+
x402Version: version,
|
|
117
|
+
resource: challenge.resource,
|
|
118
|
+
accepted: requirement,
|
|
119
|
+
payload,
|
|
120
|
+
extensions: challenge.extensions ?? {}
|
|
121
|
+
};
|
|
122
|
+
return { name: "PAYMENT-SIGNATURE", value: encodePayment(envelope) };
|
|
123
|
+
}
|
|
124
|
+
const v1 = {
|
|
125
|
+
x402Version: 1,
|
|
126
|
+
scheme: "exact",
|
|
127
|
+
network: requirement.network,
|
|
128
|
+
payload
|
|
129
|
+
};
|
|
130
|
+
return { name: "X-PAYMENT", value: encodePayment(v1) };
|
|
131
|
+
}
|
|
132
|
+
async function signExactAuthorization(wallet, requirement) {
|
|
96
133
|
const account = wallet.account;
|
|
97
134
|
if (!account) throw new PaymentError("Wallet has no account attached");
|
|
135
|
+
const value = requirementAtomicAmount(requirement);
|
|
136
|
+
if (value === null) {
|
|
137
|
+
throw new PaymentError("x402 requirement is missing a payment amount");
|
|
138
|
+
}
|
|
139
|
+
const chainId = chainIdForNetwork(requirement.network);
|
|
140
|
+
if (chainId === null) {
|
|
141
|
+
throw new PaymentError(`Unsupported x402 network: ${requirement.network}`);
|
|
142
|
+
}
|
|
98
143
|
const now = Math.floor(Date.now() / 1e3);
|
|
99
144
|
const validAfter = BigInt(now - 60);
|
|
100
145
|
const validBefore = BigInt(now + (requirement.maxTimeoutSeconds ?? 600));
|
|
@@ -106,7 +151,7 @@ async function signPayment(wallet, requirement) {
|
|
|
106
151
|
domain: {
|
|
107
152
|
name: tokenName,
|
|
108
153
|
version: tokenVersion,
|
|
109
|
-
chainId
|
|
154
|
+
chainId,
|
|
110
155
|
verifyingContract: requirement.asset
|
|
111
156
|
},
|
|
112
157
|
types: {
|
|
@@ -123,30 +168,37 @@ async function signPayment(wallet, requirement) {
|
|
|
123
168
|
message: {
|
|
124
169
|
from: account.address,
|
|
125
170
|
to: requirement.payTo,
|
|
126
|
-
value
|
|
171
|
+
value,
|
|
127
172
|
validAfter,
|
|
128
173
|
validBefore,
|
|
129
174
|
nonce
|
|
130
175
|
}
|
|
131
176
|
});
|
|
177
|
+
return {
|
|
178
|
+
signature,
|
|
179
|
+
authorization: {
|
|
180
|
+
from: account.address,
|
|
181
|
+
to: requirement.payTo,
|
|
182
|
+
value: value.toString(),
|
|
183
|
+
validAfter: validAfter.toString(),
|
|
184
|
+
validBefore: validBefore.toString(),
|
|
185
|
+
nonce
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function signPayment(wallet, requirement) {
|
|
132
190
|
return {
|
|
133
191
|
x402Version: 1,
|
|
134
192
|
scheme: "exact",
|
|
135
193
|
network: requirement.network,
|
|
136
|
-
payload:
|
|
137
|
-
signature,
|
|
138
|
-
authorization: {
|
|
139
|
-
from: account.address,
|
|
140
|
-
to: requirement.payTo,
|
|
141
|
-
value: requirement.maxAmountRequired,
|
|
142
|
-
validAfter: validAfter.toString(),
|
|
143
|
-
validBefore: validBefore.toString(),
|
|
144
|
-
nonce
|
|
145
|
-
}
|
|
146
|
-
}
|
|
194
|
+
payload: await signExactAuthorization(wallet, requirement)
|
|
147
195
|
};
|
|
148
196
|
}
|
|
149
197
|
function chainIdForNetwork(network) {
|
|
198
|
+
if (network.startsWith("eip155:")) {
|
|
199
|
+
const id = Number(network.slice("eip155:".length));
|
|
200
|
+
return Number.isInteger(id) && id > 0 ? id : null;
|
|
201
|
+
}
|
|
150
202
|
switch (network) {
|
|
151
203
|
case "base":
|
|
152
204
|
return 8453;
|
|
@@ -160,6 +212,8 @@ function chainIdForNetwork(network) {
|
|
|
160
212
|
return 42161;
|
|
161
213
|
case "polygon":
|
|
162
214
|
return 137;
|
|
215
|
+
default:
|
|
216
|
+
return null;
|
|
163
217
|
}
|
|
164
218
|
}
|
|
165
219
|
|
|
@@ -191,7 +245,7 @@ function hostOf(input) {
|
|
|
191
245
|
}
|
|
192
246
|
}
|
|
193
247
|
function txHashOf(res) {
|
|
194
|
-
const header = res.headers.get("x-payment-response");
|
|
248
|
+
const header = res.headers.get("payment-response") ?? res.headers.get("x-payment-response");
|
|
195
249
|
if (!header) return void 0;
|
|
196
250
|
try {
|
|
197
251
|
return decodePayment(header).transaction;
|
|
@@ -206,7 +260,36 @@ function yeetful(options) {
|
|
|
206
260
|
let spentToday = 0;
|
|
207
261
|
let spentTotal = 0;
|
|
208
262
|
let dayIndex = utcDayIndex(Date.now());
|
|
263
|
+
const ledgerEndpoint = options.apiKey && grant.id ? `${(options.ledgerUrl ?? "https://yeetful.com").replace(/\/+$/, "")}/api/grants/${grant.id}/ledger` : null;
|
|
264
|
+
if (options.apiKey && !grant.id) {
|
|
265
|
+
log("hosted-ledger sync disabled: grant.id is not set (use the id of your yeetful.com grant)");
|
|
266
|
+
}
|
|
267
|
+
let ledgerChain = Promise.resolve();
|
|
268
|
+
const ledgerFetch = options.fetch ?? globalThis.fetch;
|
|
269
|
+
const sync = (r) => {
|
|
270
|
+
if (!ledgerEndpoint) return;
|
|
271
|
+
ledgerChain = ledgerChain.then(async () => {
|
|
272
|
+
const res = await ledgerFetch(ledgerEndpoint, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: {
|
|
275
|
+
"content-type": "application/json",
|
|
276
|
+
authorization: `Bearer ${options.apiKey}`
|
|
277
|
+
},
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
host: r.host,
|
|
280
|
+
amountUsd: r.amountUsd,
|
|
281
|
+
ok: r.ok,
|
|
282
|
+
txHash: r.txHash,
|
|
283
|
+
note: r.note
|
|
284
|
+
})
|
|
285
|
+
});
|
|
286
|
+
if (!res.ok) log(`ledger sync \u2192 ${res.status} for ${r.host}`);
|
|
287
|
+
}).catch((err) => {
|
|
288
|
+
log(`ledger sync failed for ${r.host}: ${err instanceof Error ? err.message : err}`);
|
|
289
|
+
});
|
|
290
|
+
};
|
|
209
291
|
const emit = (r) => {
|
|
292
|
+
sync(r);
|
|
210
293
|
void Promise.resolve(onReceipt?.(r)).catch(() => {
|
|
211
294
|
});
|
|
212
295
|
};
|
|
@@ -235,7 +318,7 @@ function yeetful(options) {
|
|
|
235
318
|
// Per-call enforcement lives in the hook (not maxAmountAtomic) so an
|
|
236
319
|
// over-cap call surfaces a clean GrantError instead of a filtered no-match.
|
|
237
320
|
onPaymentRequired: (req) => {
|
|
238
|
-
const price = Number(req
|
|
321
|
+
const price = Number(requirementAtomicAmount(req) ?? 0n) / 1e6;
|
|
239
322
|
if (price > grant.perCallUsd) {
|
|
240
323
|
deny(host, "OVER_PER_CALL", `$${price.toFixed(4)} exceeds per-call cap $${grant.perCallUsd}`);
|
|
241
324
|
}
|
|
@@ -270,6 +353,7 @@ function yeetful(options) {
|
|
|
270
353
|
pay.spentTodayUsd = () => spentToday;
|
|
271
354
|
pay.remainingTodayUsd = () => Math.max(0, grant.perDayUsd - spentToday);
|
|
272
355
|
pay.spentTotalUsd = () => spentTotal;
|
|
356
|
+
pay.flushLedger = () => ledgerChain;
|
|
273
357
|
return pay;
|
|
274
358
|
}
|
|
275
359
|
|
|
@@ -405,6 +489,8 @@ exports.createPaymentClient = createPaymentClient;
|
|
|
405
489
|
exports.decodePayment = decodePayment;
|
|
406
490
|
exports.encodePayment = encodePayment;
|
|
407
491
|
exports.gate = gate;
|
|
492
|
+
exports.requirementAtomicAmount = requirementAtomicAmount;
|
|
493
|
+
exports.signExactAuthorization = signExactAuthorization;
|
|
408
494
|
exports.signPayment = signPayment;
|
|
409
495
|
exports.usdToAtomic = usdToAtomic;
|
|
410
496
|
exports.usdcAddress = usdcAddress;
|