moltspay 0.8.14 → 0.9.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.
@@ -33,15 +33,459 @@ __export(server_exports, {
33
33
  MoltsPayServer: () => MoltsPayServer
34
34
  });
35
35
  module.exports = __toCommonJS(server_exports);
36
- var import_fs = require("fs");
36
+ var import_fs2 = require("fs");
37
37
  var import_http = require("http");
38
+ var path2 = __toESM(require("path"));
39
+
40
+ // src/facilitators/interface.ts
41
+ var BaseFacilitator = class {
42
+ supportsNetwork(network) {
43
+ return this.supportedNetworks.includes(network);
44
+ }
45
+ };
46
+
47
+ // src/facilitators/cdp.ts
48
+ var import_fs = require("fs");
38
49
  var path = __toESM(require("path"));
39
50
  var X402_VERSION = 2;
51
+ var CDP_MAINNET_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
52
+ var CDP_TESTNET_URL = "https://www.x402.org/facilitator";
53
+ function loadEnvFile() {
54
+ const envPaths = [
55
+ path.join(process.cwd(), ".env"),
56
+ path.join(process.env.HOME || "", ".moltspay", ".env")
57
+ ];
58
+ for (const envPath of envPaths) {
59
+ if ((0, import_fs.existsSync)(envPath)) {
60
+ try {
61
+ const content = (0, import_fs.readFileSync)(envPath, "utf-8");
62
+ for (const line of content.split("\n")) {
63
+ const trimmed = line.trim();
64
+ if (!trimmed || trimmed.startsWith("#")) continue;
65
+ const eqIndex = trimmed.indexOf("=");
66
+ if (eqIndex === -1) continue;
67
+ const key = trimmed.slice(0, eqIndex).trim();
68
+ let value = trimmed.slice(eqIndex + 1).trim();
69
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
70
+ value = value.slice(1, -1);
71
+ }
72
+ if (!process.env[key]) {
73
+ process.env[key] = value;
74
+ }
75
+ }
76
+ break;
77
+ } catch {
78
+ }
79
+ }
80
+ }
81
+ }
82
+ var CDPFacilitator = class extends BaseFacilitator {
83
+ name = "cdp";
84
+ displayName = "Coinbase CDP";
85
+ supportedNetworks;
86
+ endpoint;
87
+ useMainnet;
88
+ apiKeyId;
89
+ apiKeySecret;
90
+ constructor(config = {}) {
91
+ super();
92
+ loadEnvFile();
93
+ this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
94
+ this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
95
+ this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
96
+ this.endpoint = this.useMainnet ? CDP_MAINNET_URL : CDP_TESTNET_URL;
97
+ this.supportedNetworks = this.useMainnet ? ["eip155:8453"] : ["eip155:8453", "eip155:84532"];
98
+ if (this.useMainnet && (!this.apiKeyId || !this.apiKeySecret)) {
99
+ console.warn("[CDPFacilitator] WARNING: Mainnet mode but missing CDP credentials!");
100
+ console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
101
+ }
102
+ }
103
+ /**
104
+ * Get auth headers for CDP API requests
105
+ */
106
+ async getAuthHeaders(method, urlPath, body) {
107
+ if (!this.useMainnet) {
108
+ return {};
109
+ }
110
+ if (!this.apiKeyId || !this.apiKeySecret) {
111
+ throw new Error("CDP credentials required for mainnet");
112
+ }
113
+ try {
114
+ const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
115
+ return await getAuthHeaders({
116
+ apiKeyId: this.apiKeyId,
117
+ apiKeySecret: this.apiKeySecret,
118
+ requestMethod: method,
119
+ requestHost: "api.cdp.coinbase.com",
120
+ requestPath: urlPath,
121
+ requestBody: body
122
+ });
123
+ } catch (err) {
124
+ throw new Error(`Failed to generate CDP auth: ${err.message}`);
125
+ }
126
+ }
127
+ /**
128
+ * Health check - verify facilitator is reachable
129
+ */
130
+ async healthCheck() {
131
+ const start = Date.now();
132
+ try {
133
+ const controller = new AbortController();
134
+ const timeout = setTimeout(() => controller.abort(), 5e3);
135
+ const response = await fetch(this.endpoint.replace("/x402", ""), {
136
+ method: "HEAD",
137
+ signal: controller.signal
138
+ }).catch(() => null);
139
+ clearTimeout(timeout);
140
+ const latencyMs = Date.now() - start;
141
+ return {
142
+ healthy: response !== null,
143
+ latencyMs
144
+ };
145
+ } catch (err) {
146
+ return {
147
+ healthy: false,
148
+ error: err.message,
149
+ latencyMs: Date.now() - start
150
+ };
151
+ }
152
+ }
153
+ /**
154
+ * Verify payment signature with facilitator
155
+ */
156
+ async verify(paymentPayload, requirements) {
157
+ try {
158
+ const requestBody = {
159
+ x402Version: X402_VERSION,
160
+ paymentPayload,
161
+ paymentRequirements: requirements
162
+ };
163
+ const headers = {
164
+ "Content-Type": "application/json"
165
+ };
166
+ if (this.useMainnet) {
167
+ const authHeaders = await this.getAuthHeaders(
168
+ "POST",
169
+ "/platform/v2/x402/verify",
170
+ requestBody
171
+ );
172
+ Object.assign(headers, authHeaders);
173
+ }
174
+ const response = await fetch(`${this.endpoint}/verify`, {
175
+ method: "POST",
176
+ headers,
177
+ body: JSON.stringify(requestBody)
178
+ });
179
+ const result = await response.json();
180
+ if (!response.ok || !result.isValid) {
181
+ return {
182
+ valid: false,
183
+ error: result.invalidReason || result.error || "Verification failed",
184
+ details: result
185
+ };
186
+ }
187
+ return { valid: true, details: result };
188
+ } catch (err) {
189
+ return {
190
+ valid: false,
191
+ error: `Facilitator error: ${err.message}`
192
+ };
193
+ }
194
+ }
195
+ /**
196
+ * Settle payment on-chain via facilitator
197
+ */
198
+ async settle(paymentPayload, requirements) {
199
+ try {
200
+ const requestBody = {
201
+ x402Version: X402_VERSION,
202
+ paymentPayload,
203
+ paymentRequirements: requirements
204
+ };
205
+ const headers = {
206
+ "Content-Type": "application/json"
207
+ };
208
+ if (this.useMainnet) {
209
+ const authHeaders = await this.getAuthHeaders(
210
+ "POST",
211
+ "/platform/v2/x402/settle",
212
+ requestBody
213
+ );
214
+ Object.assign(headers, authHeaders);
215
+ }
216
+ const response = await fetch(`${this.endpoint}/settle`, {
217
+ method: "POST",
218
+ headers,
219
+ body: JSON.stringify(requestBody)
220
+ });
221
+ const result = await response.json();
222
+ if (!response.ok || !result.success) {
223
+ return {
224
+ success: false,
225
+ error: result.error || result.errorReason || "Settlement failed"
226
+ };
227
+ }
228
+ return {
229
+ success: true,
230
+ transaction: result.transaction,
231
+ status: result.status || "settled"
232
+ };
233
+ } catch (err) {
234
+ return {
235
+ success: false,
236
+ error: `Settlement error: ${err.message}`
237
+ };
238
+ }
239
+ }
240
+ /**
241
+ * Get CDP fee information
242
+ */
243
+ async getFee() {
244
+ return {
245
+ perTx: 1e-3,
246
+ currency: "USD",
247
+ freeQuota: 1e3
248
+ };
249
+ }
250
+ /**
251
+ * Get configuration summary (for logging)
252
+ */
253
+ getConfigSummary() {
254
+ const mode = this.useMainnet ? "mainnet" : "testnet";
255
+ const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
256
+ return `CDP Facilitator (${mode}, credentials: ${hasCredentials ? "yes" : "no"})`;
257
+ }
258
+ };
259
+
260
+ // src/facilitators/registry.ts
261
+ var FacilitatorRegistry = class {
262
+ factories = /* @__PURE__ */ new Map();
263
+ instances = /* @__PURE__ */ new Map();
264
+ selection;
265
+ roundRobinIndex = 0;
266
+ constructor(selection) {
267
+ this.registerFactory("cdp", (config) => new CDPFacilitator(config));
268
+ this.selection = selection || { primary: "cdp", strategy: "failover" };
269
+ }
270
+ /**
271
+ * Register a new facilitator factory
272
+ */
273
+ registerFactory(name, factory) {
274
+ this.factories.set(name, factory);
275
+ }
276
+ /**
277
+ * Get or create a facilitator instance
278
+ */
279
+ get(name, config) {
280
+ if (this.instances.has(name)) {
281
+ return this.instances.get(name);
282
+ }
283
+ const factory = this.factories.get(name);
284
+ if (!factory) {
285
+ throw new Error(`Unknown facilitator: ${name}. Available: ${Array.from(this.factories.keys()).join(", ")}`);
286
+ }
287
+ const mergedConfig = {
288
+ ...this.selection.config?.[name],
289
+ ...config
290
+ };
291
+ const instance = factory(mergedConfig);
292
+ this.instances.set(name, instance);
293
+ return instance;
294
+ }
295
+ /**
296
+ * Get all configured facilitator names
297
+ */
298
+ getConfiguredNames() {
299
+ const names = [this.selection.primary];
300
+ if (this.selection.fallback) {
301
+ names.push(...this.selection.fallback);
302
+ }
303
+ return names;
304
+ }
305
+ /**
306
+ * Get list of facilitators based on selection strategy
307
+ */
308
+ async getOrderedFacilitators(network) {
309
+ const names = this.getConfiguredNames();
310
+ const facilitators = [];
311
+ for (const name of names) {
312
+ try {
313
+ const f = this.get(name);
314
+ if (f.supportsNetwork(network)) {
315
+ facilitators.push(f);
316
+ }
317
+ } catch (err) {
318
+ console.warn(`[Registry] Failed to get facilitator ${name}:`, err);
319
+ }
320
+ }
321
+ if (facilitators.length === 0) {
322
+ throw new Error(`No facilitators available for network: ${network}`);
323
+ }
324
+ switch (this.selection.strategy) {
325
+ case "random":
326
+ return this.shuffle(facilitators);
327
+ case "roundrobin":
328
+ this.roundRobinIndex = (this.roundRobinIndex + 1) % facilitators.length;
329
+ return [
330
+ ...facilitators.slice(this.roundRobinIndex),
331
+ ...facilitators.slice(0, this.roundRobinIndex)
332
+ ];
333
+ case "cheapest":
334
+ return this.sortByCheapest(facilitators);
335
+ case "fastest":
336
+ return this.sortByFastest(facilitators);
337
+ case "failover":
338
+ default:
339
+ return facilitators;
340
+ }
341
+ }
342
+ shuffle(array) {
343
+ const result = [...array];
344
+ for (let i = result.length - 1; i > 0; i--) {
345
+ const j = Math.floor(Math.random() * (i + 1));
346
+ [result[i], result[j]] = [result[j], result[i]];
347
+ }
348
+ return result;
349
+ }
350
+ async sortByCheapest(facilitators) {
351
+ const withFees = await Promise.all(
352
+ facilitators.map(async (f) => {
353
+ try {
354
+ const fee = await f.getFee?.();
355
+ return { facilitator: f, perTx: fee?.perTx ?? Infinity };
356
+ } catch {
357
+ return { facilitator: f, perTx: Infinity };
358
+ }
359
+ })
360
+ );
361
+ withFees.sort((a, b) => a.perTx - b.perTx);
362
+ return withFees.map((w) => w.facilitator);
363
+ }
364
+ async sortByFastest(facilitators) {
365
+ const withLatency = await Promise.all(
366
+ facilitators.map(async (f) => {
367
+ try {
368
+ const health = await f.healthCheck();
369
+ return { facilitator: f, latency: health.latencyMs ?? Infinity };
370
+ } catch {
371
+ return { facilitator: f, latency: Infinity };
372
+ }
373
+ })
374
+ );
375
+ withLatency.sort((a, b) => a.latency - b.latency);
376
+ return withLatency.map((w) => w.facilitator);
377
+ }
378
+ /**
379
+ * Verify payment using configured facilitators
380
+ */
381
+ async verify(paymentPayload, requirements) {
382
+ const network = paymentPayload.accepted?.network || paymentPayload.network || requirements.network;
383
+ const facilitators = await this.getOrderedFacilitators(network);
384
+ let lastError;
385
+ for (const f of facilitators) {
386
+ try {
387
+ console.log(`[Registry] Trying ${f.name} for verify...`);
388
+ const result = await f.verify(paymentPayload, requirements);
389
+ if (result.valid) {
390
+ console.log(`[Registry] ${f.name} verify succeeded`);
391
+ return { ...result, facilitator: f.name };
392
+ }
393
+ lastError = result.error;
394
+ console.log(`[Registry] ${f.name} verify failed: ${result.error}`);
395
+ if (this.selection.strategy === "failover" && !this.isTransientError(result.error)) {
396
+ break;
397
+ }
398
+ } catch (err) {
399
+ lastError = err.message;
400
+ console.error(`[Registry] ${f.name} error:`, err.message);
401
+ }
402
+ }
403
+ return {
404
+ valid: false,
405
+ error: lastError || "All facilitators failed",
406
+ facilitator: "none"
407
+ };
408
+ }
409
+ /**
410
+ * Settle payment using configured facilitators
411
+ */
412
+ async settle(paymentPayload, requirements) {
413
+ const network = paymentPayload.accepted?.network || paymentPayload.network || requirements.network;
414
+ const facilitators = await this.getOrderedFacilitators(network);
415
+ let lastError;
416
+ for (const f of facilitators) {
417
+ try {
418
+ console.log(`[Registry] Trying ${f.name} for settle...`);
419
+ const result = await f.settle(paymentPayload, requirements);
420
+ if (result.success) {
421
+ console.log(`[Registry] ${f.name} settle succeeded: ${result.transaction}`);
422
+ return { ...result, facilitator: f.name };
423
+ }
424
+ lastError = result.error;
425
+ console.log(`[Registry] ${f.name} settle failed: ${result.error}`);
426
+ } catch (err) {
427
+ lastError = err.message;
428
+ console.error(`[Registry] ${f.name} error:`, err.message);
429
+ }
430
+ }
431
+ return {
432
+ success: false,
433
+ error: lastError || "All facilitators failed",
434
+ facilitator: "none"
435
+ };
436
+ }
437
+ /**
438
+ * Check health of all configured facilitators
439
+ */
440
+ async healthCheckAll() {
441
+ const results = {};
442
+ for (const name of this.getConfiguredNames()) {
443
+ try {
444
+ const f = this.get(name);
445
+ results[name] = await f.healthCheck();
446
+ } catch (err) {
447
+ results[name] = { healthy: false, error: err.message };
448
+ }
449
+ }
450
+ return results;
451
+ }
452
+ /**
453
+ * Check if an error is transient (network/server issue) vs permanent (bad request)
454
+ */
455
+ isTransientError(error) {
456
+ if (!error) return true;
457
+ const transientPatterns = [
458
+ /timeout/i,
459
+ /network/i,
460
+ /connection/i,
461
+ /ECONNREFUSED/i,
462
+ /ETIMEDOUT/i,
463
+ /503/,
464
+ /502/,
465
+ /500/
466
+ ];
467
+ return transientPatterns.some((p) => p.test(error));
468
+ }
469
+ /**
470
+ * Update selection configuration
471
+ */
472
+ setSelection(selection) {
473
+ this.selection = selection;
474
+ this.instances.clear();
475
+ }
476
+ /**
477
+ * Get current selection configuration
478
+ */
479
+ getSelection() {
480
+ return { ...this.selection };
481
+ }
482
+ };
483
+
484
+ // src/server/index.ts
485
+ var X402_VERSION2 = 2;
40
486
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
41
487
  var PAYMENT_HEADER = "x-payment";
42
488
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
43
- var FACILITATOR_TESTNET = "https://www.x402.org/facilitator";
44
- var FACILITATOR_MAINNET = "https://api.cdp.coinbase.com/platform/v2/x402";
45
489
  var USDC_ADDRESSES = {
46
490
  "eip155:8453": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
47
491
  // Base mainnet
@@ -52,15 +496,15 @@ var USDC_DOMAIN = {
52
496
  name: "USD Coin",
53
497
  version: "2"
54
498
  };
55
- function loadEnvFiles() {
499
+ function loadEnvFile2() {
56
500
  const envPaths = [
57
- path.join(process.cwd(), ".env"),
58
- path.join(process.env.HOME || "", ".moltspay", ".env")
501
+ path2.join(process.cwd(), ".env"),
502
+ path2.join(process.env.HOME || "", ".moltspay", ".env")
59
503
  ];
60
504
  for (const envPath of envPaths) {
61
- if ((0, import_fs.existsSync)(envPath)) {
505
+ if ((0, import_fs2.existsSync)(envPath)) {
62
506
  try {
63
- const content = (0, import_fs.readFileSync)(envPath, "utf-8");
507
+ const content = (0, import_fs2.readFileSync)(envPath, "utf-8");
64
508
  for (const line of content.split("\n")) {
65
509
  const trimmed = line.trim();
66
510
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -77,77 +521,44 @@ function loadEnvFiles() {
77
521
  }
78
522
  console.log(`[MoltsPay] Loaded config from ${envPath}`);
79
523
  break;
80
- } catch (err) {
81
- console.warn(`[MoltsPay] Failed to load ${envPath}:`, err);
524
+ } catch {
82
525
  }
83
526
  }
84
527
  }
85
528
  }
86
- function getCDPConfig() {
87
- loadEnvFiles();
88
- return {
89
- useMainnet: process.env.USE_MAINNET?.toLowerCase() === "true",
90
- apiKeyId: process.env.CDP_API_KEY_ID,
91
- apiKeySecret: process.env.CDP_API_KEY_SECRET
92
- };
93
- }
94
- async function getCDPAuthHeaders(method, urlPath, body) {
95
- const config = getCDPConfig();
96
- if (!config.apiKeyId || !config.apiKeySecret) {
97
- throw new Error("CDP_API_KEY_ID and CDP_API_KEY_SECRET required for mainnet");
98
- }
99
- try {
100
- const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
101
- const headers = await getAuthHeaders({
102
- apiKeyId: config.apiKeyId,
103
- apiKeySecret: config.apiKeySecret,
104
- requestMethod: method,
105
- requestHost: "api.cdp.coinbase.com",
106
- requestPath: urlPath,
107
- requestBody: body
108
- });
109
- return headers;
110
- } catch (err) {
111
- console.error("[MoltsPay] Failed to generate CDP auth headers:", err.message);
112
- throw err;
113
- }
114
- }
115
529
  var MoltsPayServer = class {
116
530
  manifest;
117
531
  skills = /* @__PURE__ */ new Map();
118
532
  options;
119
- cdpConfig;
120
- facilitatorUrl;
533
+ registry;
121
534
  networkId;
535
+ useMainnet;
122
536
  constructor(servicesPath, options = {}) {
123
- this.cdpConfig = getCDPConfig();
124
- const content = (0, import_fs.readFileSync)(servicesPath, "utf-8");
537
+ loadEnvFile2();
538
+ const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
125
539
  this.manifest = JSON.parse(content);
126
540
  this.options = {
127
541
  port: options.port || 3e3,
128
- host: options.host || "0.0.0.0"
542
+ host: options.host || "0.0.0.0",
543
+ ...options
129
544
  };
130
- if (this.cdpConfig.useMainnet) {
131
- if (!this.cdpConfig.apiKeyId || !this.cdpConfig.apiKeySecret) {
132
- console.warn("[MoltsPay] WARNING: USE_MAINNET=true but CDP keys not set!");
133
- console.warn("[MoltsPay] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
545
+ this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
546
+ this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
547
+ const facilitatorConfig = options.facilitators || {
548
+ primary: "cdp",
549
+ strategy: "failover",
550
+ config: {
551
+ cdp: { useMainnet: this.useMainnet }
134
552
  }
135
- this.facilitatorUrl = FACILITATOR_MAINNET;
136
- this.networkId = "eip155:8453";
137
- } else {
138
- this.facilitatorUrl = options.facilitatorUrl || FACILITATOR_TESTNET;
139
- this.networkId = "eip155:84532";
140
- }
141
- const networkName = this.cdpConfig.useMainnet ? "Base mainnet" : "Base Sepolia (testnet)";
142
- const facilitatorName = this.cdpConfig.useMainnet ? "CDP" : "x402.org";
553
+ };
554
+ this.registry = new FacilitatorRegistry(facilitatorConfig);
555
+ const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
556
+ const networkName = this.useMainnet ? "Base mainnet" : "Base Sepolia (testnet)";
143
557
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
144
558
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
145
559
  console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
146
560
  console.log(`[MoltsPay] Network: ${this.networkId} (${networkName})`);
147
- console.log(`[MoltsPay] Facilitator: ${facilitatorName} (${this.facilitatorUrl})`);
148
- if (this.cdpConfig.useMainnet && this.cdpConfig.apiKeyId) {
149
- console.log(`[MoltsPay] CDP API Key: ${this.cdpConfig.apiKeyId.slice(0, 8)}...`);
150
- }
561
+ console.log(`[MoltsPay] Facilitator: ${primaryFacilitator.displayName} (${facilitatorConfig.strategy || "failover"})`);
151
562
  console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
152
563
  }
153
564
  /**
@@ -173,6 +584,7 @@ var MoltsPayServer = class {
173
584
  console.log(`[MoltsPay] Endpoints:`);
174
585
  console.log(` GET /services - List available services`);
175
586
  console.log(` POST /execute - Execute service (x402 payment)`);
587
+ console.log(` GET /health - Health check (incl. facilitators)`);
176
588
  });
177
589
  }
178
590
  /**
@@ -193,6 +605,9 @@ var MoltsPayServer = class {
193
605
  if (url.pathname === "/services" && req.method === "GET") {
194
606
  return this.handleGetServices(res);
195
607
  }
608
+ if (url.pathname === "/health" && req.method === "GET") {
609
+ return await this.handleHealthCheck(res);
610
+ }
196
611
  if (url.pathname === "/execute" && req.method === "POST") {
197
612
  const body = await this.readBody(req);
198
613
  const paymentHeader = req.headers[PAYMENT_HEADER];
@@ -218,18 +633,37 @@ var MoltsPayServer = class {
218
633
  output: s.output,
219
634
  available: this.skills.has(s.id)
220
635
  }));
636
+ const selection = this.registry.getSelection();
221
637
  this.sendJson(res, 200, {
222
638
  provider: this.manifest.provider,
223
639
  services,
224
640
  x402: {
225
- version: X402_VERSION,
641
+ version: X402_VERSION2,
226
642
  network: this.networkId,
227
643
  schemes: ["exact"],
228
- facilitator: this.cdpConfig.useMainnet ? "cdp" : "x402.org",
229
- mainnet: this.cdpConfig.useMainnet
644
+ facilitators: {
645
+ primary: selection.primary,
646
+ fallback: selection.fallback,
647
+ strategy: selection.strategy
648
+ },
649
+ mainnet: this.useMainnet
230
650
  }
231
651
  });
232
652
  }
653
+ /**
654
+ * GET /health - Health check endpoint
655
+ */
656
+ async handleHealthCheck(res) {
657
+ const facilitatorHealth = await this.registry.healthCheckAll();
658
+ const allHealthy = Object.values(facilitatorHealth).every((h) => h.healthy);
659
+ this.sendJson(res, allHealthy ? 200 : 503, {
660
+ status: allHealthy ? "healthy" : "degraded",
661
+ network: this.networkId,
662
+ facilitators: facilitatorHealth,
663
+ services: this.manifest.services.length,
664
+ registered: this.skills.size
665
+ });
666
+ }
233
667
  /**
234
668
  * POST /execute - Execute service with x402 payment
235
669
  */
@@ -261,11 +695,16 @@ var MoltsPayServer = class {
261
695
  if (!validation.valid) {
262
696
  return this.sendJson(res, 402, { error: validation.error });
263
697
  }
264
- console.log(`[MoltsPay] Verifying payment with facilitator...`);
265
- const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
698
+ const requirements = this.buildPaymentRequirements(skill.config);
699
+ console.log(`[MoltsPay] Verifying payment...`);
700
+ const verifyResult = await this.registry.verify(payment, requirements);
266
701
  if (!verifyResult.valid) {
267
- return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
702
+ return this.sendJson(res, 402, {
703
+ error: `Payment verification failed: ${verifyResult.error}`,
704
+ facilitator: verifyResult.facilitator
705
+ });
268
706
  }
707
+ console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
269
708
  console.log(`[MoltsPay] Executing skill: ${service}`);
270
709
  let result;
271
710
  try {
@@ -280,17 +719,18 @@ var MoltsPayServer = class {
280
719
  console.log(`[MoltsPay] Skill succeeded, settling payment...`);
281
720
  let settlement = null;
282
721
  try {
283
- settlement = await this.settleWithFacilitator(payment, skill.config);
284
- console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
722
+ settlement = await this.registry.settle(payment, requirements);
723
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
285
724
  } catch (err) {
286
725
  console.error("[MoltsPay] Settlement failed:", err.message);
287
726
  }
288
727
  const responseHeaders = {};
289
- if (settlement) {
728
+ if (settlement?.success) {
290
729
  const responsePayload = {
291
730
  success: true,
292
731
  transaction: settlement.transaction,
293
- network: payment.network
732
+ network: payment.network || payment.accepted?.network,
733
+ facilitator: settlement.facilitator
294
734
  };
295
735
  responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
296
736
  JSON.stringify(responsePayload)
@@ -299,7 +739,7 @@ var MoltsPayServer = class {
299
739
  this.sendJson(res, 200, {
300
740
  success: true,
301
741
  result,
302
- payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
742
+ payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
303
743
  }, responseHeaders);
304
744
  }
305
745
  /**
@@ -308,7 +748,7 @@ var MoltsPayServer = class {
308
748
  sendPaymentRequired(config, res) {
309
749
  const requirements = this.buildPaymentRequirements(config);
310
750
  const paymentRequired = {
311
- x402Version: X402_VERSION,
751
+ x402Version: X402_VERSION2,
312
752
  accepts: [requirements],
313
753
  resource: {
314
754
  url: `/execute?service=${config.id}`,
@@ -331,7 +771,7 @@ var MoltsPayServer = class {
331
771
  * Basic payment validation
332
772
  */
333
773
  validatePayment(payment, config) {
334
- if (payment.x402Version !== X402_VERSION) {
774
+ if (payment.x402Version !== X402_VERSION2) {
335
775
  return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
336
776
  }
337
777
  const scheme = payment.accepted?.scheme || payment.scheme;
@@ -345,7 +785,7 @@ var MoltsPayServer = class {
345
785
  return { valid: true };
346
786
  }
347
787
  /**
348
- * Build complete payment requirements for facilitator
788
+ * Build payment requirements for facilitator
349
789
  */
350
790
  buildPaymentRequirements(config) {
351
791
  const amountInUnits = Math.floor(config.price * 1e6).toString();
@@ -360,77 +800,6 @@ var MoltsPayServer = class {
360
800
  extra: USDC_DOMAIN
361
801
  };
362
802
  }
363
- /**
364
- * Verify payment with facilitator (testnet or CDP)
365
- */
366
- async verifyWithFacilitator(payment, config) {
367
- try {
368
- const requirements = this.buildPaymentRequirements(config);
369
- const requestBody = {
370
- x402Version: X402_VERSION,
371
- // Required at top level for CDP
372
- paymentPayload: payment,
373
- paymentRequirements: requirements
374
- };
375
- console.log("[MoltsPay] Verify request:", JSON.stringify(requestBody, null, 2));
376
- let headers = { "Content-Type": "application/json" };
377
- if (this.cdpConfig.useMainnet) {
378
- const authHeaders = await getCDPAuthHeaders(
379
- "POST",
380
- "/platform/v2/x402/verify",
381
- requestBody
382
- );
383
- headers = { ...headers, ...authHeaders };
384
- }
385
- const response = await fetch(`${this.facilitatorUrl}/verify`, {
386
- method: "POST",
387
- headers,
388
- body: JSON.stringify(requestBody)
389
- });
390
- const result = await response.json();
391
- console.log("[MoltsPay] Verify response:", JSON.stringify(result, null, 2));
392
- if (!response.ok || !result.isValid) {
393
- return { valid: false, error: result.invalidReason || result.error || "Verification failed" };
394
- }
395
- return { valid: true };
396
- } catch (err) {
397
- return { valid: false, error: `Facilitator error: ${err.message}` };
398
- }
399
- }
400
- /**
401
- * Settle payment with facilitator (execute on-chain transfer)
402
- */
403
- async settleWithFacilitator(payment, config) {
404
- const requirements = this.buildPaymentRequirements(config);
405
- const requestBody = {
406
- x402Version: X402_VERSION,
407
- // Required at top level for CDP
408
- paymentPayload: payment,
409
- paymentRequirements: requirements
410
- };
411
- let headers = { "Content-Type": "application/json" };
412
- if (this.cdpConfig.useMainnet) {
413
- const authHeaders = await getCDPAuthHeaders(
414
- "POST",
415
- "/platform/v2/x402/settle",
416
- requestBody
417
- );
418
- headers = { ...headers, ...authHeaders };
419
- }
420
- const response = await fetch(`${this.facilitatorUrl}/settle`, {
421
- method: "POST",
422
- headers,
423
- body: JSON.stringify(requestBody)
424
- });
425
- const result = await response.json();
426
- if (!response.ok || !result.success) {
427
- throw new Error(result.error || result.errorReason || "Settlement failed");
428
- }
429
- return {
430
- transaction: result.transaction,
431
- status: result.status || "settled"
432
- };
433
- }
434
803
  async readBody(req) {
435
804
  return new Promise((resolve, reject) => {
436
805
  let body = "";