@bankofai/x402-core 2.6.0-beta.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.
Files changed (74) hide show
  1. package/README.md +293 -0
  2. package/dist/cjs/assetRegistry-CL0zA4s0.d.ts +831 -0
  3. package/dist/cjs/assetRegistry-CRVM0KEs.d.ts +831 -0
  4. package/dist/cjs/client/index.d.ts +329 -0
  5. package/dist/cjs/client/index.js +712 -0
  6. package/dist/cjs/client/index.js.map +1 -0
  7. package/dist/cjs/facilitator/index.d.ts +206 -0
  8. package/dist/cjs/facilitator/index.js +625 -0
  9. package/dist/cjs/facilitator/index.js.map +1 -0
  10. package/dist/cjs/http/index.d.ts +51 -0
  11. package/dist/cjs/http/index.js +1178 -0
  12. package/dist/cjs/http/index.js.map +1 -0
  13. package/dist/cjs/index.d.ts +13 -0
  14. package/dist/cjs/index.js +250 -0
  15. package/dist/cjs/index.js.map +1 -0
  16. package/dist/cjs/mechanisms-q7I6xfUE.d.ts +726 -0
  17. package/dist/cjs/schemas/index.d.ts +825 -0
  18. package/dist/cjs/schemas/index.js +212 -0
  19. package/dist/cjs/schemas/index.js.map +1 -0
  20. package/dist/cjs/server/index.d.ts +2 -0
  21. package/dist/cjs/server/index.js +1782 -0
  22. package/dist/cjs/server/index.js.map +1 -0
  23. package/dist/cjs/types/index.d.ts +1 -0
  24. package/dist/cjs/types/index.js +72 -0
  25. package/dist/cjs/types/index.js.map +1 -0
  26. package/dist/cjs/types/v1/index.d.ts +1 -0
  27. package/dist/cjs/types/v1/index.js +19 -0
  28. package/dist/cjs/types/v1/index.js.map +1 -0
  29. package/dist/cjs/utils/index.d.ts +48 -0
  30. package/dist/cjs/utils/index.js +116 -0
  31. package/dist/cjs/utils/index.js.map +1 -0
  32. package/dist/cjs/x402HTTPResourceServer-BFVo1_74.d.ts +433 -0
  33. package/dist/cjs/x402HTTPResourceServer-DaU2yFzy.d.ts +434 -0
  34. package/dist/cjs/x402HTTPResourceServer-DswI2hZQ.d.ts +434 -0
  35. package/dist/esm/assetRegistry-CRVM0KEs.d.mts +831 -0
  36. package/dist/esm/chunk-BJTO5JO5.mjs +11 -0
  37. package/dist/esm/chunk-BJTO5JO5.mjs.map +1 -0
  38. package/dist/esm/chunk-DACUCTGT.mjs +891 -0
  39. package/dist/esm/chunk-DACUCTGT.mjs.map +1 -0
  40. package/dist/esm/chunk-DFUINDLZ.mjs +221 -0
  41. package/dist/esm/chunk-DFUINDLZ.mjs.map +1 -0
  42. package/dist/esm/chunk-HRQUGJ3Y.mjs +45 -0
  43. package/dist/esm/chunk-HRQUGJ3Y.mjs.map +1 -0
  44. package/dist/esm/chunk-TDLQZ6MP.mjs +86 -0
  45. package/dist/esm/chunk-TDLQZ6MP.mjs.map +1 -0
  46. package/dist/esm/client/index.d.mts +329 -0
  47. package/dist/esm/client/index.mjs +330 -0
  48. package/dist/esm/client/index.mjs.map +1 -0
  49. package/dist/esm/facilitator/index.d.mts +206 -0
  50. package/dist/esm/facilitator/index.mjs +398 -0
  51. package/dist/esm/facilitator/index.mjs.map +1 -0
  52. package/dist/esm/http/index.d.mts +51 -0
  53. package/dist/esm/http/index.mjs +29 -0
  54. package/dist/esm/http/index.mjs.map +1 -0
  55. package/dist/esm/index.d.mts +13 -0
  56. package/dist/esm/index.mjs +14 -0
  57. package/dist/esm/index.mjs.map +1 -0
  58. package/dist/esm/schemas/index.d.mts +825 -0
  59. package/dist/esm/schemas/index.mjs +158 -0
  60. package/dist/esm/schemas/index.mjs.map +1 -0
  61. package/dist/esm/server/index.d.mts +2 -0
  62. package/dist/esm/server/index.mjs +712 -0
  63. package/dist/esm/server/index.mjs.map +1 -0
  64. package/dist/esm/types/index.d.mts +1 -0
  65. package/dist/esm/types/index.mjs +10 -0
  66. package/dist/esm/types/index.mjs.map +1 -0
  67. package/dist/esm/types/v1/index.d.mts +1 -0
  68. package/dist/esm/types/v1/index.mjs +1 -0
  69. package/dist/esm/types/v1/index.mjs.map +1 -0
  70. package/dist/esm/utils/index.d.mts +48 -0
  71. package/dist/esm/utils/index.mjs +20 -0
  72. package/dist/esm/utils/index.mjs.map +1 -0
  73. package/dist/esm/x402HTTPResourceServer-B6uf_UDm.d.mts +434 -0
  74. package/package.json +139 -0
@@ -0,0 +1,891 @@
1
+ import {
2
+ x402Version
3
+ } from "./chunk-DFUINDLZ.mjs";
4
+ import {
5
+ SettleError,
6
+ VerifyError
7
+ } from "./chunk-HRQUGJ3Y.mjs";
8
+ import {
9
+ Base64EncodedRegex,
10
+ safeBase64Decode,
11
+ safeBase64Encode
12
+ } from "./chunk-TDLQZ6MP.mjs";
13
+ import {
14
+ __require
15
+ } from "./chunk-BJTO5JO5.mjs";
16
+
17
+ // src/http/x402HTTPResourceServer.ts
18
+ var RouteConfigurationError = class extends Error {
19
+ /**
20
+ * Creates a new RouteConfigurationError with the given validation errors.
21
+ *
22
+ * @param errors - The validation errors that caused this exception.
23
+ */
24
+ constructor(errors) {
25
+ const message = `x402 Route Configuration Errors:
26
+ ${errors.map((e) => ` - ${e.message}`).join("\n")}`;
27
+ super(message);
28
+ this.name = "RouteConfigurationError";
29
+ this.errors = errors;
30
+ }
31
+ };
32
+ var x402HTTPResourceServer = class {
33
+ /**
34
+ * Creates a new x402HTTPResourceServer instance.
35
+ *
36
+ * @param ResourceServer - The core x402ResourceServer instance to use
37
+ * @param routes - Route configuration for payment-protected endpoints
38
+ */
39
+ constructor(ResourceServer, routes) {
40
+ this.compiledRoutes = [];
41
+ this.protectedRequestHooks = [];
42
+ this.ResourceServer = ResourceServer;
43
+ this.routesConfig = routes;
44
+ const normalizedRoutes = typeof routes === "object" && !("accepts" in routes) ? routes : { "*": routes };
45
+ for (const [pattern, config] of Object.entries(normalizedRoutes)) {
46
+ const parsed = this.parseRoutePattern(pattern);
47
+ this.compiledRoutes.push({
48
+ verb: parsed.verb,
49
+ regex: parsed.regex,
50
+ config
51
+ });
52
+ }
53
+ }
54
+ /**
55
+ * Get the underlying x402ResourceServer instance.
56
+ *
57
+ * @returns The underlying x402ResourceServer instance
58
+ */
59
+ get server() {
60
+ return this.ResourceServer;
61
+ }
62
+ /**
63
+ * Get the routes configuration.
64
+ *
65
+ * @returns The routes configuration
66
+ */
67
+ get routes() {
68
+ return this.routesConfig;
69
+ }
70
+ /**
71
+ * Initialize the HTTP resource server.
72
+ *
73
+ * This method initializes the underlying resource server (fetching facilitator support)
74
+ * and then validates that all route payment configurations have corresponding
75
+ * registered schemes and facilitator support.
76
+ *
77
+ * @throws RouteConfigurationError if any route's payment options don't have
78
+ * corresponding registered schemes or facilitator support
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const httpServer = new x402HTTPResourceServer(server, routes);
83
+ * await httpServer.initialize();
84
+ * ```
85
+ */
86
+ async initialize() {
87
+ await this.ResourceServer.initialize();
88
+ const errors = this.validateRouteConfiguration();
89
+ if (errors.length > 0) {
90
+ throw new RouteConfigurationError(errors);
91
+ }
92
+ }
93
+ /**
94
+ * Register a custom paywall provider for generating HTML
95
+ *
96
+ * @param provider - PaywallProvider instance
97
+ * @returns This service instance for chaining
98
+ */
99
+ registerPaywallProvider(provider) {
100
+ this.paywallProvider = provider;
101
+ return this;
102
+ }
103
+ /**
104
+ * Register a hook that runs on every request to a protected route, before payment processing.
105
+ * Hooks are executed in order of registration. The first hook to return a non-void result wins.
106
+ *
107
+ * @param hook - The request hook function
108
+ * @returns The x402HTTPResourceServer instance for chaining
109
+ */
110
+ onProtectedRequest(hook) {
111
+ this.protectedRequestHooks.push(hook);
112
+ return this;
113
+ }
114
+ /**
115
+ * Process HTTP request and return response instructions
116
+ * This is the main entry point for framework middleware
117
+ *
118
+ * @param context - HTTP request context
119
+ * @param paywallConfig - Optional paywall configuration
120
+ * @returns Process result indicating next action for middleware
121
+ */
122
+ async processHTTPRequest(context, paywallConfig) {
123
+ const { adapter, path, method } = context;
124
+ const routeConfig = this.getRouteConfig(path, method);
125
+ if (!routeConfig) {
126
+ return { type: "no-payment-required" };
127
+ }
128
+ for (const hook of this.protectedRequestHooks) {
129
+ const result = await hook(context, routeConfig);
130
+ if (result && "grantAccess" in result) {
131
+ return { type: "no-payment-required" };
132
+ }
133
+ if (result && "abort" in result) {
134
+ return {
135
+ type: "payment-error",
136
+ response: {
137
+ status: 403,
138
+ headers: { "Content-Type": "application/json" },
139
+ body: { error: result.reason }
140
+ }
141
+ };
142
+ }
143
+ }
144
+ const paymentOptions = this.normalizePaymentOptions(routeConfig);
145
+ const paymentPayload = this.extractPayment(adapter);
146
+ const resourceInfo = {
147
+ url: routeConfig.resource || context.adapter.getUrl(),
148
+ description: routeConfig.description || "",
149
+ mimeType: routeConfig.mimeType || ""
150
+ };
151
+ let requirements = await this.ResourceServer.buildPaymentRequirementsFromOptions(
152
+ paymentOptions,
153
+ context
154
+ );
155
+ let extensions = routeConfig.extensions;
156
+ if (extensions) {
157
+ extensions = this.ResourceServer.enrichExtensions(extensions, context);
158
+ }
159
+ const transportContext = { request: context };
160
+ const paymentRequired = await this.ResourceServer.createPaymentRequiredResponse(
161
+ requirements,
162
+ resourceInfo,
163
+ !paymentPayload ? "Payment required" : void 0,
164
+ extensions,
165
+ transportContext
166
+ );
167
+ if (!paymentPayload) {
168
+ const unpaidBody = routeConfig.unpaidResponseBody ? await routeConfig.unpaidResponseBody(context) : void 0;
169
+ return {
170
+ type: "payment-error",
171
+ response: this.createHTTPResponse(
172
+ paymentRequired,
173
+ this.isWebBrowser(adapter),
174
+ paywallConfig,
175
+ routeConfig.customPaywallHtml,
176
+ unpaidBody
177
+ )
178
+ };
179
+ }
180
+ try {
181
+ const matchingRequirements = this.ResourceServer.findMatchingRequirements(
182
+ paymentRequired.accepts,
183
+ paymentPayload
184
+ );
185
+ if (!matchingRequirements) {
186
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
187
+ requirements,
188
+ resourceInfo,
189
+ "No matching payment requirements",
190
+ routeConfig.extensions,
191
+ transportContext
192
+ );
193
+ return {
194
+ type: "payment-error",
195
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
196
+ };
197
+ }
198
+ const verifyResult = await this.ResourceServer.verifyPayment(
199
+ paymentPayload,
200
+ matchingRequirements
201
+ );
202
+ if (!verifyResult.isValid) {
203
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
204
+ requirements,
205
+ resourceInfo,
206
+ verifyResult.invalidReason,
207
+ routeConfig.extensions,
208
+ transportContext
209
+ );
210
+ return {
211
+ type: "payment-error",
212
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
213
+ };
214
+ }
215
+ return {
216
+ type: "payment-verified",
217
+ paymentPayload,
218
+ paymentRequirements: matchingRequirements,
219
+ declaredExtensions: routeConfig.extensions
220
+ };
221
+ } catch (error) {
222
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
223
+ requirements,
224
+ resourceInfo,
225
+ error instanceof Error ? error.message : "Payment verification failed",
226
+ routeConfig.extensions,
227
+ transportContext
228
+ );
229
+ return {
230
+ type: "payment-error",
231
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
232
+ };
233
+ }
234
+ }
235
+ /**
236
+ * Process settlement after successful response
237
+ *
238
+ * @param paymentPayload - The verified payment payload
239
+ * @param requirements - The matching payment requirements
240
+ * @param declaredExtensions - Optional declared extensions (for per-key enrichment)
241
+ * @param transportContext - Optional HTTP transport context
242
+ * @returns ProcessSettleResultResponse - SettleResponse with headers if success or errorReason if failure
243
+ */
244
+ async processSettlement(paymentPayload, requirements, declaredExtensions, transportContext) {
245
+ try {
246
+ const settleResponse = await this.ResourceServer.settlePayment(
247
+ paymentPayload,
248
+ requirements,
249
+ declaredExtensions,
250
+ transportContext
251
+ );
252
+ if (!settleResponse.success) {
253
+ const failure = {
254
+ ...settleResponse,
255
+ success: false,
256
+ errorReason: settleResponse.errorReason || "Settlement failed",
257
+ errorMessage: settleResponse.errorMessage || settleResponse.errorReason || "Settlement failed",
258
+ headers: this.createSettlementHeaders(settleResponse)
259
+ };
260
+ const response = await this.buildSettlementFailureResponse(failure, transportContext);
261
+ return { ...failure, response };
262
+ }
263
+ return {
264
+ ...settleResponse,
265
+ success: true,
266
+ headers: this.createSettlementHeaders(settleResponse),
267
+ requirements
268
+ };
269
+ } catch (error) {
270
+ if (error instanceof SettleError) {
271
+ const errorReason2 = error.errorReason || error.message;
272
+ const settleResponse2 = {
273
+ success: false,
274
+ errorReason: errorReason2,
275
+ errorMessage: error.errorMessage || errorReason2,
276
+ payer: error.payer,
277
+ network: error.network,
278
+ transaction: error.transaction
279
+ };
280
+ const failure2 = {
281
+ ...settleResponse2,
282
+ success: false,
283
+ errorReason: errorReason2,
284
+ headers: this.createSettlementHeaders(settleResponse2)
285
+ };
286
+ const response2 = await this.buildSettlementFailureResponse(failure2, transportContext);
287
+ return { ...failure2, response: response2 };
288
+ }
289
+ const errorReason = error instanceof Error ? error.message : "Settlement failed";
290
+ const settleResponse = {
291
+ success: false,
292
+ errorReason,
293
+ errorMessage: errorReason,
294
+ network: requirements.network,
295
+ transaction: ""
296
+ };
297
+ const failure = {
298
+ ...settleResponse,
299
+ success: false,
300
+ errorReason,
301
+ headers: this.createSettlementHeaders(settleResponse)
302
+ };
303
+ const response = await this.buildSettlementFailureResponse(failure, transportContext);
304
+ return { ...failure, response };
305
+ }
306
+ }
307
+ /**
308
+ * Check if a request requires payment based on route configuration
309
+ *
310
+ * @param context - HTTP request context
311
+ * @returns True if the route requires payment, false otherwise
312
+ */
313
+ requiresPayment(context) {
314
+ const routeConfig = this.getRouteConfig(context.path, context.method);
315
+ return routeConfig !== void 0;
316
+ }
317
+ /**
318
+ * Build HTTPResponseInstructions for settlement failure.
319
+ * Uses settlementFailedResponseBody hook if configured, otherwise defaults to empty body.
320
+ *
321
+ * @param failure - Settlement failure result with headers
322
+ * @param transportContext - Optional HTTP transport context for the request
323
+ * @returns HTTP response instructions for the 402 settlement failure response
324
+ */
325
+ async buildSettlementFailureResponse(failure, transportContext) {
326
+ const settlementHeaders = failure.headers;
327
+ const routeConfig = transportContext ? this.getRouteConfig(transportContext.request.path, transportContext.request.method) : void 0;
328
+ const customBody = routeConfig?.settlementFailedResponseBody ? await routeConfig.settlementFailedResponseBody(transportContext.request, failure) : void 0;
329
+ const contentType = customBody ? customBody.contentType : "application/json";
330
+ const body = customBody ? customBody.body : {};
331
+ return {
332
+ status: 402,
333
+ headers: {
334
+ "Content-Type": contentType,
335
+ ...settlementHeaders
336
+ },
337
+ body,
338
+ isHtml: contentType.includes("text/html")
339
+ };
340
+ }
341
+ /**
342
+ * Normalizes a RouteConfig's accepts field into an array of PaymentOptions
343
+ * Handles both single PaymentOption and array formats
344
+ *
345
+ * @param routeConfig - Route configuration
346
+ * @returns Array of payment options
347
+ */
348
+ normalizePaymentOptions(routeConfig) {
349
+ return Array.isArray(routeConfig.accepts) ? routeConfig.accepts : [routeConfig.accepts];
350
+ }
351
+ /**
352
+ * Validates that all payment options in routes have corresponding registered schemes
353
+ * and facilitator support.
354
+ *
355
+ * @returns Array of validation errors (empty if all routes are valid)
356
+ */
357
+ validateRouteConfiguration() {
358
+ const errors = [];
359
+ const normalizedRoutes = typeof this.routesConfig === "object" && !("accepts" in this.routesConfig) ? Object.entries(this.routesConfig) : [["*", this.routesConfig]];
360
+ for (const [pattern, config] of normalizedRoutes) {
361
+ const paymentOptions = this.normalizePaymentOptions(config);
362
+ for (const option of paymentOptions) {
363
+ if (!this.ResourceServer.hasRegisteredScheme(option.network, option.scheme)) {
364
+ errors.push({
365
+ routePattern: pattern,
366
+ scheme: option.scheme,
367
+ network: option.network,
368
+ reason: "missing_scheme",
369
+ message: `Route "${pattern}": No scheme implementation registered for "${option.scheme}" on network "${option.network}"`
370
+ });
371
+ continue;
372
+ }
373
+ const supportedKind = this.ResourceServer.getSupportedKind(
374
+ x402Version,
375
+ option.network,
376
+ option.scheme
377
+ );
378
+ if (!supportedKind) {
379
+ errors.push({
380
+ routePattern: pattern,
381
+ scheme: option.scheme,
382
+ network: option.network,
383
+ reason: "missing_facilitator",
384
+ message: `Route "${pattern}": Facilitator does not support scheme "${option.scheme}" on network "${option.network}"`
385
+ });
386
+ }
387
+ }
388
+ }
389
+ return errors;
390
+ }
391
+ /**
392
+ * Get route configuration for a request
393
+ *
394
+ * @param path - Request path
395
+ * @param method - HTTP method
396
+ * @returns Route configuration or undefined if no match
397
+ */
398
+ getRouteConfig(path, method) {
399
+ const normalizedPath = this.normalizePath(path);
400
+ const upperMethod = method.toUpperCase();
401
+ const matchingRoute = this.compiledRoutes.find(
402
+ (route) => route.regex.test(normalizedPath) && (route.verb === "*" || route.verb === upperMethod)
403
+ );
404
+ return matchingRoute?.config;
405
+ }
406
+ /**
407
+ * Extract payment from HTTP headers (handles v1 and v2)
408
+ *
409
+ * @param adapter - HTTP adapter
410
+ * @returns Decoded payment payload or null
411
+ */
412
+ extractPayment(adapter) {
413
+ const header = adapter.getHeader("payment-signature") || adapter.getHeader("PAYMENT-SIGNATURE");
414
+ if (header) {
415
+ try {
416
+ return decodePaymentSignatureHeader(header);
417
+ } catch (error) {
418
+ console.warn("Failed to decode PAYMENT-SIGNATURE header:", error);
419
+ }
420
+ }
421
+ return null;
422
+ }
423
+ /**
424
+ * Check if request is from a web browser
425
+ *
426
+ * @param adapter - HTTP adapter
427
+ * @returns True if request appears to be from a browser
428
+ */
429
+ isWebBrowser(adapter) {
430
+ const accept = adapter.getAcceptHeader();
431
+ const userAgent = adapter.getUserAgent();
432
+ return accept.includes("text/html") && userAgent.includes("Mozilla");
433
+ }
434
+ /**
435
+ * Create HTTP response instructions from payment required
436
+ *
437
+ * @param paymentRequired - Payment requirements
438
+ * @param isWebBrowser - Whether request is from browser
439
+ * @param paywallConfig - Paywall configuration
440
+ * @param customHtml - Custom HTML template
441
+ * @param unpaidResponse - Optional custom response (content type and body) for unpaid API requests
442
+ * @returns Response instructions
443
+ */
444
+ createHTTPResponse(paymentRequired, isWebBrowser, paywallConfig, customHtml, unpaidResponse) {
445
+ const status = paymentRequired.error === "permit2_allowance_required" ? 412 : 402;
446
+ if (isWebBrowser) {
447
+ const html = this.generatePaywallHTML(paymentRequired, paywallConfig, customHtml);
448
+ return {
449
+ status,
450
+ headers: { "Content-Type": "text/html" },
451
+ body: html,
452
+ isHtml: true
453
+ };
454
+ }
455
+ const response = this.createHTTPPaymentRequiredResponse(paymentRequired);
456
+ const contentType = unpaidResponse ? unpaidResponse.contentType : "application/json";
457
+ const body = unpaidResponse ? unpaidResponse.body : {};
458
+ return {
459
+ status,
460
+ headers: {
461
+ "Content-Type": contentType,
462
+ ...response.headers
463
+ },
464
+ body
465
+ };
466
+ }
467
+ /**
468
+ * Create HTTP payment required response (v1 puts in body, v2 puts in header)
469
+ *
470
+ * @param paymentRequired - Payment required object
471
+ * @returns Headers and body for the HTTP response
472
+ */
473
+ createHTTPPaymentRequiredResponse(paymentRequired) {
474
+ return {
475
+ headers: {
476
+ "PAYMENT-REQUIRED": encodePaymentRequiredHeader(paymentRequired)
477
+ }
478
+ };
479
+ }
480
+ /**
481
+ * Create settlement response headers
482
+ *
483
+ * @param settleResponse - Settlement response
484
+ * @returns Headers to add to response
485
+ */
486
+ createSettlementHeaders(settleResponse) {
487
+ const encoded = encodePaymentResponseHeader(settleResponse);
488
+ return { "PAYMENT-RESPONSE": encoded };
489
+ }
490
+ /**
491
+ * Parse route pattern into verb and regex
492
+ *
493
+ * @param pattern - Route pattern like "GET /api/*" or "/api/[id]"
494
+ * @returns Parsed pattern with verb and regex
495
+ */
496
+ parseRoutePattern(pattern) {
497
+ const [verb, path] = pattern.includes(" ") ? pattern.split(/\s+/) : ["*", pattern];
498
+ const regex = new RegExp(
499
+ `^${path.replace(/[$()+.?^{|}]/g, "\\$&").replace(/\*/g, ".*?").replace(/\[([^\]]+)\]/g, "[^/]+").replace(/\//g, "\\/")}$`,
500
+ "i"
501
+ );
502
+ return { verb: verb.toUpperCase(), regex };
503
+ }
504
+ /**
505
+ * Normalize path for matching
506
+ *
507
+ * @param path - Raw path from request
508
+ * @returns Normalized path
509
+ */
510
+ normalizePath(path) {
511
+ const pathWithoutQuery = path.split(/[?#]/)[0];
512
+ let decodedOrRawPath;
513
+ try {
514
+ decodedOrRawPath = decodeURIComponent(pathWithoutQuery);
515
+ } catch {
516
+ decodedOrRawPath = pathWithoutQuery;
517
+ }
518
+ return decodedOrRawPath.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/(.+?)\/+$/, "$1");
519
+ }
520
+ /**
521
+ * Generate paywall HTML for browser requests
522
+ *
523
+ * @param paymentRequired - Payment required response
524
+ * @param paywallConfig - Optional paywall configuration
525
+ * @param customHtml - Optional custom HTML template
526
+ * @returns HTML string
527
+ */
528
+ generatePaywallHTML(paymentRequired, paywallConfig, customHtml) {
529
+ if (customHtml) {
530
+ return customHtml;
531
+ }
532
+ if (this.paywallProvider) {
533
+ return this.paywallProvider.generateHtml(paymentRequired, paywallConfig);
534
+ }
535
+ try {
536
+ const paywall = __require("@bankofai/x402-paywall");
537
+ const displayAmount2 = this.getDisplayAmount(paymentRequired);
538
+ const resource2 = paymentRequired.resource;
539
+ return paywall.getPaywallHtml({
540
+ amount: displayAmount2,
541
+ paymentRequired,
542
+ currentUrl: resource2?.url || paywallConfig?.currentUrl || "",
543
+ testnet: paywallConfig?.testnet ?? true,
544
+ appName: paywallConfig?.appName,
545
+ appLogo: paywallConfig?.appLogo,
546
+ sessionTokenEndpoint: paywallConfig?.sessionTokenEndpoint
547
+ });
548
+ } catch {
549
+ }
550
+ const resource = paymentRequired.resource;
551
+ const displayAmount = this.getDisplayAmount(paymentRequired);
552
+ return `
553
+ <!DOCTYPE html>
554
+ <html>
555
+ <head>
556
+ <title>Payment Required</title>
557
+ <meta charset="UTF-8">
558
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
559
+ </head>
560
+ <body>
561
+ <div style="max-width: 600px; margin: 50px auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
562
+ ${paywallConfig?.appLogo ? `<img src="${paywallConfig.appLogo}" alt="${paywallConfig.appName || "App"}" style="max-width: 200px; margin-bottom: 20px;">` : ""}
563
+ <h1>Payment Required</h1>
564
+ ${resource ? `<p><strong>Resource:</strong> ${resource.description || resource.url}</p>` : ""}
565
+ <p><strong>Amount:</strong> $${displayAmount.toFixed(2)} USDC</p>
566
+ <div id="payment-widget"
567
+ data-requirements='${JSON.stringify(paymentRequired)}'
568
+ data-app-name="${paywallConfig?.appName || ""}"
569
+ data-testnet="${paywallConfig?.testnet || false}">
570
+ <!-- Install @bankofai/x402-paywall for full wallet integration -->
571
+ <p style="margin-top: 2rem; padding: 1rem; background: #fef3c7; border-radius: 0.5rem;">
572
+ <strong>Note:</strong> Install <code>@bankofai/x402-paywall</code> for full wallet connection and payment UI.
573
+ </p>
574
+ </div>
575
+ </div>
576
+ </body>
577
+ </html>
578
+ `;
579
+ }
580
+ /**
581
+ * Extract display amount from payment requirements.
582
+ *
583
+ * @param paymentRequired - The payment required object
584
+ * @returns The display amount in decimal format
585
+ */
586
+ getDisplayAmount(paymentRequired) {
587
+ const accepts = paymentRequired.accepts;
588
+ if (accepts && accepts.length > 0) {
589
+ const firstReq = accepts[0];
590
+ if ("amount" in firstReq) {
591
+ return parseFloat(firstReq.amount) / 1e6;
592
+ }
593
+ }
594
+ return 0;
595
+ }
596
+ };
597
+
598
+ // src/http/httpFacilitatorClient.ts
599
+ var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
600
+ var GET_SUPPORTED_RETRIES = 3;
601
+ var GET_SUPPORTED_RETRY_DELAY_MS = 1e3;
602
+ var HTTPFacilitatorClient = class {
603
+ /**
604
+ * Creates a new HTTPFacilitatorClient instance.
605
+ *
606
+ * @param config - Configuration options for the facilitator client
607
+ */
608
+ constructor(config) {
609
+ this.url = config?.url || DEFAULT_FACILITATOR_URL;
610
+ this._createAuthHeaders = config?.createAuthHeaders;
611
+ }
612
+ /**
613
+ * Verify a payment with the facilitator
614
+ *
615
+ * @param paymentPayload - The payment to verify
616
+ * @param paymentRequirements - The requirements to verify against
617
+ * @returns Verification response
618
+ */
619
+ async verify(paymentPayload, paymentRequirements) {
620
+ let headers = {
621
+ "Content-Type": "application/json"
622
+ };
623
+ if (this._createAuthHeaders) {
624
+ const authHeaders = await this.createAuthHeaders("verify");
625
+ headers = { ...headers, ...authHeaders.headers };
626
+ }
627
+ const response = await fetch(`${this.url}/verify`, {
628
+ method: "POST",
629
+ headers,
630
+ body: JSON.stringify({
631
+ x402Version: paymentPayload.x402Version,
632
+ paymentPayload: this.toJsonSafe(paymentPayload),
633
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
634
+ })
635
+ });
636
+ const data = await response.json();
637
+ if (typeof data === "object" && data !== null && "isValid" in data) {
638
+ const verifyResponse = data;
639
+ if (!response.ok) {
640
+ throw new VerifyError(response.status, verifyResponse);
641
+ }
642
+ return verifyResponse;
643
+ }
644
+ throw new Error(`Facilitator verify failed (${response.status}): ${JSON.stringify(data)}`);
645
+ }
646
+ /**
647
+ * Settle a payment with the facilitator
648
+ *
649
+ * @param paymentPayload - The payment to settle
650
+ * @param paymentRequirements - The requirements for settlement
651
+ * @returns Settlement response
652
+ */
653
+ async settle(paymentPayload, paymentRequirements) {
654
+ let headers = {
655
+ "Content-Type": "application/json"
656
+ };
657
+ if (this._createAuthHeaders) {
658
+ const authHeaders = await this.createAuthHeaders("settle");
659
+ headers = { ...headers, ...authHeaders.headers };
660
+ }
661
+ const response = await fetch(`${this.url}/settle`, {
662
+ method: "POST",
663
+ headers,
664
+ body: JSON.stringify({
665
+ x402Version: paymentPayload.x402Version,
666
+ paymentPayload: this.toJsonSafe(paymentPayload),
667
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
668
+ })
669
+ });
670
+ const data = await response.json();
671
+ if (typeof data === "object" && data !== null && "success" in data) {
672
+ const settleResponse = data;
673
+ if (!response.ok) {
674
+ throw new SettleError(response.status, settleResponse);
675
+ }
676
+ return settleResponse;
677
+ }
678
+ throw new Error(`Facilitator settle failed (${response.status}): ${JSON.stringify(data)}`);
679
+ }
680
+ /**
681
+ * Get supported payment kinds and extensions from the facilitator.
682
+ * Retries with exponential backoff on 429 rate limit errors.
683
+ *
684
+ * @returns Supported payment kinds and extensions
685
+ */
686
+ async getSupported() {
687
+ let headers = {
688
+ "Content-Type": "application/json"
689
+ };
690
+ if (this._createAuthHeaders) {
691
+ const authHeaders = await this.createAuthHeaders("supported");
692
+ headers = { ...headers, ...authHeaders.headers };
693
+ }
694
+ let lastError = null;
695
+ for (let attempt = 0; attempt < GET_SUPPORTED_RETRIES; attempt++) {
696
+ const response = await fetch(`${this.url}/supported`, {
697
+ method: "GET",
698
+ headers
699
+ });
700
+ if (response.ok) {
701
+ return await response.json();
702
+ }
703
+ const errorText = await response.text().catch(() => response.statusText);
704
+ lastError = new Error(`Facilitator getSupported failed (${response.status}): ${errorText}`);
705
+ if (response.status === 429 && attempt < GET_SUPPORTED_RETRIES - 1) {
706
+ const delay = GET_SUPPORTED_RETRY_DELAY_MS * Math.pow(2, attempt);
707
+ await new Promise((resolve) => setTimeout(resolve, delay));
708
+ continue;
709
+ }
710
+ throw lastError;
711
+ }
712
+ throw lastError ?? new Error("Facilitator getSupported failed after retries");
713
+ }
714
+ /**
715
+ * Creates authentication headers for a specific path.
716
+ *
717
+ * @param path - The path to create authentication headers for (e.g., "verify", "settle", "supported")
718
+ * @returns An object containing the authentication headers for the specified path
719
+ */
720
+ async createAuthHeaders(path) {
721
+ if (this._createAuthHeaders) {
722
+ const authHeaders = await this._createAuthHeaders();
723
+ return {
724
+ headers: authHeaders[path] ?? {}
725
+ };
726
+ }
727
+ return {
728
+ headers: {}
729
+ };
730
+ }
731
+ /**
732
+ * Helper to convert objects to JSON-safe format.
733
+ * Handles BigInt and other non-JSON types.
734
+ *
735
+ * @param obj - The object to convert
736
+ * @returns The JSON-safe representation of the object
737
+ */
738
+ toJsonSafe(obj) {
739
+ return JSON.parse(
740
+ JSON.stringify(obj, (_, value) => typeof value === "bigint" ? value.toString() : value)
741
+ );
742
+ }
743
+ };
744
+
745
+ // src/http/x402HTTPClient.ts
746
+ var x402HTTPClient = class {
747
+ /**
748
+ * Creates a new x402HTTPClient instance.
749
+ *
750
+ * @param client - The underlying x402Client for payment logic
751
+ */
752
+ constructor(client) {
753
+ this.client = client;
754
+ this.paymentRequiredHooks = [];
755
+ }
756
+ /**
757
+ * Register a hook to handle 402 responses before payment.
758
+ * Hooks run in order; first to return headers wins.
759
+ *
760
+ * @param hook - The hook function to register
761
+ * @returns This instance for chaining
762
+ */
763
+ onPaymentRequired(hook) {
764
+ this.paymentRequiredHooks.push(hook);
765
+ return this;
766
+ }
767
+ /**
768
+ * Run hooks and return headers if any hook provides them.
769
+ *
770
+ * @param paymentRequired - The payment required response from the server
771
+ * @returns Headers to use for retry, or null to proceed to payment
772
+ */
773
+ async handlePaymentRequired(paymentRequired) {
774
+ for (const hook of this.paymentRequiredHooks) {
775
+ const result = await hook({ paymentRequired });
776
+ if (result?.headers) {
777
+ return result.headers;
778
+ }
779
+ }
780
+ return null;
781
+ }
782
+ /**
783
+ * Encodes a payment payload into appropriate HTTP headers based on version.
784
+ *
785
+ * @param paymentPayload - The payment payload to encode
786
+ * @returns HTTP headers containing the encoded payment signature
787
+ */
788
+ encodePaymentSignatureHeader(paymentPayload) {
789
+ switch (paymentPayload.x402Version) {
790
+ case 2:
791
+ return {
792
+ "PAYMENT-SIGNATURE": encodePaymentSignatureHeader(paymentPayload)
793
+ };
794
+ case 1:
795
+ return {
796
+ "X-PAYMENT": encodePaymentSignatureHeader(paymentPayload)
797
+ };
798
+ default:
799
+ throw new Error(
800
+ `Unsupported x402 version: ${paymentPayload.x402Version}`
801
+ );
802
+ }
803
+ }
804
+ /**
805
+ * Extracts payment required information from HTTP response.
806
+ *
807
+ * @param getHeader - Function to retrieve header value by name (case-insensitive)
808
+ * @param body - Optional response body for v1 compatibility
809
+ * @returns The payment required object
810
+ */
811
+ getPaymentRequiredResponse(getHeader, body) {
812
+ const paymentRequired = getHeader("PAYMENT-REQUIRED");
813
+ if (paymentRequired) {
814
+ return decodePaymentRequiredHeader(paymentRequired);
815
+ }
816
+ if (body && body instanceof Object && "x402Version" in body && body.x402Version === 1) {
817
+ return body;
818
+ }
819
+ throw new Error("Invalid payment required response");
820
+ }
821
+ /**
822
+ * Extracts payment settlement response from HTTP headers.
823
+ *
824
+ * @param getHeader - Function to retrieve header value by name (case-insensitive)
825
+ * @returns The settlement response object
826
+ */
827
+ getPaymentSettleResponse(getHeader) {
828
+ const paymentResponse = getHeader("PAYMENT-RESPONSE");
829
+ if (paymentResponse) {
830
+ return decodePaymentResponseHeader(paymentResponse);
831
+ }
832
+ const xPaymentResponse = getHeader("X-PAYMENT-RESPONSE");
833
+ if (xPaymentResponse) {
834
+ return decodePaymentResponseHeader(xPaymentResponse);
835
+ }
836
+ throw new Error("Payment response header not found");
837
+ }
838
+ /**
839
+ * Creates a payment payload for the given payment requirements.
840
+ * Delegates to the underlying x402Client.
841
+ *
842
+ * @param paymentRequired - The payment required response from the server
843
+ * @returns Promise resolving to the payment payload
844
+ */
845
+ async createPaymentPayload(paymentRequired) {
846
+ return this.client.createPaymentPayload(paymentRequired);
847
+ }
848
+ };
849
+
850
+ // src/http/index.ts
851
+ function encodePaymentSignatureHeader(paymentPayload) {
852
+ return safeBase64Encode(JSON.stringify(paymentPayload));
853
+ }
854
+ function decodePaymentSignatureHeader(paymentSignatureHeader) {
855
+ if (!Base64EncodedRegex.test(paymentSignatureHeader)) {
856
+ throw new Error("Invalid payment signature header");
857
+ }
858
+ return JSON.parse(safeBase64Decode(paymentSignatureHeader));
859
+ }
860
+ function encodePaymentRequiredHeader(paymentRequired) {
861
+ return safeBase64Encode(JSON.stringify(paymentRequired));
862
+ }
863
+ function decodePaymentRequiredHeader(paymentRequiredHeader) {
864
+ if (!Base64EncodedRegex.test(paymentRequiredHeader)) {
865
+ throw new Error("Invalid payment required header");
866
+ }
867
+ return JSON.parse(safeBase64Decode(paymentRequiredHeader));
868
+ }
869
+ function encodePaymentResponseHeader(paymentResponse) {
870
+ return safeBase64Encode(JSON.stringify(paymentResponse));
871
+ }
872
+ function decodePaymentResponseHeader(paymentResponseHeader) {
873
+ if (!Base64EncodedRegex.test(paymentResponseHeader)) {
874
+ throw new Error("Invalid payment response header");
875
+ }
876
+ return JSON.parse(safeBase64Decode(paymentResponseHeader));
877
+ }
878
+
879
+ export {
880
+ RouteConfigurationError,
881
+ x402HTTPResourceServer,
882
+ HTTPFacilitatorClient,
883
+ encodePaymentSignatureHeader,
884
+ decodePaymentSignatureHeader,
885
+ encodePaymentRequiredHeader,
886
+ decodePaymentRequiredHeader,
887
+ encodePaymentResponseHeader,
888
+ decodePaymentResponseHeader,
889
+ x402HTTPClient
890
+ };
891
+ //# sourceMappingURL=chunk-DACUCTGT.mjs.map