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.mjs
CHANGED
|
@@ -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
|
|
7
|
-
import { existsSync as
|
|
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
|
|
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
|
|
876
|
+
function loadEnvFile2() {
|
|
433
877
|
const envPaths = [
|
|
434
|
-
|
|
435
|
-
|
|
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 (
|
|
882
|
+
if (existsSync3(envPath)) {
|
|
439
883
|
try {
|
|
440
|
-
const content =
|
|
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
|
|
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
|
-
|
|
497
|
-
facilitatorUrl;
|
|
910
|
+
registry;
|
|
498
911
|
networkId;
|
|
912
|
+
useMainnet;
|
|
499
913
|
constructor(servicesPath, options = {}) {
|
|
500
|
-
|
|
501
|
-
const content =
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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: ${
|
|
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:
|
|
1018
|
+
version: X402_VERSION3,
|
|
603
1019
|
network: this.networkId,
|
|
604
1020
|
schemes: ["exact"],
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
642
|
-
|
|
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, {
|
|
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.
|
|
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:
|
|
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 !==
|
|
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
|
|
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 =
|
|
839
|
-
var PID_FILE =
|
|
840
|
-
if (!
|
|
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 (
|
|
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 ${
|
|
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 (
|
|
1008
|
-
manifestPath =
|
|
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 (
|
|
1380
|
+
} else if (existsSync4(resolvedPath) && resolvedPath.endsWith(".json")) {
|
|
1012
1381
|
manifestPath = resolvedPath;
|
|
1013
1382
|
skillDir = dirname(resolvedPath);
|
|
1014
|
-
} else if (
|
|
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(
|
|
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
|
-
|
|
1030
|
-
|
|
1398
|
+
let entryPoint = "index.js";
|
|
1399
|
+
const pkgJsonPath = join4(skillDir, "package.json");
|
|
1400
|
+
if (existsSync4(pkgJsonPath)) {
|
|
1031
1401
|
try {
|
|
1032
|
-
|
|
1033
|
-
|
|
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 =
|
|
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 (
|
|
1125
|
-
if (
|
|
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 (!
|
|
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(
|
|
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 (
|
|
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 (!
|
|
1589
|
+
if (!existsSync4(filePath)) {
|
|
1208
1590
|
console.error(`\u274C Image file not found: ${filePath}`);
|
|
1209
1591
|
process.exit(1);
|
|
1210
1592
|
}
|
|
1211
|
-
const imageData =
|
|
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 (
|
|
1253
|
-
manifestPath =
|
|
1254
|
-
} else if (resolvedPath.endsWith(".json") &&
|
|
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(
|
|
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");
|