moltspay 0.8.10 → 0.8.13
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 +86 -3
- package/dist/cli/index.js +178 -61
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +179 -62
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +9 -1
- package/dist/client/index.d.ts +9 -1
- package/dist/client/index.js +50 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +51 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +57 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +58 -7
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +7 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +7 -4
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { existsSync as existsSync3, writeFileSync as writeFileSync2, readFileSyn
|
|
|
8
8
|
import { spawn } from "child_process";
|
|
9
9
|
|
|
10
10
|
// src/client/index.ts
|
|
11
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, chmodSync } from "fs";
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import { join } from "path";
|
|
14
14
|
import { Wallet, ethers } from "ethers";
|
|
@@ -93,6 +93,7 @@ var MoltsPayClient = class {
|
|
|
93
93
|
this.configDir = options.configDir || join(homedir(), ".moltspay");
|
|
94
94
|
this.config = this.loadConfig();
|
|
95
95
|
this.walletData = this.loadWallet();
|
|
96
|
+
this.loadSpending();
|
|
96
97
|
if (this.walletData) {
|
|
97
98
|
this.wallet = new Wallet(this.walletData.privateKey);
|
|
98
99
|
}
|
|
@@ -277,6 +278,7 @@ var MoltsPayClient = class {
|
|
|
277
278
|
if (today > this.lastSpendingReset) {
|
|
278
279
|
this.todaySpending = 0;
|
|
279
280
|
this.lastSpendingReset = today;
|
|
281
|
+
this.saveSpending();
|
|
280
282
|
}
|
|
281
283
|
if (this.todaySpending + amount > this.config.limits.maxPerDay) {
|
|
282
284
|
throw new Error(
|
|
@@ -285,10 +287,11 @@ var MoltsPayClient = class {
|
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
/**
|
|
288
|
-
* Record spending
|
|
290
|
+
* Record spending and persist to disk
|
|
289
291
|
*/
|
|
290
292
|
recordSpending(amount) {
|
|
291
293
|
this.todaySpending += amount;
|
|
294
|
+
this.saveSpending();
|
|
292
295
|
}
|
|
293
296
|
// --- Config & Wallet Management ---
|
|
294
297
|
loadConfig() {
|
|
@@ -304,9 +307,54 @@ var MoltsPayClient = class {
|
|
|
304
307
|
const configPath = join(this.configDir, "config.json");
|
|
305
308
|
writeFileSync(configPath, JSON.stringify(this.config, null, 2));
|
|
306
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Load spending data from disk
|
|
312
|
+
*/
|
|
313
|
+
loadSpending() {
|
|
314
|
+
const spendingPath = join(this.configDir, "spending.json");
|
|
315
|
+
if (existsSync(spendingPath)) {
|
|
316
|
+
try {
|
|
317
|
+
const data = JSON.parse(readFileSync(spendingPath, "utf-8"));
|
|
318
|
+
const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
|
|
319
|
+
if (data.date && data.date === today) {
|
|
320
|
+
this.todaySpending = data.amount || 0;
|
|
321
|
+
this.lastSpendingReset = data.date;
|
|
322
|
+
} else {
|
|
323
|
+
this.todaySpending = 0;
|
|
324
|
+
this.lastSpendingReset = today;
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
this.todaySpending = 0;
|
|
328
|
+
this.lastSpendingReset = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Save spending data to disk
|
|
334
|
+
*/
|
|
335
|
+
saveSpending() {
|
|
336
|
+
mkdirSync(this.configDir, { recursive: true });
|
|
337
|
+
const spendingPath = join(this.configDir, "spending.json");
|
|
338
|
+
const data = {
|
|
339
|
+
date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
|
|
340
|
+
amount: this.todaySpending,
|
|
341
|
+
updatedAt: Date.now()
|
|
342
|
+
};
|
|
343
|
+
writeFileSync(spendingPath, JSON.stringify(data, null, 2));
|
|
344
|
+
}
|
|
307
345
|
loadWallet() {
|
|
308
346
|
const walletPath = join(this.configDir, "wallet.json");
|
|
309
347
|
if (existsSync(walletPath)) {
|
|
348
|
+
try {
|
|
349
|
+
const stats = statSync(walletPath);
|
|
350
|
+
const mode = stats.mode & 511;
|
|
351
|
+
if (mode !== 384) {
|
|
352
|
+
console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
|
|
353
|
+
console.warn(`[MoltsPay] Fixing permissions to 0600...`);
|
|
354
|
+
chmodSync(walletPath, 384);
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
}
|
|
310
358
|
const content = readFileSync(walletPath, "utf-8");
|
|
311
359
|
return JSON.parse(content);
|
|
312
360
|
}
|
|
@@ -324,7 +372,7 @@ var MoltsPayClient = class {
|
|
|
324
372
|
createdAt: Date.now()
|
|
325
373
|
};
|
|
326
374
|
const walletPath = join(configDir, "wallet.json");
|
|
327
|
-
writeFileSync(walletPath, JSON.stringify(walletData, null, 2));
|
|
375
|
+
writeFileSync(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
|
|
328
376
|
const config = {
|
|
329
377
|
chain: options.chain,
|
|
330
378
|
limits: {
|
|
@@ -636,10 +684,14 @@ var MoltsPayServer = class {
|
|
|
636
684
|
*/
|
|
637
685
|
sendPaymentRequired(config, res) {
|
|
638
686
|
const requirements = this.buildPaymentRequirements(config);
|
|
639
|
-
requirements.description = `${config.name} - $${config.price} ${config.currency}`;
|
|
640
687
|
const paymentRequired = {
|
|
641
688
|
x402Version: X402_VERSION2,
|
|
642
|
-
accepts: [requirements]
|
|
689
|
+
accepts: [requirements],
|
|
690
|
+
resource: {
|
|
691
|
+
url: `/execute?service=${config.id}`,
|
|
692
|
+
description: `${config.name} - $${config.price} ${config.currency}`,
|
|
693
|
+
mimeType: "application/json"
|
|
694
|
+
}
|
|
643
695
|
};
|
|
644
696
|
const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
|
|
645
697
|
res.writeHead(402, {
|
|
@@ -678,9 +730,8 @@ var MoltsPayServer = class {
|
|
|
678
730
|
return {
|
|
679
731
|
scheme: "exact",
|
|
680
732
|
network: this.networkId,
|
|
681
|
-
maxAmountRequired: amountInUnits,
|
|
682
|
-
amount: amountInUnits,
|
|
683
733
|
asset: usdcAddress,
|
|
734
|
+
amount: amountInUnits,
|
|
684
735
|
payTo: this.manifest.provider.wallet,
|
|
685
736
|
maxTimeoutSeconds: 300,
|
|
686
737
|
extra: USDC_DOMAIN
|
|
@@ -937,75 +988,141 @@ program.command("services <url>").description("List services from a provider").o
|
|
|
937
988
|
console.error("\u274C Error:", err.message);
|
|
938
989
|
}
|
|
939
990
|
});
|
|
940
|
-
program.command("start <
|
|
941
|
-
const manifestPath = resolve(manifest);
|
|
942
|
-
if (!existsSync3(manifestPath)) {
|
|
943
|
-
console.error(`\u274C Manifest not found: ${manifestPath}`);
|
|
944
|
-
process.exit(1);
|
|
945
|
-
}
|
|
991
|
+
program.command("start <paths...>").description("Start MoltsPay server from skill directories or manifest files").option("-p, --port <port>", "Port to listen on", "3000").option("--host <host>", "Host to bind", "0.0.0.0").option("--facilitator <url>", "x402 facilitator URL (default: https://x402.org/facilitator)").action(async (paths, options) => {
|
|
946
992
|
const port = parseInt(options.port, 10);
|
|
947
993
|
const host = options.host;
|
|
948
994
|
const facilitatorUrl = options.facilitator;
|
|
995
|
+
const allPaths = paths.flatMap((p) => p.split(",").map((s) => s.trim())).filter(Boolean);
|
|
949
996
|
console.log(`
|
|
950
997
|
\u{1F680} Starting MoltsPay Server (x402 protocol)
|
|
951
998
|
`);
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
999
|
+
const allServices = [];
|
|
1000
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
1001
|
+
let provider = null;
|
|
1002
|
+
for (const inputPath of allPaths) {
|
|
1003
|
+
const resolvedPath = resolve(inputPath);
|
|
1004
|
+
let manifestPath;
|
|
1005
|
+
let skillDir;
|
|
1006
|
+
let isSkillDir = false;
|
|
1007
|
+
if (existsSync3(join3(resolvedPath, "moltspay.services.json"))) {
|
|
1008
|
+
manifestPath = join3(resolvedPath, "moltspay.services.json");
|
|
1009
|
+
skillDir = resolvedPath;
|
|
1010
|
+
isSkillDir = true;
|
|
1011
|
+
} else if (existsSync3(resolvedPath) && resolvedPath.endsWith(".json")) {
|
|
1012
|
+
manifestPath = resolvedPath;
|
|
1013
|
+
skillDir = dirname(resolvedPath);
|
|
1014
|
+
} else if (existsSync3(resolvedPath)) {
|
|
1015
|
+
console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
|
|
1016
|
+
continue;
|
|
1017
|
+
} else {
|
|
1018
|
+
console.error(`\u274C Path not found: ${resolvedPath}`);
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
console.log(`\u{1F4E6} Loading: ${manifestPath}`);
|
|
1022
|
+
try {
|
|
1023
|
+
const manifestContent = JSON.parse(readFileSync3(manifestPath, "utf-8"));
|
|
1024
|
+
if (!provider) {
|
|
1025
|
+
provider = manifestContent.provider;
|
|
1026
|
+
}
|
|
1027
|
+
let skillModule = null;
|
|
1028
|
+
if (isSkillDir) {
|
|
1029
|
+
const indexPath = join3(skillDir, "index.js");
|
|
1030
|
+
if (existsSync3(indexPath)) {
|
|
1031
|
+
try {
|
|
1032
|
+
skillModule = await import(indexPath);
|
|
1033
|
+
console.log(` \u2705 Loaded module: ${indexPath}`);
|
|
1034
|
+
} catch (err) {
|
|
1035
|
+
console.error(` \u26A0\uFE0F Failed to load module: ${err.message}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
for (const service of manifestContent.services) {
|
|
1040
|
+
allServices.push(service);
|
|
1041
|
+
if (service.function && skillModule) {
|
|
1042
|
+
const fn = skillModule[service.function] || skillModule.default?.[service.function];
|
|
1043
|
+
if (fn && typeof fn === "function") {
|
|
1044
|
+
handlers.set(service.id, fn);
|
|
1045
|
+
console.log(` \u2705 ${service.id} \u2192 ${service.function}()`);
|
|
1046
|
+
} else {
|
|
1047
|
+
console.error(` \u274C Function '${service.function}' not found in index.js`);
|
|
1048
|
+
}
|
|
1049
|
+
} else if (service.command) {
|
|
1050
|
+
const workdir = skillDir;
|
|
1051
|
+
handlers.set(service.id, async (params) => {
|
|
1052
|
+
return new Promise((resolvePromise, reject) => {
|
|
1053
|
+
const proc = spawn("sh", ["-c", service.command], {
|
|
1054
|
+
cwd: workdir,
|
|
1055
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1056
|
+
});
|
|
1057
|
+
let stdout = "";
|
|
1058
|
+
let stderr = "";
|
|
1059
|
+
proc.stdout.on("data", (data) => {
|
|
1060
|
+
stdout += data.toString();
|
|
1061
|
+
});
|
|
1062
|
+
proc.stderr.on("data", (data) => {
|
|
1063
|
+
stderr += data.toString();
|
|
1064
|
+
process.stderr.write(data);
|
|
1065
|
+
});
|
|
1066
|
+
proc.stdin.write(JSON.stringify(params));
|
|
1067
|
+
proc.stdin.end();
|
|
1068
|
+
proc.on("close", (code) => {
|
|
1069
|
+
if (code !== 0) {
|
|
1070
|
+
reject(new Error(`Command failed (exit ${code}): ${stderr || "Unknown error"}`));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
resolvePromise(JSON.parse(stdout.trim()));
|
|
1075
|
+
} catch {
|
|
1076
|
+
resolvePromise({ output: stdout.trim() });
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
proc.on("error", (err) => {
|
|
1080
|
+
reject(new Error(`Failed to spawn command: ${err.message}`));
|
|
1081
|
+
});
|
|
994
1082
|
});
|
|
995
1083
|
});
|
|
996
|
-
|
|
1084
|
+
console.log(` \u2705 ${service.id} \u2192 command`);
|
|
1085
|
+
} else {
|
|
1086
|
+
console.warn(` \u26A0\uFE0F ${service.id}: no function or command defined`);
|
|
1087
|
+
}
|
|
997
1088
|
}
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
console.error(`\u274C Failed to load ${manifestPath}: ${err.message}`);
|
|
1091
|
+
continue;
|
|
998
1092
|
}
|
|
999
|
-
|
|
1093
|
+
}
|
|
1094
|
+
if (allServices.length === 0) {
|
|
1095
|
+
console.error("\n\u274C No services loaded. Exiting.");
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
if (!provider) {
|
|
1099
|
+
console.error("\n\u274C No provider config found. Exiting.");
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
const combinedManifest = {
|
|
1103
|
+
provider,
|
|
1104
|
+
services: allServices
|
|
1105
|
+
};
|
|
1106
|
+
const tempManifestPath = join3(DEFAULT_CONFIG_DIR, "combined-manifest.json");
|
|
1107
|
+
writeFileSync2(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
|
|
1108
|
+
console.log(`
|
|
1109
|
+
\u{1F4CB} Combined manifest: ${allServices.length} services`);
|
|
1110
|
+
console.log(` Provider: ${provider.name}`);
|
|
1111
|
+
console.log(` Wallet: ${provider.wallet}`);
|
|
1112
|
+
console.log(` Port: ${port}`);
|
|
1113
|
+
console.log("");
|
|
1114
|
+
try {
|
|
1115
|
+
const server = new MoltsPayServer(tempManifestPath, { port, host, facilitatorUrl });
|
|
1116
|
+
for (const [serviceId, handler] of handlers) {
|
|
1117
|
+
server.skill(serviceId, handler);
|
|
1118
|
+
}
|
|
1119
|
+
const pidData = { pid: process.pid, port, paths: allPaths };
|
|
1000
1120
|
writeFileSync2(PID_FILE, JSON.stringify(pidData, null, 2));
|
|
1001
|
-
console.log(` PID file: ${PID_FILE}`);
|
|
1002
|
-
console.log("");
|
|
1003
1121
|
server.listen(port);
|
|
1004
1122
|
const cleanup = () => {
|
|
1005
1123
|
try {
|
|
1006
|
-
if (existsSync3(PID_FILE))
|
|
1007
|
-
|
|
1008
|
-
}
|
|
1124
|
+
if (existsSync3(PID_FILE)) unlinkSync(PID_FILE);
|
|
1125
|
+
if (existsSync3(tempManifestPath)) unlinkSync(tempManifestPath);
|
|
1009
1126
|
} catch {
|
|
1010
1127
|
}
|
|
1011
1128
|
};
|