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