@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,1792 @@
1
+ import {
2
+ x402Version
3
+ } from "./chunk-O3IYMTNT.mjs";
4
+ import {
5
+ FacilitatorResponseError,
6
+ SettleError,
7
+ VerifyError
8
+ } from "./chunk-CAXWAW23.mjs";
9
+ import {
10
+ Base64EncodedRegex,
11
+ deepEqual,
12
+ findByNetworkAndScheme,
13
+ safeBase64Decode,
14
+ safeBase64Encode
15
+ } from "./chunk-TDLQZ6MP.mjs";
16
+ import {
17
+ z
18
+ } from "./chunk-CS33MEMU.mjs";
19
+
20
+ // src/http/httpFacilitatorClient.ts
21
+ var DEFAULT_FACILITATOR_URL = "https://web3.okx.com/facilitator";
22
+ var GET_SUPPORTED_RETRIES = 3;
23
+ var GET_SUPPORTED_RETRY_DELAY_MS = 1e3;
24
+ var verifyResponseSchema = z.object({
25
+ isValid: z.boolean(),
26
+ invalidReason: z.string().nullish().transform((v) => v ?? void 0),
27
+ invalidMessage: z.string().nullish().transform((v) => v ?? void 0),
28
+ payer: z.string().nullish().transform((v) => v ?? void 0),
29
+ extensions: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0)
30
+ });
31
+ var settleResponseSchema = z.object({
32
+ success: z.boolean(),
33
+ // OKX extension: pending (async), success (immediate), timeout (on-chain timed out)
34
+ status: z.enum(["pending", "success", "timeout"]).nullish().transform((v) => v ?? void 0).optional(),
35
+ errorReason: z.string().nullish().transform((v) => v ?? void 0),
36
+ errorMessage: z.string().nullish().transform((v) => v ?? void 0),
37
+ payer: z.string().nullish().transform((v) => v ?? void 0),
38
+ transaction: z.string(),
39
+ network: z.custom((value) => typeof value === "string"),
40
+ amount: z.string().nullish().transform((v) => v ?? void 0),
41
+ extensions: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0)
42
+ });
43
+ var supportedKindSchema = z.object({
44
+ x402Version: z.number(),
45
+ scheme: z.string(),
46
+ network: z.custom(
47
+ (value) => typeof value === "string"
48
+ ),
49
+ extra: z.record(z.string(), z.unknown()).nullish().transform((v) => v ?? void 0)
50
+ });
51
+ var supportedResponseSchema = z.object({
52
+ kinds: z.array(supportedKindSchema),
53
+ extensions: z.array(z.string()).default([]),
54
+ signers: z.record(z.string(), z.array(z.string())).default({})
55
+ });
56
+ function responseExcerpt(text, limit = 200) {
57
+ const compact = text.trim().replace(/\s+/g, " ");
58
+ if (!compact) {
59
+ return "<empty response>";
60
+ }
61
+ if (compact.length <= limit) {
62
+ return compact;
63
+ }
64
+ return `${compact.slice(0, limit - 3)}...`;
65
+ }
66
+ async function parseSuccessResponse(response, schema, operation) {
67
+ const text = await response.text();
68
+ let data;
69
+ try {
70
+ data = JSON.parse(text);
71
+ } catch {
72
+ throw new FacilitatorResponseError(
73
+ `Facilitator ${operation} returned invalid JSON: ${responseExcerpt(text)}`
74
+ );
75
+ }
76
+ const parsed = schema.safeParse(data);
77
+ if (!parsed.success) {
78
+ throw new FacilitatorResponseError(
79
+ `Facilitator ${operation} returned invalid data: ${responseExcerpt(text)}`
80
+ );
81
+ }
82
+ return parsed.data;
83
+ }
84
+ var HTTPFacilitatorClient = class {
85
+ /**
86
+ * Creates a new HTTPFacilitatorClient instance.
87
+ *
88
+ * @param config - Configuration options for the facilitator client
89
+ */
90
+ constructor(config) {
91
+ this.url = config?.url || DEFAULT_FACILITATOR_URL;
92
+ this._createAuthHeaders = config?.createAuthHeaders;
93
+ }
94
+ /**
95
+ * Verify a payment with the facilitator
96
+ *
97
+ * @param paymentPayload - The payment to verify
98
+ * @param paymentRequirements - The requirements to verify against
99
+ * @returns Verification response
100
+ */
101
+ async verify(paymentPayload, paymentRequirements) {
102
+ let headers = {
103
+ "Content-Type": "application/json"
104
+ };
105
+ if (this._createAuthHeaders) {
106
+ const authHeaders = await this.createAuthHeaders("verify");
107
+ headers = { ...headers, ...authHeaders.headers };
108
+ }
109
+ const response = await fetch(`${this.url}/verify`, {
110
+ method: "POST",
111
+ headers,
112
+ body: JSON.stringify({
113
+ x402Version: paymentPayload.x402Version,
114
+ paymentPayload: this.toJsonSafe(paymentPayload),
115
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
116
+ })
117
+ });
118
+ if (!response.ok) {
119
+ const text = await response.text();
120
+ let data;
121
+ try {
122
+ data = JSON.parse(text);
123
+ } catch {
124
+ throw new Error(`Facilitator verify failed (${response.status}): ${responseExcerpt(text)}`);
125
+ }
126
+ if (typeof data === "object" && data !== null && "isValid" in data) {
127
+ throw new VerifyError(response.status, data);
128
+ }
129
+ throw new Error(
130
+ `Facilitator verify failed (${response.status}): ${responseExcerpt(JSON.stringify(data))}`
131
+ );
132
+ }
133
+ return parseSuccessResponse(response, verifyResponseSchema, "verify");
134
+ }
135
+ /**
136
+ * Settle a payment with the facilitator
137
+ *
138
+ * @param paymentPayload - The payment to settle
139
+ * @param paymentRequirements - The requirements for settlement
140
+ * @returns Settlement response
141
+ */
142
+ async settle(paymentPayload, paymentRequirements) {
143
+ let headers = {
144
+ "Content-Type": "application/json"
145
+ };
146
+ if (this._createAuthHeaders) {
147
+ const authHeaders = await this.createAuthHeaders("settle");
148
+ headers = { ...headers, ...authHeaders.headers };
149
+ }
150
+ const response = await fetch(`${this.url}/settle`, {
151
+ method: "POST",
152
+ headers,
153
+ body: JSON.stringify({
154
+ x402Version: paymentPayload.x402Version,
155
+ paymentPayload: this.toJsonSafe(paymentPayload),
156
+ paymentRequirements: this.toJsonSafe(paymentRequirements)
157
+ })
158
+ });
159
+ if (!response.ok) {
160
+ const text = await response.text();
161
+ let data;
162
+ try {
163
+ data = JSON.parse(text);
164
+ } catch {
165
+ throw new Error(`Facilitator settle failed (${response.status}): ${responseExcerpt(text)}`);
166
+ }
167
+ if (typeof data === "object" && data !== null && "success" in data) {
168
+ throw new SettleError(response.status, data);
169
+ }
170
+ throw new Error(
171
+ `Facilitator settle failed (${response.status}): ${responseExcerpt(JSON.stringify(data))}`
172
+ );
173
+ }
174
+ return parseSuccessResponse(response, settleResponseSchema, "settle");
175
+ }
176
+ /**
177
+ * Get supported payment kinds and extensions from the facilitator.
178
+ * Retries with exponential backoff on 429 rate limit errors.
179
+ *
180
+ * @returns Supported payment kinds and extensions
181
+ */
182
+ async getSupported() {
183
+ let headers = {
184
+ "Content-Type": "application/json"
185
+ };
186
+ if (this._createAuthHeaders) {
187
+ const authHeaders = await this.createAuthHeaders("supported");
188
+ headers = { ...headers, ...authHeaders.headers };
189
+ }
190
+ let lastError = null;
191
+ for (let attempt = 0; attempt < GET_SUPPORTED_RETRIES; attempt++) {
192
+ const response = await fetch(`${this.url}/supported`, {
193
+ method: "GET",
194
+ headers
195
+ });
196
+ if (response.ok) {
197
+ return parseSuccessResponse(response, supportedResponseSchema, "supported");
198
+ }
199
+ const errorText = await response.text().catch(() => response.statusText);
200
+ lastError = new Error(
201
+ `Facilitator getSupported failed (${response.status}): ${responseExcerpt(errorText)}`
202
+ );
203
+ if (response.status === 429 && attempt < GET_SUPPORTED_RETRIES - 1) {
204
+ const delay = GET_SUPPORTED_RETRY_DELAY_MS * Math.pow(2, attempt);
205
+ await new Promise((resolve) => setTimeout(resolve, delay));
206
+ continue;
207
+ }
208
+ throw lastError;
209
+ }
210
+ throw lastError ?? new Error("Facilitator getSupported failed after retries");
211
+ }
212
+ /**
213
+ * Query on-chain settlement status by transaction hash.
214
+ *
215
+ * @param txHash - The transaction hash to query
216
+ * @returns Settlement status response
217
+ */
218
+ async getSettleStatus(txHash) {
219
+ let headers = {
220
+ "Content-Type": "application/json"
221
+ };
222
+ if (this._createAuthHeaders) {
223
+ const authHeaders = await this.createAuthHeaders("settle/status");
224
+ headers = { ...headers, ...authHeaders.headers };
225
+ }
226
+ const response = await fetch(`${this.url}/settle/status?txHash=${encodeURIComponent(txHash)}`, {
227
+ method: "GET",
228
+ headers
229
+ });
230
+ if (!response.ok) {
231
+ const text = await response.text().catch(() => response.statusText);
232
+ throw new Error(
233
+ `Facilitator getSettleStatus failed (${response.status}): ${responseExcerpt(text)}`
234
+ );
235
+ }
236
+ const json = await response.json();
237
+ return json;
238
+ }
239
+ /**
240
+ * Creates authentication headers for a specific path.
241
+ *
242
+ * @param path - The path to create authentication headers for (e.g., "verify", "settle", "supported")
243
+ * @returns An object containing the authentication headers for the specified path
244
+ */
245
+ async createAuthHeaders(path) {
246
+ if (this._createAuthHeaders) {
247
+ const authHeaders = await this._createAuthHeaders();
248
+ return {
249
+ headers: authHeaders[path] ?? {}
250
+ };
251
+ }
252
+ return {
253
+ headers: {}
254
+ };
255
+ }
256
+ /**
257
+ * Helper to convert objects to JSON-safe format.
258
+ * Handles BigInt and other non-JSON types.
259
+ *
260
+ * @param obj - The object to convert
261
+ * @returns The JSON-safe representation of the object
262
+ */
263
+ toJsonSafe(obj) {
264
+ return JSON.parse(
265
+ JSON.stringify(obj, (_, value) => typeof value === "bigint" ? value.toString() : value)
266
+ );
267
+ }
268
+ };
269
+
270
+ // src/server/x402ResourceServer.ts
271
+ var DEFAULT_POLL_INTERVAL_MS = 1e3;
272
+ var DEFAULT_POLL_DEADLINE_MS = 5e3;
273
+ function resolveSettlementOverrideAmount(rawAmount, requirements) {
274
+ const percentMatch = rawAmount.match(/^(\d+(?:\.\d{0,2})?)%$/);
275
+ if (percentMatch) {
276
+ const [intPart, decPart = ""] = percentMatch[1].split(".");
277
+ const scaledPercent = BigInt(intPart) * 100n + BigInt(decPart.padEnd(2, "0").slice(0, 2));
278
+ const base = BigInt(requirements.amount);
279
+ return (base * scaledPercent / 10000n).toString();
280
+ }
281
+ const dollarMatch = rawAmount.match(/^\$(\d+(?:\.\d+)?)$/);
282
+ if (dollarMatch) {
283
+ const decimals = typeof requirements.extra?.decimals === "number" ? requirements.extra.decimals : 6;
284
+ const dollars = parseFloat(dollarMatch[1]);
285
+ return Math.round(dollars * 10 ** decimals).toString();
286
+ }
287
+ return rawAmount;
288
+ }
289
+ var x402ResourceServer = class {
290
+ /**
291
+ * Creates a new x402ResourceServer instance.
292
+ *
293
+ * @param facilitatorClients - Optional facilitator client(s) for payment processing
294
+ */
295
+ constructor(facilitatorClients) {
296
+ this.registeredServerSchemes = /* @__PURE__ */ new Map();
297
+ this.supportedResponsesMap = /* @__PURE__ */ new Map();
298
+ this.facilitatorClientsMap = /* @__PURE__ */ new Map();
299
+ this.registeredExtensions = /* @__PURE__ */ new Map();
300
+ this.beforeVerifyHooks = [];
301
+ this.afterVerifyHooks = [];
302
+ this.onVerifyFailureHooks = [];
303
+ this.beforeSettleHooks = [];
304
+ this.afterSettleHooks = [];
305
+ this.onSettleFailureHooks = [];
306
+ if (!facilitatorClients) {
307
+ this.facilitatorClients = [new HTTPFacilitatorClient()];
308
+ } else if (Array.isArray(facilitatorClients)) {
309
+ this.facilitatorClients = facilitatorClients.length > 0 ? facilitatorClients : [new HTTPFacilitatorClient()];
310
+ } else {
311
+ this.facilitatorClients = [facilitatorClients];
312
+ }
313
+ }
314
+ /**
315
+ * Register a scheme/network server implementation.
316
+ *
317
+ * @param network - The network identifier
318
+ * @param server - The scheme/network server implementation
319
+ * @returns The x402ResourceServer instance for chaining
320
+ */
321
+ register(network, server) {
322
+ if (!this.registeredServerSchemes.has(network)) {
323
+ this.registeredServerSchemes.set(network, /* @__PURE__ */ new Map());
324
+ }
325
+ const serverByScheme = this.registeredServerSchemes.get(network);
326
+ if (!serverByScheme.has(server.scheme)) {
327
+ serverByScheme.set(server.scheme, server);
328
+ }
329
+ return this;
330
+ }
331
+ /**
332
+ * Check if a scheme is registered for a given network.
333
+ *
334
+ * @param network - The network identifier
335
+ * @param scheme - The payment scheme name
336
+ * @returns True if the scheme is registered for the network, false otherwise
337
+ */
338
+ hasRegisteredScheme(network, scheme) {
339
+ return !!findByNetworkAndScheme(this.registeredServerSchemes, scheme, network);
340
+ }
341
+ /**
342
+ * Registers a resource service extension that can enrich extension declarations.
343
+ *
344
+ * @param extension - The extension to register
345
+ * @returns The x402ResourceServer instance for chaining
346
+ */
347
+ registerExtension(extension) {
348
+ this.registeredExtensions.set(extension.key, extension);
349
+ return this;
350
+ }
351
+ /**
352
+ * Check if an extension is registered.
353
+ *
354
+ * @param key - The extension key
355
+ * @returns True if the extension is registered
356
+ */
357
+ hasExtension(key) {
358
+ return this.registeredExtensions.has(key);
359
+ }
360
+ /**
361
+ * Get all registered extensions.
362
+ *
363
+ * @returns Array of registered extensions
364
+ */
365
+ getExtensions() {
366
+ return Array.from(this.registeredExtensions.values());
367
+ }
368
+ /**
369
+ * Enriches declared extensions using registered extension hooks.
370
+ *
371
+ * @param declaredExtensions - Extensions declared on the route
372
+ * @param transportContext - Transport-specific context (HTTP, A2A, MCP, etc.)
373
+ * @returns Enriched extensions map
374
+ */
375
+ enrichExtensions(declaredExtensions, transportContext) {
376
+ const enriched = {};
377
+ for (const [key, declaration] of Object.entries(declaredExtensions)) {
378
+ const extension = this.registeredExtensions.get(key);
379
+ if (extension?.enrichDeclaration) {
380
+ enriched[key] = extension.enrichDeclaration(declaration, transportContext);
381
+ } else {
382
+ enriched[key] = declaration;
383
+ }
384
+ }
385
+ return enriched;
386
+ }
387
+ /**
388
+ * Register a hook to execute before payment verification.
389
+ * Can abort verification by returning { abort: true, reason: string }
390
+ *
391
+ * @param hook - The hook function to register
392
+ * @returns The x402ResourceServer instance for chaining
393
+ */
394
+ onBeforeVerify(hook) {
395
+ this.beforeVerifyHooks.push(hook);
396
+ return this;
397
+ }
398
+ /**
399
+ * Register a hook to execute after successful payment verification.
400
+ *
401
+ * @param hook - The hook function to register
402
+ * @returns The x402ResourceServer instance for chaining
403
+ */
404
+ onAfterVerify(hook) {
405
+ this.afterVerifyHooks.push(hook);
406
+ return this;
407
+ }
408
+ /**
409
+ * Register a hook to execute when payment verification fails.
410
+ * Can recover from failure by returning { recovered: true, result: VerifyResponse }
411
+ *
412
+ * @param hook - The hook function to register
413
+ * @returns The x402ResourceServer instance for chaining
414
+ */
415
+ onVerifyFailure(hook) {
416
+ this.onVerifyFailureHooks.push(hook);
417
+ return this;
418
+ }
419
+ /**
420
+ * Register a hook to execute before payment settlement.
421
+ * Can abort settlement by returning { abort: true, reason: string }
422
+ *
423
+ * @param hook - The hook function to register
424
+ * @returns The x402ResourceServer instance for chaining
425
+ */
426
+ onBeforeSettle(hook) {
427
+ this.beforeSettleHooks.push(hook);
428
+ return this;
429
+ }
430
+ /**
431
+ * Register a hook to execute after successful payment settlement.
432
+ *
433
+ * @param hook - The hook function to register
434
+ * @returns The x402ResourceServer instance for chaining
435
+ */
436
+ onAfterSettle(hook) {
437
+ this.afterSettleHooks.push(hook);
438
+ return this;
439
+ }
440
+ /**
441
+ * Register a hook to execute when payment settlement fails.
442
+ * Can recover from failure by returning { recovered: true, result: SettleResponse }
443
+ *
444
+ * @param hook - The hook function to register
445
+ * @returns The x402ResourceServer instance for chaining
446
+ */
447
+ onSettleFailure(hook) {
448
+ this.onSettleFailureHooks.push(hook);
449
+ return this;
450
+ }
451
+ /**
452
+ * Initialize by fetching supported kinds from all facilitators
453
+ * Creates mappings for supported responses and facilitator clients
454
+ * Earlier facilitators in the array get precedence
455
+ */
456
+ async initialize() {
457
+ this.supportedResponsesMap.clear();
458
+ this.facilitatorClientsMap.clear();
459
+ let lastError;
460
+ for (const facilitatorClient of this.facilitatorClients) {
461
+ try {
462
+ const supported = await facilitatorClient.getSupported();
463
+ for (const kind of supported.kinds) {
464
+ const x402Version2 = kind.x402Version;
465
+ if (!this.supportedResponsesMap.has(x402Version2)) {
466
+ this.supportedResponsesMap.set(x402Version2, /* @__PURE__ */ new Map());
467
+ }
468
+ const responseVersionMap = this.supportedResponsesMap.get(x402Version2);
469
+ if (!this.facilitatorClientsMap.has(x402Version2)) {
470
+ this.facilitatorClientsMap.set(x402Version2, /* @__PURE__ */ new Map());
471
+ }
472
+ const clientVersionMap = this.facilitatorClientsMap.get(x402Version2);
473
+ if (!responseVersionMap.has(kind.network)) {
474
+ responseVersionMap.set(kind.network, /* @__PURE__ */ new Map());
475
+ }
476
+ const responseNetworkMap = responseVersionMap.get(kind.network);
477
+ if (!clientVersionMap.has(kind.network)) {
478
+ clientVersionMap.set(kind.network, /* @__PURE__ */ new Map());
479
+ }
480
+ const clientNetworkMap = clientVersionMap.get(kind.network);
481
+ if (!responseNetworkMap.has(kind.scheme)) {
482
+ responseNetworkMap.set(kind.scheme, supported);
483
+ clientNetworkMap.set(kind.scheme, facilitatorClient);
484
+ }
485
+ }
486
+ } catch (error) {
487
+ lastError = error;
488
+ console.warn(`Failed to fetch supported kinds from facilitator: ${error}`);
489
+ }
490
+ }
491
+ if (this.supportedResponsesMap.size === 0) {
492
+ throw lastError ? new Error(
493
+ "Failed to initialize: no supported payment kinds loaded from any facilitator.",
494
+ {
495
+ cause: lastError
496
+ }
497
+ ) : new Error(
498
+ "Failed to initialize: no supported payment kinds loaded from any facilitator."
499
+ );
500
+ }
501
+ }
502
+ /**
503
+ * Get supported kind for a specific version, network, and scheme
504
+ *
505
+ * @param x402Version - The x402 version
506
+ * @param network - The network identifier
507
+ * @param scheme - The payment scheme
508
+ * @returns The supported kind or undefined if not found
509
+ */
510
+ getSupportedKind(x402Version2, network, scheme) {
511
+ const versionMap = this.supportedResponsesMap.get(x402Version2);
512
+ if (!versionMap) return void 0;
513
+ const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network);
514
+ if (!supportedResponse) return void 0;
515
+ return supportedResponse.kinds.find(
516
+ (kind) => kind.x402Version === x402Version2 && kind.network === network && kind.scheme === scheme
517
+ );
518
+ }
519
+ /**
520
+ * Get facilitator extensions for a specific version, network, and scheme
521
+ *
522
+ * @param x402Version - The x402 version
523
+ * @param network - The network identifier
524
+ * @param scheme - The payment scheme
525
+ * @returns The facilitator extensions or empty array if not found
526
+ */
527
+ getFacilitatorExtensions(x402Version2, network, scheme) {
528
+ const versionMap = this.supportedResponsesMap.get(x402Version2);
529
+ if (!versionMap) return [];
530
+ const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network);
531
+ return supportedResponse?.extensions || [];
532
+ }
533
+ /**
534
+ * Build payment requirements for a protected resource
535
+ *
536
+ * @param resourceConfig - Configuration for the protected resource
537
+ * @returns Array of payment requirements
538
+ */
539
+ async buildPaymentRequirements(resourceConfig) {
540
+ const requirements = [];
541
+ const scheme = resourceConfig.scheme;
542
+ const SchemeNetworkServer = findByNetworkAndScheme(
543
+ this.registeredServerSchemes,
544
+ scheme,
545
+ resourceConfig.network
546
+ );
547
+ if (!SchemeNetworkServer) {
548
+ console.warn(
549
+ `No server implementation registered for scheme: ${scheme}, network: ${resourceConfig.network}`
550
+ );
551
+ return requirements;
552
+ }
553
+ const supportedKind = this.getSupportedKind(
554
+ x402Version,
555
+ resourceConfig.network,
556
+ SchemeNetworkServer.scheme
557
+ );
558
+ if (!supportedKind) {
559
+ throw new Error(
560
+ `Facilitator does not support ${SchemeNetworkServer.scheme} on ${resourceConfig.network}. Make sure to call initialize() to fetch supported kinds from facilitators.`
561
+ );
562
+ }
563
+ const facilitatorExtensions = this.getFacilitatorExtensions(
564
+ x402Version,
565
+ resourceConfig.network,
566
+ SchemeNetworkServer.scheme
567
+ );
568
+ const parsedPrice = await SchemeNetworkServer.parsePrice(
569
+ resourceConfig.price,
570
+ resourceConfig.network
571
+ );
572
+ const baseRequirements = {
573
+ scheme: SchemeNetworkServer.scheme,
574
+ network: resourceConfig.network,
575
+ amount: parsedPrice.amount,
576
+ asset: parsedPrice.asset,
577
+ payTo: resourceConfig.payTo,
578
+ maxTimeoutSeconds: resourceConfig.maxTimeoutSeconds || 300,
579
+ // Default 5 minutes
580
+ extra: {
581
+ ...parsedPrice.extra,
582
+ ...resourceConfig.extra
583
+ // Merge user-provided extra
584
+ }
585
+ };
586
+ const requirement = await SchemeNetworkServer.enhancePaymentRequirements(
587
+ baseRequirements,
588
+ {
589
+ ...supportedKind,
590
+ x402Version
591
+ },
592
+ facilitatorExtensions
593
+ );
594
+ requirements.push(requirement);
595
+ return requirements;
596
+ }
597
+ /**
598
+ * Build payment requirements from multiple payment options
599
+ * This method handles resolving dynamic payTo/price functions and builds requirements for each option
600
+ *
601
+ * @param paymentOptions - Array of payment options to convert
602
+ * @param context - HTTP request context for resolving dynamic functions
603
+ * @returns Array of payment requirements (one per option)
604
+ */
605
+ async buildPaymentRequirementsFromOptions(paymentOptions, context) {
606
+ const allRequirements = [];
607
+ for (const option of paymentOptions) {
608
+ const resolvedPayTo = typeof option.payTo === "function" ? await option.payTo(context) : option.payTo;
609
+ const resolvedPrice = typeof option.price === "function" ? await option.price(context) : option.price;
610
+ const resourceConfig = {
611
+ scheme: option.scheme,
612
+ payTo: resolvedPayTo,
613
+ price: resolvedPrice,
614
+ network: option.network,
615
+ maxTimeoutSeconds: option.maxTimeoutSeconds,
616
+ extra: option.extra
617
+ };
618
+ const requirements = await this.buildPaymentRequirements(resourceConfig);
619
+ allRequirements.push(...requirements);
620
+ }
621
+ return allRequirements;
622
+ }
623
+ /**
624
+ * Create a payment required response
625
+ *
626
+ * @param requirements - Payment requirements
627
+ * @param resourceInfo - Resource information
628
+ * @param error - Error message
629
+ * @param extensions - Optional declared extensions (for per-key enrichment)
630
+ * @param transportContext - Optional transport-specific context (e.g., HTTP request, MCP tool context)
631
+ * @returns Payment required response object
632
+ */
633
+ async createPaymentRequiredResponse(requirements, resourceInfo, error, extensions, transportContext) {
634
+ let response = {
635
+ x402Version: 2,
636
+ error,
637
+ resource: resourceInfo,
638
+ accepts: requirements
639
+ };
640
+ if (extensions && Object.keys(extensions).length > 0) {
641
+ response.extensions = extensions;
642
+ }
643
+ if (extensions) {
644
+ for (const [key, declaration] of Object.entries(extensions)) {
645
+ const extension = this.registeredExtensions.get(key);
646
+ if (extension?.enrichPaymentRequiredResponse) {
647
+ try {
648
+ const context = {
649
+ requirements,
650
+ resourceInfo,
651
+ error,
652
+ paymentRequiredResponse: response,
653
+ transportContext
654
+ };
655
+ const extensionData = await extension.enrichPaymentRequiredResponse(
656
+ declaration,
657
+ context
658
+ );
659
+ if (extensionData !== void 0) {
660
+ if (!response.extensions) {
661
+ response.extensions = {};
662
+ }
663
+ response.extensions[key] = extensionData;
664
+ }
665
+ } catch (error2) {
666
+ console.error(
667
+ `Error in enrichPaymentRequiredResponse hook for extension ${key}:`,
668
+ error2
669
+ );
670
+ }
671
+ }
672
+ }
673
+ }
674
+ return response;
675
+ }
676
+ /**
677
+ * Verify a payment against requirements
678
+ *
679
+ * @param paymentPayload - The payment payload to verify
680
+ * @param requirements - The payment requirements
681
+ * @returns Verification response
682
+ */
683
+ async verifyPayment(paymentPayload, requirements) {
684
+ const context = {
685
+ paymentPayload,
686
+ requirements
687
+ };
688
+ for (const hook of this.beforeVerifyHooks) {
689
+ try {
690
+ const result = await hook(context);
691
+ if (result && "abort" in result && result.abort) {
692
+ return {
693
+ isValid: false,
694
+ invalidReason: result.reason,
695
+ invalidMessage: result.message
696
+ };
697
+ }
698
+ } catch (error) {
699
+ throw new VerifyError(400, {
700
+ isValid: false,
701
+ invalidReason: "before_verify_hook_error",
702
+ invalidMessage: error instanceof Error ? error.message : ""
703
+ });
704
+ }
705
+ }
706
+ try {
707
+ const facilitatorClient = this.getFacilitatorClient(
708
+ paymentPayload.x402Version,
709
+ requirements.network,
710
+ requirements.scheme
711
+ );
712
+ let verifyResult;
713
+ if (!facilitatorClient) {
714
+ let lastError;
715
+ for (const client of this.facilitatorClients) {
716
+ try {
717
+ verifyResult = await client.verify(paymentPayload, requirements);
718
+ break;
719
+ } catch (error) {
720
+ lastError = error;
721
+ }
722
+ }
723
+ if (!verifyResult) {
724
+ throw lastError || new Error(
725
+ `No facilitator supports ${requirements.scheme} on ${requirements.network} for v${paymentPayload.x402Version}`
726
+ );
727
+ }
728
+ } else {
729
+ verifyResult = await facilitatorClient.verify(paymentPayload, requirements);
730
+ }
731
+ const resultContext = {
732
+ ...context,
733
+ result: verifyResult
734
+ };
735
+ for (const hook of this.afterVerifyHooks) {
736
+ await hook(resultContext);
737
+ }
738
+ return verifyResult;
739
+ } catch (error) {
740
+ const failureContext = {
741
+ ...context,
742
+ error
743
+ };
744
+ for (const hook of this.onVerifyFailureHooks) {
745
+ const result = await hook(failureContext);
746
+ if (result && "recovered" in result && result.recovered) {
747
+ return result.result;
748
+ }
749
+ }
750
+ throw error;
751
+ }
752
+ }
753
+ /**
754
+ * Settle a verified payment
755
+ *
756
+ * @param paymentPayload - The payment payload to settle
757
+ * @param requirements - The payment requirements
758
+ * @param declaredExtensions - Optional declared extensions (for per-key enrichment)
759
+ * @param transportContext - Optional transport-specific context (e.g., HTTP request/response, MCP tool context)
760
+ * @param settlementOverrides - Optional overrides for settlement parameters (e.g., partial settlement amount)
761
+ * @returns Settlement response
762
+ */
763
+ async settlePayment(paymentPayload, requirements, declaredExtensions, transportContext, settlementOverrides) {
764
+ let effectiveRequirements = requirements;
765
+ if (settlementOverrides?.amount !== void 0) {
766
+ effectiveRequirements = {
767
+ ...requirements,
768
+ amount: resolveSettlementOverrideAmount(settlementOverrides.amount, requirements)
769
+ };
770
+ }
771
+ const context = {
772
+ paymentPayload,
773
+ requirements: effectiveRequirements
774
+ };
775
+ for (const hook of this.beforeSettleHooks) {
776
+ try {
777
+ const result = await hook(context);
778
+ if (result && "abort" in result && result.abort) {
779
+ throw new SettleError(400, {
780
+ success: false,
781
+ errorReason: result.reason,
782
+ errorMessage: result.message,
783
+ transaction: "",
784
+ network: requirements.network
785
+ });
786
+ }
787
+ } catch (error) {
788
+ if (error instanceof SettleError) {
789
+ throw error;
790
+ }
791
+ throw new SettleError(400, {
792
+ success: false,
793
+ errorReason: "before_settle_hook_error",
794
+ errorMessage: error instanceof Error ? error.message : "",
795
+ transaction: "",
796
+ network: requirements.network
797
+ });
798
+ }
799
+ }
800
+ try {
801
+ const facilitatorClient = this.getFacilitatorClient(
802
+ paymentPayload.x402Version,
803
+ effectiveRequirements.network,
804
+ effectiveRequirements.scheme
805
+ );
806
+ let settleResult;
807
+ if (!facilitatorClient) {
808
+ let lastError;
809
+ for (const client of this.facilitatorClients) {
810
+ try {
811
+ settleResult = await client.settle(paymentPayload, effectiveRequirements);
812
+ break;
813
+ } catch (error) {
814
+ lastError = error;
815
+ }
816
+ }
817
+ if (!settleResult) {
818
+ throw lastError || new Error(
819
+ `No facilitator supports ${effectiveRequirements.scheme} on ${effectiveRequirements.network} for v${paymentPayload.x402Version}`
820
+ );
821
+ }
822
+ } else {
823
+ settleResult = await facilitatorClient.settle(paymentPayload, effectiveRequirements);
824
+ }
825
+ const resultContext = {
826
+ ...context,
827
+ result: settleResult,
828
+ transportContext
829
+ };
830
+ for (const hook of this.afterSettleHooks) {
831
+ await hook(resultContext);
832
+ }
833
+ if (declaredExtensions) {
834
+ for (const [key, declaration] of Object.entries(declaredExtensions)) {
835
+ const extension = this.registeredExtensions.get(key);
836
+ if (extension?.enrichSettlementResponse) {
837
+ try {
838
+ const extensionData = await extension.enrichSettlementResponse(
839
+ declaration,
840
+ resultContext
841
+ );
842
+ if (extensionData !== void 0) {
843
+ if (!settleResult.extensions) {
844
+ settleResult.extensions = {};
845
+ }
846
+ settleResult.extensions[key] = extensionData;
847
+ }
848
+ } catch (error) {
849
+ console.error(`Error in enrichSettlementResponse hook for extension ${key}:`, error);
850
+ }
851
+ }
852
+ }
853
+ }
854
+ return settleResult;
855
+ } catch (error) {
856
+ const failureContext = {
857
+ ...context,
858
+ error
859
+ };
860
+ for (const hook of this.onSettleFailureHooks) {
861
+ const result = await hook(failureContext);
862
+ if (result && "recovered" in result && result.recovered) {
863
+ return result.result;
864
+ }
865
+ }
866
+ throw error;
867
+ }
868
+ }
869
+ /**
870
+ * Find matching payment requirements for a payment
871
+ *
872
+ * @param availableRequirements - Array of available payment requirements
873
+ * @param paymentPayload - The payment payload
874
+ * @returns Matching payment requirements or undefined
875
+ */
876
+ findMatchingRequirements(availableRequirements, paymentPayload) {
877
+ return availableRequirements.find((serverReq) => {
878
+ const { extra: serverExtra, ...serverBase } = serverReq;
879
+ const { extra: buyerExtra, ...buyerBase } = paymentPayload.accepted ?? {};
880
+ if (!deepEqual(serverBase, buyerBase)) return false;
881
+ if (!serverExtra && !buyerExtra) return true;
882
+ if (!serverExtra) return true;
883
+ if (!buyerExtra) return false;
884
+ for (const [key, value] of Object.entries(serverExtra)) {
885
+ if (!deepEqual(buyerExtra[key], value)) return false;
886
+ }
887
+ return true;
888
+ });
889
+ }
890
+ /**
891
+ * Process a payment request
892
+ *
893
+ * @param paymentPayload - Optional payment payload if provided
894
+ * @param resourceConfig - Configuration for the protected resource
895
+ * @param resourceInfo - Information about the resource being accessed
896
+ * @param extensions - Optional extensions to include in the response
897
+ * @returns Processing result
898
+ */
899
+ async processPaymentRequest(paymentPayload, resourceConfig, resourceInfo, extensions) {
900
+ const requirements = await this.buildPaymentRequirements(resourceConfig);
901
+ if (!paymentPayload) {
902
+ return {
903
+ success: false,
904
+ requiresPayment: await this.createPaymentRequiredResponse(
905
+ requirements,
906
+ resourceInfo,
907
+ "Payment required",
908
+ extensions
909
+ )
910
+ };
911
+ }
912
+ const matchingRequirements = this.findMatchingRequirements(requirements, paymentPayload);
913
+ if (!matchingRequirements) {
914
+ return {
915
+ success: false,
916
+ requiresPayment: await this.createPaymentRequiredResponse(
917
+ requirements,
918
+ resourceInfo,
919
+ "No matching payment requirements found",
920
+ extensions
921
+ )
922
+ };
923
+ }
924
+ const verificationResult = await this.verifyPayment(paymentPayload, matchingRequirements);
925
+ if (!verificationResult.isValid) {
926
+ return {
927
+ success: false,
928
+ error: verificationResult.invalidReason,
929
+ verificationResult
930
+ };
931
+ }
932
+ return {
933
+ success: true,
934
+ verificationResult
935
+ };
936
+ }
937
+ /**
938
+ * Poll `GET /settle/status` until a terminal state is reached or deadline expires.
939
+ *
940
+ * Used when settle returns `status="timeout"` (exact + syncSettle=true).
941
+ * Mirrors Rust: `X402ResourceServer::poll_settle_status`.
942
+ *
943
+ * @param txHash - Transaction hash to query
944
+ * @param paymentPayload - The payment payload (used to find the correct facilitator client)
945
+ * @param requirements - The payment requirements (used to find the correct facilitator client)
946
+ * @param pollDeadlineMs - Max time to poll in ms (default 5000)
947
+ * @returns Poll result: "success", "failed", or "timeout"
948
+ */
949
+ async pollSettleStatus(txHash, paymentPayload, requirements, pollDeadlineMs = DEFAULT_POLL_DEADLINE_MS) {
950
+ const facilitatorClient = this.getFacilitatorClient(
951
+ paymentPayload.x402Version,
952
+ requirements.network,
953
+ requirements.scheme
954
+ ) ?? this.facilitatorClients[0];
955
+ if (!facilitatorClient?.getSettleStatus) {
956
+ return "timeout";
957
+ }
958
+ const deadline = Date.now() + pollDeadlineMs;
959
+ while (Date.now() < deadline) {
960
+ try {
961
+ const resp = await facilitatorClient.getSettleStatus(txHash);
962
+ if (!resp.success) {
963
+ return "failed";
964
+ }
965
+ if (resp.status === "success") {
966
+ return "success";
967
+ }
968
+ } catch {
969
+ }
970
+ const remaining = deadline - Date.now();
971
+ if (remaining <= 0) break;
972
+ await new Promise(
973
+ (resolve) => setTimeout(resolve, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining))
974
+ );
975
+ }
976
+ return "timeout";
977
+ }
978
+ /**
979
+ * Get facilitator client for a specific version, network, and scheme
980
+ *
981
+ * @param x402Version - The x402 version
982
+ * @param network - The network identifier
983
+ * @param scheme - The payment scheme
984
+ * @returns The facilitator client or undefined if not found
985
+ */
986
+ getFacilitatorClient(x402Version2, network, scheme) {
987
+ const versionMap = this.facilitatorClientsMap.get(x402Version2);
988
+ if (!versionMap) return void 0;
989
+ return findByNetworkAndScheme(versionMap, scheme, network);
990
+ }
991
+ };
992
+
993
+ // src/http/x402HTTPClient.ts
994
+ var x402HTTPClient = class {
995
+ /**
996
+ * Creates a new x402HTTPClient instance.
997
+ *
998
+ * @param client - The underlying x402Client for payment logic
999
+ */
1000
+ constructor(client) {
1001
+ this.client = client;
1002
+ this.paymentRequiredHooks = [];
1003
+ }
1004
+ /**
1005
+ * Register a hook to handle 402 responses before payment.
1006
+ * Hooks run in order; first to return headers wins.
1007
+ *
1008
+ * @param hook - The hook function to register
1009
+ * @returns This instance for chaining
1010
+ */
1011
+ onPaymentRequired(hook) {
1012
+ this.paymentRequiredHooks.push(hook);
1013
+ return this;
1014
+ }
1015
+ /**
1016
+ * Run hooks and return headers if any hook provides them.
1017
+ *
1018
+ * @param paymentRequired - The payment required response from the server
1019
+ * @returns Headers to use for retry, or null to proceed to payment
1020
+ */
1021
+ async handlePaymentRequired(paymentRequired) {
1022
+ for (const hook of this.paymentRequiredHooks) {
1023
+ const result = await hook({ paymentRequired });
1024
+ if (result?.headers) {
1025
+ return result.headers;
1026
+ }
1027
+ }
1028
+ return null;
1029
+ }
1030
+ /**
1031
+ * Encodes a payment payload into appropriate HTTP headers based on version.
1032
+ *
1033
+ * @param paymentPayload - The payment payload to encode
1034
+ * @returns HTTP headers containing the encoded payment signature
1035
+ */
1036
+ encodePaymentSignatureHeader(paymentPayload) {
1037
+ return {
1038
+ "PAYMENT-SIGNATURE": encodePaymentSignatureHeader(paymentPayload)
1039
+ };
1040
+ }
1041
+ /**
1042
+ * Extracts payment required information from HTTP response.
1043
+ *
1044
+ * @param getHeader - Function to retrieve header value by name (case-insensitive)
1045
+ * @returns The payment required object
1046
+ */
1047
+ getPaymentRequiredResponse(getHeader) {
1048
+ const paymentRequired = getHeader("PAYMENT-REQUIRED");
1049
+ if (paymentRequired) {
1050
+ return decodePaymentRequiredHeader(paymentRequired);
1051
+ }
1052
+ throw new Error("Invalid payment required response");
1053
+ }
1054
+ /**
1055
+ * Extracts payment settlement response from HTTP headers.
1056
+ *
1057
+ * @param getHeader - Function to retrieve header value by name (case-insensitive)
1058
+ * @returns The settlement response object
1059
+ */
1060
+ getPaymentSettleResponse(getHeader) {
1061
+ const paymentResponse = getHeader("PAYMENT-RESPONSE");
1062
+ if (paymentResponse) {
1063
+ return decodePaymentResponseHeader(paymentResponse);
1064
+ }
1065
+ throw new Error("Payment response header not found");
1066
+ }
1067
+ /**
1068
+ * Creates a payment payload for the given payment requirements.
1069
+ * Delegates to the underlying x402Client.
1070
+ *
1071
+ * @param paymentRequired - The payment required response from the server
1072
+ * @returns Promise resolving to the payment payload
1073
+ */
1074
+ async createPaymentPayload(paymentRequired) {
1075
+ return this.client.createPaymentPayload(paymentRequired);
1076
+ }
1077
+ };
1078
+
1079
+ // src/http/index.ts
1080
+ function encodePaymentSignatureHeader(paymentPayload) {
1081
+ return safeBase64Encode(JSON.stringify(paymentPayload));
1082
+ }
1083
+ function decodePaymentSignatureHeader(paymentSignatureHeader) {
1084
+ if (!Base64EncodedRegex.test(paymentSignatureHeader)) {
1085
+ throw new Error("Invalid payment signature header");
1086
+ }
1087
+ return JSON.parse(safeBase64Decode(paymentSignatureHeader));
1088
+ }
1089
+ function encodePaymentRequiredHeader(paymentRequired) {
1090
+ return safeBase64Encode(JSON.stringify(paymentRequired));
1091
+ }
1092
+ function decodePaymentRequiredHeader(paymentRequiredHeader) {
1093
+ if (!Base64EncodedRegex.test(paymentRequiredHeader)) {
1094
+ throw new Error("Invalid payment required header");
1095
+ }
1096
+ return JSON.parse(safeBase64Decode(paymentRequiredHeader));
1097
+ }
1098
+ function encodePaymentResponseHeader(paymentResponse) {
1099
+ return safeBase64Encode(JSON.stringify(paymentResponse));
1100
+ }
1101
+ function decodePaymentResponseHeader(paymentResponseHeader) {
1102
+ if (!Base64EncodedRegex.test(paymentResponseHeader)) {
1103
+ throw new Error("Invalid payment response header");
1104
+ }
1105
+ return JSON.parse(safeBase64Decode(paymentResponseHeader));
1106
+ }
1107
+
1108
+ // src/http/x402HTTPResourceServer.ts
1109
+ var SETTLEMENT_OVERRIDES_HEADER = "settlement-overrides";
1110
+ var RouteConfigurationError = class extends Error {
1111
+ /**
1112
+ * Creates a new RouteConfigurationError with the given validation errors.
1113
+ *
1114
+ * @param errors - The validation errors that caused this exception.
1115
+ */
1116
+ constructor(errors) {
1117
+ const message = `x402 Route Configuration Errors:
1118
+ ${errors.map((e) => ` - ${e.message}`).join("\n")}`;
1119
+ super(message);
1120
+ this.name = "RouteConfigurationError";
1121
+ this.errors = errors;
1122
+ }
1123
+ };
1124
+ var x402HTTPResourceServer = class {
1125
+ /**
1126
+ * Creates a new x402HTTPResourceServer instance.
1127
+ *
1128
+ * @param ResourceServer - The core x402ResourceServer instance to use
1129
+ * @param routes - Route configuration for payment-protected endpoints
1130
+ */
1131
+ constructor(ResourceServer, routes) {
1132
+ this.compiledRoutes = [];
1133
+ this.protectedRequestHooks = [];
1134
+ this.pollDeadlineMs = DEFAULT_POLL_DEADLINE_MS;
1135
+ this.ResourceServer = ResourceServer;
1136
+ this.routesConfig = routes;
1137
+ const normalizedRoutes = typeof routes === "object" && !("accepts" in routes) ? routes : { "*": routes };
1138
+ for (const [pattern, config] of Object.entries(normalizedRoutes)) {
1139
+ const parsed = this.parseRoutePattern(pattern);
1140
+ this.compiledRoutes.push({
1141
+ verb: parsed.verb,
1142
+ regex: parsed.regex,
1143
+ config,
1144
+ pattern: parsed.path
1145
+ });
1146
+ }
1147
+ }
1148
+ /**
1149
+ * Get the underlying x402ResourceServer instance.
1150
+ *
1151
+ * @returns The underlying x402ResourceServer instance
1152
+ */
1153
+ get server() {
1154
+ return this.ResourceServer;
1155
+ }
1156
+ /**
1157
+ * Get the routes configuration.
1158
+ *
1159
+ * @returns The routes configuration
1160
+ */
1161
+ get routes() {
1162
+ return this.routesConfig;
1163
+ }
1164
+ /**
1165
+ * Initialize the HTTP resource server.
1166
+ *
1167
+ * This method initializes the underlying resource server (fetching facilitator support)
1168
+ * and then validates that all route payment configurations have corresponding
1169
+ * registered schemes and facilitator support.
1170
+ *
1171
+ * @throws RouteConfigurationError if any route's payment options don't have
1172
+ * corresponding registered schemes or facilitator support
1173
+ *
1174
+ * @example
1175
+ * ```typescript
1176
+ * const httpServer = new x402HTTPResourceServer(server, routes);
1177
+ * await httpServer.initialize();
1178
+ * ```
1179
+ */
1180
+ async initialize() {
1181
+ await this.ResourceServer.initialize();
1182
+ const errors = this.validateRouteConfiguration();
1183
+ if (errors.length > 0) {
1184
+ throw new RouteConfigurationError(errors);
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Register a custom paywall provider for generating HTML
1189
+ *
1190
+ * @param provider - PaywallProvider instance
1191
+ * @returns This service instance for chaining
1192
+ */
1193
+ registerPaywallProvider(provider) {
1194
+ this.paywallProvider = provider;
1195
+ return this;
1196
+ }
1197
+ /**
1198
+ * Register a hook that runs on every request to a protected route, before payment processing.
1199
+ * Hooks are executed in order of registration. The first hook to return a non-void result wins.
1200
+ *
1201
+ * @param hook - The request hook function
1202
+ * @returns The x402HTTPResourceServer instance for chaining
1203
+ */
1204
+ onProtectedRequest(hook) {
1205
+ this.protectedRequestHooks.push(hook);
1206
+ return this;
1207
+ }
1208
+ /**
1209
+ * Register a hook to call when the facilitator returns status="timeout".
1210
+ * The hook should verify the tx on-chain and return { confirmed: boolean }.
1211
+ * If confirmed=true the resource is delivered (200); otherwise 402 is returned.
1212
+ *
1213
+ * @param hook - On-chain verification callback
1214
+ * @returns The x402HTTPResourceServer instance for chaining
1215
+ */
1216
+ onSettlementTimeout(hook) {
1217
+ this.timeoutRecoveryHook = hook;
1218
+ return this;
1219
+ }
1220
+ /**
1221
+ * Set the poll deadline for settle/status polling on timeout recovery.
1222
+ * Default is 5000ms.
1223
+ *
1224
+ * @param deadlineMs - Maximum time to poll in milliseconds
1225
+ * @returns The x402HTTPResourceServer instance for chaining
1226
+ */
1227
+ setPollDeadline(deadlineMs) {
1228
+ this.pollDeadlineMs = deadlineMs;
1229
+ return this;
1230
+ }
1231
+ /**
1232
+ * Process HTTP request and return response instructions
1233
+ * This is the main entry point for framework middleware
1234
+ *
1235
+ * @param context - HTTP request context
1236
+ * @param paywallConfig - Optional paywall configuration
1237
+ * @returns Process result indicating next action for middleware
1238
+ */
1239
+ async processHTTPRequest(context, paywallConfig) {
1240
+ const { adapter, path, method } = context;
1241
+ const routeMatch = this.getRouteConfig(path, method);
1242
+ if (!routeMatch) {
1243
+ return { type: "no-payment-required" };
1244
+ }
1245
+ const { config: routeConfig, pattern: routePattern } = routeMatch;
1246
+ const enrichedContext = { ...context, routePattern };
1247
+ for (const hook of this.protectedRequestHooks) {
1248
+ const result = await hook(enrichedContext, routeConfig);
1249
+ if (result && "grantAccess" in result) {
1250
+ return { type: "no-payment-required" };
1251
+ }
1252
+ if (result && "abort" in result) {
1253
+ return {
1254
+ type: "payment-error",
1255
+ response: {
1256
+ status: 403,
1257
+ headers: { "Content-Type": "application/json" },
1258
+ body: { error: result.reason }
1259
+ }
1260
+ };
1261
+ }
1262
+ }
1263
+ const paymentOptions = this.normalizePaymentOptions(routeConfig);
1264
+ const paymentPayload = this.extractPayment(adapter);
1265
+ const resourceInfo = {
1266
+ url: routeConfig.resource || enrichedContext.adapter.getUrl(),
1267
+ description: routeConfig.description || "",
1268
+ mimeType: routeConfig.mimeType || ""
1269
+ };
1270
+ let requirements = await this.ResourceServer.buildPaymentRequirementsFromOptions(
1271
+ paymentOptions,
1272
+ enrichedContext
1273
+ );
1274
+ let extensions = routeConfig.extensions;
1275
+ if (extensions) {
1276
+ extensions = this.ResourceServer.enrichExtensions(extensions, enrichedContext);
1277
+ }
1278
+ const transportContext = { request: enrichedContext };
1279
+ const paymentRequired = await this.ResourceServer.createPaymentRequiredResponse(
1280
+ requirements,
1281
+ resourceInfo,
1282
+ !paymentPayload ? "Payment required" : void 0,
1283
+ extensions,
1284
+ transportContext
1285
+ );
1286
+ if (!paymentPayload) {
1287
+ const unpaidBody = routeConfig.unpaidResponseBody ? await routeConfig.unpaidResponseBody(enrichedContext) : void 0;
1288
+ return {
1289
+ type: "payment-error",
1290
+ response: this.createHTTPResponse(
1291
+ paymentRequired,
1292
+ this.isWebBrowser(adapter),
1293
+ paywallConfig,
1294
+ routeConfig.customPaywallHtml,
1295
+ unpaidBody
1296
+ )
1297
+ };
1298
+ }
1299
+ try {
1300
+ const matchingRequirements = this.ResourceServer.findMatchingRequirements(
1301
+ paymentRequired.accepts,
1302
+ paymentPayload
1303
+ );
1304
+ if (!matchingRequirements) {
1305
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
1306
+ requirements,
1307
+ resourceInfo,
1308
+ "No matching payment requirements",
1309
+ routeConfig.extensions,
1310
+ transportContext
1311
+ );
1312
+ return {
1313
+ type: "payment-error",
1314
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
1315
+ };
1316
+ }
1317
+ const verifyResult = await this.ResourceServer.verifyPayment(
1318
+ paymentPayload,
1319
+ matchingRequirements
1320
+ );
1321
+ if (!verifyResult.isValid) {
1322
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
1323
+ requirements,
1324
+ resourceInfo,
1325
+ verifyResult.invalidReason,
1326
+ routeConfig.extensions,
1327
+ transportContext
1328
+ );
1329
+ return {
1330
+ type: "payment-error",
1331
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
1332
+ };
1333
+ }
1334
+ return {
1335
+ type: "payment-verified",
1336
+ paymentPayload,
1337
+ paymentRequirements: matchingRequirements,
1338
+ declaredExtensions: routeConfig.extensions
1339
+ };
1340
+ } catch (error) {
1341
+ if (error instanceof FacilitatorResponseError) {
1342
+ throw error;
1343
+ }
1344
+ const errorResponse = await this.ResourceServer.createPaymentRequiredResponse(
1345
+ requirements,
1346
+ resourceInfo,
1347
+ error instanceof Error ? error.message : "Payment verification failed",
1348
+ routeConfig.extensions,
1349
+ transportContext
1350
+ );
1351
+ return {
1352
+ type: "payment-error",
1353
+ response: this.createHTTPResponse(errorResponse, false, paywallConfig)
1354
+ };
1355
+ }
1356
+ }
1357
+ /**
1358
+ * Process settlement after successful response
1359
+ *
1360
+ * @param paymentPayload - The verified payment payload
1361
+ * @param requirements - The matching payment requirements
1362
+ * @param declaredExtensions - Optional declared extensions (for per-key enrichment)
1363
+ * @param transportContext - Optional HTTP transport context
1364
+ * @param settlementOverrides - Optional settlement overrides (e.g., partial settlement amount)
1365
+ * @returns ProcessSettleResultResponse - SettleResponse with headers if success or errorReason if failure
1366
+ */
1367
+ async processSettlement(paymentPayload, requirements, declaredExtensions, transportContext, settlementOverrides) {
1368
+ try {
1369
+ let resolvedOverrides = settlementOverrides;
1370
+ if (!resolvedOverrides && transportContext?.responseHeaders?.[SETTLEMENT_OVERRIDES_HEADER]) {
1371
+ try {
1372
+ resolvedOverrides = JSON.parse(
1373
+ transportContext.responseHeaders[SETTLEMENT_OVERRIDES_HEADER]
1374
+ );
1375
+ } catch {
1376
+ }
1377
+ }
1378
+ const settleResponse = await this.ResourceServer.settlePayment(
1379
+ paymentPayload,
1380
+ requirements,
1381
+ declaredExtensions,
1382
+ transportContext,
1383
+ resolvedOverrides
1384
+ );
1385
+ if (settleResponse.status === "timeout") {
1386
+ if (settleResponse.transaction) {
1387
+ const pollResult = await this.ResourceServer.pollSettleStatus(
1388
+ settleResponse.transaction,
1389
+ paymentPayload,
1390
+ requirements,
1391
+ this.pollDeadlineMs
1392
+ );
1393
+ if (pollResult === "success") {
1394
+ const recovered = { ...settleResponse, status: "success" };
1395
+ return {
1396
+ ...recovered,
1397
+ success: true,
1398
+ headers: this.createSettlementHeaders(recovered),
1399
+ requirements
1400
+ };
1401
+ }
1402
+ if (this.timeoutRecoveryHook) {
1403
+ try {
1404
+ const { confirmed } = await this.timeoutRecoveryHook(
1405
+ settleResponse.transaction,
1406
+ settleResponse.network
1407
+ );
1408
+ if (confirmed) {
1409
+ const recovered = { ...settleResponse, status: "success" };
1410
+ return {
1411
+ ...recovered,
1412
+ success: true,
1413
+ headers: this.createSettlementHeaders(recovered),
1414
+ requirements
1415
+ };
1416
+ }
1417
+ } catch (err) {
1418
+ console.warn("[x402] onSettlementTimeout hook error:", err);
1419
+ }
1420
+ }
1421
+ }
1422
+ const failure = {
1423
+ ...settleResponse,
1424
+ success: false,
1425
+ errorReason: "settlement_timeout",
1426
+ errorMessage: "Settlement timed out waiting for on-chain confirmation",
1427
+ headers: this.createSettlementHeaders(settleResponse)
1428
+ };
1429
+ const response = await this.buildSettlementFailureResponse(failure, transportContext);
1430
+ return { ...failure, response };
1431
+ }
1432
+ if (settleResponse.status === "success" || settleResponse.status === "pending") {
1433
+ return {
1434
+ ...settleResponse,
1435
+ success: true,
1436
+ headers: this.createSettlementHeaders(settleResponse),
1437
+ requirements
1438
+ };
1439
+ }
1440
+ if (!settleResponse.success) {
1441
+ const failure = {
1442
+ ...settleResponse,
1443
+ success: false,
1444
+ errorReason: settleResponse.errorReason || "Settlement failed",
1445
+ errorMessage: settleResponse.errorMessage || settleResponse.errorReason || "Settlement failed",
1446
+ headers: this.createSettlementHeaders(settleResponse)
1447
+ };
1448
+ const response = await this.buildSettlementFailureResponse(failure, transportContext);
1449
+ return { ...failure, response };
1450
+ }
1451
+ return {
1452
+ ...settleResponse,
1453
+ success: true,
1454
+ headers: this.createSettlementHeaders(settleResponse),
1455
+ requirements
1456
+ };
1457
+ } catch (error) {
1458
+ if (error instanceof FacilitatorResponseError) {
1459
+ throw error;
1460
+ }
1461
+ if (error instanceof SettleError) {
1462
+ const errorReason2 = error.errorReason || error.message;
1463
+ const settleResponse2 = {
1464
+ success: false,
1465
+ errorReason: errorReason2,
1466
+ errorMessage: error.errorMessage || errorReason2,
1467
+ payer: error.payer,
1468
+ network: error.network,
1469
+ transaction: error.transaction
1470
+ };
1471
+ const failure2 = {
1472
+ ...settleResponse2,
1473
+ success: false,
1474
+ errorReason: errorReason2,
1475
+ headers: this.createSettlementHeaders(settleResponse2)
1476
+ };
1477
+ const response2 = await this.buildSettlementFailureResponse(failure2, transportContext);
1478
+ return { ...failure2, response: response2 };
1479
+ }
1480
+ const errorReason = error instanceof Error ? error.message : "Settlement failed";
1481
+ const settleResponse = {
1482
+ success: false,
1483
+ errorReason,
1484
+ errorMessage: errorReason,
1485
+ network: requirements.network,
1486
+ transaction: ""
1487
+ };
1488
+ const failure = {
1489
+ ...settleResponse,
1490
+ success: false,
1491
+ errorReason,
1492
+ headers: this.createSettlementHeaders(settleResponse)
1493
+ };
1494
+ const response = await this.buildSettlementFailureResponse(failure, transportContext);
1495
+ return { ...failure, response };
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Check if a request requires payment based on route configuration
1500
+ *
1501
+ * @param context - HTTP request context
1502
+ * @returns True if the route requires payment, false otherwise
1503
+ */
1504
+ requiresPayment(context) {
1505
+ return this.getRouteConfig(context.path, context.method) !== void 0;
1506
+ }
1507
+ /**
1508
+ * Build HTTPResponseInstructions for settlement failure.
1509
+ * Uses settlementFailedResponseBody hook if configured, otherwise defaults to empty body.
1510
+ *
1511
+ * @param failure - Settlement failure result with headers
1512
+ * @param transportContext - Optional HTTP transport context for the request
1513
+ * @returns HTTP response instructions for the 402 settlement failure response
1514
+ */
1515
+ async buildSettlementFailureResponse(failure, transportContext) {
1516
+ const settlementHeaders = failure.headers;
1517
+ const routeConfig = transportContext ? this.getRouteConfig(transportContext.request.path, transportContext.request.method) : void 0;
1518
+ const customBody = routeConfig?.config.settlementFailedResponseBody ? await routeConfig.config.settlementFailedResponseBody(transportContext.request, failure) : void 0;
1519
+ const contentType = customBody ? customBody.contentType : "application/json";
1520
+ const body = customBody ? customBody.body : {};
1521
+ return {
1522
+ status: 402,
1523
+ headers: {
1524
+ "Content-Type": contentType,
1525
+ ...settlementHeaders
1526
+ },
1527
+ body,
1528
+ isHtml: contentType.includes("text/html")
1529
+ };
1530
+ }
1531
+ /**
1532
+ * Normalizes a RouteConfig's accepts field into an array of PaymentOptions
1533
+ * Handles both single PaymentOption and array formats
1534
+ *
1535
+ * @param routeConfig - Route configuration
1536
+ * @returns Array of payment options
1537
+ */
1538
+ normalizePaymentOptions(routeConfig) {
1539
+ return Array.isArray(routeConfig.accepts) ? routeConfig.accepts : [routeConfig.accepts];
1540
+ }
1541
+ /**
1542
+ * Validates that all payment options in routes have corresponding registered schemes
1543
+ * and facilitator support.
1544
+ *
1545
+ * @returns Array of validation errors (empty if all routes are valid)
1546
+ */
1547
+ validateRouteConfiguration() {
1548
+ const errors = [];
1549
+ const normalizedRoutes = typeof this.routesConfig === "object" && !("accepts" in this.routesConfig) ? Object.entries(this.routesConfig) : [["*", this.routesConfig]];
1550
+ for (const [pattern, config] of normalizedRoutes) {
1551
+ const pathPart = pattern.includes(" ") ? pattern.split(/\s+/)[1] : pattern;
1552
+ if (pathPart && pathPart.includes("*") && config.extensions && "bazaar" in config.extensions) {
1553
+ console.warn(
1554
+ `[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.`
1555
+ );
1556
+ }
1557
+ const paymentOptions = this.normalizePaymentOptions(config);
1558
+ for (const option of paymentOptions) {
1559
+ if (!this.ResourceServer.hasRegisteredScheme(option.network, option.scheme)) {
1560
+ errors.push({
1561
+ routePattern: pattern,
1562
+ scheme: option.scheme,
1563
+ network: option.network,
1564
+ reason: "missing_scheme",
1565
+ message: `Route "${pattern}": No scheme implementation registered for "${option.scheme}" on network "${option.network}"`
1566
+ });
1567
+ continue;
1568
+ }
1569
+ const supportedKind = this.ResourceServer.getSupportedKind(
1570
+ x402Version,
1571
+ option.network,
1572
+ option.scheme
1573
+ );
1574
+ if (!supportedKind) {
1575
+ errors.push({
1576
+ routePattern: pattern,
1577
+ scheme: option.scheme,
1578
+ network: option.network,
1579
+ reason: "missing_facilitator",
1580
+ message: `Route "${pattern}": Facilitator does not support scheme "${option.scheme}" on network "${option.network}"`
1581
+ });
1582
+ }
1583
+ }
1584
+ }
1585
+ return errors;
1586
+ }
1587
+ /**
1588
+ * Get route configuration for a request
1589
+ *
1590
+ * @param path - Request path
1591
+ * @param method - HTTP method
1592
+ * @returns Route configuration and pattern, or undefined if no match
1593
+ */
1594
+ getRouteConfig(path, method) {
1595
+ const normalizedPath = this.normalizePath(path);
1596
+ const upperMethod = method.toUpperCase();
1597
+ const matchingRoute = this.compiledRoutes.find(
1598
+ (route) => route.regex.test(normalizedPath) && (route.verb === "*" || route.verb === upperMethod)
1599
+ );
1600
+ if (!matchingRoute) return void 0;
1601
+ return { config: matchingRoute.config, pattern: matchingRoute.pattern };
1602
+ }
1603
+ /**
1604
+ * Extract payment from HTTP headers (handles v1 and v2)
1605
+ *
1606
+ * @param adapter - HTTP adapter
1607
+ * @returns Decoded payment payload or null
1608
+ */
1609
+ extractPayment(adapter) {
1610
+ const header = adapter.getHeader("payment-signature") || adapter.getHeader("PAYMENT-SIGNATURE");
1611
+ if (header) {
1612
+ try {
1613
+ return decodePaymentSignatureHeader(header);
1614
+ } catch (error) {
1615
+ console.warn("Failed to decode PAYMENT-SIGNATURE header:", error);
1616
+ }
1617
+ }
1618
+ return null;
1619
+ }
1620
+ /**
1621
+ * Check if request is from a web browser
1622
+ *
1623
+ * @param adapter - HTTP adapter
1624
+ * @returns True if request appears to be from a browser
1625
+ */
1626
+ isWebBrowser(adapter) {
1627
+ const accept = adapter.getAcceptHeader();
1628
+ const userAgent = adapter.getUserAgent();
1629
+ return accept.includes("text/html") && userAgent.includes("Mozilla");
1630
+ }
1631
+ /**
1632
+ * Create HTTP response instructions from payment required
1633
+ *
1634
+ * @param paymentRequired - Payment requirements
1635
+ * @param isWebBrowser - Whether request is from browser
1636
+ * @param paywallConfig - Paywall configuration
1637
+ * @param customHtml - Custom HTML template
1638
+ * @param unpaidResponse - Optional custom response (content type and body) for unpaid API requests
1639
+ * @returns Response instructions
1640
+ */
1641
+ createHTTPResponse(paymentRequired, isWebBrowser, paywallConfig, customHtml, unpaidResponse) {
1642
+ const status = paymentRequired.error === "permit2_allowance_required" ? 412 : 402;
1643
+ if (isWebBrowser) {
1644
+ const html = this.generatePaywallHTML(paymentRequired, paywallConfig, customHtml);
1645
+ return {
1646
+ status,
1647
+ headers: { "Content-Type": "text/html" },
1648
+ body: html,
1649
+ isHtml: true
1650
+ };
1651
+ }
1652
+ const response = this.createHTTPPaymentRequiredResponse(paymentRequired);
1653
+ const contentType = unpaidResponse ? unpaidResponse.contentType : "application/json";
1654
+ const body = unpaidResponse ? unpaidResponse.body : {};
1655
+ return {
1656
+ status,
1657
+ headers: {
1658
+ "Content-Type": contentType,
1659
+ ...response.headers
1660
+ },
1661
+ body
1662
+ };
1663
+ }
1664
+ /**
1665
+ * Create HTTP payment required response (v1 puts in body, v2 puts in header)
1666
+ *
1667
+ * @param paymentRequired - Payment required object
1668
+ * @returns Headers and body for the HTTP response
1669
+ */
1670
+ createHTTPPaymentRequiredResponse(paymentRequired) {
1671
+ return {
1672
+ headers: {
1673
+ "PAYMENT-REQUIRED": encodePaymentRequiredHeader(paymentRequired)
1674
+ }
1675
+ };
1676
+ }
1677
+ /**
1678
+ * Create settlement response headers
1679
+ *
1680
+ * @param settleResponse - Settlement response
1681
+ * @returns Headers to add to response
1682
+ */
1683
+ createSettlementHeaders(settleResponse) {
1684
+ const encoded = encodePaymentResponseHeader(settleResponse);
1685
+ return { "PAYMENT-RESPONSE": encoded };
1686
+ }
1687
+ /**
1688
+ * Parse route pattern into verb and regex
1689
+ *
1690
+ * @param pattern - Route pattern like "GET /api/*", "/api/[id]", or "/api/:id"
1691
+ * @returns Parsed pattern with verb and regex
1692
+ */
1693
+ parseRoutePattern(pattern) {
1694
+ const [verb, path] = pattern.includes(" ") ? pattern.split(/\s+/) : ["*", pattern];
1695
+ const regex = new RegExp(
1696
+ `^${path.replace(/[$()+.?^{|}]/g, "\\$&").replace(/\*/g, ".*?").replace(/\[([^\]]+)\]/g, "[^/]+").replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, "[^/]+").replace(/\//g, "\\/")}$`,
1697
+ "i"
1698
+ );
1699
+ return { verb: verb.toUpperCase(), regex, path };
1700
+ }
1701
+ /**
1702
+ * Normalize path for matching
1703
+ *
1704
+ * @param path - Raw path from request
1705
+ * @returns Normalized path
1706
+ */
1707
+ normalizePath(path) {
1708
+ const pathWithoutQuery = path.split(/[?#]/)[0];
1709
+ let decodedOrRawPath;
1710
+ try {
1711
+ decodedOrRawPath = decodeURIComponent(pathWithoutQuery);
1712
+ } catch {
1713
+ decodedOrRawPath = pathWithoutQuery;
1714
+ }
1715
+ return decodedOrRawPath.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/(.+?)\/+$/, "$1");
1716
+ }
1717
+ /**
1718
+ * Generate paywall HTML for browser requests
1719
+ *
1720
+ * @param paymentRequired - Payment required response
1721
+ * @param paywallConfig - Optional paywall configuration
1722
+ * @param customHtml - Optional custom HTML template
1723
+ * @returns HTML string
1724
+ */
1725
+ generatePaywallHTML(paymentRequired, paywallConfig, customHtml) {
1726
+ if (customHtml) {
1727
+ return customHtml;
1728
+ }
1729
+ if (this.paywallProvider) {
1730
+ return this.paywallProvider.generateHtml(paymentRequired, paywallConfig);
1731
+ }
1732
+ const resource = paymentRequired.resource;
1733
+ const displayAmount = this.getDisplayAmount(paymentRequired);
1734
+ return `
1735
+ <!DOCTYPE html>
1736
+ <html>
1737
+ <head>
1738
+ <title>Payment Required</title>
1739
+ <meta charset="UTF-8">
1740
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1741
+ </head>
1742
+ <body>
1743
+ <div style="max-width: 600px; margin: 50px auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
1744
+ ${paywallConfig?.appLogo ? `<img src="${paywallConfig.appLogo}" alt="${paywallConfig.appName || "App"}" style="max-width: 200px; margin-bottom: 20px;">` : ""}
1745
+ <h1>Payment Required</h1>
1746
+ ${resource ? `<p><strong>Resource:</strong> ${resource.description || resource.url}</p>` : ""}
1747
+ <p><strong>Amount:</strong> $${displayAmount.toFixed(2)} USDC</p>
1748
+ <div id="payment-widget"
1749
+ data-requirements='${JSON.stringify(paymentRequired)}'
1750
+ data-app-name="${paywallConfig?.appName || ""}"
1751
+ data-testnet="${paywallConfig?.testnet || false}">
1752
+ </div>
1753
+ </div>
1754
+ </body>
1755
+ </html>
1756
+ `;
1757
+ }
1758
+ /**
1759
+ * Extract display amount from payment requirements.
1760
+ *
1761
+ * @param paymentRequired - The payment required object
1762
+ * @returns The display amount in decimal format
1763
+ */
1764
+ getDisplayAmount(paymentRequired) {
1765
+ const accepts = paymentRequired.accepts;
1766
+ if (accepts && accepts.length > 0) {
1767
+ const firstReq = accepts[0];
1768
+ if ("amount" in firstReq) {
1769
+ return parseFloat(firstReq.amount) / 1e6;
1770
+ }
1771
+ }
1772
+ return 0;
1773
+ }
1774
+ };
1775
+
1776
+ export {
1777
+ HTTPFacilitatorClient,
1778
+ DEFAULT_POLL_INTERVAL_MS,
1779
+ DEFAULT_POLL_DEADLINE_MS,
1780
+ x402ResourceServer,
1781
+ SETTLEMENT_OVERRIDES_HEADER,
1782
+ RouteConfigurationError,
1783
+ x402HTTPResourceServer,
1784
+ encodePaymentSignatureHeader,
1785
+ decodePaymentSignatureHeader,
1786
+ encodePaymentRequiredHeader,
1787
+ decodePaymentRequiredHeader,
1788
+ encodePaymentResponseHeader,
1789
+ decodePaymentResponseHeader,
1790
+ x402HTTPClient
1791
+ };
1792
+ //# sourceMappingURL=chunk-XBQG2CDV.mjs.map