@bankofai/x402-core 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +294 -0
  2. package/dist/cjs/client/index.d.ts +149 -0
  3. package/dist/cjs/client/index.js +909 -0
  4. package/dist/cjs/client/index.js.map +1 -0
  5. package/dist/cjs/facilitator/index.d.ts +206 -0
  6. package/dist/cjs/facilitator/index.js +431 -0
  7. package/dist/cjs/facilitator/index.js.map +1 -0
  8. package/dist/cjs/http/index.d.ts +50 -0
  9. package/dist/cjs/http/index.js +1452 -0
  10. package/dist/cjs/http/index.js.map +1 -0
  11. package/dist/cjs/index.d.ts +3 -0
  12. package/dist/cjs/index.js +31 -0
  13. package/dist/cjs/index.js.map +1 -0
  14. package/dist/cjs/schemas/index.d.ts +891 -0
  15. package/dist/cjs/schemas/index.js +216 -0
  16. package/dist/cjs/schemas/index.js.map +1 -0
  17. package/dist/cjs/server/index.d.ts +79 -0
  18. package/dist/cjs/server/index.js +2568 -0
  19. package/dist/cjs/server/index.js.map +1 -0
  20. package/dist/cjs/types/index.d.ts +1 -0
  21. package/dist/cjs/types/index.js +97 -0
  22. package/dist/cjs/types/index.js.map +1 -0
  23. package/dist/cjs/types/v1/index.d.ts +1 -0
  24. package/dist/cjs/types/v1/index.js +19 -0
  25. package/dist/cjs/types/v1/index.js.map +1 -0
  26. package/dist/cjs/utils/index.d.ts +77 -0
  27. package/dist/cjs/utils/index.js +179 -0
  28. package/dist/cjs/utils/index.js.map +1 -0
  29. package/dist/cjs/x402Client-BgegfQgE.d.ts +1807 -0
  30. package/dist/cjs/x402Client-TQHctrG7.d.ts +1807 -0
  31. package/dist/esm/chunk-ABS7D6VX.mjs +145 -0
  32. package/dist/esm/chunk-ABS7D6VX.mjs.map +1 -0
  33. package/dist/esm/chunk-AGOUMC4P.mjs +68 -0
  34. package/dist/esm/chunk-AGOUMC4P.mjs.map +1 -0
  35. package/dist/esm/chunk-BJTO5JO5.mjs +11 -0
  36. package/dist/esm/chunk-BJTO5JO5.mjs.map +1 -0
  37. package/dist/esm/chunk-FPXAE3OS.mjs +161 -0
  38. package/dist/esm/chunk-FPXAE3OS.mjs.map +1 -0
  39. package/dist/esm/chunk-IL77TMJL.mjs +1275 -0
  40. package/dist/esm/chunk-IL77TMJL.mjs.map +1 -0
  41. package/dist/esm/chunk-VE37GDG2.mjs +7 -0
  42. package/dist/esm/chunk-VE37GDG2.mjs.map +1 -0
  43. package/dist/esm/client/index.d.mts +149 -0
  44. package/dist/esm/client/index.mjs +504 -0
  45. package/dist/esm/client/index.mjs.map +1 -0
  46. package/dist/esm/facilitator/index.d.mts +206 -0
  47. package/dist/esm/facilitator/index.mjs +399 -0
  48. package/dist/esm/facilitator/index.mjs.map +1 -0
  49. package/dist/esm/http/index.d.mts +50 -0
  50. package/dist/esm/http/index.mjs +35 -0
  51. package/dist/esm/http/index.mjs.map +1 -0
  52. package/dist/esm/index.d.mts +3 -0
  53. package/dist/esm/index.mjs +8 -0
  54. package/dist/esm/index.mjs.map +1 -0
  55. package/dist/esm/schemas/index.d.mts +891 -0
  56. package/dist/esm/schemas/index.mjs +70 -0
  57. package/dist/esm/schemas/index.mjs.map +1 -0
  58. package/dist/esm/server/index.d.mts +79 -0
  59. package/dist/esm/server/index.mjs +1318 -0
  60. package/dist/esm/server/index.mjs.map +1 -0
  61. package/dist/esm/types/index.d.mts +1 -0
  62. package/dist/esm/types/index.mjs +14 -0
  63. package/dist/esm/types/index.mjs.map +1 -0
  64. package/dist/esm/types/v1/index.d.mts +1 -0
  65. package/dist/esm/types/v1/index.mjs +1 -0
  66. package/dist/esm/types/v1/index.mjs.map +1 -0
  67. package/dist/esm/utils/index.d.mts +77 -0
  68. package/dist/esm/utils/index.mjs +28 -0
  69. package/dist/esm/utils/index.mjs.map +1 -0
  70. package/dist/esm/x402Client-BgegfQgE.d.mts +1807 -0
  71. package/dist/esm/x402Client-TQHctrG7.d.mts +1807 -0
  72. package/package.json +142 -0
@@ -0,0 +1,1318 @@
1
+ import {
2
+ HTTPFacilitatorClient,
3
+ RouteConfigurationError,
4
+ SETTLEMENT_OVERRIDES_HEADER,
5
+ checkIfBazaarNeeded,
6
+ x402HTTPResourceServer
7
+ } from "../chunk-IL77TMJL.mjs";
8
+ import "../chunk-FPXAE3OS.mjs";
9
+ import {
10
+ x402Version
11
+ } from "../chunk-VE37GDG2.mjs";
12
+ import {
13
+ FacilitatorResponseError,
14
+ SettleError,
15
+ getFacilitatorResponseError
16
+ } from "../chunk-AGOUMC4P.mjs";
17
+ import {
18
+ deepEqual,
19
+ findByNetworkAndScheme
20
+ } from "../chunk-ABS7D6VX.mjs";
21
+ import "../chunk-BJTO5JO5.mjs";
22
+
23
+ // src/server/hookPolicy.ts
24
+ function isVacantStringField(value) {
25
+ return value.trim() === "";
26
+ }
27
+ function snapshotPaymentRequirementsList(requirements) {
28
+ return requirements.map((req) => ({
29
+ ...req,
30
+ extra: structuredClone(req.extra)
31
+ }));
32
+ }
33
+ function assertAcceptsAllowlistedAfterExtensionEnrich(baseline, current, extensionKey) {
34
+ if (baseline.length !== current.length) {
35
+ throw new Error(
36
+ `[x402] extension "${extensionKey}" violated accepts mutation policy: accepts length changed (${baseline.length} \u2192 ${current.length})`
37
+ );
38
+ }
39
+ for (let i = 0; i < baseline.length; i++) {
40
+ const b = baseline[i];
41
+ const c = current[i];
42
+ if (b.scheme !== c.scheme || b.network !== c.network) {
43
+ throw new Error(
44
+ `[x402] extension "${extensionKey}" violated accepts mutation policy: scheme/network are immutable (index ${i})`
45
+ );
46
+ }
47
+ if (b.maxTimeoutSeconds !== c.maxTimeoutSeconds) {
48
+ throw new Error(
49
+ `[x402] extension "${extensionKey}" violated accepts mutation policy: maxTimeoutSeconds is immutable (index ${i})`
50
+ );
51
+ }
52
+ for (const field of ["payTo", "amount", "asset"]) {
53
+ const bv = b[field];
54
+ const cv = c[field];
55
+ if (!isVacantStringField(bv) && cv !== bv) {
56
+ throw new Error(
57
+ `[x402] extension "${extensionKey}" violated accepts mutation policy: "${field}" may only be set when the resource left it vacant (""); non-vacant values are immutable (index ${i})`
58
+ );
59
+ }
60
+ }
61
+ for (const key of Object.keys(b.extra)) {
62
+ if (!Object.prototype.hasOwnProperty.call(c.extra, key)) {
63
+ throw new Error(
64
+ `[x402] extension "${extensionKey}" violated accepts mutation policy: extra["${key}"] was removed (index ${i})`
65
+ );
66
+ }
67
+ if (!deepEqual(c.extra[key], b.extra[key])) {
68
+ throw new Error(
69
+ `[x402] extension "${extensionKey}" violated accepts mutation policy: extra["${key}"] may not be changed (index ${i})`
70
+ );
71
+ }
72
+ }
73
+ }
74
+ }
75
+ function assertAcceptsAdditiveExtraAfterSchemeEnrich(baseline, current, scheme, network) {
76
+ if (baseline.length !== current.length) {
77
+ throw new Error(
78
+ `[x402] scheme "${scheme}" violated accepts mutation policy: accepts length changed (${baseline.length} \u2192 ${current.length})`
79
+ );
80
+ }
81
+ for (let i = 0; i < baseline.length; i++) {
82
+ const b = baseline[i];
83
+ const c = current[i];
84
+ const isMatchingAccept = b.scheme === scheme && b.network === network;
85
+ if (b.scheme !== c.scheme || b.network !== c.network) {
86
+ throw new Error(
87
+ `[x402] scheme "${scheme}" violated accepts mutation policy: scheme/network are immutable (index ${i})`
88
+ );
89
+ }
90
+ if (b.maxTimeoutSeconds !== c.maxTimeoutSeconds || b.payTo !== c.payTo || b.amount !== c.amount || b.asset !== c.asset) {
91
+ throw new Error(
92
+ `[x402] scheme "${scheme}" violated accepts mutation policy: payment terms are immutable (index ${i})`
93
+ );
94
+ }
95
+ for (const key of Object.keys(b.extra)) {
96
+ if (!Object.prototype.hasOwnProperty.call(c.extra, key)) {
97
+ throw new Error(
98
+ `[x402] scheme "${scheme}" violated accepts mutation policy: extra["${key}"] was removed (index ${i})`
99
+ );
100
+ }
101
+ if (!deepEqual(c.extra[key], b.extra[key])) {
102
+ throw new Error(
103
+ `[x402] scheme "${scheme}" violated accepts mutation policy: extra["${key}"] may not be changed (index ${i})`
104
+ );
105
+ }
106
+ }
107
+ if (!isMatchingAccept && Object.keys(c.extra).length !== Object.keys(b.extra).length) {
108
+ throw new Error(
109
+ `[x402] scheme "${scheme}" violated accepts mutation policy: only matching accepts may receive new extra fields (index ${i})`
110
+ );
111
+ }
112
+ }
113
+ }
114
+ function snapshotSettleResponseCore(result) {
115
+ return {
116
+ success: result.success,
117
+ transaction: result.transaction,
118
+ network: result.network,
119
+ amount: result.amount,
120
+ payer: result.payer,
121
+ errorReason: result.errorReason,
122
+ errorMessage: result.errorMessage
123
+ };
124
+ }
125
+ function assertSettleResponseCoreUnchanged(before, after, extensionKey) {
126
+ const keys = [
127
+ "success",
128
+ "transaction",
129
+ "network",
130
+ "amount",
131
+ "payer",
132
+ "errorReason",
133
+ "errorMessage"
134
+ ];
135
+ for (const k of keys) {
136
+ if (!deepEqual(after[k], before[k])) {
137
+ throw new Error(
138
+ `[x402] extension "${extensionKey}" violated settlement mutation policy: field "${String(k)}" is immutable after facilitator settle`
139
+ );
140
+ }
141
+ }
142
+ }
143
+ function assertAdditivePayloadEnrichment(payload, enrichment, callerLabel) {
144
+ for (const key of Object.keys(enrichment)) {
145
+ if (!Object.prototype.hasOwnProperty.call(payload, key)) continue;
146
+ throw new Error(
147
+ `[x402] ${callerLabel} violated settlement payload enrichment policy: "${key}" already exists on the client payload`
148
+ );
149
+ }
150
+ }
151
+ function isPlainRecord(value) {
152
+ return typeof value === "object" && value !== null && !Array.isArray(value);
153
+ }
154
+ function assertAdditiveSettlementExtra(extra, enrichment, callerLabel) {
155
+ assertAdditiveRecord(extra, enrichment, callerLabel, "extra");
156
+ }
157
+ function mergeAdditiveSettlementExtra(extra, enrichment) {
158
+ return mergeAdditiveRecord(extra, enrichment);
159
+ }
160
+ function assertAdditiveRecord(target, enrichment, callerLabel, path) {
161
+ for (const [key, enrichmentValue] of Object.entries(enrichment)) {
162
+ const nextPath = `${path}["${key}"]`;
163
+ if (!Object.prototype.hasOwnProperty.call(target, key)) continue;
164
+ const targetValue = target[key];
165
+ if (isPlainRecord(targetValue) && isPlainRecord(enrichmentValue)) {
166
+ assertAdditiveRecord(targetValue, enrichmentValue, callerLabel, nextPath);
167
+ continue;
168
+ }
169
+ throw new Error(
170
+ `[x402] ${callerLabel} violated settlement response enrichment policy: ${nextPath} already exists on the settlement result`
171
+ );
172
+ }
173
+ }
174
+ function mergeAdditiveRecord(target, enrichment) {
175
+ const merged = { ...target };
176
+ for (const [key, enrichmentValue] of Object.entries(enrichment)) {
177
+ const targetValue = merged[key];
178
+ if (isPlainRecord(targetValue) && isPlainRecord(enrichmentValue)) {
179
+ merged[key] = mergeAdditiveRecord(targetValue, enrichmentValue);
180
+ continue;
181
+ }
182
+ merged[key] = enrichmentValue;
183
+ }
184
+ return merged;
185
+ }
186
+
187
+ // src/server/x402ResourceServer.ts
188
+ function resolveSettlementOverrideAmount(rawAmount, requirements, decimals = 6) {
189
+ const percentMatch = rawAmount.match(/^(\d+(?:\.\d{0,2})?)%$/);
190
+ if (percentMatch) {
191
+ const [intPart, decPart = ""] = percentMatch[1].split(".");
192
+ const scaledPercent = BigInt(intPart) * 100n + BigInt(decPart.padEnd(2, "0").slice(0, 2));
193
+ const base = BigInt(requirements.amount);
194
+ return (base * scaledPercent / 10000n).toString();
195
+ }
196
+ const dollarMatch = rawAmount.match(/^\$(\d+(?:\.\d+)?)$/);
197
+ if (dollarMatch) {
198
+ const dollars = parseFloat(dollarMatch[1]);
199
+ return Math.round(dollars * 10 ** decimals).toString();
200
+ }
201
+ return rawAmount;
202
+ }
203
+ var x402ResourceServer = class {
204
+ /**
205
+ * Creates a new x402ResourceServer instance.
206
+ *
207
+ * @param facilitatorClients - Optional facilitator client(s) for payment processing
208
+ */
209
+ constructor(facilitatorClients) {
210
+ this.registeredServerSchemes = /* @__PURE__ */ new Map();
211
+ this.schemeHookAdapters = /* @__PURE__ */ new Map();
212
+ this.supportedResponsesMap = /* @__PURE__ */ new Map();
213
+ this.facilitatorClientsMap = /* @__PURE__ */ new Map();
214
+ this.registeredExtensions = /* @__PURE__ */ new Map();
215
+ this.extensionHookAdapters = /* @__PURE__ */ new Map();
216
+ this.beforeVerifyHooks = [];
217
+ this.afterVerifyHooks = [];
218
+ this.onVerifyFailureHooks = [];
219
+ this.beforeSettleHooks = [];
220
+ this.afterSettleHooks = [];
221
+ this.onSettleFailureHooks = [];
222
+ this.onVerifiedPaymentCanceledHooks = [];
223
+ if (!facilitatorClients) {
224
+ this.facilitatorClients = [new HTTPFacilitatorClient()];
225
+ } else if (Array.isArray(facilitatorClients)) {
226
+ this.facilitatorClients = facilitatorClients.length > 0 ? facilitatorClients : [new HTTPFacilitatorClient()];
227
+ } else {
228
+ this.facilitatorClients = [facilitatorClients];
229
+ }
230
+ }
231
+ /**
232
+ * Register a scheme/network server implementation.
233
+ *
234
+ * @param network - The network identifier
235
+ * @param server - The scheme/network server implementation
236
+ * @returns The x402ResourceServer instance for chaining
237
+ */
238
+ register(network, server) {
239
+ if (!this.registeredServerSchemes.has(network)) {
240
+ this.registeredServerSchemes.set(network, /* @__PURE__ */ new Map());
241
+ }
242
+ const serverByScheme = this.registeredServerSchemes.get(network);
243
+ serverByScheme.set(server.scheme, server);
244
+ if (!this.schemeHookAdapters.has(network)) {
245
+ this.schemeHookAdapters.set(network, /* @__PURE__ */ new Map());
246
+ }
247
+ const hooksByScheme = this.schemeHookAdapters.get(network);
248
+ const hooks = server.schemeHooks;
249
+ if (!hooks) {
250
+ hooksByScheme.delete(server.scheme);
251
+ return this;
252
+ }
253
+ const handles = {};
254
+ if (hooks.onBeforeVerify) handles.beforeVerify = hooks.onBeforeVerify;
255
+ if (hooks.onAfterVerify) handles.afterVerify = hooks.onAfterVerify;
256
+ if (hooks.onVerifyFailure) handles.onVerifyFailure = hooks.onVerifyFailure;
257
+ if (hooks.onBeforeSettle) handles.beforeSettle = hooks.onBeforeSettle;
258
+ if (hooks.onAfterSettle) handles.afterSettle = hooks.onAfterSettle;
259
+ if (hooks.onSettleFailure) handles.onSettleFailure = hooks.onSettleFailure;
260
+ if (hooks.onVerifiedPaymentCanceled) {
261
+ handles.onVerifiedPaymentCanceled = hooks.onVerifiedPaymentCanceled;
262
+ }
263
+ if (Object.keys(handles).length > 0) {
264
+ hooksByScheme.set(server.scheme, handles);
265
+ } else {
266
+ hooksByScheme.delete(server.scheme);
267
+ }
268
+ return this;
269
+ }
270
+ /**
271
+ * Check if a scheme is registered for a given network.
272
+ *
273
+ * @param network - The network identifier
274
+ * @param scheme - The payment scheme name
275
+ * @returns True if the scheme is registered for the network, false otherwise
276
+ */
277
+ hasRegisteredScheme(network, scheme) {
278
+ return !!findByNetworkAndScheme(this.registeredServerSchemes, scheme, network);
279
+ }
280
+ /**
281
+ * Returns the decimal precision for the asset specified in the given payment requirements.
282
+ * Looks up the registered scheme for the network and delegates to its getAssetDecimals
283
+ * method if available. Falls back to 6 (standard for USDC stablecoins) when the scheme
284
+ * does not implement getAssetDecimals or is not registered.
285
+ *
286
+ * @param requirements - The payment requirements containing scheme, network, and asset
287
+ * @returns The number of decimal places for the asset
288
+ */
289
+ getAssetDecimalsForRequirements(requirements) {
290
+ const scheme = findByNetworkAndScheme(
291
+ this.registeredServerSchemes,
292
+ requirements.scheme,
293
+ requirements.network
294
+ );
295
+ return scheme?.getAssetDecimals?.(requirements.asset ?? "", requirements.network) ?? 6;
296
+ }
297
+ /**
298
+ * Registers a resource server extension (enrichment and optional verify/settle hooks).
299
+ * Re-registering the same key overwrites; omitting `hooks` removes adapter handles for that key.
300
+ *
301
+ * @param extension - Extension definition including `key` and optional `hooks`
302
+ * @returns This server instance for chaining
303
+ */
304
+ registerExtension(extension) {
305
+ this.registeredExtensions.set(extension.key, extension);
306
+ const extensionKey = extension.key;
307
+ const extensionHooks = extension.hooks;
308
+ if (!extensionHooks) {
309
+ this.extensionHookAdapters.delete(extensionKey);
310
+ return this;
311
+ }
312
+ const handles = {};
313
+ const bindExtensionHookAdapter = (extensionHookKey, adapterPhase) => {
314
+ const impl = extensionHooks[extensionHookKey];
315
+ if (!impl) return;
316
+ handles[adapterPhase] = (async (ctx) => {
317
+ if (ctx.declaredExtensions[extensionKey] === void 0) return;
318
+ return impl(
319
+ ctx.declaredExtensions[extensionKey],
320
+ ctx
321
+ );
322
+ });
323
+ };
324
+ bindExtensionHookAdapter("onBeforeVerify", "beforeVerify");
325
+ bindExtensionHookAdapter("onAfterVerify", "afterVerify");
326
+ bindExtensionHookAdapter("onVerifyFailure", "onVerifyFailure");
327
+ bindExtensionHookAdapter("onBeforeSettle", "beforeSettle");
328
+ bindExtensionHookAdapter("onAfterSettle", "afterSettle");
329
+ bindExtensionHookAdapter("onSettleFailure", "onSettleFailure");
330
+ bindExtensionHookAdapter("onVerifiedPaymentCanceled", "onVerifiedPaymentCanceled");
331
+ if (Object.keys(handles).length > 0) {
332
+ this.extensionHookAdapters.set(extensionKey, handles);
333
+ } else {
334
+ this.extensionHookAdapters.delete(extensionKey);
335
+ }
336
+ return this;
337
+ }
338
+ /**
339
+ * Check if an extension is registered.
340
+ *
341
+ * @param key - The extension key
342
+ * @returns True if the extension is registered
343
+ */
344
+ hasExtension(key) {
345
+ return this.registeredExtensions.has(key);
346
+ }
347
+ /**
348
+ * Get all registered extensions.
349
+ *
350
+ * @returns Array of registered extensions
351
+ */
352
+ getExtensions() {
353
+ return Array.from(this.registeredExtensions.values());
354
+ }
355
+ /**
356
+ * Enriches declared extensions using registered extension hooks.
357
+ *
358
+ * @param declaredExtensions - Extensions declared on the route
359
+ * @param transportContext - Transport-specific context (HTTP, A2A, MCP, etc.)
360
+ * @returns Enriched extensions map
361
+ */
362
+ enrichExtensions(declaredExtensions, transportContext) {
363
+ const enriched = {};
364
+ for (const [key, declaration] of Object.entries(declaredExtensions)) {
365
+ const extension = this.registeredExtensions.get(key);
366
+ if (extension?.enrichDeclaration) {
367
+ try {
368
+ enriched[key] = extension.enrichDeclaration(declaration, transportContext);
369
+ } catch (error) {
370
+ this.warnExtensionHookFailure(key, "enrichDeclaration", error);
371
+ enriched[key] = declaration;
372
+ }
373
+ } else {
374
+ enriched[key] = declaration;
375
+ }
376
+ }
377
+ return enriched;
378
+ }
379
+ /**
380
+ * Register a hook to execute before payment verification.
381
+ * Can abort verification by returning { abort: true, reason: string }
382
+ *
383
+ * @param hook - The hook function to register
384
+ * @returns The x402ResourceServer instance for chaining
385
+ */
386
+ onBeforeVerify(hook) {
387
+ this.beforeVerifyHooks.push(hook);
388
+ return this;
389
+ }
390
+ /**
391
+ * Register a hook to execute after successful payment verification.
392
+ *
393
+ * @param hook - The hook function to register
394
+ * @returns The x402ResourceServer instance for chaining
395
+ */
396
+ onAfterVerify(hook) {
397
+ this.afterVerifyHooks.push(hook);
398
+ return this;
399
+ }
400
+ /**
401
+ * Register a hook to execute when payment verification fails.
402
+ * Can recover from failure by returning { recovered: true, result: VerifyResponse }
403
+ *
404
+ * @param hook - The hook function to register
405
+ * @returns The x402ResourceServer instance for chaining
406
+ */
407
+ onVerifyFailure(hook) {
408
+ this.onVerifyFailureHooks.push(hook);
409
+ return this;
410
+ }
411
+ /**
412
+ * Register a hook to execute before payment settlement.
413
+ * Can abort settlement by returning { abort: true, reason: string }
414
+ *
415
+ * @param hook - The hook function to register
416
+ * @returns The x402ResourceServer instance for chaining
417
+ */
418
+ onBeforeSettle(hook) {
419
+ this.beforeSettleHooks.push(hook);
420
+ return this;
421
+ }
422
+ /**
423
+ * Register a hook to execute after successful payment settlement.
424
+ *
425
+ * @param hook - The hook function to register
426
+ * @returns The x402ResourceServer instance for chaining
427
+ */
428
+ onAfterSettle(hook) {
429
+ this.afterSettleHooks.push(hook);
430
+ return this;
431
+ }
432
+ /**
433
+ * Register a hook to execute when payment settlement fails.
434
+ * Can recover from failure by returning { recovered: true, result: SettleResponse }
435
+ *
436
+ * @param hook - The hook function to register
437
+ * @returns The x402ResourceServer instance for chaining
438
+ */
439
+ onSettleFailure(hook) {
440
+ this.onSettleFailureHooks.push(hook);
441
+ return this;
442
+ }
443
+ /**
444
+ * Register a hook to execute when verified payment work is canceled before settlement.
445
+ *
446
+ * @param hook - The hook function to register
447
+ * @returns The x402ResourceServer instance for chaining
448
+ */
449
+ onVerifiedPaymentCanceled(hook) {
450
+ this.onVerifiedPaymentCanceledHooks.push(hook);
451
+ return this;
452
+ }
453
+ /**
454
+ * Initialize by fetching supported kinds from all facilitators
455
+ * Creates mappings for supported responses and facilitator clients
456
+ * Earlier facilitators in the array get precedence
457
+ */
458
+ async initialize() {
459
+ this.supportedResponsesMap.clear();
460
+ this.facilitatorClientsMap.clear();
461
+ let lastError;
462
+ for (const facilitatorClient of this.facilitatorClients) {
463
+ try {
464
+ const supported = await facilitatorClient.getSupported();
465
+ for (const kind of supported.kinds) {
466
+ const x402Version2 = kind.x402Version;
467
+ if (!this.supportedResponsesMap.has(x402Version2)) {
468
+ this.supportedResponsesMap.set(x402Version2, /* @__PURE__ */ new Map());
469
+ }
470
+ const responseVersionMap = this.supportedResponsesMap.get(x402Version2);
471
+ if (!this.facilitatorClientsMap.has(x402Version2)) {
472
+ this.facilitatorClientsMap.set(x402Version2, /* @__PURE__ */ new Map());
473
+ }
474
+ const clientVersionMap = this.facilitatorClientsMap.get(x402Version2);
475
+ if (!responseVersionMap.has(kind.network)) {
476
+ responseVersionMap.set(kind.network, /* @__PURE__ */ new Map());
477
+ }
478
+ const responseNetworkMap = responseVersionMap.get(kind.network);
479
+ if (!clientVersionMap.has(kind.network)) {
480
+ clientVersionMap.set(kind.network, /* @__PURE__ */ new Map());
481
+ }
482
+ const clientNetworkMap = clientVersionMap.get(kind.network);
483
+ if (!responseNetworkMap.has(kind.scheme)) {
484
+ responseNetworkMap.set(kind.scheme, supported);
485
+ clientNetworkMap.set(kind.scheme, facilitatorClient);
486
+ }
487
+ }
488
+ } catch (error) {
489
+ lastError = error;
490
+ console.warn(`Failed to fetch supported kinds from facilitator: ${error}`);
491
+ }
492
+ }
493
+ if (this.supportedResponsesMap.size === 0) {
494
+ throw lastError ? new Error(
495
+ "Failed to initialize: no supported payment kinds loaded from any facilitator.",
496
+ {
497
+ cause: lastError
498
+ }
499
+ ) : new Error(
500
+ "Failed to initialize: no supported payment kinds loaded from any facilitator."
501
+ );
502
+ }
503
+ }
504
+ /**
505
+ * Get supported kind for a specific version, network, and scheme
506
+ *
507
+ * @param x402Version - The x402 version
508
+ * @param network - The network identifier
509
+ * @param scheme - The payment scheme
510
+ * @returns The supported kind or undefined if not found
511
+ */
512
+ getSupportedKind(x402Version2, network, scheme) {
513
+ const versionMap = this.supportedResponsesMap.get(x402Version2);
514
+ if (!versionMap) return void 0;
515
+ const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network);
516
+ if (!supportedResponse) return void 0;
517
+ return supportedResponse.kinds.find(
518
+ (kind) => kind.x402Version === x402Version2 && kind.network === network && kind.scheme === scheme
519
+ );
520
+ }
521
+ /**
522
+ * Get facilitator extensions for a specific version, network, and scheme
523
+ *
524
+ * @param x402Version - The x402 version
525
+ * @param network - The network identifier
526
+ * @param scheme - The payment scheme
527
+ * @returns The facilitator extensions or empty array if not found
528
+ */
529
+ getFacilitatorExtensions(x402Version2, network, scheme) {
530
+ const versionMap = this.supportedResponsesMap.get(x402Version2);
531
+ if (!versionMap) return [];
532
+ const supportedResponse = findByNetworkAndScheme(versionMap, scheme, network);
533
+ return supportedResponse?.extensions || [];
534
+ }
535
+ /**
536
+ * Build payment requirements for a protected resource
537
+ *
538
+ * @param resourceConfig - Configuration for the protected resource
539
+ * @returns Array of payment requirements
540
+ */
541
+ async buildPaymentRequirements(resourceConfig) {
542
+ const requirements = [];
543
+ const scheme = resourceConfig.scheme;
544
+ const SchemeNetworkServer = findByNetworkAndScheme(
545
+ this.registeredServerSchemes,
546
+ scheme,
547
+ resourceConfig.network
548
+ );
549
+ if (!SchemeNetworkServer) {
550
+ console.warn(
551
+ `No server implementation registered for scheme: ${scheme}, network: ${resourceConfig.network}`
552
+ );
553
+ return requirements;
554
+ }
555
+ const supportedKind = this.getSupportedKind(
556
+ x402Version,
557
+ resourceConfig.network,
558
+ SchemeNetworkServer.scheme
559
+ );
560
+ if (!supportedKind) {
561
+ throw new Error(
562
+ `Facilitator does not support ${SchemeNetworkServer.scheme} on ${resourceConfig.network}. Make sure to call initialize() to fetch supported kinds from facilitators.`
563
+ );
564
+ }
565
+ const facilitatorExtensions = this.getFacilitatorExtensions(
566
+ x402Version,
567
+ resourceConfig.network,
568
+ SchemeNetworkServer.scheme
569
+ );
570
+ const parsedPrice = await SchemeNetworkServer.parsePrice(
571
+ resourceConfig.price,
572
+ resourceConfig.network
573
+ );
574
+ const baseRequirements = {
575
+ scheme: SchemeNetworkServer.scheme,
576
+ network: resourceConfig.network,
577
+ amount: parsedPrice.amount,
578
+ asset: parsedPrice.asset,
579
+ payTo: resourceConfig.payTo,
580
+ maxTimeoutSeconds: resourceConfig.maxTimeoutSeconds || 300,
581
+ // Default 5 minutes
582
+ extra: {
583
+ ...parsedPrice.extra,
584
+ ...resourceConfig.extra
585
+ // Merge user-provided extra
586
+ }
587
+ };
588
+ const requirement = await SchemeNetworkServer.enhancePaymentRequirements(
589
+ baseRequirements,
590
+ supportedKind,
591
+ facilitatorExtensions
592
+ );
593
+ requirements.push(requirement);
594
+ return requirements;
595
+ }
596
+ /**
597
+ * Build payment requirements from multiple payment options
598
+ * This method handles resolving dynamic payTo/price functions and builds requirements for each option
599
+ *
600
+ * @param paymentOptions - Array of payment options to convert
601
+ * @param context - HTTP request context for resolving dynamic functions
602
+ * @returns Array of payment requirements (one per option)
603
+ */
604
+ async buildPaymentRequirementsFromOptions(paymentOptions, context) {
605
+ const allRequirements = [];
606
+ for (const option of paymentOptions) {
607
+ const resolvedPayTo = typeof option.payTo === "function" ? await option.payTo(context) : option.payTo;
608
+ const resolvedPrice = typeof option.price === "function" ? await option.price(context) : option.price;
609
+ const resourceConfig = {
610
+ scheme: option.scheme,
611
+ payTo: resolvedPayTo,
612
+ price: resolvedPrice,
613
+ network: option.network,
614
+ maxTimeoutSeconds: option.maxTimeoutSeconds,
615
+ extra: option.extra
616
+ };
617
+ const requirements = await this.buildPaymentRequirements(resourceConfig);
618
+ allRequirements.push(...requirements);
619
+ }
620
+ return allRequirements;
621
+ }
622
+ /**
623
+ * Create a payment required response
624
+ *
625
+ * @param requirements - Payment requirements
626
+ * @param resourceInfo - Resource information
627
+ * @param error - Error message
628
+ * @param extensions - Optional declared extensions (for per-key enrichment)
629
+ * @param transportContext - Optional transport-specific context (e.g., HTTP request, MCP tool context)
630
+ * @param paymentPayload - Optional failed payment payload for response-time scheme enrichment
631
+ * @returns Payment required response object
632
+ */
633
+ async createPaymentRequiredResponse(requirements, resourceInfo, error, extensions, transportContext, paymentPayload) {
634
+ const acceptsClone = requirements.map((req) => ({
635
+ ...req,
636
+ extra: structuredClone(req.extra)
637
+ }));
638
+ let workingAccepts = acceptsClone;
639
+ let baselineAccepts = snapshotPaymentRequirementsList(workingAccepts);
640
+ let response = {
641
+ x402Version: 2,
642
+ error,
643
+ resource: resourceInfo,
644
+ accepts: workingAccepts
645
+ };
646
+ if (extensions && Object.keys(extensions).length > 0) {
647
+ response.extensions = extensions;
648
+ }
649
+ for (let i = 0; i < workingAccepts.length; i++) {
650
+ const accept = workingAccepts[i];
651
+ const scheme = findByNetworkAndScheme(
652
+ this.registeredServerSchemes,
653
+ accept.scheme,
654
+ accept.network
655
+ );
656
+ if (!scheme?.enrichPaymentRequiredResponse) {
657
+ continue;
658
+ }
659
+ const context = {
660
+ requirements: workingAccepts,
661
+ paymentPayload,
662
+ resourceInfo,
663
+ error,
664
+ paymentRequiredResponse: response,
665
+ transportContext
666
+ };
667
+ const enrichedAccepts = await scheme.enrichPaymentRequiredResponse(context);
668
+ if (enrichedAccepts !== void 0) {
669
+ workingAccepts = enrichedAccepts;
670
+ response.accepts = workingAccepts;
671
+ }
672
+ assertAcceptsAdditiveExtraAfterSchemeEnrich(
673
+ baselineAccepts,
674
+ response.accepts,
675
+ accept.scheme,
676
+ accept.network
677
+ );
678
+ baselineAccepts = snapshotPaymentRequirementsList(response.accepts);
679
+ }
680
+ if (extensions) {
681
+ for (const [key, declaration] of Object.entries(extensions)) {
682
+ const extension = this.registeredExtensions.get(key);
683
+ if (extension?.enrichPaymentRequiredResponse) {
684
+ try {
685
+ const context = {
686
+ requirements: workingAccepts,
687
+ resourceInfo,
688
+ error,
689
+ paymentRequiredResponse: response,
690
+ transportContext
691
+ };
692
+ const extensionData = await extension.enrichPaymentRequiredResponse(
693
+ declaration,
694
+ context
695
+ );
696
+ if (extensionData !== void 0) {
697
+ if (!response.extensions) {
698
+ response.extensions = {};
699
+ }
700
+ response.extensions[key] = extensionData;
701
+ }
702
+ } catch (error2) {
703
+ this.warnExtensionHookFailure(key, "enrichPaymentRequiredResponse", error2);
704
+ }
705
+ assertAcceptsAllowlistedAfterExtensionEnrich(baselineAccepts, workingAccepts, key);
706
+ baselineAccepts = snapshotPaymentRequirementsList(workingAccepts);
707
+ }
708
+ }
709
+ }
710
+ return response;
711
+ }
712
+ /**
713
+ * Verifies a payment against requirements, running manual and in-use extension hooks.
714
+ *
715
+ * @param paymentPayload - Signed payment payload from the client
716
+ * @param requirements - Requirements matched to the payload
717
+ * @param declaredExtensions - Optional per-extension declarations for the request
718
+ * @param transportContext - Optional transport-specific context (e.g. HTTP, MCP)
719
+ * @returns Facilitator verify outcome (optionally carrying a `skipHandler` directive),
720
+ * or abort/recovery as driven by hooks
721
+ */
722
+ async verifyPayment(paymentPayload, requirements, declaredExtensions, transportContext) {
723
+ const resolvedDeclaredExtensions = declaredExtensions ?? {};
724
+ const extensionKeysInUse = Object.keys(resolvedDeclaredExtensions);
725
+ const matchedScheme = {
726
+ network: requirements.network,
727
+ scheme: requirements.scheme
728
+ };
729
+ const context = {
730
+ paymentPayload,
731
+ requirements,
732
+ declaredExtensions: resolvedDeclaredExtensions,
733
+ transportContext
734
+ };
735
+ for (const { label, hook } of this.getLabeledHooks(
736
+ "beforeVerify",
737
+ extensionKeysInUse,
738
+ matchedScheme
739
+ )) {
740
+ try {
741
+ const result = await hook(context);
742
+ if (result && "abort" in result && result.abort) {
743
+ return {
744
+ isValid: false,
745
+ invalidReason: result.reason,
746
+ invalidMessage: result.message
747
+ };
748
+ }
749
+ if (result && "skip" in result && result.skip) {
750
+ return this.runAfterVerifyHooks(
751
+ result.result,
752
+ context,
753
+ extensionKeysInUse,
754
+ matchedScheme
755
+ );
756
+ }
757
+ } catch (error) {
758
+ this.warnResourceServerHookFailure("beforeVerify", label, error);
759
+ }
760
+ }
761
+ try {
762
+ const facilitatorClient = this.getFacilitatorClient(
763
+ paymentPayload.x402Version,
764
+ requirements.network,
765
+ requirements.scheme
766
+ );
767
+ let verifyResult;
768
+ if (!facilitatorClient) {
769
+ let lastError;
770
+ for (const client of this.facilitatorClients) {
771
+ try {
772
+ verifyResult = await client.verify(paymentPayload, requirements);
773
+ break;
774
+ } catch (error) {
775
+ lastError = error;
776
+ }
777
+ }
778
+ if (!verifyResult) {
779
+ throw lastError || new Error(
780
+ `No facilitator supports ${requirements.scheme} on ${requirements.network} for v${paymentPayload.x402Version}`
781
+ );
782
+ }
783
+ } else {
784
+ verifyResult = await facilitatorClient.verify(paymentPayload, requirements);
785
+ }
786
+ return this.runAfterVerifyHooks(verifyResult, context, extensionKeysInUse, matchedScheme);
787
+ } catch (error) {
788
+ const failureContext = {
789
+ ...context,
790
+ error
791
+ };
792
+ for (const { label, hook } of this.getLabeledHooks(
793
+ "onVerifyFailure",
794
+ extensionKeysInUse,
795
+ matchedScheme
796
+ )) {
797
+ try {
798
+ const result = await hook(failureContext);
799
+ if (result && "recovered" in result && result.recovered) {
800
+ return result.result;
801
+ }
802
+ } catch (error2) {
803
+ this.warnResourceServerHookFailure("onVerifyFailure", label, error2);
804
+ }
805
+ }
806
+ throw error;
807
+ }
808
+ }
809
+ /**
810
+ * Create cancellation controls for a verified payment attempt.
811
+ *
812
+ * @param paymentPayload - Signed payment payload from the client
813
+ * @param requirements - Requirements matched to the payload
814
+ * @param declaredExtensions - Optional per-extension declarations for the request
815
+ * @param transportContext - Optional transport-specific context
816
+ * @returns Cancellation controls for the verified payment attempt
817
+ */
818
+ createPaymentCancellationDispatcher(paymentPayload, requirements, declaredExtensions, transportContext) {
819
+ const resolvedDeclaredExtensions = declaredExtensions ?? {};
820
+ let cancelPromise;
821
+ return {
822
+ cancel: (options) => {
823
+ if (!cancelPromise) {
824
+ cancelPromise = this.dispatchVerifiedPaymentCanceled(
825
+ paymentPayload,
826
+ requirements,
827
+ resolvedDeclaredExtensions,
828
+ options,
829
+ transportContext
830
+ );
831
+ }
832
+ return cancelPromise;
833
+ }
834
+ };
835
+ }
836
+ /**
837
+ * Settle a verified payment
838
+ *
839
+ * @param paymentPayload - The payment payload to settle
840
+ * @param requirements - The payment requirements
841
+ * @param declaredExtensions - Optional declared extensions (for per-key enrichment)
842
+ * @param transportContext - Optional transport-specific context (e.g., HTTP request/response, MCP tool context)
843
+ * @param settlementOverrides - Optional overrides for settlement parameters (e.g., partial settlement amount)
844
+ * @returns Settlement response
845
+ */
846
+ async settlePayment(paymentPayload, requirements, declaredExtensions, transportContext, settlementOverrides) {
847
+ const resolvedDeclaredExtensions = declaredExtensions ?? {};
848
+ const extensionKeysInUse = Object.keys(resolvedDeclaredExtensions);
849
+ let effectiveRequirements = requirements;
850
+ if (settlementOverrides?.amount !== void 0) {
851
+ const scheme = findByNetworkAndScheme(
852
+ this.registeredServerSchemes,
853
+ requirements.scheme,
854
+ requirements.network
855
+ );
856
+ const decimals = scheme?.getAssetDecimals?.(requirements.asset ?? "", requirements.network) ?? 6;
857
+ effectiveRequirements = {
858
+ ...requirements,
859
+ amount: resolveSettlementOverrideAmount(settlementOverrides.amount, requirements, decimals)
860
+ };
861
+ }
862
+ const context = {
863
+ paymentPayload,
864
+ requirements: effectiveRequirements,
865
+ declaredExtensions: resolvedDeclaredExtensions,
866
+ transportContext
867
+ };
868
+ const matchedScheme = {
869
+ network: effectiveRequirements.network,
870
+ scheme: effectiveRequirements.scheme
871
+ };
872
+ for (const { label, hook } of this.getLabeledHooks(
873
+ "beforeSettle",
874
+ extensionKeysInUse,
875
+ matchedScheme
876
+ )) {
877
+ try {
878
+ const result = await hook(context);
879
+ if (result && "abort" in result && result.abort) {
880
+ throw new SettleError(400, {
881
+ success: false,
882
+ errorReason: result.reason,
883
+ errorMessage: result.message,
884
+ transaction: "",
885
+ network: requirements.network
886
+ });
887
+ }
888
+ if (result && "skip" in result && result.skip) {
889
+ const settleResult = result.result;
890
+ const skipResultContext = {
891
+ ...context,
892
+ result: settleResult,
893
+ transportContext
894
+ };
895
+ for (const { label: label2, hook: hook2 } of this.getLabeledHooks(
896
+ "afterSettle",
897
+ extensionKeysInUse,
898
+ matchedScheme
899
+ )) {
900
+ try {
901
+ await hook2(skipResultContext);
902
+ } catch (error) {
903
+ this.warnResourceServerHookFailure("afterSettle", label2, error);
904
+ }
905
+ }
906
+ await this.enrichSettlementResponse(
907
+ settleResult,
908
+ skipResultContext,
909
+ resolvedDeclaredExtensions,
910
+ matchedScheme
911
+ );
912
+ return settleResult;
913
+ }
914
+ } catch (error) {
915
+ if (error instanceof SettleError) {
916
+ throw error;
917
+ }
918
+ this.warnResourceServerHookFailure("beforeSettle", label, error);
919
+ }
920
+ }
921
+ try {
922
+ const scheme = findByNetworkAndScheme(
923
+ this.registeredServerSchemes,
924
+ matchedScheme.scheme,
925
+ matchedScheme.network
926
+ );
927
+ const payloadEnrichmentHook = scheme?.enrichSettlementPayload;
928
+ if (payloadEnrichmentHook) {
929
+ const label = `scheme "${matchedScheme.scheme}" enrichSettlementPayload`;
930
+ const enrichment = await payloadEnrichmentHook(context);
931
+ if (enrichment !== void 0) {
932
+ assertAdditivePayloadEnrichment(paymentPayload.payload, enrichment, label);
933
+ paymentPayload.payload = { ...paymentPayload.payload, ...enrichment };
934
+ }
935
+ }
936
+ const facilitatorClient = this.getFacilitatorClient(
937
+ paymentPayload.x402Version,
938
+ effectiveRequirements.network,
939
+ effectiveRequirements.scheme
940
+ );
941
+ let settleResult;
942
+ if (!facilitatorClient) {
943
+ let lastError;
944
+ for (const client of this.facilitatorClients) {
945
+ try {
946
+ settleResult = await client.settle(paymentPayload, effectiveRequirements);
947
+ break;
948
+ } catch (error) {
949
+ lastError = error;
950
+ }
951
+ }
952
+ if (!settleResult) {
953
+ throw lastError || new Error(
954
+ `No facilitator supports ${effectiveRequirements.scheme} on ${effectiveRequirements.network} for v${paymentPayload.x402Version}`
955
+ );
956
+ }
957
+ } else {
958
+ settleResult = await facilitatorClient.settle(paymentPayload, effectiveRequirements);
959
+ }
960
+ const resultContext = {
961
+ ...context,
962
+ result: settleResult
963
+ };
964
+ for (const { label, hook } of this.getLabeledHooks(
965
+ "afterSettle",
966
+ extensionKeysInUse,
967
+ matchedScheme
968
+ )) {
969
+ try {
970
+ await hook(resultContext);
971
+ } catch (error) {
972
+ this.warnResourceServerHookFailure("afterSettle", label, error);
973
+ }
974
+ }
975
+ await this.enrichSettlementResponse(
976
+ settleResult,
977
+ resultContext,
978
+ resolvedDeclaredExtensions,
979
+ matchedScheme
980
+ );
981
+ return settleResult;
982
+ } catch (error) {
983
+ const failureContext = {
984
+ ...context,
985
+ error
986
+ };
987
+ for (const { label, hook } of this.getLabeledHooks(
988
+ "onSettleFailure",
989
+ extensionKeysInUse,
990
+ matchedScheme
991
+ )) {
992
+ try {
993
+ const result = await hook(failureContext);
994
+ if (result && "recovered" in result && result.recovered) {
995
+ return result.result;
996
+ }
997
+ } catch (error2) {
998
+ this.warnResourceServerHookFailure("onSettleFailure", label, error2);
999
+ }
1000
+ }
1001
+ throw error;
1002
+ }
1003
+ }
1004
+ /**
1005
+ * Find matching payment requirements for a payment
1006
+ *
1007
+ * @param availableRequirements - Array of available payment requirements
1008
+ * @param paymentPayload - The payment payload
1009
+ * @returns Matching payment requirements or undefined
1010
+ */
1011
+ /**
1012
+ * Validates optional client extension echoes against server-advertised extension info.
1013
+ * When the client omits extensions entirely, validation passes.
1014
+ *
1015
+ * @param paymentRequired - Server payment required response used for matching
1016
+ * @param paymentPayload - Client payment payload
1017
+ * @returns Whether echoed extension info preserves server-advertised values
1018
+ */
1019
+ validateExtensions(paymentRequired, paymentPayload) {
1020
+ if (paymentPayload.x402Version !== 2) {
1021
+ return { valid: true };
1022
+ }
1023
+ const serverExtensions = paymentRequired.extensions;
1024
+ if (!serverExtensions || Object.keys(serverExtensions).length === 0) {
1025
+ return { valid: true };
1026
+ }
1027
+ const clientExtensions = paymentPayload.extensions;
1028
+ if (!clientExtensions || Object.keys(clientExtensions).length === 0) {
1029
+ return { valid: true };
1030
+ }
1031
+ for (const [key, echoedValue] of Object.entries(clientExtensions)) {
1032
+ if (!Object.prototype.hasOwnProperty.call(serverExtensions, key)) {
1033
+ continue;
1034
+ }
1035
+ const advertisedInfo = getExtensionInfo(serverExtensions[key]);
1036
+ const echoedInfo = getExtensionInfo(echoedValue);
1037
+ if (!extensionInfoMatchesAdvertised(advertisedInfo, echoedInfo)) {
1038
+ return {
1039
+ valid: false,
1040
+ invalidReason: "extension_echo_mismatch",
1041
+ extensionKey: key
1042
+ };
1043
+ }
1044
+ }
1045
+ return { valid: true };
1046
+ }
1047
+ /**
1048
+ * Finds the server-advertised requirement that matches a client payment payload.
1049
+ *
1050
+ * @param availableRequirements - Payment requirements advertised for the resource.
1051
+ * @param paymentPayload - Signed payment payload from the client.
1052
+ * @returns The matching requirement, or undefined when none match.
1053
+ */
1054
+ findMatchingRequirements(availableRequirements, paymentPayload) {
1055
+ switch (paymentPayload.x402Version) {
1056
+ case 2:
1057
+ return availableRequirements.find(
1058
+ (paymentRequirements) => paymentRequirementsMatchAccepted(paymentRequirements, paymentPayload.accepted)
1059
+ );
1060
+ case 1:
1061
+ return availableRequirements.find(
1062
+ (req) => req.scheme === paymentPayload.accepted.scheme && req.network === paymentPayload.accepted.network
1063
+ );
1064
+ default:
1065
+ throw new Error(
1066
+ `Unsupported x402 version: ${paymentPayload.x402Version}`
1067
+ );
1068
+ }
1069
+ }
1070
+ /**
1071
+ * Logs a warning when a manual or extension adapter lifecycle hook throws.
1072
+ *
1073
+ * @param phase - Lifecycle phase name (e.g. `beforeVerify`)
1074
+ * @param label - Hook source label from {@link getLabeledHooks} (manual index or extension key)
1075
+ * @param error - Thrown value or rejection reason
1076
+ */
1077
+ warnResourceServerHookFailure(phase, label, error) {
1078
+ const detail = error instanceof Error ? error.message : String(error);
1079
+ console.warn(`[x402] Resource server ${phase} hook threw (${label}): ${detail}`);
1080
+ }
1081
+ /**
1082
+ * Logs a warning when a registered extension enrichment hook throws.
1083
+ *
1084
+ * @param extensionKey - Registered extension identifier
1085
+ * @param hookName - Hook method name (e.g. `enrichDeclaration`)
1086
+ * @param error - Thrown value or rejection reason
1087
+ */
1088
+ warnExtensionHookFailure(extensionKey, hookName, error) {
1089
+ const detail = error instanceof Error ? error.message : String(error);
1090
+ console.warn(`[x402] extension "${extensionKey}" ${hookName} threw: ${detail}`);
1091
+ }
1092
+ /**
1093
+ * Executes after-verify hooks for facilitator and hook-provided verify results.
1094
+ *
1095
+ * @param verifyResult - Verify response passed to after-verify hooks.
1096
+ * @param context - Verify context shared with before-verify hooks.
1097
+ * @param extensionKeysInUse - Declared extension keys for this request.
1098
+ * @param matchedScheme - Scheme/network selected for this payment.
1099
+ * @param matchedScheme.network - Matched payment network.
1100
+ * @param matchedScheme.scheme - Matched payment scheme.
1101
+ * @returns Verify response with any in-process skip handler directive.
1102
+ */
1103
+ async runAfterVerifyHooks(verifyResult, context, extensionKeysInUse, matchedScheme) {
1104
+ const resultContext = {
1105
+ ...context,
1106
+ result: verifyResult
1107
+ };
1108
+ let skipHandler;
1109
+ for (const { label, hook } of this.getLabeledHooks(
1110
+ "afterVerify",
1111
+ extensionKeysInUse,
1112
+ matchedScheme
1113
+ )) {
1114
+ try {
1115
+ const directive = await hook(resultContext);
1116
+ if (directive && "skipHandler" in directive && directive.skipHandler) {
1117
+ skipHandler = directive.response ?? {};
1118
+ }
1119
+ } catch (error) {
1120
+ this.warnResourceServerHookFailure("afterVerify", label, error);
1121
+ }
1122
+ }
1123
+ return skipHandler ? { ...verifyResult, skipHandler } : verifyResult;
1124
+ }
1125
+ /**
1126
+ * Runs response enrichment after settlement lifecycle hooks complete.
1127
+ *
1128
+ * @param settleResult - Mutable settlement result being returned to the caller
1129
+ * @param context - Read-only hook context for enrichment callbacks
1130
+ * @param declaredExtensions - Extension declarations present on this payment
1131
+ * @param matchedScheme - Scheme/network selected for this settlement
1132
+ * @param matchedScheme.network - Matched payment network
1133
+ * @param matchedScheme.scheme - Matched payment scheme
1134
+ */
1135
+ async enrichSettlementResponse(settleResult, context, declaredExtensions, matchedScheme) {
1136
+ if (Object.keys(declaredExtensions).length > 0) {
1137
+ const settleCoreSnapshot = snapshotSettleResponseCore(settleResult);
1138
+ for (const [key, declaration] of Object.entries(declaredExtensions)) {
1139
+ const extension = this.registeredExtensions.get(key);
1140
+ if (!extension?.enrichSettlementResponse) continue;
1141
+ try {
1142
+ const extensionData = await extension.enrichSettlementResponse(declaration, context);
1143
+ if (extensionData !== void 0) {
1144
+ if (!settleResult.extensions) {
1145
+ settleResult.extensions = {};
1146
+ }
1147
+ settleResult.extensions[key] = extensionData;
1148
+ }
1149
+ } catch (error) {
1150
+ this.warnExtensionHookFailure(key, "enrichSettlementResponse", error);
1151
+ }
1152
+ assertSettleResponseCoreUnchanged(settleCoreSnapshot, settleResult, key);
1153
+ }
1154
+ }
1155
+ const scheme = findByNetworkAndScheme(
1156
+ this.registeredServerSchemes,
1157
+ matchedScheme.scheme,
1158
+ matchedScheme.network
1159
+ );
1160
+ const hook = scheme?.enrichSettlementResponse;
1161
+ if (!hook) return;
1162
+ const label = `scheme "${matchedScheme.scheme}" enrichSettlementResponse`;
1163
+ try {
1164
+ const enrichment = await hook(context);
1165
+ if (enrichment === void 0) return;
1166
+ assertAdditiveSettlementExtra(settleResult.extra ?? {}, enrichment, label);
1167
+ settleResult.extra = mergeAdditiveSettlementExtra(settleResult.extra ?? {}, enrichment);
1168
+ } catch (error) {
1169
+ this.warnResourceServerHookFailure("enrichSettlementResponse", label, error);
1170
+ }
1171
+ }
1172
+ /**
1173
+ * Notify hooks that verified work ended before settlement.
1174
+ *
1175
+ * @param paymentPayload - Signed payment payload from the client
1176
+ * @param requirements - Requirements matched to the payload
1177
+ * @param declaredExtensions - Optional per-extension declarations for the request
1178
+ * @param options - Cancellation reason and optional diagnostics
1179
+ * @param fallbackTransportContext - Optional transport-specific context
1180
+ */
1181
+ async dispatchVerifiedPaymentCanceled(paymentPayload, requirements, declaredExtensions, options, fallbackTransportContext) {
1182
+ const extensionKeysInUse = Object.keys(declaredExtensions);
1183
+ const matchedScheme = {
1184
+ network: requirements.network,
1185
+ scheme: requirements.scheme
1186
+ };
1187
+ const context = {
1188
+ paymentPayload,
1189
+ requirements,
1190
+ declaredExtensions,
1191
+ transportContext: fallbackTransportContext,
1192
+ reason: options.reason,
1193
+ error: options.error,
1194
+ responseStatus: options.responseStatus
1195
+ };
1196
+ for (const { label, hook } of this.getLabeledHooks(
1197
+ "onVerifiedPaymentCanceled",
1198
+ extensionKeysInUse,
1199
+ matchedScheme
1200
+ )) {
1201
+ try {
1202
+ await hook(context);
1203
+ } catch (error) {
1204
+ this.warnResourceServerHookFailure("onVerifiedPaymentCanceled", label, error);
1205
+ }
1206
+ }
1207
+ }
1208
+ /**
1209
+ * Manual hooks first, then the matched scheme adapter, then extension adapters for keys in use.
1210
+ * Each entry carries a stable label for logging when a hook throws.
1211
+ *
1212
+ * @param phase - Hook slot (e.g. `beforeVerify`)
1213
+ * @param extensionKeysInUse - Declared extension keys for this request
1214
+ * @param matchedScheme - Scheme/network selected for this payment
1215
+ * @param matchedScheme.network - Matched payment network
1216
+ * @param matchedScheme.scheme - Matched payment scheme
1217
+ * @returns Hooks in invocation order with source labels
1218
+ */
1219
+ getLabeledHooks(phase, extensionKeysInUse, matchedScheme) {
1220
+ const manualKey = `${phase}Hooks`;
1221
+ const manual = this[manualKey];
1222
+ const out = [];
1223
+ manual.forEach((hook, index) => {
1224
+ out.push({ label: `manual ${phase} hook #${index}`, hook });
1225
+ });
1226
+ if (matchedScheme) {
1227
+ const schemeHandles = findByNetworkAndScheme(
1228
+ this.schemeHookAdapters,
1229
+ matchedScheme.scheme,
1230
+ matchedScheme.network
1231
+ );
1232
+ const hook = schemeHandles?.[phase];
1233
+ if (hook !== void 0) {
1234
+ out.push({
1235
+ label: `scheme "${matchedScheme.scheme}" ${phase}`,
1236
+ hook
1237
+ });
1238
+ }
1239
+ }
1240
+ const inUse = new Set(extensionKeysInUse);
1241
+ for (const [extensionKey, adapterHandles] of this.extensionHookAdapters.entries()) {
1242
+ if (!inUse.has(extensionKey)) continue;
1243
+ const hook = adapterHandles[phase];
1244
+ if (hook !== void 0) {
1245
+ out.push({ label: `extension "${extensionKey}" ${phase}`, hook });
1246
+ }
1247
+ }
1248
+ return out;
1249
+ }
1250
+ /**
1251
+ * Get facilitator client for a specific version, network, and scheme
1252
+ *
1253
+ * @param x402Version - The x402 version
1254
+ * @param network - The network identifier
1255
+ * @param scheme - The payment scheme
1256
+ * @returns The facilitator client or undefined if not found
1257
+ */
1258
+ getFacilitatorClient(x402Version2, network, scheme) {
1259
+ const versionMap = this.facilitatorClientsMap.get(x402Version2);
1260
+ if (!versionMap) return void 0;
1261
+ return findByNetworkAndScheme(versionMap, scheme, network);
1262
+ }
1263
+ };
1264
+ function getExtensionInfo(value) {
1265
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, "info")) {
1266
+ return value.info;
1267
+ }
1268
+ return value;
1269
+ }
1270
+ function extensionInfoMatchesAdvertised(advertised, echoed) {
1271
+ return objectContainsSubset(advertised, echoed);
1272
+ }
1273
+ function paymentRequirementsMatchAccepted(required, accepted) {
1274
+ const { extra: requiredExtra, ...requiredCore } = required;
1275
+ const { extra: acceptedExtra, ...acceptedCore } = accepted;
1276
+ if (!deepEqual(requiredCore, acceptedCore)) {
1277
+ return false;
1278
+ }
1279
+ if (requiredExtra === void 0) {
1280
+ return true;
1281
+ }
1282
+ return objectContainsSubset(requiredExtra, acceptedExtra);
1283
+ }
1284
+ function objectContainsSubset(expected, actual) {
1285
+ if (expected === null || typeof expected !== "object" || Array.isArray(expected)) {
1286
+ return deepEqual(expected, actual);
1287
+ }
1288
+ if (actual === null || typeof actual !== "object" || Array.isArray(actual)) {
1289
+ return false;
1290
+ }
1291
+ const actualRecord = actual;
1292
+ return Object.entries(expected).every(([key, value]) => {
1293
+ const hasActualKey = Object.prototype.hasOwnProperty.call(actualRecord, key);
1294
+ if (!hasActualKey) {
1295
+ return value === void 0;
1296
+ }
1297
+ return objectContainsSubset(value, actualRecord[key]);
1298
+ });
1299
+ }
1300
+ export {
1301
+ FacilitatorResponseError,
1302
+ HTTPFacilitatorClient,
1303
+ RouteConfigurationError,
1304
+ SETTLEMENT_OVERRIDES_HEADER,
1305
+ assertAcceptsAdditiveExtraAfterSchemeEnrich,
1306
+ assertAcceptsAllowlistedAfterExtensionEnrich,
1307
+ assertAdditivePayloadEnrichment,
1308
+ assertAdditiveSettlementExtra,
1309
+ assertSettleResponseCoreUnchanged,
1310
+ checkIfBazaarNeeded,
1311
+ getFacilitatorResponseError,
1312
+ isVacantStringField,
1313
+ snapshotPaymentRequirementsList,
1314
+ snapshotSettleResponseCore,
1315
+ x402HTTPResourceServer,
1316
+ x402ResourceServer
1317
+ };
1318
+ //# sourceMappingURL=index.mjs.map