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.
package/dist/cli/index.js CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_commander = require("commander");
28
28
  var import_os2 = require("os");
29
29
  var import_path2 = require("path");
30
- var import_fs3 = require("fs");
30
+ var import_fs4 = require("fs");
31
31
  var import_child_process = require("child_process");
32
32
 
33
33
  // src/client/index.ts
@@ -433,15 +433,459 @@ var MoltsPayClient = class {
433
433
  };
434
434
 
435
435
  // src/server/index.ts
436
- var import_fs2 = require("fs");
436
+ var import_fs3 = require("fs");
437
437
  var import_http = require("http");
438
+ var path2 = __toESM(require("path"));
439
+
440
+ // src/facilitators/interface.ts
441
+ var BaseFacilitator = class {
442
+ supportsNetwork(network) {
443
+ return this.supportedNetworks.includes(network);
444
+ }
445
+ };
446
+
447
+ // src/facilitators/cdp.ts
448
+ var import_fs2 = require("fs");
438
449
  var path = __toESM(require("path"));
439
450
  var X402_VERSION2 = 2;
451
+ var CDP_MAINNET_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
452
+ var CDP_TESTNET_URL = "https://www.x402.org/facilitator";
453
+ function loadEnvFile() {
454
+ const envPaths = [
455
+ path.join(process.cwd(), ".env"),
456
+ path.join(process.env.HOME || "", ".moltspay", ".env")
457
+ ];
458
+ for (const envPath of envPaths) {
459
+ if ((0, import_fs2.existsSync)(envPath)) {
460
+ try {
461
+ const content = (0, import_fs2.readFileSync)(envPath, "utf-8");
462
+ for (const line of content.split("\n")) {
463
+ const trimmed = line.trim();
464
+ if (!trimmed || trimmed.startsWith("#")) continue;
465
+ const eqIndex = trimmed.indexOf("=");
466
+ if (eqIndex === -1) continue;
467
+ const key = trimmed.slice(0, eqIndex).trim();
468
+ let value = trimmed.slice(eqIndex + 1).trim();
469
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
470
+ value = value.slice(1, -1);
471
+ }
472
+ if (!process.env[key]) {
473
+ process.env[key] = value;
474
+ }
475
+ }
476
+ break;
477
+ } catch {
478
+ }
479
+ }
480
+ }
481
+ }
482
+ var CDPFacilitator = class extends BaseFacilitator {
483
+ name = "cdp";
484
+ displayName = "Coinbase CDP";
485
+ supportedNetworks;
486
+ endpoint;
487
+ useMainnet;
488
+ apiKeyId;
489
+ apiKeySecret;
490
+ constructor(config = {}) {
491
+ super();
492
+ loadEnvFile();
493
+ this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
494
+ this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
495
+ this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
496
+ this.endpoint = this.useMainnet ? CDP_MAINNET_URL : CDP_TESTNET_URL;
497
+ this.supportedNetworks = this.useMainnet ? ["eip155:8453"] : ["eip155:8453", "eip155:84532"];
498
+ if (this.useMainnet && (!this.apiKeyId || !this.apiKeySecret)) {
499
+ console.warn("[CDPFacilitator] WARNING: Mainnet mode but missing CDP credentials!");
500
+ console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
501
+ }
502
+ }
503
+ /**
504
+ * Get auth headers for CDP API requests
505
+ */
506
+ async getAuthHeaders(method, urlPath, body) {
507
+ if (!this.useMainnet) {
508
+ return {};
509
+ }
510
+ if (!this.apiKeyId || !this.apiKeySecret) {
511
+ throw new Error("CDP credentials required for mainnet");
512
+ }
513
+ try {
514
+ const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
515
+ return await getAuthHeaders({
516
+ apiKeyId: this.apiKeyId,
517
+ apiKeySecret: this.apiKeySecret,
518
+ requestMethod: method,
519
+ requestHost: "api.cdp.coinbase.com",
520
+ requestPath: urlPath,
521
+ requestBody: body
522
+ });
523
+ } catch (err) {
524
+ throw new Error(`Failed to generate CDP auth: ${err.message}`);
525
+ }
526
+ }
527
+ /**
528
+ * Health check - verify facilitator is reachable
529
+ */
530
+ async healthCheck() {
531
+ const start = Date.now();
532
+ try {
533
+ const controller = new AbortController();
534
+ const timeout = setTimeout(() => controller.abort(), 5e3);
535
+ const response = await fetch(this.endpoint.replace("/x402", ""), {
536
+ method: "HEAD",
537
+ signal: controller.signal
538
+ }).catch(() => null);
539
+ clearTimeout(timeout);
540
+ const latencyMs = Date.now() - start;
541
+ return {
542
+ healthy: response !== null,
543
+ latencyMs
544
+ };
545
+ } catch (err) {
546
+ return {
547
+ healthy: false,
548
+ error: err.message,
549
+ latencyMs: Date.now() - start
550
+ };
551
+ }
552
+ }
553
+ /**
554
+ * Verify payment signature with facilitator
555
+ */
556
+ async verify(paymentPayload, requirements) {
557
+ try {
558
+ const requestBody = {
559
+ x402Version: X402_VERSION2,
560
+ paymentPayload,
561
+ paymentRequirements: requirements
562
+ };
563
+ const headers = {
564
+ "Content-Type": "application/json"
565
+ };
566
+ if (this.useMainnet) {
567
+ const authHeaders = await this.getAuthHeaders(
568
+ "POST",
569
+ "/platform/v2/x402/verify",
570
+ requestBody
571
+ );
572
+ Object.assign(headers, authHeaders);
573
+ }
574
+ const response = await fetch(`${this.endpoint}/verify`, {
575
+ method: "POST",
576
+ headers,
577
+ body: JSON.stringify(requestBody)
578
+ });
579
+ const result = await response.json();
580
+ if (!response.ok || !result.isValid) {
581
+ return {
582
+ valid: false,
583
+ error: result.invalidReason || result.error || "Verification failed",
584
+ details: result
585
+ };
586
+ }
587
+ return { valid: true, details: result };
588
+ } catch (err) {
589
+ return {
590
+ valid: false,
591
+ error: `Facilitator error: ${err.message}`
592
+ };
593
+ }
594
+ }
595
+ /**
596
+ * Settle payment on-chain via facilitator
597
+ */
598
+ async settle(paymentPayload, requirements) {
599
+ try {
600
+ const requestBody = {
601
+ x402Version: X402_VERSION2,
602
+ paymentPayload,
603
+ paymentRequirements: requirements
604
+ };
605
+ const headers = {
606
+ "Content-Type": "application/json"
607
+ };
608
+ if (this.useMainnet) {
609
+ const authHeaders = await this.getAuthHeaders(
610
+ "POST",
611
+ "/platform/v2/x402/settle",
612
+ requestBody
613
+ );
614
+ Object.assign(headers, authHeaders);
615
+ }
616
+ const response = await fetch(`${this.endpoint}/settle`, {
617
+ method: "POST",
618
+ headers,
619
+ body: JSON.stringify(requestBody)
620
+ });
621
+ const result = await response.json();
622
+ if (!response.ok || !result.success) {
623
+ return {
624
+ success: false,
625
+ error: result.error || result.errorReason || "Settlement failed"
626
+ };
627
+ }
628
+ return {
629
+ success: true,
630
+ transaction: result.transaction,
631
+ status: result.status || "settled"
632
+ };
633
+ } catch (err) {
634
+ return {
635
+ success: false,
636
+ error: `Settlement error: ${err.message}`
637
+ };
638
+ }
639
+ }
640
+ /**
641
+ * Get CDP fee information
642
+ */
643
+ async getFee() {
644
+ return {
645
+ perTx: 1e-3,
646
+ currency: "USD",
647
+ freeQuota: 1e3
648
+ };
649
+ }
650
+ /**
651
+ * Get configuration summary (for logging)
652
+ */
653
+ getConfigSummary() {
654
+ const mode = this.useMainnet ? "mainnet" : "testnet";
655
+ const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
656
+ return `CDP Facilitator (${mode}, credentials: ${hasCredentials ? "yes" : "no"})`;
657
+ }
658
+ };
659
+
660
+ // src/facilitators/registry.ts
661
+ var FacilitatorRegistry = class {
662
+ factories = /* @__PURE__ */ new Map();
663
+ instances = /* @__PURE__ */ new Map();
664
+ selection;
665
+ roundRobinIndex = 0;
666
+ constructor(selection) {
667
+ this.registerFactory("cdp", (config) => new CDPFacilitator(config));
668
+ this.selection = selection || { primary: "cdp", strategy: "failover" };
669
+ }
670
+ /**
671
+ * Register a new facilitator factory
672
+ */
673
+ registerFactory(name, factory) {
674
+ this.factories.set(name, factory);
675
+ }
676
+ /**
677
+ * Get or create a facilitator instance
678
+ */
679
+ get(name, config) {
680
+ if (this.instances.has(name)) {
681
+ return this.instances.get(name);
682
+ }
683
+ const factory = this.factories.get(name);
684
+ if (!factory) {
685
+ throw new Error(`Unknown facilitator: ${name}. Available: ${Array.from(this.factories.keys()).join(", ")}`);
686
+ }
687
+ const mergedConfig = {
688
+ ...this.selection.config?.[name],
689
+ ...config
690
+ };
691
+ const instance = factory(mergedConfig);
692
+ this.instances.set(name, instance);
693
+ return instance;
694
+ }
695
+ /**
696
+ * Get all configured facilitator names
697
+ */
698
+ getConfiguredNames() {
699
+ const names = [this.selection.primary];
700
+ if (this.selection.fallback) {
701
+ names.push(...this.selection.fallback);
702
+ }
703
+ return names;
704
+ }
705
+ /**
706
+ * Get list of facilitators based on selection strategy
707
+ */
708
+ async getOrderedFacilitators(network) {
709
+ const names = this.getConfiguredNames();
710
+ const facilitators = [];
711
+ for (const name of names) {
712
+ try {
713
+ const f = this.get(name);
714
+ if (f.supportsNetwork(network)) {
715
+ facilitators.push(f);
716
+ }
717
+ } catch (err) {
718
+ console.warn(`[Registry] Failed to get facilitator ${name}:`, err);
719
+ }
720
+ }
721
+ if (facilitators.length === 0) {
722
+ throw new Error(`No facilitators available for network: ${network}`);
723
+ }
724
+ switch (this.selection.strategy) {
725
+ case "random":
726
+ return this.shuffle(facilitators);
727
+ case "roundrobin":
728
+ this.roundRobinIndex = (this.roundRobinIndex + 1) % facilitators.length;
729
+ return [
730
+ ...facilitators.slice(this.roundRobinIndex),
731
+ ...facilitators.slice(0, this.roundRobinIndex)
732
+ ];
733
+ case "cheapest":
734
+ return this.sortByCheapest(facilitators);
735
+ case "fastest":
736
+ return this.sortByFastest(facilitators);
737
+ case "failover":
738
+ default:
739
+ return facilitators;
740
+ }
741
+ }
742
+ shuffle(array) {
743
+ const result = [...array];
744
+ for (let i = result.length - 1; i > 0; i--) {
745
+ const j = Math.floor(Math.random() * (i + 1));
746
+ [result[i], result[j]] = [result[j], result[i]];
747
+ }
748
+ return result;
749
+ }
750
+ async sortByCheapest(facilitators) {
751
+ const withFees = await Promise.all(
752
+ facilitators.map(async (f) => {
753
+ try {
754
+ const fee = await f.getFee?.();
755
+ return { facilitator: f, perTx: fee?.perTx ?? Infinity };
756
+ } catch {
757
+ return { facilitator: f, perTx: Infinity };
758
+ }
759
+ })
760
+ );
761
+ withFees.sort((a, b) => a.perTx - b.perTx);
762
+ return withFees.map((w) => w.facilitator);
763
+ }
764
+ async sortByFastest(facilitators) {
765
+ const withLatency = await Promise.all(
766
+ facilitators.map(async (f) => {
767
+ try {
768
+ const health = await f.healthCheck();
769
+ return { facilitator: f, latency: health.latencyMs ?? Infinity };
770
+ } catch {
771
+ return { facilitator: f, latency: Infinity };
772
+ }
773
+ })
774
+ );
775
+ withLatency.sort((a, b) => a.latency - b.latency);
776
+ return withLatency.map((w) => w.facilitator);
777
+ }
778
+ /**
779
+ * Verify payment using configured facilitators
780
+ */
781
+ async verify(paymentPayload, requirements) {
782
+ const network = paymentPayload.accepted?.network || paymentPayload.network || requirements.network;
783
+ const facilitators = await this.getOrderedFacilitators(network);
784
+ let lastError;
785
+ for (const f of facilitators) {
786
+ try {
787
+ console.log(`[Registry] Trying ${f.name} for verify...`);
788
+ const result = await f.verify(paymentPayload, requirements);
789
+ if (result.valid) {
790
+ console.log(`[Registry] ${f.name} verify succeeded`);
791
+ return { ...result, facilitator: f.name };
792
+ }
793
+ lastError = result.error;
794
+ console.log(`[Registry] ${f.name} verify failed: ${result.error}`);
795
+ if (this.selection.strategy === "failover" && !this.isTransientError(result.error)) {
796
+ break;
797
+ }
798
+ } catch (err) {
799
+ lastError = err.message;
800
+ console.error(`[Registry] ${f.name} error:`, err.message);
801
+ }
802
+ }
803
+ return {
804
+ valid: false,
805
+ error: lastError || "All facilitators failed",
806
+ facilitator: "none"
807
+ };
808
+ }
809
+ /**
810
+ * Settle payment using configured facilitators
811
+ */
812
+ async settle(paymentPayload, requirements) {
813
+ const network = paymentPayload.accepted?.network || paymentPayload.network || requirements.network;
814
+ const facilitators = await this.getOrderedFacilitators(network);
815
+ let lastError;
816
+ for (const f of facilitators) {
817
+ try {
818
+ console.log(`[Registry] Trying ${f.name} for settle...`);
819
+ const result = await f.settle(paymentPayload, requirements);
820
+ if (result.success) {
821
+ console.log(`[Registry] ${f.name} settle succeeded: ${result.transaction}`);
822
+ return { ...result, facilitator: f.name };
823
+ }
824
+ lastError = result.error;
825
+ console.log(`[Registry] ${f.name} settle failed: ${result.error}`);
826
+ } catch (err) {
827
+ lastError = err.message;
828
+ console.error(`[Registry] ${f.name} error:`, err.message);
829
+ }
830
+ }
831
+ return {
832
+ success: false,
833
+ error: lastError || "All facilitators failed",
834
+ facilitator: "none"
835
+ };
836
+ }
837
+ /**
838
+ * Check health of all configured facilitators
839
+ */
840
+ async healthCheckAll() {
841
+ const results = {};
842
+ for (const name of this.getConfiguredNames()) {
843
+ try {
844
+ const f = this.get(name);
845
+ results[name] = await f.healthCheck();
846
+ } catch (err) {
847
+ results[name] = { healthy: false, error: err.message };
848
+ }
849
+ }
850
+ return results;
851
+ }
852
+ /**
853
+ * Check if an error is transient (network/server issue) vs permanent (bad request)
854
+ */
855
+ isTransientError(error) {
856
+ if (!error) return true;
857
+ const transientPatterns = [
858
+ /timeout/i,
859
+ /network/i,
860
+ /connection/i,
861
+ /ECONNREFUSED/i,
862
+ /ETIMEDOUT/i,
863
+ /503/,
864
+ /502/,
865
+ /500/
866
+ ];
867
+ return transientPatterns.some((p) => p.test(error));
868
+ }
869
+ /**
870
+ * Update selection configuration
871
+ */
872
+ setSelection(selection) {
873
+ this.selection = selection;
874
+ this.instances.clear();
875
+ }
876
+ /**
877
+ * Get current selection configuration
878
+ */
879
+ getSelection() {
880
+ return { ...this.selection };
881
+ }
882
+ };
883
+
884
+ // src/server/index.ts
885
+ var X402_VERSION3 = 2;
440
886
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
441
887
  var PAYMENT_HEADER2 = "x-payment";
442
888
  var PAYMENT_RESPONSE_HEADER = "x-payment-response";
443
- var FACILITATOR_TESTNET = "https://www.x402.org/facilitator";
444
- var FACILITATOR_MAINNET = "https://api.cdp.coinbase.com/platform/v2/x402";
445
889
  var USDC_ADDRESSES = {
446
890
  "eip155:8453": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
447
891
  // Base mainnet
@@ -452,15 +896,15 @@ var USDC_DOMAIN = {
452
896
  name: "USD Coin",
453
897
  version: "2"
454
898
  };
455
- function loadEnvFiles() {
899
+ function loadEnvFile2() {
456
900
  const envPaths = [
457
- path.join(process.cwd(), ".env"),
458
- path.join(process.env.HOME || "", ".moltspay", ".env")
901
+ path2.join(process.cwd(), ".env"),
902
+ path2.join(process.env.HOME || "", ".moltspay", ".env")
459
903
  ];
460
904
  for (const envPath of envPaths) {
461
- if ((0, import_fs2.existsSync)(envPath)) {
905
+ if ((0, import_fs3.existsSync)(envPath)) {
462
906
  try {
463
- const content = (0, import_fs2.readFileSync)(envPath, "utf-8");
907
+ const content = (0, import_fs3.readFileSync)(envPath, "utf-8");
464
908
  for (const line of content.split("\n")) {
465
909
  const trimmed = line.trim();
466
910
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -477,77 +921,44 @@ function loadEnvFiles() {
477
921
  }
478
922
  console.log(`[MoltsPay] Loaded config from ${envPath}`);
479
923
  break;
480
- } catch (err) {
481
- console.warn(`[MoltsPay] Failed to load ${envPath}:`, err);
924
+ } catch {
482
925
  }
483
926
  }
484
927
  }
485
928
  }
486
- function getCDPConfig() {
487
- loadEnvFiles();
488
- return {
489
- useMainnet: process.env.USE_MAINNET?.toLowerCase() === "true",
490
- apiKeyId: process.env.CDP_API_KEY_ID,
491
- apiKeySecret: process.env.CDP_API_KEY_SECRET
492
- };
493
- }
494
- async function getCDPAuthHeaders(method, urlPath, body) {
495
- const config = getCDPConfig();
496
- if (!config.apiKeyId || !config.apiKeySecret) {
497
- throw new Error("CDP_API_KEY_ID and CDP_API_KEY_SECRET required for mainnet");
498
- }
499
- try {
500
- const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
501
- const headers = await getAuthHeaders({
502
- apiKeyId: config.apiKeyId,
503
- apiKeySecret: config.apiKeySecret,
504
- requestMethod: method,
505
- requestHost: "api.cdp.coinbase.com",
506
- requestPath: urlPath,
507
- requestBody: body
508
- });
509
- return headers;
510
- } catch (err) {
511
- console.error("[MoltsPay] Failed to generate CDP auth headers:", err.message);
512
- throw err;
513
- }
514
- }
515
929
  var MoltsPayServer = class {
516
930
  manifest;
517
931
  skills = /* @__PURE__ */ new Map();
518
932
  options;
519
- cdpConfig;
520
- facilitatorUrl;
933
+ registry;
521
934
  networkId;
935
+ useMainnet;
522
936
  constructor(servicesPath, options = {}) {
523
- this.cdpConfig = getCDPConfig();
524
- const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
937
+ loadEnvFile2();
938
+ const content = (0, import_fs3.readFileSync)(servicesPath, "utf-8");
525
939
  this.manifest = JSON.parse(content);
526
940
  this.options = {
527
941
  port: options.port || 3e3,
528
- host: options.host || "0.0.0.0"
942
+ host: options.host || "0.0.0.0",
943
+ ...options
529
944
  };
530
- if (this.cdpConfig.useMainnet) {
531
- if (!this.cdpConfig.apiKeyId || !this.cdpConfig.apiKeySecret) {
532
- console.warn("[MoltsPay] WARNING: USE_MAINNET=true but CDP keys not set!");
533
- console.warn("[MoltsPay] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
945
+ this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
946
+ this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
947
+ const facilitatorConfig = options.facilitators || {
948
+ primary: "cdp",
949
+ strategy: "failover",
950
+ config: {
951
+ cdp: { useMainnet: this.useMainnet }
534
952
  }
535
- this.facilitatorUrl = FACILITATOR_MAINNET;
536
- this.networkId = "eip155:8453";
537
- } else {
538
- this.facilitatorUrl = options.facilitatorUrl || FACILITATOR_TESTNET;
539
- this.networkId = "eip155:84532";
540
- }
541
- const networkName = this.cdpConfig.useMainnet ? "Base mainnet" : "Base Sepolia (testnet)";
542
- const facilitatorName = this.cdpConfig.useMainnet ? "CDP" : "x402.org";
953
+ };
954
+ this.registry = new FacilitatorRegistry(facilitatorConfig);
955
+ const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
956
+ const networkName = this.useMainnet ? "Base mainnet" : "Base Sepolia (testnet)";
543
957
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
544
958
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
545
959
  console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
546
960
  console.log(`[MoltsPay] Network: ${this.networkId} (${networkName})`);
547
- console.log(`[MoltsPay] Facilitator: ${facilitatorName} (${this.facilitatorUrl})`);
548
- if (this.cdpConfig.useMainnet && this.cdpConfig.apiKeyId) {
549
- console.log(`[MoltsPay] CDP API Key: ${this.cdpConfig.apiKeyId.slice(0, 8)}...`);
550
- }
961
+ console.log(`[MoltsPay] Facilitator: ${primaryFacilitator.displayName} (${facilitatorConfig.strategy || "failover"})`);
551
962
  console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
552
963
  }
553
964
  /**
@@ -573,6 +984,7 @@ var MoltsPayServer = class {
573
984
  console.log(`[MoltsPay] Endpoints:`);
574
985
  console.log(` GET /services - List available services`);
575
986
  console.log(` POST /execute - Execute service (x402 payment)`);
987
+ console.log(` GET /health - Health check (incl. facilitators)`);
576
988
  });
577
989
  }
578
990
  /**
@@ -593,6 +1005,9 @@ var MoltsPayServer = class {
593
1005
  if (url.pathname === "/services" && req.method === "GET") {
594
1006
  return this.handleGetServices(res);
595
1007
  }
1008
+ if (url.pathname === "/health" && req.method === "GET") {
1009
+ return await this.handleHealthCheck(res);
1010
+ }
596
1011
  if (url.pathname === "/execute" && req.method === "POST") {
597
1012
  const body = await this.readBody(req);
598
1013
  const paymentHeader = req.headers[PAYMENT_HEADER2];
@@ -618,18 +1033,37 @@ var MoltsPayServer = class {
618
1033
  output: s.output,
619
1034
  available: this.skills.has(s.id)
620
1035
  }));
1036
+ const selection = this.registry.getSelection();
621
1037
  this.sendJson(res, 200, {
622
1038
  provider: this.manifest.provider,
623
1039
  services,
624
1040
  x402: {
625
- version: X402_VERSION2,
1041
+ version: X402_VERSION3,
626
1042
  network: this.networkId,
627
1043
  schemes: ["exact"],
628
- facilitator: this.cdpConfig.useMainnet ? "cdp" : "x402.org",
629
- mainnet: this.cdpConfig.useMainnet
1044
+ facilitators: {
1045
+ primary: selection.primary,
1046
+ fallback: selection.fallback,
1047
+ strategy: selection.strategy
1048
+ },
1049
+ mainnet: this.useMainnet
630
1050
  }
631
1051
  });
632
1052
  }
1053
+ /**
1054
+ * GET /health - Health check endpoint
1055
+ */
1056
+ async handleHealthCheck(res) {
1057
+ const facilitatorHealth = await this.registry.healthCheckAll();
1058
+ const allHealthy = Object.values(facilitatorHealth).every((h) => h.healthy);
1059
+ this.sendJson(res, allHealthy ? 200 : 503, {
1060
+ status: allHealthy ? "healthy" : "degraded",
1061
+ network: this.networkId,
1062
+ facilitators: facilitatorHealth,
1063
+ services: this.manifest.services.length,
1064
+ registered: this.skills.size
1065
+ });
1066
+ }
633
1067
  /**
634
1068
  * POST /execute - Execute service with x402 payment
635
1069
  */
@@ -661,11 +1095,16 @@ var MoltsPayServer = class {
661
1095
  if (!validation.valid) {
662
1096
  return this.sendJson(res, 402, { error: validation.error });
663
1097
  }
664
- console.log(`[MoltsPay] Verifying payment with facilitator...`);
665
- const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
1098
+ const requirements = this.buildPaymentRequirements(skill.config);
1099
+ console.log(`[MoltsPay] Verifying payment...`);
1100
+ const verifyResult = await this.registry.verify(payment, requirements);
666
1101
  if (!verifyResult.valid) {
667
- return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
1102
+ return this.sendJson(res, 402, {
1103
+ error: `Payment verification failed: ${verifyResult.error}`,
1104
+ facilitator: verifyResult.facilitator
1105
+ });
668
1106
  }
1107
+ console.log(`[MoltsPay] Verified by ${verifyResult.facilitator}`);
669
1108
  console.log(`[MoltsPay] Executing skill: ${service}`);
670
1109
  let result;
671
1110
  try {
@@ -680,17 +1119,18 @@ var MoltsPayServer = class {
680
1119
  console.log(`[MoltsPay] Skill succeeded, settling payment...`);
681
1120
  let settlement = null;
682
1121
  try {
683
- settlement = await this.settleWithFacilitator(payment, skill.config);
684
- console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
1122
+ settlement = await this.registry.settle(payment, requirements);
1123
+ console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
685
1124
  } catch (err) {
686
1125
  console.error("[MoltsPay] Settlement failed:", err.message);
687
1126
  }
688
1127
  const responseHeaders = {};
689
- if (settlement) {
1128
+ if (settlement?.success) {
690
1129
  const responsePayload = {
691
1130
  success: true,
692
1131
  transaction: settlement.transaction,
693
- network: payment.network
1132
+ network: payment.network || payment.accepted?.network,
1133
+ facilitator: settlement.facilitator
694
1134
  };
695
1135
  responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
696
1136
  JSON.stringify(responsePayload)
@@ -699,7 +1139,7 @@ var MoltsPayServer = class {
699
1139
  this.sendJson(res, 200, {
700
1140
  success: true,
701
1141
  result,
702
- payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
1142
+ payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
703
1143
  }, responseHeaders);
704
1144
  }
705
1145
  /**
@@ -708,7 +1148,7 @@ var MoltsPayServer = class {
708
1148
  sendPaymentRequired(config, res) {
709
1149
  const requirements = this.buildPaymentRequirements(config);
710
1150
  const paymentRequired = {
711
- x402Version: X402_VERSION2,
1151
+ x402Version: X402_VERSION3,
712
1152
  accepts: [requirements],
713
1153
  resource: {
714
1154
  url: `/execute?service=${config.id}`,
@@ -731,7 +1171,7 @@ var MoltsPayServer = class {
731
1171
  * Basic payment validation
732
1172
  */
733
1173
  validatePayment(payment, config) {
734
- if (payment.x402Version !== X402_VERSION2) {
1174
+ if (payment.x402Version !== X402_VERSION3) {
735
1175
  return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
736
1176
  }
737
1177
  const scheme = payment.accepted?.scheme || payment.scheme;
@@ -745,7 +1185,7 @@ var MoltsPayServer = class {
745
1185
  return { valid: true };
746
1186
  }
747
1187
  /**
748
- * Build complete payment requirements for facilitator
1188
+ * Build payment requirements for facilitator
749
1189
  */
750
1190
  buildPaymentRequirements(config) {
751
1191
  const amountInUnits = Math.floor(config.price * 1e6).toString();
@@ -760,77 +1200,6 @@ var MoltsPayServer = class {
760
1200
  extra: USDC_DOMAIN
761
1201
  };
762
1202
  }
763
- /**
764
- * Verify payment with facilitator (testnet or CDP)
765
- */
766
- async verifyWithFacilitator(payment, config) {
767
- try {
768
- const requirements = this.buildPaymentRequirements(config);
769
- const requestBody = {
770
- x402Version: X402_VERSION2,
771
- // Required at top level for CDP
772
- paymentPayload: payment,
773
- paymentRequirements: requirements
774
- };
775
- console.log("[MoltsPay] Verify request:", JSON.stringify(requestBody, null, 2));
776
- let headers = { "Content-Type": "application/json" };
777
- if (this.cdpConfig.useMainnet) {
778
- const authHeaders = await getCDPAuthHeaders(
779
- "POST",
780
- "/platform/v2/x402/verify",
781
- requestBody
782
- );
783
- headers = { ...headers, ...authHeaders };
784
- }
785
- const response = await fetch(`${this.facilitatorUrl}/verify`, {
786
- method: "POST",
787
- headers,
788
- body: JSON.stringify(requestBody)
789
- });
790
- const result = await response.json();
791
- console.log("[MoltsPay] Verify response:", JSON.stringify(result, null, 2));
792
- if (!response.ok || !result.isValid) {
793
- return { valid: false, error: result.invalidReason || result.error || "Verification failed" };
794
- }
795
- return { valid: true };
796
- } catch (err) {
797
- return { valid: false, error: `Facilitator error: ${err.message}` };
798
- }
799
- }
800
- /**
801
- * Settle payment with facilitator (execute on-chain transfer)
802
- */
803
- async settleWithFacilitator(payment, config) {
804
- const requirements = this.buildPaymentRequirements(config);
805
- const requestBody = {
806
- x402Version: X402_VERSION2,
807
- // Required at top level for CDP
808
- paymentPayload: payment,
809
- paymentRequirements: requirements
810
- };
811
- let headers = { "Content-Type": "application/json" };
812
- if (this.cdpConfig.useMainnet) {
813
- const authHeaders = await getCDPAuthHeaders(
814
- "POST",
815
- "/platform/v2/x402/settle",
816
- requestBody
817
- );
818
- headers = { ...headers, ...authHeaders };
819
- }
820
- const response = await fetch(`${this.facilitatorUrl}/settle`, {
821
- method: "POST",
822
- headers,
823
- body: JSON.stringify(requestBody)
824
- });
825
- const result = await response.json();
826
- if (!response.ok || !result.success) {
827
- throw new Error(result.error || result.errorReason || "Settlement failed");
828
- }
829
- return {
830
- transaction: result.transaction,
831
- status: result.status || "settled"
832
- };
833
- }
834
1203
  async readBody(req) {
835
1204
  return new Promise((resolve2, reject) => {
836
1205
  let body = "";
@@ -860,8 +1229,8 @@ var readline = __toESM(require("readline"));
860
1229
  var program = new import_commander.Command();
861
1230
  var DEFAULT_CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
862
1231
  var PID_FILE = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "server.pid");
863
- if (!(0, import_fs3.existsSync)(DEFAULT_CONFIG_DIR)) {
864
- (0, import_fs3.mkdirSync)(DEFAULT_CONFIG_DIR, { recursive: true });
1232
+ if (!(0, import_fs4.existsSync)(DEFAULT_CONFIG_DIR)) {
1233
+ (0, import_fs4.mkdirSync)(DEFAULT_CONFIG_DIR, { recursive: true });
865
1234
  }
866
1235
  function prompt(question) {
867
1236
  const rl = readline.createInterface({
@@ -878,7 +1247,7 @@ function prompt(question) {
878
1247
  program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version("1.0.0");
879
1248
  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) => {
880
1249
  console.log("\n\u{1F510} MoltsPay Client Setup\n");
881
- if ((0, import_fs3.existsSync)((0, import_path2.join)(options.configDir, "wallet.json"))) {
1250
+ if ((0, import_fs4.existsSync)((0, import_path2.join)(options.configDir, "wallet.json"))) {
882
1251
  console.log('\u26A0\uFE0F Already initialized. Use "moltspay config" to update settings.');
883
1252
  console.log(` Config dir: ${options.configDir}`);
884
1253
  return;
@@ -1027,14 +1396,14 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1027
1396
  let manifestPath;
1028
1397
  let skillDir;
1029
1398
  let isSkillDir = false;
1030
- if ((0, import_fs3.existsSync)((0, import_path2.join)(resolvedPath, "moltspay.services.json"))) {
1399
+ if ((0, import_fs4.existsSync)((0, import_path2.join)(resolvedPath, "moltspay.services.json"))) {
1031
1400
  manifestPath = (0, import_path2.join)(resolvedPath, "moltspay.services.json");
1032
1401
  skillDir = resolvedPath;
1033
1402
  isSkillDir = true;
1034
- } else if ((0, import_fs3.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
1403
+ } else if ((0, import_fs4.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
1035
1404
  manifestPath = resolvedPath;
1036
1405
  skillDir = (0, import_path2.dirname)(resolvedPath);
1037
- } else if ((0, import_fs3.existsSync)(resolvedPath)) {
1406
+ } else if ((0, import_fs4.existsSync)(resolvedPath)) {
1038
1407
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
1039
1408
  continue;
1040
1409
  } else {
@@ -1043,20 +1412,33 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1043
1412
  }
1044
1413
  console.log(`\u{1F4E6} Loading: ${manifestPath}`);
1045
1414
  try {
1046
- const manifestContent = JSON.parse((0, import_fs3.readFileSync)(manifestPath, "utf-8"));
1415
+ const manifestContent = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
1047
1416
  if (!provider) {
1048
1417
  provider = manifestContent.provider;
1049
1418
  }
1050
1419
  let skillModule = null;
1051
1420
  if (isSkillDir) {
1052
- const indexPath = (0, import_path2.join)(skillDir, "index.js");
1053
- if ((0, import_fs3.existsSync)(indexPath)) {
1421
+ let entryPoint = "index.js";
1422
+ const pkgJsonPath = (0, import_path2.join)(skillDir, "package.json");
1423
+ if ((0, import_fs4.existsSync)(pkgJsonPath)) {
1054
1424
  try {
1055
- skillModule = await import(indexPath);
1056
- console.log(` \u2705 Loaded module: ${indexPath}`);
1425
+ const pkgJson = JSON.parse((0, import_fs4.readFileSync)(pkgJsonPath, "utf-8"));
1426
+ if (pkgJson.main) {
1427
+ entryPoint = pkgJson.main;
1428
+ }
1429
+ } catch {
1430
+ }
1431
+ }
1432
+ const modulePath = (0, import_path2.join)(skillDir, entryPoint);
1433
+ if ((0, import_fs4.existsSync)(modulePath)) {
1434
+ try {
1435
+ skillModule = await import(modulePath);
1436
+ console.log(` \u2705 Loaded module: ${modulePath}`);
1057
1437
  } catch (err) {
1058
1438
  console.error(` \u26A0\uFE0F Failed to load module: ${err.message}`);
1059
1439
  }
1440
+ } else {
1441
+ console.error(` \u26A0\uFE0F Entry point not found: ${modulePath}`);
1060
1442
  }
1061
1443
  }
1062
1444
  for (const service of manifestContent.services) {
@@ -1127,7 +1509,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1127
1509
  services: allServices
1128
1510
  };
1129
1511
  const tempManifestPath = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "combined-manifest.json");
1130
- (0, import_fs3.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
1512
+ (0, import_fs4.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
1131
1513
  console.log(`
1132
1514
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
1133
1515
  console.log(` Provider: ${provider.name}`);
@@ -1140,12 +1522,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1140
1522
  server.skill(serviceId, handler);
1141
1523
  }
1142
1524
  const pidData = { pid: process.pid, port, paths: allPaths };
1143
- (0, import_fs3.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
1525
+ (0, import_fs4.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
1144
1526
  server.listen(port);
1145
1527
  const cleanup = () => {
1146
1528
  try {
1147
- if ((0, import_fs3.existsSync)(PID_FILE)) (0, import_fs3.unlinkSync)(PID_FILE);
1148
- if ((0, import_fs3.existsSync)(tempManifestPath)) (0, import_fs3.unlinkSync)(tempManifestPath);
1529
+ if ((0, import_fs4.existsSync)(PID_FILE)) (0, import_fs4.unlinkSync)(PID_FILE);
1530
+ if ((0, import_fs4.existsSync)(tempManifestPath)) (0, import_fs4.unlinkSync)(tempManifestPath);
1149
1531
  } catch {
1150
1532
  }
1151
1533
  };
@@ -1166,12 +1548,12 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
1166
1548
  }
1167
1549
  });
1168
1550
  program.command("stop").description("Stop the running MoltsPay server").action(async () => {
1169
- if (!(0, import_fs3.existsSync)(PID_FILE)) {
1551
+ if (!(0, import_fs4.existsSync)(PID_FILE)) {
1170
1552
  console.log("\u274C No running server found (no PID file)");
1171
1553
  process.exit(1);
1172
1554
  }
1173
1555
  try {
1174
- const pidData = JSON.parse((0, import_fs3.readFileSync)(PID_FILE, "utf-8"));
1556
+ const pidData = JSON.parse((0, import_fs4.readFileSync)(PID_FILE, "utf-8"));
1175
1557
  const { pid, port, manifest } = pidData;
1176
1558
  console.log(`
1177
1559
  \u{1F6D1} Stopping MoltsPay Server
@@ -1184,7 +1566,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
1184
1566
  process.kill(pid, 0);
1185
1567
  } catch {
1186
1568
  console.log("\u26A0\uFE0F Process not running, cleaning up PID file...");
1187
- (0, import_fs3.unlinkSync)(PID_FILE);
1569
+ (0, import_fs4.unlinkSync)(PID_FILE);
1188
1570
  process.exit(0);
1189
1571
  }
1190
1572
  process.kill(pid, "SIGTERM");
@@ -1196,8 +1578,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
1196
1578
  process.kill(pid, "SIGKILL");
1197
1579
  } catch {
1198
1580
  }
1199
- if ((0, import_fs3.existsSync)(PID_FILE)) {
1200
- (0, import_fs3.unlinkSync)(PID_FILE);
1581
+ if ((0, import_fs4.existsSync)(PID_FILE)) {
1582
+ (0, import_fs4.unlinkSync)(PID_FILE);
1201
1583
  }
1202
1584
  console.log("\u2705 Server stopped\n");
1203
1585
  } catch (err) {
@@ -1227,11 +1609,11 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
1227
1609
  params.image_url = imagePath;
1228
1610
  } else {
1229
1611
  const filePath = (0, import_path2.resolve)(imagePath);
1230
- if (!(0, import_fs3.existsSync)(filePath)) {
1612
+ if (!(0, import_fs4.existsSync)(filePath)) {
1231
1613
  console.error(`\u274C Image file not found: ${filePath}`);
1232
1614
  process.exit(1);
1233
1615
  }
1234
- const imageData = (0, import_fs3.readFileSync)(filePath);
1616
+ const imageData = (0, import_fs4.readFileSync)(filePath);
1235
1617
  params.image_base64 = imageData.toString("base64");
1236
1618
  }
1237
1619
  }
@@ -1272,9 +1654,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
1272
1654
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
1273
1655
  const resolvedPath = (0, import_path2.resolve)(inputPath);
1274
1656
  let manifestPath;
1275
- if ((0, import_fs3.existsSync)((0, import_path2.join)(resolvedPath, "moltspay.services.json"))) {
1657
+ if ((0, import_fs4.existsSync)((0, import_path2.join)(resolvedPath, "moltspay.services.json"))) {
1276
1658
  manifestPath = (0, import_path2.join)(resolvedPath, "moltspay.services.json");
1277
- } else if (resolvedPath.endsWith(".json") && (0, import_fs3.existsSync)(resolvedPath)) {
1659
+ } else if (resolvedPath.endsWith(".json") && (0, import_fs4.existsSync)(resolvedPath)) {
1278
1660
  manifestPath = resolvedPath;
1279
1661
  } else {
1280
1662
  console.error(`\u274C Not found: ${resolvedPath}`);
@@ -1284,7 +1666,7 @@ program.command("validate <path>").description("Validate a moltspay.services.jso
1284
1666
  \u{1F4CB} Validating: ${manifestPath}
1285
1667
  `);
1286
1668
  try {
1287
- const content = JSON.parse((0, import_fs3.readFileSync)(manifestPath, "utf-8"));
1669
+ const content = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
1288
1670
  const errors = [];
1289
1671
  if (!content.provider) {
1290
1672
  errors.push("Missing required field: provider");