@okxweb3/app-x402-core 0.1.2

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