@parallel-protocol/x402 0.6.0 → 0.7.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.
@@ -1,29 +1,53 @@
1
1
  'use strict';
2
2
 
3
- var zod = require('zod');
3
+ var chunkJPMK5YBU_cjs = require('./chunk-JPMK5YBU.cjs');
4
4
  var chains = require('@parallel-protocol/chains');
5
+ var zod = require('zod');
5
6
 
6
- // src/errors.ts
7
- var X402_ERROR_CODES = {
8
- FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
9
- FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
10
- INVALID_PAYMENT: "INVALID_PAYMENT"
11
- };
12
- var X402ConfigError = class extends Error {
13
- constructor(message, cause) {
14
- super(message);
15
- this.name = "X402ConfigError";
16
- this.cause = cause;
17
- }
18
- };
19
- var X402RuntimeError = class extends Error {
20
- constructor(code) {
21
- super(code);
22
- this.code = code;
23
- this.name = "X402RuntimeError";
7
+ var PARALLEL_TOKENS = {
8
+ ethereum: {
9
+ usdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
10
+ susdp: "0xd3a452B305C8285c0Dd7b8537665c734d3D279eF",
11
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
12
+ frxUSD: "0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29",
13
+ sfrxUSD: "0xcf62F905562626CfcDD2261162a51fd02Fc9c5b6",
14
+ USDe: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3",
15
+ sUSDe: "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497"
16
+ },
17
+ base: {
18
+ usdp: "0x76A9A0062ec6712b99B4f63bD2b4270185759dd5",
19
+ susdp: "0x472eD57b376fE400259FB28e5C46eB53f0E3e7E7",
20
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
21
+ USDS: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc",
22
+ sUSDS: "0x5875eEE11Cf8398102FdAd704C9E96607675467a"
23
+ },
24
+ avalanche: {
25
+ usdp: "0x9eE1963f05553eF838604Dd39403be21ceF26AA4",
26
+ susdp: "0x9d92c21205383651610f90722131655a5b8ed3e0",
27
+ usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
28
+ ygamiUSDC: "0x9fD32FD5e32C6B95483d36C5E724C5C5250Ce010"
29
+ },
30
+ hyperevm: {
31
+ usdp: "0xBE65F0F410A72BeC163dC65d46c83699e957D588",
32
+ susdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
33
+ usdc: "0xb88339cb7199b77e23db6e890353e22632ba630f",
34
+ USDe: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
35
+ sUSDe: "0x211Cc4DD073734dA055fbF44a2b4667d5E5fE5d2"
24
36
  }
25
- code;
26
37
  };
38
+ function getDefaultAcceptedTokens(network) {
39
+ const entry = PARALLEL_TOKENS[network];
40
+ if (!entry) return void 0;
41
+ return [entry.usdp, entry.usdc, entry.susdp];
42
+ }
43
+ function getMeteredAcceptedTokens(network) {
44
+ const entry = PARALLEL_TOKENS[network];
45
+ if (!entry) return void 0;
46
+ return [entry.usdp];
47
+ }
48
+ function getTokenDecimals(network, address) {
49
+ return chains.getTokenDecimals(network, address);
50
+ }
27
51
 
28
52
  // src/utils.ts
29
53
  var EIP155_NETWORKS = {
@@ -47,6 +71,28 @@ function parsePrice(price, decimals = 18) {
47
71
  `Invalid price format: "${price}". Expected "0.01" or a bigint.`
48
72
  );
49
73
  }
74
+ function isJsonContentType(contentType) {
75
+ return /^application\/(?:[\w.-]+\+)?json(?:\s*;.*)?$/i.test(
76
+ contentType?.trim() ?? ""
77
+ );
78
+ }
79
+ function parseJsonBody(contentType, text) {
80
+ if (text === void 0 || !isJsonContentType(contentType)) return void 0;
81
+ try {
82
+ return JSON.parse(text);
83
+ } catch {
84
+ return void 0;
85
+ }
86
+ }
87
+ async function jsonFromResponse(res) {
88
+ const contentType = res.headers.get("content-type") ?? void 0;
89
+ if (!isJsonContentType(contentType)) return void 0;
90
+ try {
91
+ return parseJsonBody(contentType, await res.clone().text());
92
+ } catch {
93
+ return void 0;
94
+ }
95
+ }
50
96
  function validateAddress(address) {
51
97
  return /^0x[0-9a-fA-F]{40}$/.test(address);
52
98
  }
@@ -76,7 +122,12 @@ var ethereumAddressSchema = zod.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "Invalid
76
122
  var txHashSchema = zod.z.string().regex(/^0x[0-9a-fA-F]{64}$/);
77
123
  var priceSchema = zod.z.union([
78
124
  zod.z.bigint().positive("Price must be greater than 0"),
79
- zod.z.string().regex(/^\d+(?:\.\d+)?$/, 'Price must be in format "0.01"').refine((price) => Number(price) > 0, "Price must be greater than 0")
125
+ zod.z.string().regex(/^\d+(?:\.\d+)?$/, 'Price must be in format "0.01"').refine((price) => Number(price) > 0, "Price must be greater than 0"),
126
+ // A per-request price function; its return value is checked per request.
127
+ zod.z.custom(
128
+ (value) => typeof value === "function",
129
+ "Price must be a string, a bigint or a function of the request"
130
+ )
80
131
  ]);
81
132
  var facilitatorConfigSchema = zod.z.object({
82
133
  url: zod.z.string().refine((v) => URL.canParse(v), "Invalid facilitator URL"),
@@ -90,11 +141,25 @@ var routeConfigSchema = zod.z.object({
90
141
  acceptedTokens: zod.z.array(ethereumAddressSchema).optional(),
91
142
  description: zod.z.string().optional(),
92
143
  scheme: zod.z.enum(["exact", "upto"]).optional(),
93
- maxTimeoutSeconds: zod.z.number().int().positive().optional()
144
+ maxTimeoutSeconds: zod.z.number().int().positive().optional(),
145
+ meter: zod.z.custom(
146
+ (value) => typeof value === "function",
147
+ "meter must be a function of the response"
148
+ ).optional()
94
149
  }).refine(
150
+ (route) => route.meter === void 0 || route.scheme === "upto",
151
+ "meter only applies to upto routes"
152
+ ).refine(
95
153
  (route) => route.scheme !== "upto" || (route.maxTimeoutSeconds ?? 300) <= UPTO_MAX_TIMEOUT_SECONDS,
96
154
  `maxTimeoutSeconds must be at most ${UPTO_MAX_TIMEOUT_SECONDS} on an upto route`
97
- );
155
+ ).refine((route) => {
156
+ if (route.scheme !== "upto" || !route.acceptedTokens?.length) return true;
157
+ const usdp = PARALLEL_TOKENS[route.network]?.usdp;
158
+ if (!usdp) return true;
159
+ return route.acceptedTokens.every(
160
+ (token) => token.toLowerCase() === usdp.toLowerCase()
161
+ );
162
+ }, "acceptedTokens on an upto route may only list the network's USDp");
98
163
  var paymentMiddlewareConfigSchema = zod.z.object({
99
164
  facilitator: facilitatorConfigSchema,
100
165
  routes: zod.z.record(zod.z.string(), routeConfigSchema).refine((r) => Object.keys(r).length > 0, "At least one route is required")
@@ -144,7 +209,7 @@ function decodePaymentHeader(paymentHeader) {
144
209
  try {
145
210
  return decodeBase64(paymentHeader);
146
211
  } catch {
147
- throw new X402RuntimeError(X402_ERROR_CODES.INVALID_PAYMENT);
212
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.INVALID_PAYMENT);
148
213
  }
149
214
  }
150
215
  var FacilitatorClient = class {
@@ -164,18 +229,18 @@ var FacilitatorClient = class {
164
229
  });
165
230
  if (!response.ok) {
166
231
  const errorBody = await response.json().catch(() => null);
167
- const code = errorBody?.error ?? X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
168
- throw new X402RuntimeError(code);
232
+ const code = errorBody?.error ?? chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
233
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(code);
169
234
  }
170
235
  json = await response.json();
171
236
  } catch (e) {
172
- if (e instanceof X402RuntimeError) throw e;
173
- throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
237
+ if (e instanceof chunkJPMK5YBU_cjs.X402RuntimeError) throw e;
238
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
174
239
  }
175
240
  try {
176
241
  verifyResponseSchema.parse(json);
177
242
  } catch {
178
- throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
243
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
179
244
  }
180
245
  }
181
246
  async settle(paymentHeader, paymentRequirements, options) {
@@ -203,8 +268,8 @@ var FacilitatorClient = class {
203
268
  }
204
269
  json = await response.json();
205
270
  } catch (e) {
206
- if (e instanceof X402RuntimeError) throw e;
207
- throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
271
+ if (e instanceof chunkJPMK5YBU_cjs.X402RuntimeError) throw e;
272
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
208
273
  }
209
274
  try {
210
275
  const parsed = settleResponseSchema.parse(json);
@@ -226,7 +291,7 @@ var FacilitatorClient = class {
226
291
  error: { code: parsed.error, message: parsed.message }
227
292
  };
228
293
  } catch {
229
- throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
294
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
230
295
  }
231
296
  }
232
297
  async pay(paymentHeader) {
@@ -240,17 +305,17 @@ var FacilitatorClient = class {
240
305
  signal: AbortSignal.timeout(FACILITATOR_TIMEOUT_MS)
241
306
  });
242
307
  if (!response.ok) {
243
- throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
308
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
244
309
  }
245
310
  json = await response.json();
246
311
  } catch (e) {
247
- if (e instanceof X402RuntimeError) throw e;
248
- throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
312
+ if (e instanceof chunkJPMK5YBU_cjs.X402RuntimeError) throw e;
313
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE);
249
314
  }
250
315
  try {
251
316
  return facilitatorResponseSchema.parse(json);
252
317
  } catch {
253
- throw new X402RuntimeError(X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
318
+ throw new chunkJPMK5YBU_cjs.X402RuntimeError(chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_INVALID_RESPONSE);
254
319
  }
255
320
  }
256
321
  buildHeaders() {
@@ -279,47 +344,41 @@ async function raceTimeout(promise, ms = HANDLER_TIMEOUT_MS) {
279
344
  clearTimeout(timer);
280
345
  }
281
346
  }
282
- var PARALLEL_TOKENS = {
283
- ethereum: {
284
- usdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
285
- susdp: "0xd3a452B305C8285c0Dd7b8537665c734d3D279eF",
286
- usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
287
- frxUSD: "0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29",
288
- sfrxUSD: "0xcf62F905562626CfcDD2261162a51fd02Fc9c5b6",
289
- USDe: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3",
290
- sUSDe: "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497"
291
- },
292
- base: {
293
- usdp: "0x76A9A0062ec6712b99B4f63bD2b4270185759dd5",
294
- susdp: "0x472eD57b376fE400259FB28e5C46eB53f0E3e7E7",
295
- usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
296
- USDS: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc",
297
- sUSDS: "0x5875eEE11Cf8398102FdAd704C9E96607675467a"
298
- },
299
- avalanche: {
300
- usdp: "0x9eE1963f05553eF838604Dd39403be21ceF26AA4",
301
- susdp: "0x9d92c21205383651610f90722131655a5b8ed3e0",
302
- usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
303
- ygamiUSDC: "0x9fD32FD5e32C6B95483d36C5E724C5C5250Ce010"
304
- },
305
- hyperevm: {
306
- usdp: "0xBE65F0F410A72BeC163dC65d46c83699e957D588",
307
- susdp: "0x9B3a8f7CEC208e247d97dEE13313690977e24459",
308
- usdc: "0xb88339cb7199b77e23db6e890353e22632ba630f",
309
- USDe: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
310
- sUSDe: "0x211Cc4DD073734dA055fbF44a2b4667d5E5fE5d2"
311
- }
312
- };
313
- function getDefaultAcceptedTokens(network) {
314
- const entry = PARALLEL_TOKENS[network];
315
- if (!entry) return void 0;
316
- return [entry.usdp, entry.usdc, entry.susdp];
317
- }
318
- function getTokenDecimals(network, address) {
319
- return chains.getTokenDecimals(network, address);
320
- }
321
347
 
322
348
  // src/middleware.ts
349
+ var PRICE_FORMAT = /^\d+(?:\.\d+)?$/;
350
+ async function requestContext(adapter) {
351
+ return {
352
+ method: adapter.getMethod(),
353
+ path: adapter.getPath(),
354
+ url: adapter.getUrl(),
355
+ header: (name) => adapter.getHeader(name),
356
+ body: adapter.getBody ? await adapter.getBody() : void 0
357
+ };
358
+ }
359
+ async function resolvePrice(config, ctx) {
360
+ if (typeof config.price !== "function") return config.price;
361
+ if (!ctx) throw new chunkJPMK5YBU_cjs.X402ConfigError("A price function needs the request");
362
+ let price;
363
+ try {
364
+ price = await config.price(ctx);
365
+ } catch (cause) {
366
+ if (chunkJPMK5YBU_cjs.isPriceRejection(cause)) throw cause;
367
+ throw new chunkJPMK5YBU_cjs.X402ConfigError(
368
+ `The price function of route "${config.network}${ctx.path}" threw`,
369
+ cause
370
+ );
371
+ }
372
+ const valid = typeof price === "bigint" ? price > 0n : typeof price === "string" && PRICE_FORMAT.test(price) && Number(price) > 0;
373
+ if (!valid) {
374
+ throw new chunkJPMK5YBU_cjs.X402ConfigError(
375
+ `The price function of route "${config.network}${ctx.path}" returned ${JSON.stringify(
376
+ typeof price === "bigint" ? price.toString() : price
377
+ )} \u2014 expected a positive amount like "0.01" or a bigint`
378
+ );
379
+ }
380
+ return price;
381
+ }
323
382
  var SETTLE_AMOUNT_HEADER = "payment-settle-amount";
324
383
  var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
325
384
  function handlerTimeoutFor(config) {
@@ -348,26 +407,91 @@ var INVALID_SETTLE_AMOUNT_RESPONSE = {
348
407
  error: "INVALID_SETTLE_AMOUNT",
349
408
  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.`
350
409
  };
410
+ var INVALID_METER_RESPONSE = {
411
+ error: "INVALID_SETTLE_AMOUNT",
412
+ message: "The route's meter function failed or returned something that is not an amount; the response was discarded and nothing was charged."
413
+ };
414
+ function invalidSettleAmountResponse(reason) {
415
+ return reason === "meter" ? INVALID_METER_RESPONSE : INVALID_SETTLE_AMOUNT_RESPONSE;
416
+ }
417
+ var AMOUNT_FORMAT = /^\d+(?:\.\d+)?$/;
418
+ async function resolveCharge(verified, settleHeader, response) {
419
+ if (verified.meter) {
420
+ if (!verified.request) {
421
+ throw new chunkJPMK5YBU_cjs.X402ConfigError(
422
+ "A metered payment carries a meter but no request context"
423
+ );
424
+ }
425
+ let result;
426
+ try {
427
+ const body = await raceTimeout(
428
+ response.body(),
429
+ verified.handlerTimeoutMs
430
+ );
431
+ if (body === HANDLER_TIMEOUT) return { kind: "invalid", reason: "meter" };
432
+ result = await verified.meter({
433
+ status: response.status,
434
+ header: response.header,
435
+ body,
436
+ request: verified.request
437
+ });
438
+ } catch {
439
+ return { kind: "invalid", reason: "meter" };
440
+ }
441
+ if (result != null) {
442
+ let amount;
443
+ if (typeof result === "bigint") {
444
+ if (result < 0n) return { kind: "invalid", reason: "meter" };
445
+ amount = result;
446
+ } else if (typeof result === "string" && AMOUNT_FORMAT.test(result)) {
447
+ const fraction = result.split(".")[1] ?? "";
448
+ if (fraction.length > verified.decimals) {
449
+ return { kind: "invalid", reason: "meter" };
450
+ }
451
+ amount = parsePrice(result, verified.decimals);
452
+ } else {
453
+ return { kind: "invalid", reason: "meter" };
454
+ }
455
+ if (amount === 0n) return { kind: "skip" };
456
+ if (amount > verified.maxAmount) amount = verified.maxAmount;
457
+ return { kind: "settle", amount: amount.toString() };
458
+ }
459
+ }
460
+ return resolveSettleAmount(
461
+ settleHeader,
462
+ verified.maxAmount,
463
+ verified.decimals
464
+ );
465
+ }
351
466
  function resolveAcceptedTokens(config) {
352
467
  if (config.acceptedTokens?.length) {
353
468
  return config.acceptedTokens;
354
469
  }
355
- const defaults = getDefaultAcceptedTokens(config.network);
470
+ const defaults = config.scheme === "upto" ? getMeteredAcceptedTokens(config.network) : getDefaultAcceptedTokens(config.network);
356
471
  if (defaults) return defaults;
357
- throw new X402ConfigError(
472
+ throw new chunkJPMK5YBU_cjs.X402ConfigError(
358
473
  `acceptedTokens is required for network "${config.network}" \u2014 no defaults available for this chain`
359
474
  );
360
475
  }
361
476
  function resolveDecimals(config, asset) {
362
477
  const decimals = config.decimals ?? getTokenDecimals(config.network, asset);
363
478
  if (decimals === void 0) {
364
- throw new X402ConfigError(
479
+ throw new chunkJPMK5YBU_cjs.X402ConfigError(
365
480
  `Unknown decimals for token ${asset} on "${config.network}" \u2014 add it to the @parallel-protocol/chains catalog or set the route's \`decimals\` explicitly`
366
481
  );
367
482
  }
368
483
  return decimals;
369
484
  }
370
- function buildPaymentRequired(url, config, resolvedTokens) {
485
+ function priceInUnits(price, decimals, asset) {
486
+ const units = parsePrice(price, decimals);
487
+ if (units === 0n) {
488
+ throw new chunkJPMK5YBU_cjs.X402ConfigError(
489
+ `Price ${typeof price === "bigint" ? price.toString() : `"${price}"`} is below the precision of token ${asset} (${decimals} decimals)`
490
+ );
491
+ }
492
+ return units.toString();
493
+ }
494
+ function buildPaymentRequired(url, config, resolvedTokens, price) {
371
495
  const network = toEip155Network(config.network);
372
496
  const accepts = resolvedTokens.map((asset) => {
373
497
  const decimals = resolveDecimals(config, asset);
@@ -375,7 +499,7 @@ function buildPaymentRequired(url, config, resolvedTokens) {
375
499
  scheme: config.scheme ?? "exact",
376
500
  network,
377
501
  asset,
378
- amount: parsePrice(config.price, decimals).toString(),
502
+ amount: priceInUnits(price, decimals, asset),
379
503
  payTo: config.payTo,
380
504
  maxTimeoutSeconds: config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS,
381
505
  extra: { decimals }
@@ -421,11 +545,11 @@ function resolveDeliveredAsset(header, config, resolvedTokens) {
421
545
  const lower = target?.toLowerCase();
422
546
  return resolvedTokens.find((token) => token.toLowerCase() === lower) ?? resolvedTokens[0];
423
547
  }
424
- function buildFacilitatorRequirements(config, asset) {
548
+ function buildFacilitatorRequirements(config, asset, price) {
425
549
  const decimals = resolveDecimals(config, asset);
426
550
  const scheme = config.scheme ?? "exact";
427
551
  return {
428
- maxAmountRequired: parsePrice(config.price, decimals).toString(),
552
+ maxAmountRequired: priceInUnits(price, decimals, asset),
429
553
  asset,
430
554
  payTo: config.payTo,
431
555
  network: config.network,
@@ -436,8 +560,13 @@ function buildFacilitatorRequirements(config, asset) {
436
560
  }
437
561
  };
438
562
  }
439
- function paymentRequiredResponse(url, config, resolvedTokens, error) {
440
- const paymentRequired = buildPaymentRequired(url, config, resolvedTokens);
563
+ function paymentRequiredResponse(url, config, resolvedTokens, price, error) {
564
+ const paymentRequired = buildPaymentRequired(
565
+ url,
566
+ config,
567
+ resolvedTokens,
568
+ price
569
+ );
441
570
  if (error) paymentRequired.error = error;
442
571
  return {
443
572
  status: 402,
@@ -453,7 +582,7 @@ function createPaymentGate(config) {
453
582
  try {
454
583
  paymentMiddlewareConfigSchema.parse(config);
455
584
  } catch (cause) {
456
- throw new X402ConfigError(
585
+ throw new chunkJPMK5YBU_cjs.X402ConfigError(
457
586
  "Invalid paymentMiddleware config \u2014 check your routes, facilitator URL and payTo addresses",
458
587
  cause
459
588
  );
@@ -465,6 +594,22 @@ function createPaymentGate(config) {
465
594
  const routeConfig = matchRoute(adapter.getPath(), config.routes);
466
595
  if (!routeConfig) return { type: "pass" };
467
596
  const resolvedTokens = resolveAcceptedTokens(routeConfig);
597
+ let request = typeof routeConfig.price === "function" ? await requestContext(adapter) : void 0;
598
+ let price;
599
+ try {
600
+ price = await resolvePrice(routeConfig, request);
601
+ } catch (err) {
602
+ if (!chunkJPMK5YBU_cjs.isPriceRejection(err)) throw err;
603
+ const status = Number.isInteger(err.status) && err.status >= 400 && err.status <= 599 ? err.status : 400;
604
+ return {
605
+ type: "error",
606
+ result: {
607
+ status,
608
+ headers: { "Content-Type": "application/json" },
609
+ body: { error: err.code, message: err.message }
610
+ }
611
+ };
612
+ }
468
613
  const paymentHeader = adapter.getHeader("payment-signature") ?? adapter.getHeader("x-payment");
469
614
  if (!paymentHeader) {
470
615
  return {
@@ -472,7 +617,8 @@ function createPaymentGate(config) {
472
617
  result: paymentRequiredResponse(
473
618
  adapter.getUrl(),
474
619
  routeConfig,
475
- resolvedTokens
620
+ resolvedTokens,
621
+ price
476
622
  )
477
623
  };
478
624
  }
@@ -481,21 +627,27 @@ function createPaymentGate(config) {
481
627
  routeConfig,
482
628
  resolvedTokens
483
629
  );
484
- const requirements = buildFacilitatorRequirements(routeConfig, asset);
630
+ const requirements = buildFacilitatorRequirements(
631
+ routeConfig,
632
+ asset,
633
+ price
634
+ );
485
635
  try {
486
636
  await client.verify(paymentHeader, requirements);
487
637
  } catch (e) {
488
- const code = e instanceof X402RuntimeError ? e.code : X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
638
+ const code = e instanceof chunkJPMK5YBU_cjs.X402RuntimeError ? e.code : chunkJPMK5YBU_cjs.X402_ERROR_CODES.FACILITATOR_UNAVAILABLE;
489
639
  return {
490
640
  type: "error",
491
641
  result: paymentRequiredResponse(
492
642
  adapter.getUrl(),
493
643
  routeConfig,
494
644
  resolvedTokens,
645
+ price,
495
646
  code
496
647
  )
497
648
  };
498
649
  }
650
+ if (routeConfig.meter && !request) request = await requestContext(adapter);
499
651
  return {
500
652
  type: "verified",
501
653
  scheme: routeConfig.scheme ?? "exact",
@@ -503,7 +655,8 @@ function createPaymentGate(config) {
503
655
  decimals: resolveDecimals(routeConfig, asset),
504
656
  handlerTimeoutMs: handlerTimeoutFor(routeConfig),
505
657
  // Exact routes keep the historical two-argument client call.
506
- settle: (options) => options ? client.settle(paymentHeader, requirements, options) : client.settle(paymentHeader, requirements)
658
+ settle: (options) => options ? client.settle(paymentHeader, requirements, options) : client.settle(paymentHeader, requirements),
659
+ ...routeConfig.meter && { meter: routeConfig.meter, request }
507
660
  };
508
661
  };
509
662
  }
@@ -529,17 +682,17 @@ function createPaymentMiddleware(config) {
529
682
  if (handlerResult.status >= 200 && handlerResult.status < 300) {
530
683
  let settleOptions;
531
684
  if (gateResult.scheme === "upto") {
532
- const resolved = resolveSettleAmount(
533
- raw,
534
- gateResult.maxAmount,
535
- gateResult.decimals
536
- );
685
+ const resolved = await resolveCharge(gateResult, raw, {
686
+ status: handlerResult.status,
687
+ header: (name) => handlerResult.getResponseHeader?.(name),
688
+ body: async () => handlerResult.getResponseBody?.()
689
+ });
537
690
  if (resolved.kind === "invalid") {
538
691
  handlerResult.discardResponse();
539
692
  return {
540
693
  status: 402,
541
694
  headers: { "Content-Type": "application/json" },
542
- body: INVALID_SETTLE_AMOUNT_RESPONSE
695
+ body: invalidSettleAmountResponse(resolved.reason)
543
696
  };
544
697
  }
545
698
  if (resolved.kind === "skip") {
@@ -583,18 +736,20 @@ function createPaymentMiddleware(config) {
583
736
 
584
737
  exports.FacilitatorClient = FacilitatorClient;
585
738
  exports.HANDLER_TIMEOUT = HANDLER_TIMEOUT;
586
- exports.INVALID_SETTLE_AMOUNT_RESPONSE = INVALID_SETTLE_AMOUNT_RESPONSE;
587
739
  exports.PARALLEL_TOKENS = PARALLEL_TOKENS;
588
740
  exports.SETTLE_AMOUNT_HEADER = SETTLE_AMOUNT_HEADER;
589
- exports.X402ConfigError = X402ConfigError;
590
- exports.X402RuntimeError = X402RuntimeError;
591
- exports.X402_ERROR_CODES = X402_ERROR_CODES;
592
741
  exports.createPaymentGate = createPaymentGate;
593
742
  exports.createPaymentMiddleware = createPaymentMiddleware;
594
743
  exports.encodeBase64 = encodeBase64;
595
744
  exports.getDefaultAcceptedTokens = getDefaultAcceptedTokens;
745
+ exports.invalidSettleAmountResponse = invalidSettleAmountResponse;
746
+ exports.isJsonContentType = isJsonContentType;
747
+ exports.jsonFromResponse = jsonFromResponse;
748
+ exports.matchRoute = matchRoute;
749
+ exports.parseJsonBody = parseJsonBody;
596
750
  exports.parsePrice = parsePrice;
597
751
  exports.raceTimeout = raceTimeout;
752
+ exports.resolveCharge = resolveCharge;
598
753
  exports.resolveSettleAmount = resolveSettleAmount;
599
754
  exports.toEip155Network = toEip155Network;
600
755
  exports.validateAddress = validateAddress;
package/dist/express.cjs CHANGED
@@ -1,12 +1,38 @@
1
1
  'use strict';
2
2
 
3
- var chunkK6QETMQA_cjs = require('./chunk-K6QETMQA.cjs');
3
+ var chunkPOU7EH3E_cjs = require('./chunk-POU7EH3E.cjs');
4
+ require('./chunk-JPMK5YBU.cjs');
4
5
 
5
6
  // src/express/adapter.ts
7
+ function chunkBytes(args) {
8
+ const [chunk, encoding] = args;
9
+ if (typeof chunk === "string") {
10
+ return Buffer.from(
11
+ chunk,
12
+ typeof encoding === "string" ? encoding : "utf8"
13
+ );
14
+ }
15
+ if (chunk instanceof Uint8Array) return Buffer.from(chunk);
16
+ return void 0;
17
+ }
6
18
  function headerValue(value) {
7
19
  if (value === void 0) return void 0;
8
20
  return Array.isArray(value) ? value[0] : String(value);
9
21
  }
22
+ function writeHeadContentType(args) {
23
+ const headers = args.find((arg) => typeof arg === "object" && arg !== null);
24
+ if (!headers) return void 0;
25
+ if (Array.isArray(headers)) {
26
+ const index = headers.findIndex(
27
+ (entry, i) => i % 2 === 0 && typeof entry === "string" && entry.toLowerCase() === "content-type"
28
+ );
29
+ return index === -1 ? void 0 : headerValue(headers[index + 1]);
30
+ }
31
+ for (const [name, value] of Object.entries(headers)) {
32
+ if (name.toLowerCase() === "content-type") return headerValue(value);
33
+ }
34
+ return void 0;
35
+ }
10
36
  var TIMEOUT_BODY = JSON.stringify({
11
37
  error: "HANDLER_TIMEOUT",
12
38
  message: "The resource handler did not respond in time."
@@ -33,6 +59,11 @@ var ExpressAdapter = class {
33
59
  getUrl() {
34
60
  return `${this.req.protocol}://${this.req.headers.host}${this.req.originalUrl}`;
35
61
  }
62
+ // Whatever a body parser mounted before this middleware left on `req.body`
63
+ // (`express.json()` for JSON); `undefined` when none ran.
64
+ async getBody() {
65
+ return this.req.body;
66
+ }
36
67
  async runHandler(options) {
37
68
  const res = this.res;
38
69
  const next = this.next;
@@ -77,8 +108,8 @@ var ExpressAdapter = class {
77
108
  return originalEnd(...args);
78
109
  });
79
110
  next();
80
- const outcome = await chunkK6QETMQA_cjs.raceTimeout(endPromise, options?.timeoutMs);
81
- if (outcome === chunkK6QETMQA_cjs.HANDLER_TIMEOUT) {
111
+ const outcome = await chunkPOU7EH3E_cjs.raceTimeout(endPromise, options?.timeoutMs);
112
+ if (outcome === chunkPOU7EH3E_cjs.HANDLER_TIMEOUT) {
82
113
  discarded = true;
83
114
  return {
84
115
  status: 504,
@@ -102,6 +133,24 @@ var ExpressAdapter = class {
102
133
  setResponseHeader: (name, value) => res.setHeader(name, value),
103
134
  getResponseHeader: (name) => headerValue(res.getHeader(name)),
104
135
  removeResponseHeader: (name) => res.removeHeader(name),
136
+ // The buffered chunks are the whole response: parse them when the
137
+ // handler declared JSON.
138
+ getResponseBody: async () => {
139
+ const bytes = [];
140
+ let contentType = headerValue(res.getHeader("content-type"));
141
+ for (const [method, args] of buffered) {
142
+ if (method === "writeHead") {
143
+ contentType ??= writeHeadContentType(args);
144
+ continue;
145
+ }
146
+ const chunk = chunkBytes(args);
147
+ if (chunk) bytes.push(chunk);
148
+ }
149
+ return chunkPOU7EH3E_cjs.parseJsonBody(
150
+ contentType,
151
+ Buffer.concat(bytes).toString("utf8")
152
+ );
153
+ },
105
154
  sendResponse: () => {
106
155
  settled = true;
107
156
  restore();
@@ -121,7 +170,7 @@ var ExpressAdapter = class {
121
170
 
122
171
  // src/express/index.ts
123
172
  function paymentMiddleware(config) {
124
- const middleware = chunkK6QETMQA_cjs.createPaymentMiddleware(config);
173
+ const middleware = chunkPOU7EH3E_cjs.createPaymentMiddleware(config);
125
174
  return async (req, res, next) => {
126
175
  const result = await middleware(new ExpressAdapter(req, res, next));
127
176
  if ("pass" in result) {
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-CBzm5CtM.cjs';
2
+ import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-q_gu5wjz.cjs';
3
3
  import 'viem';
4
4
 
5
5
  declare class ExpressAdapter implements HTTPAdapter {
@@ -11,6 +11,7 @@ declare class ExpressAdapter implements HTTPAdapter {
11
11
  getMethod(): string;
12
12
  getPath(): string;
13
13
  getUrl(): string;
14
+ getBody(): Promise<unknown>;
14
15
  runHandler(options?: RunHandlerOptions): Promise<RunHandlerResult>;
15
16
  }
16
17
 
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-CBzm5CtM.js';
2
+ import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-q_gu5wjz.js';
3
3
  import 'viem';
4
4
 
5
5
  declare class ExpressAdapter implements HTTPAdapter {
@@ -11,6 +11,7 @@ declare class ExpressAdapter implements HTTPAdapter {
11
11
  getMethod(): string;
12
12
  getPath(): string;
13
13
  getUrl(): string;
14
+ getBody(): Promise<unknown>;
14
15
  runHandler(options?: RunHandlerOptions): Promise<RunHandlerResult>;
15
16
  }
16
17