@parallel-protocol/x402 0.5.3 → 0.7.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/README.md +226 -6
- package/dist/{chunk-75GLDK7L.js → chunk-AENXD4OC.js} +338 -81
- package/dist/chunk-AFRDHGDT.js +36 -0
- package/dist/chunk-JPMK5YBU.cjs +42 -0
- package/dist/chunk-POU7EH3E.cjs +755 -0
- package/dist/express.cjs +63 -6
- package/dist/express.d.cts +3 -2
- package/dist/express.d.ts +3 -2
- package/dist/express.js +61 -4
- package/dist/fastify.cjs +88 -14
- package/dist/fastify.d.cts +2 -1
- package/dist/fastify.d.ts +2 -1
- package/dist/fastify.js +86 -12
- package/dist/hono.cjs +27 -6
- package/dist/hono.d.cts +3 -2
- package/dist/hono.d.ts +3 -2
- package/dist/hono.js +25 -4
- package/dist/index.cjs +41 -20
- package/dist/index.d.cts +64 -5
- package/dist/index.d.ts +64 -5
- package/dist/index.js +2 -1
- package/dist/llm.cjs +486 -0
- package/dist/llm.d.cts +203 -0
- package/dist/llm.d.ts +203 -0
- package/dist/llm.js +473 -0
- package/dist/next.cjs +57 -7
- package/dist/next.d.cts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +54 -4
- package/dist/types-q_gu5wjz.d.cts +185 -0
- package/dist/types-q_gu5wjz.d.ts +185 -0
- package/package.json +12 -2
- package/dist/chunk-2UEAVMCH.js +0 -17
- package/dist/chunk-KQPIZ6SZ.cjs +0 -491
- package/dist/chunk-XUNNMCO5.cjs +0 -20
- package/dist/types-CUi55YSx.d.cts +0 -95
- package/dist/types-CUi55YSx.d.ts +0 -95
|
@@ -1,27 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { X402_ERROR_CODES, X402RuntimeError, X402ConfigError, isPriceRejection } from './chunk-AFRDHGDT.js';
|
|
2
2
|
import { getTokenDecimals as getTokenDecimals$1 } from '@parallel-protocol/chains';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
var PARALLEL_TOKENS = {
|
|
6
|
+
ethereum: {
|
|
7
|
+
usdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
|
|
8
|
+
susdp: "0xd3a452B305C8285c0Dd7b8537665c734d3D279eF",
|
|
9
|
+
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
10
|
+
frxUSD: "0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29",
|
|
11
|
+
sfrxUSD: "0xcf62F905562626CfcDD2261162a51fd02Fc9c5b6",
|
|
12
|
+
USDe: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3",
|
|
13
|
+
sUSDe: "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497"
|
|
14
|
+
},
|
|
15
|
+
base: {
|
|
16
|
+
usdp: "0x76A9A0062ec6712b99B4f63bD2b4270185759dd5",
|
|
17
|
+
susdp: "0x472eD57b376fE400259FB28e5C46eB53f0E3e7E7",
|
|
18
|
+
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
19
|
+
USDS: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc",
|
|
20
|
+
sUSDS: "0x5875eEE11Cf8398102FdAd704C9E96607675467a"
|
|
21
|
+
},
|
|
22
|
+
avalanche: {
|
|
23
|
+
usdp: "0x9eE1963f05553eF838604Dd39403be21ceF26AA4",
|
|
24
|
+
susdp: "0x9d92c21205383651610f90722131655a5b8ed3e0",
|
|
25
|
+
usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
26
|
+
ygamiUSDC: "0x9fD32FD5e32C6B95483d36C5E724C5C5250Ce010"
|
|
27
|
+
},
|
|
28
|
+
hyperevm: {
|
|
29
|
+
usdp: "0xBE65F0F410A72BeC163dC65d46c83699e957D588",
|
|
30
|
+
susdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
|
|
31
|
+
usdc: "0xb88339cb7199b77e23db6e890353e22632ba630f",
|
|
32
|
+
USDe: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
|
|
33
|
+
sUSDe: "0x211Cc4DD073734dA055fbF44a2b4667d5E5fE5d2"
|
|
22
34
|
}
|
|
23
|
-
code;
|
|
24
35
|
};
|
|
36
|
+
function getDefaultAcceptedTokens(network) {
|
|
37
|
+
const entry = PARALLEL_TOKENS[network];
|
|
38
|
+
if (!entry) return void 0;
|
|
39
|
+
return [entry.usdp, entry.usdc, entry.susdp];
|
|
40
|
+
}
|
|
41
|
+
function getMeteredAcceptedTokens(network) {
|
|
42
|
+
const entry = PARALLEL_TOKENS[network];
|
|
43
|
+
if (!entry) return void 0;
|
|
44
|
+
return [entry.usdp];
|
|
45
|
+
}
|
|
46
|
+
function getTokenDecimals(network, address) {
|
|
47
|
+
return getTokenDecimals$1(network, address);
|
|
48
|
+
}
|
|
25
49
|
|
|
26
50
|
// src/utils.ts
|
|
27
51
|
var EIP155_NETWORKS = {
|
|
@@ -45,6 +69,28 @@ function parsePrice(price, decimals = 18) {
|
|
|
45
69
|
`Invalid price format: "${price}". Expected "0.01" or a bigint.`
|
|
46
70
|
);
|
|
47
71
|
}
|
|
72
|
+
function isJsonContentType(contentType) {
|
|
73
|
+
return /^application\/(?:[\w.-]+\+)?json(?:\s*;.*)?$/i.test(
|
|
74
|
+
contentType?.trim() ?? ""
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function parseJsonBody(contentType, text) {
|
|
78
|
+
if (text === void 0 || !isJsonContentType(contentType)) return void 0;
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(text);
|
|
81
|
+
} catch {
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function jsonFromResponse(res) {
|
|
86
|
+
const contentType = res.headers.get("content-type") ?? void 0;
|
|
87
|
+
if (!isJsonContentType(contentType)) return void 0;
|
|
88
|
+
try {
|
|
89
|
+
return parseJsonBody(contentType, await res.clone().text());
|
|
90
|
+
} catch {
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
48
94
|
function validateAddress(address) {
|
|
49
95
|
return /^0x[0-9a-fA-F]{40}$/.test(address);
|
|
50
96
|
}
|
|
@@ -74,19 +120,44 @@ var ethereumAddressSchema = z.string().regex(/^0x[0-9a-fA-F]{40}$/, "Invalid Eth
|
|
|
74
120
|
var txHashSchema = z.string().regex(/^0x[0-9a-fA-F]{64}$/);
|
|
75
121
|
var priceSchema = z.union([
|
|
76
122
|
z.bigint().positive("Price must be greater than 0"),
|
|
77
|
-
z.string().regex(/^\d+(?:\.\d+)?$/, 'Price must be in format "0.01"').refine((price) => Number(price) > 0, "Price must be greater than 0")
|
|
123
|
+
z.string().regex(/^\d+(?:\.\d+)?$/, 'Price must be in format "0.01"').refine((price) => Number(price) > 0, "Price must be greater than 0"),
|
|
124
|
+
// A per-request price function; its return value is checked per request.
|
|
125
|
+
z.custom(
|
|
126
|
+
(value) => typeof value === "function",
|
|
127
|
+
"Price must be a string, a bigint or a function of the request"
|
|
128
|
+
)
|
|
78
129
|
]);
|
|
79
130
|
var facilitatorConfigSchema = z.object({
|
|
80
131
|
url: z.string().refine((v) => URL.canParse(v), "Invalid facilitator URL"),
|
|
81
132
|
apiKey: z.string().optional()
|
|
82
133
|
});
|
|
134
|
+
var UPTO_MAX_TIMEOUT_SECONDS = 3600;
|
|
83
135
|
var routeConfigSchema = z.object({
|
|
84
136
|
price: priceSchema,
|
|
85
137
|
network: z.string().min(1, "Network is required"),
|
|
86
138
|
payTo: ethereumAddressSchema,
|
|
87
139
|
acceptedTokens: z.array(ethereumAddressSchema).optional(),
|
|
88
|
-
description: z.string().optional()
|
|
89
|
-
|
|
140
|
+
description: z.string().optional(),
|
|
141
|
+
scheme: z.enum(["exact", "upto"]).optional(),
|
|
142
|
+
maxTimeoutSeconds: z.number().int().positive().optional(),
|
|
143
|
+
meter: z.custom(
|
|
144
|
+
(value) => typeof value === "function",
|
|
145
|
+
"meter must be a function of the response"
|
|
146
|
+
).optional()
|
|
147
|
+
}).refine(
|
|
148
|
+
(route) => route.meter === void 0 || route.scheme === "upto",
|
|
149
|
+
"meter only applies to upto routes"
|
|
150
|
+
).refine(
|
|
151
|
+
(route) => route.scheme !== "upto" || (route.maxTimeoutSeconds ?? 300) <= UPTO_MAX_TIMEOUT_SECONDS,
|
|
152
|
+
`maxTimeoutSeconds must be at most ${UPTO_MAX_TIMEOUT_SECONDS} on an upto route`
|
|
153
|
+
).refine((route) => {
|
|
154
|
+
if (route.scheme !== "upto" || !route.acceptedTokens?.length) return true;
|
|
155
|
+
const usdp = PARALLEL_TOKENS[route.network]?.usdp;
|
|
156
|
+
if (!usdp) return true;
|
|
157
|
+
return route.acceptedTokens.every(
|
|
158
|
+
(token) => token.toLowerCase() === usdp.toLowerCase()
|
|
159
|
+
);
|
|
160
|
+
}, "acceptedTokens on an upto route may only list the network's USDp");
|
|
90
161
|
var paymentMiddlewareConfigSchema = z.object({
|
|
91
162
|
facilitator: facilitatorConfigSchema,
|
|
92
163
|
routes: z.record(z.string(), routeConfigSchema).refine((r) => Object.keys(r).length > 0, "At least one route is required")
|
|
@@ -98,7 +169,8 @@ var facilitatorResponseSchema = z.discriminatedUnion("success", [
|
|
|
98
169
|
route: z.string(),
|
|
99
170
|
chain: z.string(),
|
|
100
171
|
gasSponsored: z.boolean(),
|
|
101
|
-
networkId: z.string()
|
|
172
|
+
networkId: z.string(),
|
|
173
|
+
amountSettled: z.string().optional()
|
|
102
174
|
}),
|
|
103
175
|
z.object({
|
|
104
176
|
success: z.literal(false),
|
|
@@ -115,7 +187,9 @@ var verifyResponseSchema = z.object({
|
|
|
115
187
|
var settleSuccessSchema = z.object({
|
|
116
188
|
txHash: txHashSchema,
|
|
117
189
|
route: z.string(),
|
|
118
|
-
gasSponsored: z.boolean()
|
|
190
|
+
gasSponsored: z.boolean(),
|
|
191
|
+
// Metered routes: the amount actually charged (smallest unit).
|
|
192
|
+
amountSettled: z.string().optional()
|
|
119
193
|
});
|
|
120
194
|
var settleErrorSchema = z.object({
|
|
121
195
|
error: z.string(),
|
|
@@ -167,14 +241,21 @@ var FacilitatorClient = class {
|
|
|
167
241
|
throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
|
|
168
242
|
}
|
|
169
243
|
}
|
|
170
|
-
async settle(paymentHeader, paymentRequirements) {
|
|
244
|
+
async settle(paymentHeader, paymentRequirements, options) {
|
|
171
245
|
const paymentPayload = decodePaymentHeader(paymentHeader);
|
|
172
246
|
let json;
|
|
173
247
|
try {
|
|
174
248
|
const response = await fetch(`${this.config.url}/x402/settle`, {
|
|
175
249
|
method: "POST",
|
|
176
250
|
headers: this.buildHeaders(),
|
|
177
|
-
|
|
251
|
+
// settleAmount only travels for metered routes — exact bodies are unchanged.
|
|
252
|
+
body: JSON.stringify({
|
|
253
|
+
paymentPayload,
|
|
254
|
+
paymentRequirements,
|
|
255
|
+
...options?.amount !== void 0 && {
|
|
256
|
+
settleAmount: options.amount
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
178
259
|
signal: AbortSignal.timeout(SETTLE_TIMEOUT_MS)
|
|
179
260
|
});
|
|
180
261
|
if (!response.ok) {
|
|
@@ -197,7 +278,10 @@ var FacilitatorClient = class {
|
|
|
197
278
|
route: parsed.route,
|
|
198
279
|
chain: paymentRequirements.network,
|
|
199
280
|
gasSponsored: parsed.gasSponsored,
|
|
200
|
-
networkId: toEip155Network(paymentRequirements.network)
|
|
281
|
+
networkId: toEip155Network(paymentRequirements.network),
|
|
282
|
+
...parsed.amountSettled !== void 0 && {
|
|
283
|
+
amountSettled: parsed.amountSettled
|
|
284
|
+
}
|
|
201
285
|
};
|
|
202
286
|
}
|
|
203
287
|
return {
|
|
@@ -242,52 +326,146 @@ var FacilitatorClient = class {
|
|
|
242
326
|
return headers;
|
|
243
327
|
}
|
|
244
328
|
};
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
USDS: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc",
|
|
260
|
-
sUSDS: "0x5875eEE11Cf8398102FdAd704C9E96607675467a"
|
|
261
|
-
},
|
|
262
|
-
avalanche: {
|
|
263
|
-
usdp: "0x9eE1963f05553eF838604Dd39403be21ceF26AA4",
|
|
264
|
-
susdp: "0x9d92c21205383651610f90722131655a5b8ed3e0",
|
|
265
|
-
usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
266
|
-
ygamiUSDC: "0x9fD32FD5e32C6B95483d36C5E724C5C5250Ce010"
|
|
267
|
-
},
|
|
268
|
-
hyperevm: {
|
|
269
|
-
usdp: "0xBE65F0F410A72BeC163dC65d46c83699e957D588",
|
|
270
|
-
susdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
|
|
271
|
-
usdc: "0xb88339cb7199b77e23db6e890353e22632ba630f",
|
|
272
|
-
USDe: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
|
|
273
|
-
sUSDe: "0x211Cc4DD073734dA055fbF44a2b4667d5E5fE5d2"
|
|
329
|
+
|
|
330
|
+
// src/timeout.ts
|
|
331
|
+
var HANDLER_TIMEOUT_MS = 3e4;
|
|
332
|
+
var HANDLER_TIMEOUT = /* @__PURE__ */ Symbol("x402:handler-timeout");
|
|
333
|
+
async function raceTimeout(promise, ms = HANDLER_TIMEOUT_MS) {
|
|
334
|
+
let timer;
|
|
335
|
+
const timeout = new Promise((resolve) => {
|
|
336
|
+
timer = setTimeout(() => resolve(HANDLER_TIMEOUT), ms);
|
|
337
|
+
timer.unref?.();
|
|
338
|
+
});
|
|
339
|
+
try {
|
|
340
|
+
return await Promise.race([promise, timeout]);
|
|
341
|
+
} finally {
|
|
342
|
+
clearTimeout(timer);
|
|
274
343
|
}
|
|
275
|
-
};
|
|
276
|
-
function getDefaultAcceptedTokens(network) {
|
|
277
|
-
const entry = PARALLEL_TOKENS[network];
|
|
278
|
-
if (!entry) return void 0;
|
|
279
|
-
return [entry.usdp, entry.usdc, entry.susdp];
|
|
280
|
-
}
|
|
281
|
-
function getTokenDecimals(network, address) {
|
|
282
|
-
return getTokenDecimals$1(network, address);
|
|
283
344
|
}
|
|
284
345
|
|
|
285
346
|
// src/middleware.ts
|
|
347
|
+
var PRICE_FORMAT = /^\d+(?:\.\d+)?$/;
|
|
348
|
+
async function requestContext(adapter) {
|
|
349
|
+
return {
|
|
350
|
+
method: adapter.getMethod(),
|
|
351
|
+
path: adapter.getPath(),
|
|
352
|
+
url: adapter.getUrl(),
|
|
353
|
+
header: (name) => adapter.getHeader(name),
|
|
354
|
+
body: adapter.getBody ? await adapter.getBody() : void 0
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async function resolvePrice(config, ctx) {
|
|
358
|
+
if (typeof config.price !== "function") return config.price;
|
|
359
|
+
if (!ctx) throw new X402ConfigError("A price function needs the request");
|
|
360
|
+
let price;
|
|
361
|
+
try {
|
|
362
|
+
price = await config.price(ctx);
|
|
363
|
+
} catch (cause) {
|
|
364
|
+
if (isPriceRejection(cause)) throw cause;
|
|
365
|
+
throw new X402ConfigError(
|
|
366
|
+
`The price function of route "${config.network}${ctx.path}" threw`,
|
|
367
|
+
cause
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const valid = typeof price === "bigint" ? price > 0n : typeof price === "string" && PRICE_FORMAT.test(price) && Number(price) > 0;
|
|
371
|
+
if (!valid) {
|
|
372
|
+
throw new X402ConfigError(
|
|
373
|
+
`The price function of route "${config.network}${ctx.path}" returned ${JSON.stringify(
|
|
374
|
+
typeof price === "bigint" ? price.toString() : price
|
|
375
|
+
)} \u2014 expected a positive amount like "0.01" or a bigint`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return price;
|
|
379
|
+
}
|
|
380
|
+
var SETTLE_AMOUNT_HEADER = "payment-settle-amount";
|
|
381
|
+
var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
|
|
382
|
+
function handlerTimeoutFor(config) {
|
|
383
|
+
if ((config.scheme ?? "exact") !== "upto") return HANDLER_TIMEOUT_MS;
|
|
384
|
+
const seconds = config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
385
|
+
return Math.max(HANDLER_TIMEOUT_MS, (seconds - 60) * 1e3);
|
|
386
|
+
}
|
|
387
|
+
function resolveSettleAmount(raw, maxAmount, decimals) {
|
|
388
|
+
if (raw === void 0)
|
|
389
|
+
return { kind: "settle", amount: maxAmount.toString() };
|
|
390
|
+
const value = raw.trim();
|
|
391
|
+
let amount;
|
|
392
|
+
try {
|
|
393
|
+
amount = parsePrice(value, decimals);
|
|
394
|
+
} catch {
|
|
395
|
+
return { kind: "invalid", reason: "malformed" };
|
|
396
|
+
}
|
|
397
|
+
const fraction = value.split(".")[1] ?? "";
|
|
398
|
+
if (fraction.length > decimals)
|
|
399
|
+
return { kind: "invalid", reason: "precision" };
|
|
400
|
+
if (amount === 0n) return { kind: "skip" };
|
|
401
|
+
if (amount > maxAmount) return { kind: "invalid", reason: "above-cap" };
|
|
402
|
+
return { kind: "settle", amount: amount.toString() };
|
|
403
|
+
}
|
|
404
|
+
var INVALID_SETTLE_AMOUNT_RESPONSE = {
|
|
405
|
+
error: "INVALID_SETTLE_AMOUNT",
|
|
406
|
+
message: `The handler set an invalid ${SETTLE_AMOUNT_HEADER} header \u2014 expected a decimal amount in token units, formatted like the route price and within the token's precision, at most the route price.`
|
|
407
|
+
};
|
|
408
|
+
var INVALID_METER_RESPONSE = {
|
|
409
|
+
error: "INVALID_SETTLE_AMOUNT",
|
|
410
|
+
message: "The route's meter function failed or returned something that is not an amount; the response was discarded and nothing was charged."
|
|
411
|
+
};
|
|
412
|
+
function invalidSettleAmountResponse(reason) {
|
|
413
|
+
return reason === "meter" ? INVALID_METER_RESPONSE : INVALID_SETTLE_AMOUNT_RESPONSE;
|
|
414
|
+
}
|
|
415
|
+
var AMOUNT_FORMAT = /^\d+(?:\.\d+)?$/;
|
|
416
|
+
async function resolveCharge(verified, settleHeader, response) {
|
|
417
|
+
if (verified.meter) {
|
|
418
|
+
if (!verified.request) {
|
|
419
|
+
throw new X402ConfigError(
|
|
420
|
+
"A metered payment carries a meter but no request context"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
let result;
|
|
424
|
+
try {
|
|
425
|
+
const body = await raceTimeout(
|
|
426
|
+
response.body(),
|
|
427
|
+
verified.handlerTimeoutMs
|
|
428
|
+
);
|
|
429
|
+
if (body === HANDLER_TIMEOUT) return { kind: "invalid", reason: "meter" };
|
|
430
|
+
result = await verified.meter({
|
|
431
|
+
status: response.status,
|
|
432
|
+
header: response.header,
|
|
433
|
+
body,
|
|
434
|
+
request: verified.request
|
|
435
|
+
});
|
|
436
|
+
} catch {
|
|
437
|
+
return { kind: "invalid", reason: "meter" };
|
|
438
|
+
}
|
|
439
|
+
if (result != null) {
|
|
440
|
+
let amount;
|
|
441
|
+
if (typeof result === "bigint") {
|
|
442
|
+
if (result < 0n) return { kind: "invalid", reason: "meter" };
|
|
443
|
+
amount = result;
|
|
444
|
+
} else if (typeof result === "string" && AMOUNT_FORMAT.test(result)) {
|
|
445
|
+
const fraction = result.split(".")[1] ?? "";
|
|
446
|
+
if (fraction.length > verified.decimals) {
|
|
447
|
+
return { kind: "invalid", reason: "meter" };
|
|
448
|
+
}
|
|
449
|
+
amount = parsePrice(result, verified.decimals);
|
|
450
|
+
} else {
|
|
451
|
+
return { kind: "invalid", reason: "meter" };
|
|
452
|
+
}
|
|
453
|
+
if (amount === 0n) return { kind: "skip" };
|
|
454
|
+
if (amount > verified.maxAmount) amount = verified.maxAmount;
|
|
455
|
+
return { kind: "settle", amount: amount.toString() };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return resolveSettleAmount(
|
|
459
|
+
settleHeader,
|
|
460
|
+
verified.maxAmount,
|
|
461
|
+
verified.decimals
|
|
462
|
+
);
|
|
463
|
+
}
|
|
286
464
|
function resolveAcceptedTokens(config) {
|
|
287
465
|
if (config.acceptedTokens?.length) {
|
|
288
466
|
return config.acceptedTokens;
|
|
289
467
|
}
|
|
290
|
-
const defaults = getDefaultAcceptedTokens(config.network);
|
|
468
|
+
const defaults = config.scheme === "upto" ? getMeteredAcceptedTokens(config.network) : getDefaultAcceptedTokens(config.network);
|
|
291
469
|
if (defaults) return defaults;
|
|
292
470
|
throw new X402ConfigError(
|
|
293
471
|
`acceptedTokens is required for network "${config.network}" \u2014 no defaults available for this chain`
|
|
@@ -302,17 +480,26 @@ function resolveDecimals(config, asset) {
|
|
|
302
480
|
}
|
|
303
481
|
return decimals;
|
|
304
482
|
}
|
|
305
|
-
function
|
|
483
|
+
function priceInUnits(price, decimals, asset) {
|
|
484
|
+
const units = parsePrice(price, decimals);
|
|
485
|
+
if (units === 0n) {
|
|
486
|
+
throw new X402ConfigError(
|
|
487
|
+
`Price ${typeof price === "bigint" ? price.toString() : `"${price}"`} is below the precision of token ${asset} (${decimals} decimals)`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
return units.toString();
|
|
491
|
+
}
|
|
492
|
+
function buildPaymentRequired(url, config, resolvedTokens, price) {
|
|
306
493
|
const network = toEip155Network(config.network);
|
|
307
494
|
const accepts = resolvedTokens.map((asset) => {
|
|
308
495
|
const decimals = resolveDecimals(config, asset);
|
|
309
496
|
return {
|
|
310
|
-
scheme: "exact",
|
|
497
|
+
scheme: config.scheme ?? "exact",
|
|
311
498
|
network,
|
|
312
499
|
asset,
|
|
313
|
-
amount:
|
|
500
|
+
amount: priceInUnits(price, decimals, asset),
|
|
314
501
|
payTo: config.payTo,
|
|
315
|
-
maxTimeoutSeconds:
|
|
502
|
+
maxTimeoutSeconds: config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS,
|
|
316
503
|
extra: { decimals }
|
|
317
504
|
};
|
|
318
505
|
});
|
|
@@ -356,17 +543,28 @@ function resolveDeliveredAsset(header, config, resolvedTokens) {
|
|
|
356
543
|
const lower = target?.toLowerCase();
|
|
357
544
|
return resolvedTokens.find((token) => token.toLowerCase() === lower) ?? resolvedTokens[0];
|
|
358
545
|
}
|
|
359
|
-
function buildFacilitatorRequirements(config, asset) {
|
|
546
|
+
function buildFacilitatorRequirements(config, asset, price) {
|
|
360
547
|
const decimals = resolveDecimals(config, asset);
|
|
548
|
+
const scheme = config.scheme ?? "exact";
|
|
361
549
|
return {
|
|
362
|
-
maxAmountRequired:
|
|
550
|
+
maxAmountRequired: priceInUnits(price, decimals, asset),
|
|
363
551
|
asset,
|
|
364
552
|
payTo: config.payTo,
|
|
365
|
-
network: config.network
|
|
553
|
+
network: config.network,
|
|
554
|
+
// Only metered routes name their scheme — exact requirements are unchanged.
|
|
555
|
+
...scheme === "upto" && {
|
|
556
|
+
scheme,
|
|
557
|
+
maxTimeoutSeconds: config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS
|
|
558
|
+
}
|
|
366
559
|
};
|
|
367
560
|
}
|
|
368
|
-
function paymentRequiredResponse(url, config, resolvedTokens, error) {
|
|
369
|
-
const paymentRequired = buildPaymentRequired(
|
|
561
|
+
function paymentRequiredResponse(url, config, resolvedTokens, price, error) {
|
|
562
|
+
const paymentRequired = buildPaymentRequired(
|
|
563
|
+
url,
|
|
564
|
+
config,
|
|
565
|
+
resolvedTokens,
|
|
566
|
+
price
|
|
567
|
+
);
|
|
370
568
|
if (error) paymentRequired.error = error;
|
|
371
569
|
return {
|
|
372
570
|
status: 402,
|
|
@@ -394,6 +592,22 @@ function createPaymentGate(config) {
|
|
|
394
592
|
const routeConfig = matchRoute(adapter.getPath(), config.routes);
|
|
395
593
|
if (!routeConfig) return { type: "pass" };
|
|
396
594
|
const resolvedTokens = resolveAcceptedTokens(routeConfig);
|
|
595
|
+
let request = typeof routeConfig.price === "function" ? await requestContext(adapter) : void 0;
|
|
596
|
+
let price;
|
|
597
|
+
try {
|
|
598
|
+
price = await resolvePrice(routeConfig, request);
|
|
599
|
+
} catch (err) {
|
|
600
|
+
if (!isPriceRejection(err)) throw err;
|
|
601
|
+
const status = Number.isInteger(err.status) && err.status >= 400 && err.status <= 599 ? err.status : 400;
|
|
602
|
+
return {
|
|
603
|
+
type: "error",
|
|
604
|
+
result: {
|
|
605
|
+
status,
|
|
606
|
+
headers: { "Content-Type": "application/json" },
|
|
607
|
+
body: { error: err.code, message: err.message }
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
}
|
|
397
611
|
const paymentHeader = adapter.getHeader("payment-signature") ?? adapter.getHeader("x-payment");
|
|
398
612
|
if (!paymentHeader) {
|
|
399
613
|
return {
|
|
@@ -401,13 +615,20 @@ function createPaymentGate(config) {
|
|
|
401
615
|
result: paymentRequiredResponse(
|
|
402
616
|
adapter.getUrl(),
|
|
403
617
|
routeConfig,
|
|
404
|
-
resolvedTokens
|
|
618
|
+
resolvedTokens,
|
|
619
|
+
price
|
|
405
620
|
)
|
|
406
621
|
};
|
|
407
622
|
}
|
|
623
|
+
const asset = resolveDeliveredAsset(
|
|
624
|
+
paymentHeader,
|
|
625
|
+
routeConfig,
|
|
626
|
+
resolvedTokens
|
|
627
|
+
);
|
|
408
628
|
const requirements = buildFacilitatorRequirements(
|
|
409
629
|
routeConfig,
|
|
410
|
-
|
|
630
|
+
asset,
|
|
631
|
+
price
|
|
411
632
|
);
|
|
412
633
|
try {
|
|
413
634
|
await client.verify(paymentHeader, requirements);
|
|
@@ -419,13 +640,21 @@ function createPaymentGate(config) {
|
|
|
419
640
|
adapter.getUrl(),
|
|
420
641
|
routeConfig,
|
|
421
642
|
resolvedTokens,
|
|
643
|
+
price,
|
|
422
644
|
code
|
|
423
645
|
)
|
|
424
646
|
};
|
|
425
647
|
}
|
|
648
|
+
if (routeConfig.meter && !request) request = await requestContext(adapter);
|
|
426
649
|
return {
|
|
427
650
|
type: "verified",
|
|
428
|
-
|
|
651
|
+
scheme: routeConfig.scheme ?? "exact",
|
|
652
|
+
maxAmount: BigInt(requirements.maxAmountRequired),
|
|
653
|
+
decimals: resolveDecimals(routeConfig, asset),
|
|
654
|
+
handlerTimeoutMs: handlerTimeoutFor(routeConfig),
|
|
655
|
+
// Exact routes keep the historical two-argument client call.
|
|
656
|
+
settle: (options) => options ? client.settle(paymentHeader, requirements, options) : client.settle(paymentHeader, requirements),
|
|
657
|
+
...routeConfig.meter && { meter: routeConfig.meter, request }
|
|
429
658
|
};
|
|
430
659
|
};
|
|
431
660
|
}
|
|
@@ -437,14 +666,42 @@ function createPaymentMiddleware(config) {
|
|
|
437
666
|
if (gateResult.type === "error") return gateResult.result;
|
|
438
667
|
let handlerResult;
|
|
439
668
|
try {
|
|
440
|
-
handlerResult = await adapter.runHandler(
|
|
669
|
+
handlerResult = await adapter.runHandler({
|
|
670
|
+
timeoutMs: gateResult.handlerTimeoutMs
|
|
671
|
+
});
|
|
441
672
|
} catch {
|
|
442
673
|
return { pass: true, handlerRan: true };
|
|
443
674
|
}
|
|
675
|
+
let raw;
|
|
676
|
+
if (gateResult.scheme === "upto") {
|
|
677
|
+
raw = handlerResult.getResponseHeader?.(SETTLE_AMOUNT_HEADER);
|
|
678
|
+
handlerResult.removeResponseHeader?.(SETTLE_AMOUNT_HEADER);
|
|
679
|
+
}
|
|
444
680
|
if (handlerResult.status >= 200 && handlerResult.status < 300) {
|
|
681
|
+
let settleOptions;
|
|
682
|
+
if (gateResult.scheme === "upto") {
|
|
683
|
+
const resolved = await resolveCharge(gateResult, raw, {
|
|
684
|
+
status: handlerResult.status,
|
|
685
|
+
header: (name) => handlerResult.getResponseHeader?.(name),
|
|
686
|
+
body: async () => handlerResult.getResponseBody?.()
|
|
687
|
+
});
|
|
688
|
+
if (resolved.kind === "invalid") {
|
|
689
|
+
handlerResult.discardResponse();
|
|
690
|
+
return {
|
|
691
|
+
status: 402,
|
|
692
|
+
headers: { "Content-Type": "application/json" },
|
|
693
|
+
body: invalidSettleAmountResponse(resolved.reason)
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (resolved.kind === "skip") {
|
|
697
|
+
handlerResult.sendResponse();
|
|
698
|
+
return { pass: true, handlerRan: true };
|
|
699
|
+
}
|
|
700
|
+
settleOptions = { amount: resolved.amount };
|
|
701
|
+
}
|
|
445
702
|
let settled = false;
|
|
446
703
|
try {
|
|
447
|
-
const settleResponse = await gateResult.settle();
|
|
704
|
+
const settleResponse = settleOptions ? await gateResult.settle(settleOptions) : await gateResult.settle();
|
|
448
705
|
if (settleResponse.success) {
|
|
449
706
|
handlerResult.setResponseHeader(
|
|
450
707
|
"payment-response",
|
|
@@ -475,4 +732,4 @@ function createPaymentMiddleware(config) {
|
|
|
475
732
|
};
|
|
476
733
|
}
|
|
477
734
|
|
|
478
|
-
export { FacilitatorClient,
|
|
735
|
+
export { FacilitatorClient, HANDLER_TIMEOUT, PARALLEL_TOKENS, SETTLE_AMOUNT_HEADER, createPaymentGate, createPaymentMiddleware, encodeBase64, getDefaultAcceptedTokens, invalidSettleAmountResponse, isJsonContentType, jsonFromResponse, matchRoute, parseJsonBody, parsePrice, raceTimeout, resolveCharge, resolveSettleAmount, toEip155Network, validateAddress };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var X402_ERROR_CODES = {
|
|
3
|
+
FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
|
|
4
|
+
FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
|
|
5
|
+
INVALID_PAYMENT: "INVALID_PAYMENT"
|
|
6
|
+
};
|
|
7
|
+
var X402ConfigError = class extends Error {
|
|
8
|
+
constructor(message, cause) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "X402ConfigError";
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var X402RuntimeError = class extends Error {
|
|
15
|
+
constructor(code) {
|
|
16
|
+
super(code);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = "X402RuntimeError";
|
|
19
|
+
}
|
|
20
|
+
code;
|
|
21
|
+
};
|
|
22
|
+
var X402PriceRejectedError = class extends Error {
|
|
23
|
+
constructor(code, message, status = 400) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.name = "X402PriceRejectedError";
|
|
28
|
+
}
|
|
29
|
+
code;
|
|
30
|
+
status;
|
|
31
|
+
};
|
|
32
|
+
function isPriceRejection(error) {
|
|
33
|
+
return error instanceof Error && error.name === "X402PriceRejectedError" && typeof error.code === "string";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { X402ConfigError, X402PriceRejectedError, X402RuntimeError, X402_ERROR_CODES, isPriceRejection };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var X402_ERROR_CODES = {
|
|
5
|
+
FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
|
|
6
|
+
FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
|
|
7
|
+
INVALID_PAYMENT: "INVALID_PAYMENT"
|
|
8
|
+
};
|
|
9
|
+
var X402ConfigError = class extends Error {
|
|
10
|
+
constructor(message, cause) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "X402ConfigError";
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var X402RuntimeError = class extends Error {
|
|
17
|
+
constructor(code) {
|
|
18
|
+
super(code);
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.name = "X402RuntimeError";
|
|
21
|
+
}
|
|
22
|
+
code;
|
|
23
|
+
};
|
|
24
|
+
var X402PriceRejectedError = class extends Error {
|
|
25
|
+
constructor(code, message, status = 400) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.code = code;
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.name = "X402PriceRejectedError";
|
|
30
|
+
}
|
|
31
|
+
code;
|
|
32
|
+
status;
|
|
33
|
+
};
|
|
34
|
+
function isPriceRejection(error) {
|
|
35
|
+
return error instanceof Error && error.name === "X402PriceRejectedError" && typeof error.code === "string";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.X402ConfigError = X402ConfigError;
|
|
39
|
+
exports.X402PriceRejectedError = X402PriceRejectedError;
|
|
40
|
+
exports.X402RuntimeError = X402RuntimeError;
|
|
41
|
+
exports.X402_ERROR_CODES = X402_ERROR_CODES;
|
|
42
|
+
exports.isPriceRejection = isPriceRejection;
|