@bankofai/x402-core 1.0.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 (72) hide show
  1. package/README.md +294 -0
  2. package/dist/cjs/client/index.d.ts +149 -0
  3. package/dist/cjs/client/index.js +909 -0
  4. package/dist/cjs/client/index.js.map +1 -0
  5. package/dist/cjs/facilitator/index.d.ts +206 -0
  6. package/dist/cjs/facilitator/index.js +431 -0
  7. package/dist/cjs/facilitator/index.js.map +1 -0
  8. package/dist/cjs/http/index.d.ts +50 -0
  9. package/dist/cjs/http/index.js +1452 -0
  10. package/dist/cjs/http/index.js.map +1 -0
  11. package/dist/cjs/index.d.ts +3 -0
  12. package/dist/cjs/index.js +31 -0
  13. package/dist/cjs/index.js.map +1 -0
  14. package/dist/cjs/schemas/index.d.ts +891 -0
  15. package/dist/cjs/schemas/index.js +216 -0
  16. package/dist/cjs/schemas/index.js.map +1 -0
  17. package/dist/cjs/server/index.d.ts +79 -0
  18. package/dist/cjs/server/index.js +2568 -0
  19. package/dist/cjs/server/index.js.map +1 -0
  20. package/dist/cjs/types/index.d.ts +1 -0
  21. package/dist/cjs/types/index.js +97 -0
  22. package/dist/cjs/types/index.js.map +1 -0
  23. package/dist/cjs/types/v1/index.d.ts +1 -0
  24. package/dist/cjs/types/v1/index.js +19 -0
  25. package/dist/cjs/types/v1/index.js.map +1 -0
  26. package/dist/cjs/utils/index.d.ts +77 -0
  27. package/dist/cjs/utils/index.js +179 -0
  28. package/dist/cjs/utils/index.js.map +1 -0
  29. package/dist/cjs/x402Client-BgegfQgE.d.ts +1807 -0
  30. package/dist/cjs/x402Client-TQHctrG7.d.ts +1807 -0
  31. package/dist/esm/chunk-ABS7D6VX.mjs +145 -0
  32. package/dist/esm/chunk-ABS7D6VX.mjs.map +1 -0
  33. package/dist/esm/chunk-AGOUMC4P.mjs +68 -0
  34. package/dist/esm/chunk-AGOUMC4P.mjs.map +1 -0
  35. package/dist/esm/chunk-BJTO5JO5.mjs +11 -0
  36. package/dist/esm/chunk-BJTO5JO5.mjs.map +1 -0
  37. package/dist/esm/chunk-FPXAE3OS.mjs +161 -0
  38. package/dist/esm/chunk-FPXAE3OS.mjs.map +1 -0
  39. package/dist/esm/chunk-IL77TMJL.mjs +1275 -0
  40. package/dist/esm/chunk-IL77TMJL.mjs.map +1 -0
  41. package/dist/esm/chunk-VE37GDG2.mjs +7 -0
  42. package/dist/esm/chunk-VE37GDG2.mjs.map +1 -0
  43. package/dist/esm/client/index.d.mts +149 -0
  44. package/dist/esm/client/index.mjs +504 -0
  45. package/dist/esm/client/index.mjs.map +1 -0
  46. package/dist/esm/facilitator/index.d.mts +206 -0
  47. package/dist/esm/facilitator/index.mjs +399 -0
  48. package/dist/esm/facilitator/index.mjs.map +1 -0
  49. package/dist/esm/http/index.d.mts +50 -0
  50. package/dist/esm/http/index.mjs +35 -0
  51. package/dist/esm/http/index.mjs.map +1 -0
  52. package/dist/esm/index.d.mts +3 -0
  53. package/dist/esm/index.mjs +8 -0
  54. package/dist/esm/index.mjs.map +1 -0
  55. package/dist/esm/schemas/index.d.mts +891 -0
  56. package/dist/esm/schemas/index.mjs +70 -0
  57. package/dist/esm/schemas/index.mjs.map +1 -0
  58. package/dist/esm/server/index.d.mts +79 -0
  59. package/dist/esm/server/index.mjs +1318 -0
  60. package/dist/esm/server/index.mjs.map +1 -0
  61. package/dist/esm/types/index.d.mts +1 -0
  62. package/dist/esm/types/index.mjs +14 -0
  63. package/dist/esm/types/index.mjs.map +1 -0
  64. package/dist/esm/types/v1/index.d.mts +1 -0
  65. package/dist/esm/types/v1/index.mjs +1 -0
  66. package/dist/esm/types/v1/index.mjs.map +1 -0
  67. package/dist/esm/utils/index.d.mts +77 -0
  68. package/dist/esm/utils/index.mjs +28 -0
  69. package/dist/esm/utils/index.mjs.map +1 -0
  70. package/dist/esm/x402Client-BgegfQgE.d.mts +1807 -0
  71. package/dist/esm/x402Client-TQHctrG7.d.mts +1807 -0
  72. package/package.json +142 -0
@@ -0,0 +1,1275 @@
1
+ import {
2
+ z
3
+ } from "./chunk-FPXAE3OS.mjs";
4
+ import {
5
+ x402Version
6
+ } from "./chunk-VE37GDG2.mjs";
7
+ import {
8
+ FacilitatorResponseError,
9
+ SettleError,
10
+ VerifyError
11
+ } from "./chunk-AGOUMC4P.mjs";
12
+ import {
13
+ Base64EncodedRegex,
14
+ safeBase64Decode,
15
+ safeBase64Encode
16
+ } from "./chunk-ABS7D6VX.mjs";
17
+ import {
18
+ __require
19
+ } from "./chunk-BJTO5JO5.mjs";
20
+
21
+ // src/http/x402HTTPResourceServer.ts
22
+ var SETTLEMENT_OVERRIDES_HEADER = "Settlement-Overrides";
23
+ function checkIfBazaarNeeded(routes) {
24
+ if ("accepts" in routes) {
25
+ return !!(routes.extensions && "bazaar" in routes.extensions);
26
+ }
27
+ return Object.values(routes).some((routeConfig) => {
28
+ return !!(routeConfig.extensions && "bazaar" in routeConfig.extensions);
29
+ });
30
+ }
31
+ var RouteConfigurationError = class extends Error {
32
+ /**
33
+ * Creates a new RouteConfigurationError with the given validation errors.
34
+ *
35
+ * @param errors - The validation errors that caused this exception.
36
+ */
37
+ constructor(errors) {
38
+ const message = `x402 Route Configuration Errors:
39
+ ${errors.map((e) => ` - ${e.message}`).join("\n")}`;
40
+ super(message);
41
+ this.name = "RouteConfigurationError";
42
+ this.errors = errors;
43
+ }
44
+ };
45
+ var FALLBACK_PAYWALL_HTML = `<!DOCTYPE html>
46
+ <html>
47
+ <head>
48
+ <title>Payment Required</title>
49
+ <meta charset="UTF-8">
50
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
51
+ </head>
52
+ <body>
53
+ <div style="max-width: 600px; margin: 50px auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
54
+ <h1>Payment Required</h1>
55
+ <p>This resource is protected by the x402 payment protocol.</p>
56
+ <p style="margin-top: 2rem; padding: 1rem; background: #fef3c7; border-radius: 0.5rem;">
57
+ <strong>Note to developers:</strong> install <code>@bankofai/x402-paywall</code> to enable
58
+ the in-browser wallet connection and payment UI. Programmatic clients should read
59
+ the payment requirements from the 402 response headers and JSON body.
60
+ </p>
61
+ </div>
62
+ </body>
63
+ </html>`;
64
+ var x402HTTPResourceServer = class {
65
+ /**
66
+ * Creates a new x402HTTPResourceServer instance.
67
+ *
68
+ * @param ResourceServer - The core x402ResourceServer instance to use
69
+ * @param routes - Route configuration for payment-protected endpoints
70
+ */
71
+ constructor(ResourceServer, routes) {
72
+ this.compiledRoutes = [];
73
+ this.protectedRequestHooks = [];
74
+ this.ResourceServer = ResourceServer;
75
+ this.routesConfig = routes;
76
+ const normalizedRoutes = typeof routes === "object" && !("accepts" in routes) ? routes : { "*": routes };
77
+ for (const [pattern, config] of Object.entries(normalizedRoutes)) {
78
+ const parsed = this.parseRoutePattern(pattern);
79
+ this.compiledRoutes.push({
80
+ verb: parsed.verb,
81
+ regex: parsed.regex,
82
+ config,
83
+ pattern: parsed.path
84
+ });
85
+ }
86
+ }
87
+ /**
88
+ * Get the underlying x402ResourceServer instance.
89
+ *
90
+ * @returns The underlying x402ResourceServer instance
91
+ */
92
+ get server() {
93
+ return this.ResourceServer;
94
+ }
95
+ /**
96
+ * Get the routes configuration.
97
+ *
98
+ * @returns The routes configuration
99
+ */
100
+ get routes() {
101
+ return this.routesConfig;
102
+ }
103
+ /**
104
+ * Initialize the HTTP resource server.
105
+ *
106
+ * This method initializes the underlying resource server (fetching facilitator support)
107
+ * and then validates that all route payment configurations have corresponding
108
+ * registered schemes and facilitator support.
109
+ *
110
+ * @throws RouteConfigurationError if any route's payment options don't have
111
+ * corresponding registered schemes or facilitator support
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const httpServer = new x402HTTPResourceServer(server, routes);
116
+ * await httpServer.initialize();
117
+ * ```
118
+ */
119
+ async initialize() {
120
+ await this.ResourceServer.initialize();
121
+ const errors = this.validateRouteConfiguration();
122
+ if (errors.length > 0) {
123
+ throw new RouteConfigurationError(errors);
124
+ }
125
+ }
126
+ /**
127
+ * Register a custom paywall provider for generating HTML
128
+ *
129
+ * @param provider - PaywallProvider instance
130
+ * @returns This service instance for chaining
131
+ */
132
+ registerPaywallProvider(provider) {
133
+ this.paywallProvider = provider;
134
+ return this;
135
+ }
136
+ /**
137
+ * Register a hook that runs on every request to a protected route, before payment processing.
138
+ * Hooks are executed in order of registration. The first hook to return a non-void result wins.
139
+ *
140
+ * @param hook - The request hook function
141
+ * @returns The x402HTTPResourceServer instance for chaining
142
+ */
143
+ onProtectedRequest(hook) {
144
+ this.protectedRequestHooks.push(hook);
145
+ return this;
146
+ }
147
+ /**
148
+ * Process HTTP request and return response instructions
149
+ * This is the main entry point for framework middleware
150
+ *
151
+ * @param context - HTTP request context
152
+ * @param paywallConfig - Optional paywall configuration
153
+ * @returns Process result indicating next action for middleware
154
+ */
155
+ async processHTTPRequest(context, paywallConfig) {
156
+ const method = context.method || context.adapter.getMethod();
157
+ context = { ...context, method };
158
+ const { adapter, path } = context;
159
+ const routeMatch = this.getRouteConfig(path, method);
160
+ if (!routeMatch) {
161
+ return { type: "no-payment-required" };
162
+ }
163
+ const { config: routeConfig, pattern: routePattern } = routeMatch;
164
+ const enrichedContext = { ...context, routePattern };
165
+ for (const hook of this.getProtectedRequestHooks(routeConfig)) {
166
+ const result = await hook(enrichedContext, routeConfig);
167
+ if (result && "grantAccess" in result) {
168
+ return { type: "no-payment-required" };
169
+ }
170
+ if (result && "abort" in result) {
171
+ return {
172
+ type: "payment-error",
173
+ response: {
174
+ status: 403,
175
+ headers: { "Content-Type": "application/json" },
176
+ body: { error: result.reason }
177
+ }
178
+ };
179
+ }
180
+ }
181
+ const paymentOptions = this.normalizePaymentOptions(routeConfig);
182
+ const paymentPayload = this.extractPayment(adapter);
183
+ const resourceInfo = {
184
+ url: routeConfig.resource || enrichedContext.adapter.getUrl(),
185
+ description: routeConfig.description || "",
186
+ mimeType: routeConfig.mimeType || "",
187
+ ...routeConfig.serviceName !== void 0 && { serviceName: routeConfig.serviceName },
188
+ ...routeConfig.tags !== void 0 && { tags: routeConfig.tags },
189
+ ...routeConfig.iconUrl !== void 0 && { iconUrl: routeConfig.iconUrl }
190
+ };
191
+ let requirements = await this.ResourceServer.buildPaymentRequirementsFromOptions(
192
+ paymentOptions,
193
+ enrichedContext
194
+ );
195
+ let extensions = routeConfig.extensions;
196
+ if (extensions) {
197
+ extensions = this.ResourceServer.enrichExtensions(extensions, enrichedContext);
198
+ }
199
+ const transportContext = { request: enrichedContext };
200
+ const paymentRequired = await this.ResourceServer.createPaymentRequiredResponse(
201
+ requirements,
202
+ resourceInfo,
203
+ !paymentPayload ? "Payment required" : void 0,
204
+ extensions,
205
+ transportContext
206
+ );
207
+ if (!paymentPayload) {
208
+ const unpaidBody = routeConfig.unpaidResponseBody ? await routeConfig.unpaidResponseBody(enrichedContext) : void 0;
209
+ return {
210
+ type: "payment-error",
211
+ response: this.createHTTPResponse(
212
+ paymentRequired,
213
+ this.isWebBrowser(adapter),
214
+ paywallConfig,
215
+ routeConfig.customPaywallHtml,
216
+ unpaidBody
217
+ )
218
+ };
219
+ }
220
+ try {
221
+ const matchingRequirements = this.ResourceServer.findMatchingRequirements(
222
+ paymentRequired.accepts,
223
+ paymentPayload
224
+ );
225
+ if (!matchingRequirements) {
226
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
227
+ requirements,
228
+ resourceInfo,
229
+ "No matching payment requirements",
230
+ extensions,
231
+ transportContext
232
+ );
233
+ return {
234
+ type: "payment-error",
235
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
236
+ };
237
+ }
238
+ const extensionResult = this.ResourceServer.validateExtensions(
239
+ paymentRequired,
240
+ paymentPayload
241
+ );
242
+ if (!extensionResult.valid) {
243
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
244
+ requirements,
245
+ resourceInfo,
246
+ extensionResult.invalidReason,
247
+ extensions,
248
+ transportContext,
249
+ paymentPayload
250
+ );
251
+ return {
252
+ type: "payment-error",
253
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
254
+ };
255
+ }
256
+ const verifyResult = await this.ResourceServer.verifyPayment(
257
+ paymentPayload,
258
+ matchingRequirements,
259
+ extensions,
260
+ transportContext
261
+ );
262
+ if (!verifyResult.isValid) {
263
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
264
+ requirements,
265
+ resourceInfo,
266
+ verifyResult.invalidReason,
267
+ extensions,
268
+ transportContext,
269
+ paymentPayload
270
+ );
271
+ return {
272
+ type: "payment-error",
273
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
274
+ };
275
+ }
276
+ if (verifyResult.skipHandler) {
277
+ return await this.processSkipHandlerSettlement(
278
+ paymentPayload,
279
+ matchingRequirements,
280
+ extensions,
281
+ transportContext,
282
+ verifyResult.skipHandler
283
+ );
284
+ }
285
+ const cancellationDispatcher = this.ResourceServer.createPaymentCancellationDispatcher(
286
+ paymentPayload,
287
+ matchingRequirements,
288
+ extensions,
289
+ transportContext
290
+ );
291
+ return {
292
+ type: "payment-verified",
293
+ cancellationDispatcher,
294
+ paymentPayload,
295
+ paymentRequirements: matchingRequirements,
296
+ declaredExtensions: extensions
297
+ };
298
+ } catch (error) {
299
+ if (error instanceof FacilitatorResponseError) {
300
+ throw error;
301
+ }
302
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
303
+ requirements,
304
+ resourceInfo,
305
+ error instanceof Error ? error.message : "Payment verification failed",
306
+ extensions,
307
+ transportContext
308
+ );
309
+ return {
310
+ type: "payment-error",
311
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
312
+ };
313
+ }
314
+ }
315
+ /**
316
+ * Process settlement after successful response
317
+ *
318
+ * @param paymentPayload - The verified payment payload
319
+ * @param requirements - The matching payment requirements
320
+ * @param declaredExtensions - Optional declared extensions (for per-key enrichment)
321
+ * @param transportContext - Optional HTTP transport context
322
+ * @param settlementOverrides - Optional settlement overrides (e.g., partial settlement amount)
323
+ * @returns ProcessSettleResultResponse - SettleResponse with headers if success or errorReason if failure
324
+ */
325
+ async processSettlement(paymentPayload, requirements, declaredExtensions, transportContext, settlementOverrides) {
326
+ if (transportContext?.request && !transportContext.request.method) {
327
+ transportContext = {
328
+ ...transportContext,
329
+ request: {
330
+ ...transportContext.request,
331
+ method: transportContext.request.adapter.getMethod()
332
+ }
333
+ };
334
+ }
335
+ try {
336
+ let resolvedOverrides = settlementOverrides;
337
+ if (!resolvedOverrides && transportContext?.responseHeaders) {
338
+ const overridesKey = SETTLEMENT_OVERRIDES_HEADER.toLowerCase();
339
+ const rawValue = Object.entries(transportContext.responseHeaders).find(
340
+ ([key]) => key.toLowerCase() === overridesKey
341
+ )?.[1];
342
+ if (rawValue) {
343
+ try {
344
+ resolvedOverrides = JSON.parse(rawValue);
345
+ } catch {
346
+ }
347
+ }
348
+ }
349
+ const settleResponse = await this.ResourceServer.settlePayment(
350
+ paymentPayload,
351
+ requirements,
352
+ declaredExtensions,
353
+ transportContext,
354
+ resolvedOverrides
355
+ );
356
+ if (!settleResponse.success) {
357
+ const failure = {
358
+ ...settleResponse,
359
+ success: false,
360
+ errorReason: settleResponse.errorReason || "Settlement failed",
361
+ errorMessage: settleResponse.errorMessage || settleResponse.errorReason || "Settlement failed",
362
+ headers: this.createSettlementHeaders(settleResponse)
363
+ };
364
+ const response = await this.buildSettlementFailureResponse(failure, transportContext);
365
+ return { ...failure, response };
366
+ }
367
+ return {
368
+ ...settleResponse,
369
+ success: true,
370
+ headers: this.createSettlementHeaders(settleResponse),
371
+ requirements
372
+ };
373
+ } catch (error) {
374
+ if (error instanceof FacilitatorResponseError) {
375
+ throw error;
376
+ }
377
+ if (error instanceof SettleError) {
378
+ const errorReason2 = error.errorReason || error.message;
379
+ const settleResponse2 = {
380
+ success: false,
381
+ errorReason: errorReason2,
382
+ errorMessage: error.errorMessage || errorReason2,
383
+ payer: error.payer,
384
+ network: error.network,
385
+ transaction: error.transaction
386
+ };
387
+ const failure2 = {
388
+ ...settleResponse2,
389
+ success: false,
390
+ errorReason: errorReason2,
391
+ headers: this.createSettlementHeaders(settleResponse2)
392
+ };
393
+ const response2 = await this.buildSettlementFailureResponse(failure2, transportContext);
394
+ return { ...failure2, response: response2 };
395
+ }
396
+ const errorReason = error instanceof Error ? error.message : "Settlement failed";
397
+ const settleResponse = {
398
+ success: false,
399
+ errorReason,
400
+ errorMessage: errorReason,
401
+ network: requirements.network,
402
+ transaction: ""
403
+ };
404
+ const failure = {
405
+ ...settleResponse,
406
+ success: false,
407
+ errorReason,
408
+ headers: this.createSettlementHeaders(settleResponse)
409
+ };
410
+ const response = await this.buildSettlementFailureResponse(failure, transportContext);
411
+ return { ...failure, response };
412
+ }
413
+ }
414
+ /**
415
+ * Check if a request requires payment based on route configuration
416
+ *
417
+ * @param context - HTTP request context
418
+ * @returns True if the route requires payment, false otherwise
419
+ */
420
+ requiresPayment(context) {
421
+ const method = context.method || context.adapter.getMethod();
422
+ return this.getRouteConfig(context.path, method) !== void 0;
423
+ }
424
+ /**
425
+ * Settle a verified payment that requested `skipHandler`, packaging the
426
+ * result as a `payment-error` HTTPProcessResult so framework adapters can
427
+ * write the response without invoking the route handler.
428
+ *
429
+ * - On success: status 200 + PAYMENT-RESPONSE header + configured body.
430
+ * - On failure: the standard 402 settlement-failure response.
431
+ *
432
+ * @param paymentPayload - Verified payment payload.
433
+ * @param requirements - Matched payment requirements.
434
+ * @param declaredExtensions - Optional declared extensions for the route.
435
+ * @param transportContext - Optional HTTP transport context.
436
+ * @param skipHandlerResponse - Optional content type + body to return on success.
437
+ * @returns A `payment-error` HTTPProcessResult carrying the final response.
438
+ */
439
+ async processSkipHandlerSettlement(paymentPayload, requirements, declaredExtensions, transportContext, skipHandlerResponse) {
440
+ const settleResult = await this.processSettlement(
441
+ paymentPayload,
442
+ requirements,
443
+ declaredExtensions,
444
+ transportContext
445
+ );
446
+ if (!settleResult.success) {
447
+ return { type: "payment-error", response: settleResult.response };
448
+ }
449
+ const contentType = skipHandlerResponse?.contentType ?? "application/json";
450
+ const body = skipHandlerResponse?.body ?? {};
451
+ return {
452
+ type: "payment-error",
453
+ response: {
454
+ status: 200,
455
+ headers: {
456
+ "Content-Type": contentType,
457
+ ...settleResult.headers
458
+ },
459
+ body,
460
+ isHtml: contentType.includes("text/html")
461
+ }
462
+ };
463
+ }
464
+ /**
465
+ * Build HTTPResponseInstructions for settlement failure.
466
+ * Uses settlementFailedResponseBody hook if configured, otherwise defaults to empty body.
467
+ *
468
+ * @param failure - Settlement failure result with headers
469
+ * @param transportContext - Optional HTTP transport context for the request
470
+ * @returns HTTP response instructions for the 402 settlement failure response
471
+ */
472
+ async buildSettlementFailureResponse(failure, transportContext) {
473
+ const settlementHeaders = failure.headers;
474
+ const routeConfig = transportContext ? this.getRouteConfig(transportContext.request.path, transportContext.request.method) : void 0;
475
+ const customBody = routeConfig?.config.settlementFailedResponseBody ? await routeConfig.config.settlementFailedResponseBody(transportContext.request, failure) : void 0;
476
+ const contentType = customBody ? customBody.contentType : "application/json";
477
+ const body = customBody ? customBody.body : {};
478
+ return {
479
+ status: 402,
480
+ headers: {
481
+ "Content-Type": contentType,
482
+ ...settlementHeaders
483
+ },
484
+ body,
485
+ isHtml: contentType.includes("text/html")
486
+ };
487
+ }
488
+ /**
489
+ * Normalizes a RouteConfig's accepts field into an array of PaymentOptions
490
+ * Handles both single PaymentOption and array formats
491
+ *
492
+ * @param routeConfig - Route configuration
493
+ * @returns Array of payment options
494
+ */
495
+ normalizePaymentOptions(routeConfig) {
496
+ return Array.isArray(routeConfig.accepts) ? routeConfig.accepts : [routeConfig.accepts];
497
+ }
498
+ /**
499
+ * Manual request hooks run before extension transport hooks for declared extensions.
500
+ *
501
+ * @param routeConfig - Route configuration for the matched request
502
+ * @returns Hooks in invocation order
503
+ */
504
+ getProtectedRequestHooks(routeConfig) {
505
+ const hooks = [...this.protectedRequestHooks];
506
+ const declaredExtensions = routeConfig.extensions;
507
+ if (!declaredExtensions) return hooks;
508
+ for (const extension of this.ResourceServer.getExtensions()) {
509
+ const hook = extension.transportHooks?.http?.onProtectedRequest;
510
+ if (!hook || !(extension.key in declaredExtensions)) continue;
511
+ hooks.push(
512
+ (context, routeConfig2) => hook(declaredExtensions[extension.key], context, routeConfig2)
513
+ );
514
+ }
515
+ return hooks;
516
+ }
517
+ /**
518
+ * Validates that all payment options in routes have corresponding registered schemes
519
+ * and facilitator support.
520
+ *
521
+ * @returns Array of validation errors (empty if all routes are valid)
522
+ */
523
+ validateRouteConfiguration() {
524
+ const errors = [];
525
+ const normalizedRoutes = typeof this.routesConfig === "object" && !("accepts" in this.routesConfig) ? Object.entries(this.routesConfig) : [["*", this.routesConfig]];
526
+ for (const [pattern, config] of normalizedRoutes) {
527
+ const pathPart = pattern.includes(" ") ? pattern.split(/\s+/)[1] : pattern;
528
+ if (pathPart && pathPart.includes("*") && config.extensions && "bazaar" in config.extensions) {
529
+ console.warn(
530
+ `[x402] Route "${pattern}": Wildcard (*) patterns with bazaar discovery extensions will auto-generate parameter names (var1, var2, ...). Consider using named parameters instead (e.g. /weather/:city) for better discovery metadata.`
531
+ );
532
+ }
533
+ const paymentOptions = this.normalizePaymentOptions(config);
534
+ for (const option of paymentOptions) {
535
+ if (!this.ResourceServer.hasRegisteredScheme(option.network, option.scheme)) {
536
+ errors.push({
537
+ routePattern: pattern,
538
+ scheme: option.scheme,
539
+ network: option.network,
540
+ reason: "missing_scheme",
541
+ message: `Route "${pattern}": No scheme implementation registered for "${option.scheme}" on network "${option.network}"`
542
+ });
543
+ continue;
544
+ }
545
+ const supportedKind = this.ResourceServer.getSupportedKind(
546
+ x402Version,
547
+ option.network,
548
+ option.scheme
549
+ );
550
+ if (!supportedKind) {
551
+ errors.push({
552
+ routePattern: pattern,
553
+ scheme: option.scheme,
554
+ network: option.network,
555
+ reason: "missing_facilitator",
556
+ message: `Route "${pattern}": Facilitator does not support scheme "${option.scheme}" on network "${option.network}"`
557
+ });
558
+ }
559
+ }
560
+ }
561
+ return errors;
562
+ }
563
+ /**
564
+ * Get route configuration for a request
565
+ *
566
+ * @param path - Request path
567
+ * @param method - HTTP method
568
+ * @returns Route configuration and pattern, or undefined if no match
569
+ */
570
+ getRouteConfig(path, method) {
571
+ const normalizedPath = this.normalizePath(path);
572
+ const upperMethod = method.toUpperCase();
573
+ const matchingRoute = this.compiledRoutes.find(
574
+ (route) => route.regex.test(normalizedPath) && (route.verb === "*" || route.verb === upperMethod)
575
+ );
576
+ if (!matchingRoute) return void 0;
577
+ return { config: matchingRoute.config, pattern: matchingRoute.pattern };
578
+ }
579
+ /**
580
+ * Extract payment from HTTP headers (handles v1 and v2)
581
+ *
582
+ * @param adapter - HTTP adapter
583
+ * @returns Decoded payment payload or null
584
+ */
585
+ extractPayment(adapter) {
586
+ const header = adapter.getHeader("payment-signature") || adapter.getHeader("PAYMENT-SIGNATURE");
587
+ if (header) {
588
+ try {
589
+ return decodePaymentSignatureHeader(header);
590
+ } catch (error) {
591
+ console.warn("Failed to decode PAYMENT-SIGNATURE header:", error);
592
+ }
593
+ }
594
+ return null;
595
+ }
596
+ /**
597
+ * Check if request is from a web browser
598
+ *
599
+ * @param adapter - HTTP adapter
600
+ * @returns True if request appears to be from a browser
601
+ */
602
+ isWebBrowser(adapter) {
603
+ const accept = adapter.getAcceptHeader();
604
+ const userAgent = adapter.getUserAgent();
605
+ return accept.includes("text/html") && userAgent.includes("Mozilla");
606
+ }
607
+ /**
608
+ * Create HTTP response instructions from payment required
609
+ *
610
+ * @param paymentRequired - Payment requirements
611
+ * @param isWebBrowser - Whether request is from browser
612
+ * @param paywallConfig - Paywall configuration
613
+ * @param customHtml - Custom HTML template
614
+ * @param unpaidResponse - Optional custom response (content type and body) for unpaid API requests
615
+ * @returns Response instructions
616
+ */
617
+ createHTTPResponse(paymentRequired, isWebBrowser, paywallConfig, customHtml, unpaidResponse) {
618
+ const status = paymentRequired.error === "permit2_allowance_required" ? 412 : 402;
619
+ const response = this.createHTTPPaymentRequiredResponse(paymentRequired);
620
+ if (isWebBrowser) {
621
+ const html = this.generatePaywallHTML(paymentRequired, paywallConfig, customHtml);
622
+ return {
623
+ status,
624
+ headers: {
625
+ "Content-Type": "text/html",
626
+ ...response.headers
627
+ },
628
+ body: html,
629
+ isHtml: true
630
+ };
631
+ }
632
+ const contentType = unpaidResponse ? unpaidResponse.contentType : "application/json";
633
+ const body = unpaidResponse ? unpaidResponse.body : {};
634
+ return {
635
+ status,
636
+ headers: {
637
+ "Content-Type": contentType,
638
+ ...response.headers
639
+ },
640
+ body
641
+ };
642
+ }
643
+ /**
644
+ * Create HTTP payment required response (v1 puts in body, v2 puts in header)
645
+ *
646
+ * @param paymentRequired - Payment required object
647
+ * @returns Headers and body for the HTTP response
648
+ */
649
+ createHTTPPaymentRequiredResponse(paymentRequired) {
650
+ return {
651
+ headers: {
652
+ "PAYMENT-REQUIRED": encodePaymentRequiredHeader(paymentRequired)
653
+ }
654
+ };
655
+ }
656
+ /**
657
+ * Create settlement response headers
658
+ *
659
+ * @param settleResponse - Settlement response
660
+ * @returns Headers to add to response
661
+ */
662
+ createSettlementHeaders(settleResponse) {
663
+ const encoded = encodePaymentResponseHeader(settleResponse);
664
+ return { "PAYMENT-RESPONSE": encoded };
665
+ }
666
+ /**
667
+ * Parse route pattern into verb and regex
668
+ *
669
+ * @param pattern - Route pattern like "GET /api/*", "/api/[id]", or "/api/:id"
670
+ * @returns Parsed pattern with verb and regex
671
+ */
672
+ parseRoutePattern(pattern) {
673
+ const [verb, path] = pattern.includes(" ") ? pattern.split(/\s+/) : ["*", pattern];
674
+ const regex = new RegExp(
675
+ `^${path.replace(/\\/g, "\\\\").replace(/[$()+.?^{|}]/g, "\\$&").replace(/\*/g, ".*?").replace(/\[([^\]]+)\]/g, "[^/]+").replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, "[^/]+").replace(/\//g, "\\/")}$`,
676
+ "i"
677
+ );
678
+ return { verb: verb.toUpperCase(), regex, path };
679
+ }
680
+ /**
681
+ * Normalize path for matching
682
+ *
683
+ * @param path - Raw path from request
684
+ * @returns Normalized path
685
+ */
686
+ normalizePath(path) {
687
+ const pathWithoutQuery = path.split(/[?#]/)[0];
688
+ const parts = pathWithoutQuery.split(/(%2[fF]|%5[cC])/);
689
+ const decoded = parts.map((part, i) => {
690
+ if (i % 2 === 1) return part;
691
+ try {
692
+ return decodeURIComponent(part);
693
+ } catch {
694
+ return part;
695
+ }
696
+ }).join("");
697
+ return decoded.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/(.+?)\/+$/, "$1");
698
+ }
699
+ /**
700
+ * Generate paywall HTML for browser requests
701
+ *
702
+ * @param paymentRequired - Payment required response
703
+ * @param paywallConfig - Optional paywall configuration
704
+ * @param customHtml - Optional custom HTML template
705
+ * @returns HTML string
706
+ */
707
+ generatePaywallHTML(paymentRequired, paywallConfig, customHtml) {
708
+ if (customHtml) {
709
+ return customHtml;
710
+ }
711
+ if (this.paywallProvider) {
712
+ return this.paywallProvider.generateHtml(paymentRequired, paywallConfig);
713
+ }
714
+ try {
715
+ const paywall = __require("@bankofai/x402-paywall");
716
+ const displayAmount = this.getDisplayAmount(paymentRequired);
717
+ const resource = paymentRequired.resource;
718
+ return paywall.getPaywallHtml({
719
+ amount: displayAmount,
720
+ paymentRequired,
721
+ currentUrl: resource?.url || paywallConfig?.currentUrl || "",
722
+ testnet: paywallConfig?.testnet ?? true,
723
+ appName: paywallConfig?.appName,
724
+ appLogo: paywallConfig?.appLogo,
725
+ sessionTokenEndpoint: paywallConfig?.sessionTokenEndpoint
726
+ });
727
+ } catch {
728
+ }
729
+ return FALLBACK_PAYWALL_HTML;
730
+ }
731
+ /**
732
+ * Extract display amount from payment requirements.
733
+ * Uses the registered scheme's decimal precision for the asset, falling back to 6.
734
+ *
735
+ * @param paymentRequired - The payment required object
736
+ * @returns The display amount in decimal format
737
+ */
738
+ getDisplayAmount(paymentRequired) {
739
+ const accepts = paymentRequired.accepts;
740
+ if (accepts && accepts.length > 0) {
741
+ const firstReq = accepts[0];
742
+ if ("amount" in firstReq) {
743
+ const decimals = this.ResourceServer.getAssetDecimalsForRequirements(firstReq);
744
+ return parseFloat(firstReq.amount) / 10 ** decimals;
745
+ }
746
+ }
747
+ return 0;
748
+ }
749
+ };
750
+
751
+ // src/http/httpFacilitatorClient.ts
752
+ var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
753
+ var GET_SUPPORTED_RETRIES = 3;
754
+ var GET_SUPPORTED_RETRY_DELAY_MS = 1e3;
755
+ var MAX_RETRY_DELAY_MS = 3e4;
756
+ function computeRetryDelay(retryAfter, attempt) {
757
+ let delay = null;
758
+ if (retryAfter !== null) {
759
+ const seconds = Number(retryAfter);
760
+ if (!isNaN(seconds)) {
761
+ delay = seconds * 1e3;
762
+ } else {
763
+ const retryDate = Date.parse(retryAfter);
764
+ if (!isNaN(retryDate)) {
765
+ delay = retryDate - Date.now();
766
+ }
767
+ }
768
+ }
769
+ if (delay === null || delay <= 0) {
770
+ delay = GET_SUPPORTED_RETRY_DELAY_MS * Math.pow(2, attempt);
771
+ }
772
+ return Math.min(delay, MAX_RETRY_DELAY_MS);
773
+ }
774
+ var verifyResponseSchema = z.object({
775
+ isValid: z.boolean(),
776
+ invalidReason: z.string().nullish().transform((v) => v ?? void 0),
777
+ invalidMessage: z.string().nullish().transform((v) => v ?? void 0),
778
+ payer: z.string().nullish().transform((v) => v ?? void 0),
779
+ extensions: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0),
780
+ extra: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0)
781
+ });
782
+ var settleResponseSchema = z.object({
783
+ success: z.boolean(),
784
+ errorReason: z.string().nullish().transform((v) => v ?? void 0),
785
+ errorMessage: z.string().nullish().transform((v) => v ?? void 0),
786
+ payer: z.string().nullish().transform((v) => v ?? void 0),
787
+ transaction: z.string(),
788
+ network: z.custom((value) => typeof value === "string"),
789
+ amount: z.string().nullish().transform((v) => v ?? void 0),
790
+ extensions: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0),
791
+ extra: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0)
792
+ });
793
+ var supportedKindSchema = z.object({
794
+ x402Version: z.number(),
795
+ scheme: z.string(),
796
+ network: z.custom(
797
+ (value) => typeof value === "string"
798
+ ),
799
+ extra: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0)
800
+ });
801
+ var supportedResponseSchema = z.object({
802
+ kinds: z.array(supportedKindSchema),
803
+ extensions: z.array(z.string()).default([]),
804
+ signers: z.record(z.string(), z.array(z.string())).default({})
805
+ });
806
+ function responseExcerpt(text, limit = 200) {
807
+ const compact = text.trim().replace(/\s+/g, " ");
808
+ if (!compact) {
809
+ return "<empty response>";
810
+ }
811
+ if (compact.length <= limit) {
812
+ return compact;
813
+ }
814
+ return `${compact.slice(0, limit - 3)}...`;
815
+ }
816
+ var EXTENSION_RESPONSE_LOG_FIELD_ALLOWLIST = ["status", "rejectedReason", "reason", "code"];
817
+ function logExtensionResponsesHeader(response) {
818
+ const header = response.headers.get("EXTENSION-RESPONSES");
819
+ if (!header) return;
820
+ try {
821
+ const decoded = JSON.parse(safeBase64Decode(header));
822
+ if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return;
823
+ const sanitized = {};
824
+ for (const [extensionKey, payload] of Object.entries(decoded)) {
825
+ const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
826
+ const filtered = {};
827
+ for (const key of EXTENSION_RESPONSE_LOG_FIELD_ALLOWLIST) {
828
+ if (source[key] !== void 0) {
829
+ filtered[key] = source[key];
830
+ }
831
+ }
832
+ sanitized[extensionKey] = filtered;
833
+ }
834
+ console.log(`[x402] extension responses: ${JSON.stringify(sanitized)}`);
835
+ } catch {
836
+ }
837
+ }
838
+ async function parseSuccessResponse(response, schema, operation) {
839
+ const text = await response.text();
840
+ let data;
841
+ try {
842
+ data = JSON.parse(text);
843
+ } catch {
844
+ throw new FacilitatorResponseError(
845
+ `Facilitator ${operation} returned invalid JSON: ${responseExcerpt(text)}`
846
+ );
847
+ }
848
+ const parsed = schema.safeParse(data);
849
+ if (!parsed.success) {
850
+ throw new FacilitatorResponseError(
851
+ `Facilitator ${operation} returned invalid data: ${responseExcerpt(text)}`
852
+ );
853
+ }
854
+ return parsed.data;
855
+ }
856
+ var HTTPFacilitatorClient = class {
857
+ /**
858
+ * Creates a new HTTPFacilitatorClient instance.
859
+ *
860
+ * @param config - Configuration options for the facilitator client
861
+ */
862
+ constructor(config) {
863
+ this.url = (config?.url || DEFAULT_FACILITATOR_URL).replace(/\/+$/, "");
864
+ this._createAuthHeaders = config?.createAuthHeaders;
865
+ }
866
+ /**
867
+ * Verify a payment with the facilitator
868
+ *
869
+ * @param paymentPayload - The payment to verify
870
+ * @param paymentRequirements - The requirements to verify against
871
+ * @returns Verification response
872
+ */
873
+ async verify(paymentPayload, paymentRequirements) {
874
+ let headers = {
875
+ "Content-Type": "application/json"
876
+ };
877
+ if (this._createAuthHeaders) {
878
+ const authHeaders = await this.createAuthHeaders("verify");
879
+ headers = { ...headers, ...authHeaders.headers };
880
+ }
881
+ const response = await fetch(`${this.url}/verify`, {
882
+ method: "POST",
883
+ headers,
884
+ redirect: "follow",
885
+ body: JSON.stringify({
886
+ x402Version: paymentPayload.x402Version,
887
+ paymentPayload: this.toJsonSafe(paymentPayload),
888
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
889
+ })
890
+ });
891
+ if (!response.ok) {
892
+ const text = await response.text();
893
+ let data;
894
+ try {
895
+ data = JSON.parse(text);
896
+ } catch {
897
+ throw new Error(`Facilitator verify failed (${response.status}): ${responseExcerpt(text)}`);
898
+ }
899
+ if (typeof data === "object" && data !== null && "isValid" in data) {
900
+ throw new VerifyError(response.status, data);
901
+ }
902
+ throw new Error(
903
+ `Facilitator verify failed (${response.status}): ${responseExcerpt(JSON.stringify(data))}`
904
+ );
905
+ }
906
+ const verifyResult = await parseSuccessResponse(response, verifyResponseSchema, "verify");
907
+ logExtensionResponsesHeader(response);
908
+ return verifyResult;
909
+ }
910
+ /**
911
+ * Settle a payment with the facilitator
912
+ *
913
+ * @param paymentPayload - The payment to settle
914
+ * @param paymentRequirements - The requirements for settlement
915
+ * @returns Settlement response
916
+ */
917
+ async settle(paymentPayload, paymentRequirements) {
918
+ let headers = {
919
+ "Content-Type": "application/json"
920
+ };
921
+ if (this._createAuthHeaders) {
922
+ const authHeaders = await this.createAuthHeaders("settle");
923
+ headers = { ...headers, ...authHeaders.headers };
924
+ }
925
+ const response = await fetch(`${this.url}/settle`, {
926
+ method: "POST",
927
+ headers,
928
+ redirect: "follow",
929
+ body: JSON.stringify({
930
+ x402Version: paymentPayload.x402Version,
931
+ paymentPayload: this.toJsonSafe(paymentPayload),
932
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
933
+ })
934
+ });
935
+ if (!response.ok) {
936
+ const text = await response.text();
937
+ let data;
938
+ try {
939
+ data = JSON.parse(text);
940
+ } catch {
941
+ throw new Error(`Facilitator settle failed (${response.status}): ${responseExcerpt(text)}`);
942
+ }
943
+ if (typeof data === "object" && data !== null && "success" in data) {
944
+ throw new SettleError(response.status, data);
945
+ }
946
+ throw new Error(
947
+ `Facilitator settle failed (${response.status}): ${responseExcerpt(JSON.stringify(data))}`
948
+ );
949
+ }
950
+ const settleResult = await parseSuccessResponse(response, settleResponseSchema, "settle");
951
+ logExtensionResponsesHeader(response);
952
+ return settleResult;
953
+ }
954
+ /**
955
+ * Get supported payment kinds and extensions from the facilitator.
956
+ * Retries with exponential backoff on 429 rate limit errors.
957
+ *
958
+ * @returns Supported payment kinds and extensions
959
+ */
960
+ async getSupported() {
961
+ let headers = {
962
+ "Content-Type": "application/json"
963
+ };
964
+ if (this._createAuthHeaders) {
965
+ const authHeaders = await this.createAuthHeaders("supported");
966
+ headers = { ...headers, ...authHeaders.headers };
967
+ }
968
+ let lastError = null;
969
+ for (let attempt = 0; attempt < GET_SUPPORTED_RETRIES; attempt++) {
970
+ const response = await fetch(`${this.url}/supported`, {
971
+ method: "GET",
972
+ headers,
973
+ redirect: "follow"
974
+ });
975
+ if (response.ok) {
976
+ return parseSuccessResponse(response, supportedResponseSchema, "supported");
977
+ }
978
+ const errorText = await response.text().catch(() => response.statusText);
979
+ lastError = new Error(
980
+ `Facilitator getSupported failed (${response.status}): ${responseExcerpt(errorText)}`
981
+ );
982
+ if (response.status === 429 && attempt < GET_SUPPORTED_RETRIES - 1) {
983
+ const delay = computeRetryDelay(response.headers.get("Retry-After"), attempt);
984
+ await new Promise((resolve) => setTimeout(resolve, delay));
985
+ continue;
986
+ }
987
+ throw lastError;
988
+ }
989
+ throw lastError ?? new Error("Facilitator getSupported failed after retries");
990
+ }
991
+ /**
992
+ * Creates authentication headers for a specific path.
993
+ *
994
+ * @param path - The path to create authentication headers for (e.g., "verify", "settle", "supported")
995
+ * @returns An object containing the authentication headers for the specified path
996
+ */
997
+ async createAuthHeaders(path) {
998
+ if (this._createAuthHeaders) {
999
+ const authHeaders = await this._createAuthHeaders();
1000
+ return {
1001
+ headers: authHeaders[path] ?? {}
1002
+ };
1003
+ }
1004
+ return {
1005
+ headers: {}
1006
+ };
1007
+ }
1008
+ /**
1009
+ * Helper to convert objects to JSON-safe format.
1010
+ * Handles BigInt and other non-JSON types.
1011
+ *
1012
+ * @param obj - The object to convert
1013
+ * @returns The JSON-safe representation of the object
1014
+ */
1015
+ toJsonSafe(obj) {
1016
+ return JSON.parse(
1017
+ JSON.stringify(obj, (_, value) => typeof value === "bigint" ? value.toString() : value)
1018
+ );
1019
+ }
1020
+ };
1021
+
1022
+ // src/http/x402HTTPClient.ts
1023
+ var x402HTTPClient = class {
1024
+ /**
1025
+ * Creates a new x402HTTPClient instance.
1026
+ *
1027
+ * @param client - The underlying x402Client for payment logic
1028
+ */
1029
+ constructor(client) {
1030
+ this.client = client;
1031
+ this.paymentRequiredHooks = [];
1032
+ }
1033
+ /**
1034
+ * Register a hook to handle 402 responses before payment.
1035
+ * Hooks run in order; first to return headers wins.
1036
+ *
1037
+ * @param hook - The hook function to register
1038
+ * @returns This instance for chaining
1039
+ */
1040
+ onPaymentRequired(hook) {
1041
+ this.paymentRequiredHooks.push(hook);
1042
+ return this;
1043
+ }
1044
+ /**
1045
+ * Run hooks and return headers if any hook provides them.
1046
+ *
1047
+ * @param paymentRequired - The payment required response from the server
1048
+ * @returns Headers to use for retry, or null to proceed to payment
1049
+ */
1050
+ async handlePaymentRequired(paymentRequired) {
1051
+ for (const hook of this.getPaymentRequiredHooks(paymentRequired)) {
1052
+ const result = await hook({ paymentRequired });
1053
+ if (result?.headers) {
1054
+ return result.headers;
1055
+ }
1056
+ }
1057
+ return null;
1058
+ }
1059
+ /**
1060
+ * Encodes a payment payload into appropriate HTTP headers based on version.
1061
+ *
1062
+ * @param paymentPayload - The payment payload to encode
1063
+ * @returns HTTP headers containing the encoded payment signature
1064
+ */
1065
+ encodePaymentSignatureHeader(paymentPayload) {
1066
+ switch (paymentPayload.x402Version) {
1067
+ case 2:
1068
+ return {
1069
+ "PAYMENT-SIGNATURE": encodePaymentSignatureHeader(paymentPayload)
1070
+ };
1071
+ case 1:
1072
+ return {
1073
+ "X-PAYMENT": encodePaymentSignatureHeader(paymentPayload)
1074
+ };
1075
+ default:
1076
+ throw new Error(
1077
+ `Unsupported x402 version: ${paymentPayload.x402Version}`
1078
+ );
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Extracts payment required information from HTTP response.
1083
+ *
1084
+ * @param getHeader - Function to retrieve header value by name (case-insensitive)
1085
+ * @param body - Optional response body for v1 compatibility
1086
+ * @returns The payment required object
1087
+ */
1088
+ getPaymentRequiredResponse(getHeader, body) {
1089
+ const paymentRequired = getHeader("PAYMENT-REQUIRED");
1090
+ if (paymentRequired) {
1091
+ return decodePaymentRequiredHeader(paymentRequired);
1092
+ }
1093
+ if (body && body instanceof Object && "x402Version" in body && body.x402Version === 1) {
1094
+ return body;
1095
+ }
1096
+ throw new Error("Invalid payment required response");
1097
+ }
1098
+ /**
1099
+ * Extracts payment settlement response from HTTP headers.
1100
+ *
1101
+ * @param getHeader - Function to retrieve header value by name (case-insensitive)
1102
+ * @returns The settlement response object
1103
+ */
1104
+ getPaymentSettleResponse(getHeader) {
1105
+ const paymentResponse = getHeader("PAYMENT-RESPONSE");
1106
+ if (paymentResponse) {
1107
+ return decodePaymentResponseHeader(paymentResponse);
1108
+ }
1109
+ const xPaymentResponse = getHeader("X-PAYMENT-RESPONSE");
1110
+ if (xPaymentResponse) {
1111
+ return decodePaymentResponseHeader(xPaymentResponse);
1112
+ }
1113
+ throw new Error("Payment response header not found");
1114
+ }
1115
+ /**
1116
+ * Creates a payment payload for the given payment requirements.
1117
+ * Delegates to the underlying x402Client.
1118
+ *
1119
+ * @param paymentRequired - The payment required response from the server
1120
+ * @returns Promise resolving to the payment payload
1121
+ */
1122
+ async createPaymentPayload(paymentRequired) {
1123
+ return this.client.createPaymentPayload(paymentRequired);
1124
+ }
1125
+ /**
1126
+ * Parses response headers into protocol types, fires payment response hooks (v2 only),
1127
+ * and returns whether a hook signaled recovery.
1128
+ *
1129
+ * Called by transport wrappers (fetch, axios) after the paid request completes.
1130
+ *
1131
+ * @param paymentPayload - The payload that was sent with the request
1132
+ * @param getHeader - Function to retrieve a response header by name
1133
+ * @param status - The HTTP status code of the response
1134
+ * @returns Whether a hook recovered and the parsed settle response (if any)
1135
+ */
1136
+ async processPaymentResult(paymentPayload, getHeader, status) {
1137
+ let settleResponse;
1138
+ try {
1139
+ settleResponse = this.getPaymentSettleResponse(getHeader);
1140
+ } catch {
1141
+ }
1142
+ if (paymentPayload.x402Version === 1) {
1143
+ return { recovered: false, settleResponse };
1144
+ }
1145
+ let paymentRequired;
1146
+ if (!settleResponse && status === 402) {
1147
+ try {
1148
+ paymentRequired = this.getPaymentRequiredResponse(getHeader);
1149
+ } catch {
1150
+ }
1151
+ }
1152
+ const requirements = paymentPayload.accepted;
1153
+ if (!requirements) {
1154
+ throw new Error("Invalid x402 v2 payment payload: missing `accepted`");
1155
+ }
1156
+ const ctx = {
1157
+ paymentPayload,
1158
+ requirements,
1159
+ ...settleResponse ? { settleResponse } : {},
1160
+ ...paymentRequired ? { paymentRequired } : {}
1161
+ };
1162
+ const result = await this.client.handlePaymentResponse(ctx);
1163
+ return { recovered: result?.recovered === true, settleResponse };
1164
+ }
1165
+ /**
1166
+ * Parses HTTP status, headers, and body into an `HTTPResourceResponse`.
1167
+ *
1168
+ * Decodes the x402 payment header into `header`: the `PAYMENT-RESPONSE`
1169
+ * settlement if present, otherwise the `PAYMENT-REQUIRED` declaration on
1170
+ * 402 responses (whose `error` field carries the server's failure reason).
1171
+ *
1172
+ * @param args - Normalized response inputs from any HTTP transport
1173
+ * @param args.status - HTTP response status code
1174
+ * @param args.getHeader - Callback to read response headers by name
1175
+ * @param args.body - Response body payload
1176
+ * @returns The parsed status, body, and decoded payment header
1177
+ */
1178
+ parsePaymentResult(args) {
1179
+ const { status, getHeader, body } = args;
1180
+ let header;
1181
+ try {
1182
+ header = this.getPaymentSettleResponse(getHeader);
1183
+ } catch {
1184
+ if (status === 402) {
1185
+ try {
1186
+ header = this.getPaymentRequiredResponse(getHeader, body);
1187
+ } catch {
1188
+ }
1189
+ }
1190
+ }
1191
+ let paymentStatus = "none";
1192
+ if (header && !("success" in header)) {
1193
+ paymentStatus = "payment_required";
1194
+ }
1195
+ if (header && "success" in header) {
1196
+ paymentStatus = header.success ? "settled" : "settle_failed";
1197
+ }
1198
+ return { status, paymentStatus, body, header };
1199
+ }
1200
+ /**
1201
+ * Parses a fetch Response into an `HTTPResourceResponse` for app-level convenience.
1202
+ *
1203
+ * @param response - The fetch Response to process
1204
+ * @returns The parsed status, body, and decoded payment header
1205
+ */
1206
+ async processResponse(response) {
1207
+ const getHeader = (name) => response.headers.get(name);
1208
+ const contentType = response.headers.get("content-type") ?? "";
1209
+ const body = contentType.includes("application/json") ? await response.json() : await response.text();
1210
+ return this.parsePaymentResult({ status: response.status, getHeader, body });
1211
+ }
1212
+ /**
1213
+ * Manual HTTP hooks run before extension hooks scoped to the 402 response.
1214
+ *
1215
+ * @param paymentRequired - The payment required response from the server
1216
+ * @returns Hooks in invocation order
1217
+ */
1218
+ getPaymentRequiredHooks(paymentRequired) {
1219
+ const hooks = [...this.paymentRequiredHooks];
1220
+ const declaredExtensions = paymentRequired.extensions;
1221
+ if (!declaredExtensions) return hooks;
1222
+ for (const extension of this.client.getExtensions()) {
1223
+ const httpExtension = extension;
1224
+ const hook = httpExtension.transportHooks?.http?.onPaymentRequired;
1225
+ if (!hook || !(extension.key in declaredExtensions)) continue;
1226
+ hooks.push((context) => hook(declaredExtensions[extension.key], context));
1227
+ }
1228
+ return hooks;
1229
+ }
1230
+ };
1231
+
1232
+ // src/http/index.ts
1233
+ function encodePaymentSignatureHeader(paymentPayload) {
1234
+ return safeBase64Encode(JSON.stringify(paymentPayload));
1235
+ }
1236
+ function decodePaymentSignatureHeader(paymentSignatureHeader) {
1237
+ if (!Base64EncodedRegex.test(paymentSignatureHeader)) {
1238
+ throw new Error("Invalid payment signature header");
1239
+ }
1240
+ return JSON.parse(safeBase64Decode(paymentSignatureHeader));
1241
+ }
1242
+ function encodePaymentRequiredHeader(paymentRequired) {
1243
+ return safeBase64Encode(JSON.stringify(paymentRequired));
1244
+ }
1245
+ function decodePaymentRequiredHeader(paymentRequiredHeader) {
1246
+ if (!Base64EncodedRegex.test(paymentRequiredHeader)) {
1247
+ throw new Error("Invalid payment required header");
1248
+ }
1249
+ return JSON.parse(safeBase64Decode(paymentRequiredHeader));
1250
+ }
1251
+ function encodePaymentResponseHeader(paymentResponse) {
1252
+ return safeBase64Encode(JSON.stringify(paymentResponse));
1253
+ }
1254
+ function decodePaymentResponseHeader(paymentResponseHeader) {
1255
+ if (!Base64EncodedRegex.test(paymentResponseHeader)) {
1256
+ throw new Error("Invalid payment response header");
1257
+ }
1258
+ return JSON.parse(safeBase64Decode(paymentResponseHeader));
1259
+ }
1260
+
1261
+ export {
1262
+ SETTLEMENT_OVERRIDES_HEADER,
1263
+ checkIfBazaarNeeded,
1264
+ RouteConfigurationError,
1265
+ x402HTTPResourceServer,
1266
+ HTTPFacilitatorClient,
1267
+ encodePaymentSignatureHeader,
1268
+ decodePaymentSignatureHeader,
1269
+ encodePaymentRequiredHeader,
1270
+ decodePaymentRequiredHeader,
1271
+ encodePaymentResponseHeader,
1272
+ decodePaymentResponseHeader,
1273
+ x402HTTPClient
1274
+ };
1275
+ //# sourceMappingURL=chunk-IL77TMJL.mjs.map