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.
@@ -3,8 +3,8 @@
3
3
  // src/cli/index.ts
4
4
  import { Command } from "commander";
5
5
  import { homedir as homedir2 } from "os";
6
- import { join as join3, dirname, resolve } from "path";
7
- import { existsSync as existsSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync3, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
6
+ import { join as join4, dirname, resolve } from "path";
7
+ import { existsSync as existsSync4, writeFileSync as writeFileSync2, readFileSync as readFileSync4, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
8
8
  import { spawn } from "child_process";
9
9
 
10
10
  // src/client/index.ts
@@ -410,15 +410,459 @@ var MoltsPayClient = class {
410
410
  };
411
411
 
412
412
  // src/server/index.ts
413
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
413
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
414
414
  import { createServer } from "http";
415
+ import * as path2 from "path";
416
+
417
+ // src/facilitators/interface.ts
418
+ var BaseFacilitator = class {
419
+ supportsNetwork(network) {
420
+ return this.supportedNetworks.includes(network);
421
+ }
422
+ };
423
+
424
+ // src/facilitators/cdp.ts
425
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
415
426
  import * as path from "path";
416
427
  var X402_VERSION2 = 2;
428
+ var CDP_MAINNET_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
429
+ var CDP_TESTNET_URL = "https://www.x402.org/facilitator";
430
+ function loadEnvFile() {
431
+ const envPaths = [
432
+ path.join(process.cwd(), ".env"),
433
+ path.join(process.env.HOME || "", ".moltspay", ".env")
434
+ ];
435
+ for (const envPath of envPaths) {
436
+ if (existsSync2(envPath)) {
437
+ try {
438
+ const content = readFileSync2(envPath, "utf-8");
439
+ for (const line of content.split("\n")) {
440
+ const trimmed = line.trim();
441
+ if (!trimmed || trimmed.startsWith("#")) continue;
442
+ const eqIndex = trimmed.indexOf("=");
443
+ if (eqIndex === -1) continue;
444
+ const key = trimmed.slice(0, eqIndex).trim();
445
+ let value = trimmed.slice(eqIndex + 1).trim();
446
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
447
+ value = value.slice(1, -1);
448
+ }
449
+ if (!process.env[key]) {
450
+ process.env[key] = value;
451
+ }
452
+ }
453
+ break;
454
+ } catch {
455
+ }
456
+ }
457
+ }
458
+ }
459
+ var CDPFacilitator = class extends BaseFacilitator {
460
+ name = "cdp";
461
+ displayName = "Coinbase CDP";
462
+ supportedNetworks;
463
+ endpoint;
464
+ useMainnet;
465
+ apiKeyId;
466
+ apiKeySecret;
467
+ constructor(config = {}) {
468
+ super();
469
+ loadEnvFile();
470
+ this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
471
+ this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
472
+ this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
473
+ this.endpoint = this.useMainnet ? CDP_MAINNET_URL : CDP_TESTNET_URL;
474
+ this.supportedNetworks = this.useMainnet ? ["eip155:8453"] : ["eip155:8453", "eip155:84532"];
475
+ if (this.useMainnet && (!this.apiKeyId || !this.apiKeySecret)) {
476
+ console.warn("[CDPFacilitator] WARNING: Mainnet mode but missing CDP credentials!");
477
+ console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
478
+ }
479
+ }
480
+ /**
481
+ * Get auth headers for CDP API requests
482
+ */
483
+ async getAuthHeaders(method, urlPath, body) {
484
+ if (!this.useMainnet) {
485
+ return {};
486
+ }
487
+ if (!this.apiKeyId || !this.apiKeySecret) {
488
+ throw new Error("CDP credentials required for mainnet");
489
+ }
490
+ try {
491
+ const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
492
+ return await getAuthHeaders({
493
+ apiKeyId: this.apiKeyId,
494
+ apiKeySecret: this.apiKeySecret,
495
+ requestMethod: method,
496
+ requestHost: "api.cdp.coinbase.com",
497
+ requestPath: urlPath,
498
+ requestBody: body
499
+ });
500
+ } catch (err) {
501
+ throw new Error(`Failed to generate CDP auth: ${err.message}`);
502
+ }
503
+ }
504
+ /**
505
+ * Health check - verify facilitator is reachable
506
+ */
507
+ async healthCheck() {
508
+ const start = Date.now();
509
+ try {
510
+ const controller = new AbortController();
511
+ const timeout = setTimeout(() => controller.abort(), 5e3);
512
+ const response = await fetch(this.endpoint.replace("/x402", ""), {
513
+ method: "HEAD",
514
+ signal: controller.signal
515
+ }).catch(() => null);
516
+ clearTimeout(timeout);
517
+ const latencyMs = Date.now() - start;
518
+ return {
519
+ healthy: response !== null,
520
+ latencyMs
521
+ };
522
+ } catch (err) {
523
+ return {
524
+ healthy: false,
525
+ error: err.message,
526
+ latencyMs: Date.now() - start
527
+ };
528
+ }
529
+ }
530
+ /**
531
+ * Verify payment signature with facilitator
532
+ */
533
+ async verify(paymentPayload, requirements) {
534
+ try {
535
+ const requestBody = {
536
+ x402Version: X402_VERSION2,
537
+ paymentPayload,
538
+ paymentRequirements: requirements
539
+ };
540
+ const headers = {
541
+ "Content-Type": "application/json"
542
+ };
543
+ if (this.useMainnet) {
544
+ const authHeaders = await this.getAuthHeaders(
545
+ "POST",
546
+ "/platform/v2/x402/verify",
547
+ requestBody
548
+ );
549
+ Object.assign(headers, authHeaders);
550
+ }
551
+ const response = await fetch(`${this.endpoint}/verify`, {
552
+ method: "POST",
553
+ headers,
554
+ body: JSON.stringify(requestBody)
555
+ });
556
+ const result = await response.json();
557
+ if (!response.ok || !result.isValid) {
558
+ return {
559
+ valid: false,
560
+ error: result.invalidReason || result.error || "Verification failed",
561
+ details: result
562
+ };
563
+ }
564
+ return { valid: true, details: result };
565
+ } catch (err) {
566
+ return {
567
+ valid: false,
568
+ error: `Facilitator error: ${err.message}`
569
+ };
570
+ }
571
+ }
572
+ /**
573
+ * Settle payment on-chain via facilitator
574
+ */
575
+ async settle(paymentPayload, requirements) {
576
+ try {
577
+ const requestBody = {
578
+ x402Version: X402_VERSION2,
579
+ paymentPayload,
580
+ paymentRequirements: requirements
581
+ };
582
+ const headers = {
583
+ "Content-Type": "application/json"
584
+ };
585
+ if (this.useMainnet) {
586
+ const authHeaders = await this.getAuthHeaders(
587
+ "POST",
588
+ "/platform/v2/x402/settle",
589
+ requestBody
590
+ );
591
+ Object.assign(headers, authHeaders);
592
+ }
593
+ const response = await fetch(`${this.endpoint}/settle`, {
594
+ method: "POST",
595
+ headers,
596
+ body: JSON.stringify(requestBody)
597
+ });
598
+ const result = await response.json();
599
+ if (!response.ok || !result.success) {
600
+ return {
601
+ success: false,
602
+ error: result.error || result.errorReason || "Settlement failed"
603
+ };
604
+ }
605
+ return {
606
+ success: true,
607
+ transaction: result.transaction,
608
+ status: result.status || "settled"
609
+ };
610
+ } catch (err) {
611
+ return {
612
+ success: false,
613
+ error: `Settlement error: ${err.message}`
614
+ };
615
+ }
616
+ }
617
+ /**
618
+ * Get CDP fee information
619
+ */
620
+ async getFee() {
621
+ return {
622
+ perTx: 1e-3,
623
+ currency: "USD",
624
+ freeQuota: 1e3
625
+ };
626
+ }
627
+ /**
628
+ * Get configuration summary (for logging)
629
+ */
630
+ getConfigSummary() {
631
+ const mode = this.useMainnet ? "mainnet" : "testnet";
632
+ const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
633
+ return `CDP Facilitator (${mode}, credentials: ${hasCredentials ? "yes" : "no"})`;
634
+ }
635
+ };
636
+
637
+ // src/facilitators/registry.ts
638
+ var FacilitatorRegistry = class {
639
+ factories = /* @__PURE__ */ new Map();
640
+ instances = /* @__PURE__ */ new Map();
641
+ selection;
642
+ roundRobinIndex = 0;
643
+ constructor(selection) {
644
+ this.registerFactory("cdp", (config) => new CDPFacilitator(config));
645
+ this.selection = selection || { primary: "cdp", strategy: "failover" };
646
+ }
647
+ /**
648
+ * Register a new facilitator factory
649
+ */
650
+ registerFactory(name, factory) {
651
+ this.factories.set(name, factory);
652
+ }
653
+ /**
654
+ * Get or create a facilitator instance
655
+ */
656
+ get(name, config) {
657
+ if (this.instances.has(name)) {
658
+ return this.instances.get(name);
659
+ }
660
+ const factory = this.factories.get(name);
661
+ if (!factory) {
662
+ throw new Error(`Unknown facilitator: ${name}. Available: ${Array.from(this.factories.keys()).join(", ")}`);
663
+ }
664
+ const mergedConfig = {
665
+ ...this.selection.config?.[name],
666
+ ...config
667
+ };
668
+ const instance = factory(mergedConfig);
669
+ this.instances.set(name, instance);
670
+ return instance;
671
+ }
672
+ /**
673
+ * Get all configured facilitator names
674
+ */
675
+ getConfiguredNames() {
676
+ const names = [this.selection.primary];
677
+ if (this.selection.fallback) {
678
+ names.push(...this.selection.fallback);
679
+ }
680
+ return names;
681
+ }
682
+ /**
683
+ * Get list of facilitators based on selection strategy
684
+ */
685
+ async getOrderedFacilitators(network) {
686
+ const names = this.getConfiguredNames();
687
+ const facilitators = [];
688
+ for (const name of names) {
689
+ try {
690
+ const f = this.get(name);
691
+ if (f.supportsNetwork(network)) {
692
+ facilitators.push(f);
693
+ }
694
+ } catch (err) {
695
+ console.warn(`[Registry] Failed to get facilitator ${name}:`, err);
696
+ }
697
+ }
698
+ if (facilitators.length === 0) {
699
+ throw new Error(`No facilitators available for network: ${network}`);
700
+ }
701
+ switch (this.selection.strategy) {
702
+ case "random":
703
+ return this.shuffle(facilitators);
704
+ case "roundrobin":
705
+ this.roundRobinIndex = (this.roundRobinIndex + 1) % facilitators.length;
706
+ return [
707
+ ...facilitators.slice(this.roundRobinIndex),
708
+ ...facilitators.slice(0, this.roundRobinIndex)
709
+ ];
710
+ case "cheapest":
711
+ return this.sortByCheapest(facilitators);
712
+ case "fastest":
713
+ return this.sortByFastest(facilitators);
714
+ case "failover":
715
+ default:
716
+ return facilitators;
717
+ }
718
+ }
719
+ shuffle(array) {
720
+ const result = [...array];
721
+ for (let i = result.length - 1; i > 0; i--) {
722
+ const j = Math.floor(Math.random() * (i + 1));
723
+ [result[i], result[j]] = [result[j], result[i]];
724
+ }
725
+ return result;
726
+ }
727
+ async sortByCheapest(facilitators) {
728
+ const withFees = await Promise.all(
729
+ facilitators.map(async (f) => {
730
+ try {
731
+ const fee = await f.getFee?.();
732
+ return { facilitator: f, perTx: fee?.perTx ?? Infinity };
733
+ } catch {
734
+ return { facilitator: f, perTx: Infinity };
735
+ }
736
+ })
737
+ );
738
+ withFees.sort((a, b) => a.perTx - b.perTx);
739
+ return withFees.map((w) => w.facilitator);
740
+ }
741
+ async sortByFastest(facilitators) {
742
+ const withLatency = await Promise.all(
743
+ facilitators.map(async (f) => {
744
+ try {
745
+ const health = await f.healthCheck();
746
+ return { facilitator: f, latency: health.latencyMs ?? Infinity };
747
+ } catch {
748
+ return { facilitator: f, latency: Infinity };
749
+ }
750
+ })
751
+ );
752
+ withLatency.sort((a, b) => a.latency - b.latency);
753
+ return withLatency.map((w) => w.facilitator);
754
+ }
755
+ /**
756
+ * Verify payment using configured facilitators
757
+ */
758
+ async verify(paymentPayload, requirements) {
759
+ const network = paymentPayload.accepted?.network || paymentPayload.network || requirements.network;
760
+ const facilitators = await this.getOrderedFacilitators(network);
761
+ let lastError;
762
+ for (const f of facilitators) {
763
+ try {
764
+ console.log(`[Registry] Trying ${f.name} for verify...`);
765
+ const result = await f.verify(paymentPayload, requirements);
766
+ if (result.valid) {
767
+ console.log(`[Registry] ${f.name} verify succeeded`);
768
+ return { ...result, facilitator: f.name };
769
+ }
770
+ lastError = result.error;
771
+ console.log(`[Registry] ${f.name} verify failed: ${result.error}`);
772
+ if (this.selection.strategy === "failover" && !this.isTransientError(result.error)) {
773
+ break;
774
+ }
775
+ } catch (err) {
776
+ lastError = err.message;
777
+ console.error(`[Registry] ${f.name} error:`, err.message);
778
+ }
779
+ }
780
+ return {
781
+ valid: false,
782
+ error: lastError || "All facilitators failed",
783
+ facilitator: "none"
784
+ };
785
+ }
786
+ /**
787
+ * Settle payment using configured facilitators
788
+ */
789
+ async settle(paymentPayload, requirements) {
790
+ const network = paymentPayload.accepted?.network || paymentPayload.network || requirements.network;
791
+ const facilitators = await this.getOrderedFacilitators(network);
792
+ let lastError;
793
+ for (const f of facilitators) {
794
+ try {
795
+ console.log(`[Registry] Trying ${f.name} for settle...`);
796
+ const result = await f.settle(paymentPayload, requirements);
797
+ if (result.success) {
798
+ console.log(`[Registry] ${f.name} settle succeeded: ${result.transaction}`);
799
+ return { ...result, facilitator: f.name };
800
+ }
801
+ lastError = result.error;
802
+ console.log(`[Registry] ${f.name} settle failed: ${result.error}`);
803
+ } catch (err) {
804
+ lastError = err.message;
805
+ console.error(`[Registry] ${f.name} error:`, err.message);
806
+ }
807
+ }
808
+ return {
809
+ success: false,
810
+ error: lastError || "All facilitators failed",
811
+ facilitator: "none"
812
+ };
813
+ }
814
+ /**
815
+ * Check health of all configured facilitators
816
+ */
817
+ async healthCheckAll() {
818
+ const results = {};
819
+ for (const name of this.getConfiguredNames()) {
820
+ try {
821
+ const f = this.get(name);
822
+ results[name] = await f.healthCheck();
823
+ } catch (err) {
824
+ results[name] = { healthy: false, error: err.message };
825
+ }
826
+ }
827
+ return results;
828
+ }
829
+ /**
830
+ * Check if an error is transient (network/server issue) vs permanent (bad request)
831
+ */
832
+ isTransientError(error) {
833
+ if (!error) return true;
834
+ const transientPatterns = [
835
+ /timeout/i,
836
+ /network/i,
837
+ /connection/i,
838
+ /ECONNREFUSED/i,
839
+ /ETIMEDOUT/i,
840
+ /503/,
841
+ /502/,
842
+ /500/
843
+ ];
844
+ return transientPatterns.some((p) => p.test(error));
845
+ }
846
+ /**
847
+ * Update selection configuration
848
+ */
849
+ setSelection(selection) {
850
+ this.selection = selection;
851
+ this.instances.clear();
852
+ }
853
+ /**
854
+ * Get current selection configuration
855
+ */
856
+ getSelection() {
857
+ return { ...this.selection };
858
+ }
859
+ };
860
+
861
+ // src/server/index.ts
862
+ var X402_VERSION3 = 2;
417
863
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
418
864
  var PAYMENT_HEADER2 = "x-payment";
419
865
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
420
- var FACILITATOR_TESTNET = "https://www.x402.org/facilitator";
421
- var FACILITATOR_MAINNET = "https://api.cdp.coinbase.com/platform/v2/x402";
422
866
  var USDC_ADDRESSES = {
423
867
  "eip155:8453": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
424
868
  // Base mainnet
@@ -429,15 +873,15 @@ var USDC_DOMAIN = {
429
873
  name: "USD Coin",
430
874
  version: "2"
431
875
  };
432
- function loadEnvFiles() {
876
+ function loadEnvFile2() {
433
877
  const envPaths = [
434
- path.join(process.cwd(), ".env"),
435
- path.join(process.env.HOME || "", ".moltspay", ".env")
878
+ path2.join(process.cwd(), ".env"),
879
+ path2.join(process.env.HOME || "", ".moltspay", ".env")
436
880
  ];
437
881
  for (const envPath of envPaths) {
438
- if (existsSync2(envPath)) {
882
+ if (existsSync3(envPath)) {
439
883
  try {
440
- const content = readFileSync2(envPath, "utf-8");
884
+ const content = readFileSync3(envPath, "utf-8");
441
885
  for (const line of content.split("\n")) {
442
886
  const trimmed = line.trim();
443
887
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -454,77 +898,44 @@ function loadEnvFiles() {
454
898
  }
455
899
  console.log(`[MoltsPay] Loaded config from ${envPath}`);
456
900
  break;
457
- } catch (err) {
458
- console.warn(`[MoltsPay] Failed to load ${envPath}:`, err);
901
+ } catch {
459
902
  }
460
903
  }
461
904
  }
462
905
  }
463
- function getCDPConfig() {
464
- loadEnvFiles();
465
- return {
466
- useMainnet: process.env.USE_MAINNET?.toLowerCase() === "true",
467
- apiKeyId: process.env.CDP_API_KEY_ID,
468
- apiKeySecret: process.env.CDP_API_KEY_SECRET
469
- };
470
- }
471
- async function getCDPAuthHeaders(method, urlPath, body) {
472
- const config = getCDPConfig();
473
- if (!config.apiKeyId || !config.apiKeySecret) {
474
- throw new Error("CDP_API_KEY_ID and CDP_API_KEY_SECRET required for mainnet");
475
- }
476
- try {
477
- const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
478
- const headers = await getAuthHeaders({
479
- apiKeyId: config.apiKeyId,
480
- apiKeySecret: config.apiKeySecret,
481
- requestMethod: method,
482
- requestHost: "api.cdp.coinbase.com",
483
- requestPath: urlPath,
484
- requestBody: body
485
- });
486
- return headers;
487
- } catch (err) {
488
- console.error("[MoltsPay] Failed to generate CDP auth headers:", err.message);
489
- throw err;
490
- }
491
- }
492
906
  var MoltsPayServer = class {
493
907
  manifest;
494
908
  skills = /* @__PURE__ */ new Map();
495
909
  options;
496
- cdpConfig;
497
- facilitatorUrl;
910
+ registry;
498
911
  networkId;
912
+ useMainnet;
499
913
  constructor(servicesPath, options = {}) {
500
- this.cdpConfig = getCDPConfig();
501
- const content = readFileSync2(servicesPath, "utf-8");
914
+ loadEnvFile2();
915
+ const content = readFileSync3(servicesPath, "utf-8");
502
916
  this.manifest = JSON.parse(content);
503
917
  this.options = {
504
918
  port: options.port || 3e3,
505
- host: options.host || "0.0.0.0"
919
+ host: options.host || "0.0.0.0",
920
+ ...options
506
921
  };
507
- if (this.cdpConfig.useMainnet) {
508
- if (!this.cdpConfig.apiKeyId || !this.cdpConfig.apiKeySecret) {
509
- console.warn("[MoltsPay] WARNING: USE_MAINNET=true but CDP keys not set!");
510
- console.warn("[MoltsPay] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
922
+ this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
923
+ this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
924
+ const facilitatorConfig = options.facilitators || {
925
+ primary: "cdp",
926
+ strategy: "failover",
927
+ config: {
928
+ cdp: { useMainnet: this.useMainnet }
511
929
  }
512
- this.facilitatorUrl = FACILITATOR_MAINNET;
513
- this.networkId = "eip155:8453";
514
- } else {
515
- this.facilitatorUrl = options.facilitatorUrl || FACILITATOR_TESTNET;
516
- this.networkId = "eip155:84532";
517
- }
518
- const networkName = this.cdpConfig.useMainnet ? "Base mainnet" : "Base Sepolia (testnet)";
519
- const facilitatorName = this.cdpConfig.useMainnet ? "CDP" : "x402.org";
930
+ };
931
+ this.registry = new FacilitatorRegistry(facilitatorConfig);
932
+ const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
933
+ const networkName = this.useMainnet ? "Base mainnet" : "Base Sepolia (testnet)";
520
934
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
521
935
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
522
936
  console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
523
937
  console.log(`[MoltsPay] Network: ${this.networkId} (${networkName})`);
524
- console.log(`[MoltsPay] Facilitator: ${facilitatorName} (${this.facilitatorUrl})`);
525
- if (this.cdpConfig.useMainnet && this.cdpConfig.apiKeyId) {
526
- console.log(`[MoltsPay] CDP API Key: ${this.cdpConfig.apiKeyId.slice(0, 8)}...`);
527
- }
938
+ console.log(`[MoltsPay] Facilitator: ${primaryFacilitator.displayName} (${facilitatorConfig.strategy || "failover"})`);
528
939
  console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
529
940
  }
530
941
  /**
@@ -550,6 +961,7 @@ var MoltsPayServer = class {
550
961
  console.log(`[MoltsPay] Endpoints:`);
551
962
  console.log(` GET /services - List available services`);
552
963
  console.log(` POST /execute - Execute service (x402 payment)`);
964
+ console.log(` GET /health - Health check (incl. facilitators)`);
553
965
  });
554
966
  }
555
967
  /**
@@ -570,6 +982,9 @@ var MoltsPayServer = class {
570
982
  if (url.pathname === "/services" && req.method === "GET") {
571
983
  return this.handleGetServices(res);
572
984
  }
985
+ if (url.pathname === "/health" && req.method === "GET") {
986
+ return await this.handleHealthCheck(res);
987
+ }
573
988
  if (url.pathname === "/execute" && req.method === "POST") {
574
989
  const body = await this.readBody(req);
575
990
  const paymentHeader = req.headers[PAYMENT_HEADER2];
@@ -595,18 +1010,37 @@ var MoltsPayServer = class {
595
1010
  output: s.output,
596
1011
  available: this.skills.has(s.id)
597
1012
  }));
1013
+ const selection = this.registry.getSelection();
598
1014
  this.sendJson(res, 200, {
599
1015
  provider: this.manifest.provider,
600
1016
  services,
601
1017
  x402: {
602
- version: X402_VERSION2,
1018
+ version: X402_VERSION3,
603
1019
  network: this.networkId,
604
1020
  schemes: ["exact"],
605
- facilitator: this.cdpConfig.useMainnet ? "cdp" : "x402.org",
606
- mainnet: this.cdpConfig.useMainnet
1021
+ facilitators: {
1022
+ primary: selection.primary,
1023
+ fallback: selection.fallback,
1024
+ strategy: selection.strategy
1025
+ },
1026
+ mainnet: this.useMainnet
607
1027
  }
608
1028
  });
609
1029
  }
1030
+ /**
1031
+ * GET /health - Health check endpoint
1032
+ */
1033
+ async handleHealthCheck(res) {
1034
+ const facilitatorHealth = await this.registry.healthCheckAll();
1035
+ const allHealthy = Object.values(facilitatorHealth).every((h) => h.healthy);
1036
+ this.sendJson(res, allHealthy ? 200 : 503, {
1037
+ status: allHealthy ? "healthy" : "degraded",
1038
+ network: this.networkId,
1039
+ facilitators: facilitatorHealth,
1040
+ services: this.manifest.services.length,
1041
+ registered: this.skills.size
1042
+ });
1043
+ }
610
1044
  /**
611
1045
  * POST /execute - Execute service with x402 payment
612
1046
  */
@@ -638,11 +1072,16 @@ var MoltsPayServer = class {
638
1072
  if (!validation.valid) {
639
1073
  return this.sendJson(res, 402, { error: validation.error });
640
1074
  }
641
- console.log(`[MoltsPay] Verifying payment with facilitator...`);
642
- const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
1075
+ const requirements = this.buildPaymentRequirements(skill.config);
1076
+ console.log(`[MoltsPay] Verifying payment...`);
1077
+ const verifyResult = await this.registry.verify(payment, requirements);
643
1078
  if (!verifyResult.valid) {
644
- return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
1079
+ return this.sendJson(res, 402, {
1080
+ error: `Payment verification failed: ${verifyResult.error}`,
1081
+ facilitator: verifyResult.facilitator
1082
+ });
645
1083
  }
1084
+ console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
646
1085
  console.log(`[MoltsPay] Executing skill: ${service}`);
647
1086
  let result;
648
1087
  try {
@@ -657,17 +1096,18 @@ var MoltsPayServer = class {
657
1096
  console.log(`[MoltsPay] Skill succeeded, settling payment...`);
658
1097
  let settlement = null;
659
1098
  try {
660
- settlement = await this.settleWithFacilitator(payment, skill.config);
661
- console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
1099
+ settlement = await this.registry.settle(payment, requirements);
1100
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
662
1101
  } catch (err) {
663
1102
  console.error("[MoltsPay] Settlement failed:", err.message);
664
1103
  }
665
1104
  const responseHeaders = {};
666
- if (settlement) {
1105
+ if (settlement?.success) {
667
1106
  const responsePayload = {
668
1107
  success: true,
669
1108
  transaction: settlement.transaction,
670
- network: payment.network
1109
+ network: payment.network || payment.accepted?.network,
1110
+ facilitator: settlement.facilitator
671
1111
  };
672
1112
  responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
673
1113
  JSON.stringify(responsePayload)
@@ -676,7 +1116,7 @@ var MoltsPayServer = class {
676
1116
  this.sendJson(res, 200, {
677
1117
  success: true,
678
1118
  result,
679
- payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
1119
+ payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
680
1120
  }, responseHeaders);
681
1121
  }
682
1122
  /**
@@ -685,7 +1125,7 @@ var MoltsPayServer = class {
685
1125
  sendPaymentRequired(config, res) {
686
1126
  const requirements = this.buildPaymentRequirements(config);
687
1127
  const paymentRequired = {
688
- x402Version: X402_VERSION2,
1128
+ x402Version: X402_VERSION3,
689
1129
  accepts: [requirements],
690
1130
  resource: {
691
1131
  url: `/execute?service=${config.id}`,
@@ -708,7 +1148,7 @@ var MoltsPayServer = class {
708
1148
  * Basic payment validation
709
1149
  */
710
1150
  validatePayment(payment, config) {
711
- if (payment.x402Version !== X402_VERSION2) {
1151
+ if (payment.x402Version !== X402_VERSION3) {
712
1152
  return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
713
1153
  }
714
1154
  const scheme = payment.accepted?.scheme || payment.scheme;
@@ -722,7 +1162,7 @@ var MoltsPayServer = class {
722
1162
  return { valid: true };
723
1163
  }
724
1164
  /**
725
- * Build complete payment requirements for facilitator
1165
+ * Build payment requirements for facilitator
726
1166
  */
727
1167
  buildPaymentRequirements(config) {
728
1168
  const amountInUnits = Math.floor(config.price * 1e6).toString();
@@ -737,77 +1177,6 @@ var MoltsPayServer = class {
737
1177
  extra: USDC_DOMAIN
738
1178
  };
739
1179
  }
740
- /**
741
- * Verify payment with facilitator (testnet or CDP)
742
- */
743
- async verifyWithFacilitator(payment, config) {
744
- try {
745
- const requirements = this.buildPaymentRequirements(config);
746
- const requestBody = {
747
- x402Version: X402_VERSION2,
748
- // Required at top level for CDP
749
- paymentPayload: payment,
750
- paymentRequirements: requirements
751
- };
752
- console.log("[MoltsPay] Verify request:", JSON.stringify(requestBody, null, 2));
753
- let headers = { "Content-Type": "application/json" };
754
- if (this.cdpConfig.useMainnet) {
755
- const authHeaders = await getCDPAuthHeaders(
756
- "POST",
757
- "/platform/v2/x402/verify",
758
- requestBody
759
- );
760
- headers = { ...headers, ...authHeaders };
761
- }
762
- const response = await fetch(`${this.facilitatorUrl}/verify`, {
763
- method: "POST",
764
- headers,
765
- body: JSON.stringify(requestBody)
766
- });
767
- const result = await response.json();
768
- console.log("[MoltsPay] Verify response:", JSON.stringify(result, null, 2));
769
- if (!response.ok || !result.isValid) {
770
- return { valid: false, error: result.invalidReason || result.error || "Verification failed" };
771
- }
772
- return { valid: true };
773
- } catch (err) {
774
- return { valid: false, error: `Facilitator error: ${err.message}` };
775
- }
776
- }
777
- /**
778
- * Settle payment with facilitator (execute on-chain transfer)
779
- */
780
- async settleWithFacilitator(payment, config) {
781
- const requirements = this.buildPaymentRequirements(config);
782
- const requestBody = {
783
- x402Version: X402_VERSION2,
784
- // Required at top level for CDP
785
- paymentPayload: payment,
786
- paymentRequirements: requirements
787
- };
788
- let headers = { "Content-Type": "application/json" };
789
- if (this.cdpConfig.useMainnet) {
790
- const authHeaders = await getCDPAuthHeaders(
791
- "POST",
792
- "/platform/v2/x402/settle",
793
- requestBody
794
- );
795
- headers = { ...headers, ...authHeaders };
796
- }
797
- const response = await fetch(`${this.facilitatorUrl}/settle`, {
798
- method: "POST",
799
- headers,
800
- body: JSON.stringify(requestBody)
801
- });
802
- const result = await response.json();
803
- if (!response.ok || !result.success) {
804
- throw new Error(result.error || result.errorReason || "Settlement failed");
805
- }
806
- return {
807
- transaction: result.transaction,
808
- status: result.status || "settled"
809
- };
810
- }
811
1180
  async readBody(req) {
812
1181
  return new Promise((resolve2, reject) => {
813
1182
  let body = "";
@@ -835,9 +1204,9 @@ var MoltsPayServer = class {
835
1204
  // src/cli/index.ts
836
1205
  import * as readline from "readline";
837
1206
  var program = new Command();
838
- var DEFAULT_CONFIG_DIR = join3(homedir2(), ".moltspay");
839
- var PID_FILE = join3(DEFAULT_CONFIG_DIR, "server.pid");
840
- if (!existsSync3(DEFAULT_CONFIG_DIR)) {
1207
+ var DEFAULT_CONFIG_DIR = join4(homedir2(), ".moltspay");
1208
+ var PID_FILE = join4(DEFAULT_CONFIG_DIR, "server.pid");
1209
+ if (!existsSync4(DEFAULT_CONFIG_DIR)) {
841
1210
  mkdirSync2(DEFAULT_CONFIG_DIR, { recursive: true });
842
1211
  }
843
1212
  function prompt(question) {
@@ -855,7 +1224,7 @@ function prompt(question) {
855
1224
  program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version("1.0.0");
856
1225
  program.command("init").description("Initialize MoltsPay client (create wallet, set limits)").option("--chain <chain>", "Blockchain to use", "base").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
857
1226
  console.log("\n\u{1F510} MoltsPay Client Setup\n");
858
- if (existsSync3(join3(options.configDir, "wallet.json"))) {
1227
+ if (existsSync4(join4(options.configDir, "wallet.json"))) {
859
1228
  console.log('\u26A0\uFE0F Already initialized. Use "moltspay config" to update settings.');
860
1229
  console.log(` Config dir: ${options.configDir}`);
861
1230
  return;
@@ -882,7 +1251,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
882
1251
  console.log(`
883
1252
  \u{1F4C1} Config saved to: ${result.configDir}`);
884
1253
  console.log(`
885
- \u26A0\uFE0F IMPORTANT: Back up ${join3(result.configDir, "wallet.json")}`);
1254
+ \u26A0\uFE0F IMPORTANT: Back up ${join4(result.configDir, "wallet.json")}`);
886
1255
  console.log(` This file contains your private key!
887
1256
  `);
888
1257
  console.log(`\u{1F4B0} Fund your wallet with USDC on ${chain} to start using services.
@@ -1004,14 +1373,14 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1004
1373
  let manifestPath;
1005
1374
  let skillDir;
1006
1375
  let isSkillDir = false;
1007
- if (existsSync3(join3(resolvedPath, "moltspay.services.json"))) {
1008
- manifestPath = join3(resolvedPath, "moltspay.services.json");
1376
+ if (existsSync4(join4(resolvedPath, "moltspay.services.json"))) {
1377
+ manifestPath = join4(resolvedPath, "moltspay.services.json");
1009
1378
  skillDir = resolvedPath;
1010
1379
  isSkillDir = true;
1011
- } else if (existsSync3(resolvedPath) && resolvedPath.endsWith(".json")) {
1380
+ } else if (existsSync4(resolvedPath) && resolvedPath.endsWith(".json")) {
1012
1381
  manifestPath = resolvedPath;
1013
1382
  skillDir = dirname(resolvedPath);
1014
- } else if (existsSync3(resolvedPath)) {
1383
+ } else if (existsSync4(resolvedPath)) {
1015
1384
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
1016
1385
  continue;
1017
1386
  } else {
@@ -1020,20 +1389,33 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1020
1389
  }
1021
1390
  console.log(`\u{1F4E6} Loading: ${manifestPath}`);
1022
1391
  try {
1023
- const manifestContent = JSON.parse(readFileSync3(manifestPath, "utf-8"));
1392
+ const manifestContent = JSON.parse(readFileSync4(manifestPath, "utf-8"));
1024
1393
  if (!provider) {
1025
1394
  provider = manifestContent.provider;
1026
1395
  }
1027
1396
  let skillModule = null;
1028
1397
  if (isSkillDir) {
1029
- const indexPath = join3(skillDir, "index.js");
1030
- if (existsSync3(indexPath)) {
1398
+ let entryPoint = "index.js";
1399
+ const pkgJsonPath = join4(skillDir, "package.json");
1400
+ if (existsSync4(pkgJsonPath)) {
1031
1401
  try {
1032
- skillModule = await import(indexPath);
1033
- console.log(` \u2705 Loaded module: ${indexPath}`);
1402
+ const pkgJson = JSON.parse(readFileSync4(pkgJsonPath, "utf-8"));
1403
+ if (pkgJson.main) {
1404
+ entryPoint = pkgJson.main;
1405
+ }
1406
+ } catch {
1407
+ }
1408
+ }
1409
+ const modulePath = join4(skillDir, entryPoint);
1410
+ if (existsSync4(modulePath)) {
1411
+ try {
1412
+ skillModule = await import(modulePath);
1413
+ console.log(` \u2705 Loaded module: ${modulePath}`);
1034
1414
  } catch (err) {
1035
1415
  console.error(` \u26A0\uFE0F Failed to load module: ${err.message}`);
1036
1416
  }
1417
+ } else {
1418
+ console.error(` \u26A0\uFE0F Entry point not found: ${modulePath}`);
1037
1419
  }
1038
1420
  }
1039
1421
  for (const service of manifestContent.services) {
@@ -1103,7 +1485,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1103
1485
  provider,
1104
1486
  services: allServices
1105
1487
  };
1106
- const tempManifestPath = join3(DEFAULT_CONFIG_DIR, "combined-manifest.json");
1488
+ const tempManifestPath = join4(DEFAULT_CONFIG_DIR, "combined-manifest.json");
1107
1489
  writeFileSync2(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
1108
1490
  console.log(`
1109
1491
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
@@ -1121,8 +1503,8 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1121
1503
  server.listen(port);
1122
1504
  const cleanup = () => {
1123
1505
  try {
1124
- if (existsSync3(PID_FILE)) unlinkSync(PID_FILE);
1125
- if (existsSync3(tempManifestPath)) unlinkSync(tempManifestPath);
1506
+ if (existsSync4(PID_FILE)) unlinkSync(PID_FILE);
1507
+ if (existsSync4(tempManifestPath)) unlinkSync(tempManifestPath);
1126
1508
  } catch {
1127
1509
  }
1128
1510
  };
@@ -1143,12 +1525,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1143
1525
  }
1144
1526
  });
1145
1527
  program.command("stop").description("Stop the running MoltsPay server").action(async () => {
1146
- if (!existsSync3(PID_FILE)) {
1528
+ if (!existsSync4(PID_FILE)) {
1147
1529
  console.log("\u274C No running server found (no PID file)");
1148
1530
  process.exit(1);
1149
1531
  }
1150
1532
  try {
1151
- const pidData = JSON.parse(readFileSync3(PID_FILE, "utf-8"));
1533
+ const pidData = JSON.parse(readFileSync4(PID_FILE, "utf-8"));
1152
1534
  const { pid, port, manifest } = pidData;
1153
1535
  console.log(`
1154
1536
  \u{1F6D1} Stopping MoltsPay Server
@@ -1173,7 +1555,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
1173
1555
  process.kill(pid, "SIGKILL");
1174
1556
  } catch {
1175
1557
  }
1176
- if (existsSync3(PID_FILE)) {
1558
+ if (existsSync4(PID_FILE)) {
1177
1559
  unlinkSync(PID_FILE);
1178
1560
  }
1179
1561
  console.log("\u2705 Server stopped\n");
@@ -1204,11 +1586,11 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
1204
1586
  params.image_url = imagePath;
1205
1587
  } else {
1206
1588
  const filePath = resolve(imagePath);
1207
- if (!existsSync3(filePath)) {
1589
+ if (!existsSync4(filePath)) {
1208
1590
  console.error(`\u274C Image file not found: ${filePath}`);
1209
1591
  process.exit(1);
1210
1592
  }
1211
- const imageData = readFileSync3(filePath);
1593
+ const imageData = readFileSync4(filePath);
1212
1594
  params.image_base64 = imageData.toString("base64");
1213
1595
  }
1214
1596
  }
@@ -1249,9 +1631,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
1249
1631
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
1250
1632
  const resolvedPath = resolve(inputPath);
1251
1633
  let manifestPath;
1252
- if (existsSync3(join3(resolvedPath, "moltspay.services.json"))) {
1253
- manifestPath = join3(resolvedPath, "moltspay.services.json");
1254
- } else if (resolvedPath.endsWith(".json") && existsSync3(resolvedPath)) {
1634
+ if (existsSync4(join4(resolvedPath, "moltspay.services.json"))) {
1635
+ manifestPath = join4(resolvedPath, "moltspay.services.json");
1636
+ } else if (resolvedPath.endsWith(".json") && existsSync4(resolvedPath)) {
1255
1637
  manifestPath = resolvedPath;
1256
1638
  } else {
1257
1639
  console.error(`\u274C Not found: ${resolvedPath}`);
@@ -1261,7 +1643,7 @@ program.command("validate <path>").description("Validate a moltspay.services.jso
1261
1643
  \u{1F4CB} Validating: ${manifestPath}
1262
1644
  `);
1263
1645
  try {
1264
- const content = JSON.parse(readFileSync3(manifestPath, "utf-8"));
1646
+ const content = JSON.parse(readFileSync4(manifestPath, "utf-8"));
1265
1647
  const errors = [];
1266
1648
  if (!content.provider) {
1267
1649
  errors.push("Missing required field: provider");