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/README.md +156 -504
- package/dist/cli/index.js +553 -171
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +555 -173
- package/dist/cli/index.mjs.map +1 -1
- package/dist/facilitators/index.d.mts +61 -0
- package/dist/facilitators/index.d.ts +61 -0
- package/dist/facilitators/index.js +502 -0
- package/dist/facilitators/index.js.map +1 -0
- package/dist/facilitators/index.mjs +461 -0
- package/dist/facilitators/index.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +583 -190
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +580 -192
- package/dist/index.mjs.map +1 -1
- package/dist/registry-OsEO2dOu.d.mts +233 -0
- package/dist/registry-OsEO2dOu.d.ts +233 -0
- package/dist/server/index.d.mts +19 -15
- package/dist/server/index.d.ts +19 -15
- package/dist/server/index.js +514 -145
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +514 -145
- package/dist/server/index.mjs.map +1 -1
- package/package.json +7 -2
- package/schemas/moltspay.services.schema.json +1 -1
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
|
|
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
|
|
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
|
|
899
|
+
function loadEnvFile2() {
|
|
456
900
|
const envPaths = [
|
|
457
|
-
|
|
458
|
-
|
|
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,
|
|
905
|
+
if ((0, import_fs3.existsSync)(envPath)) {
|
|
462
906
|
try {
|
|
463
|
-
const content = (0,
|
|
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
|
|
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
|
-
|
|
520
|
-
facilitatorUrl;
|
|
933
|
+
registry;
|
|
521
934
|
networkId;
|
|
935
|
+
useMainnet;
|
|
522
936
|
constructor(servicesPath, options = {}) {
|
|
523
|
-
|
|
524
|
-
const content = (0,
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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: ${
|
|
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:
|
|
1041
|
+
version: X402_VERSION3,
|
|
626
1042
|
network: this.networkId,
|
|
627
1043
|
schemes: ["exact"],
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
665
|
-
|
|
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, {
|
|
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.
|
|
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:
|
|
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 !==
|
|
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
|
|
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,
|
|
864
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
1053
|
-
|
|
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
|
-
|
|
1056
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
1148
|
-
if ((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1200
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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");
|