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