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.
@@ -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 <manifest>").description("Start MoltsPay server from services manifest").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 (manifest, options) => {
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
- console.log(` Manifest: ${manifestPath}`);
953
- console.log(` Port: ${port}`);
954
- console.log("");
955
- try {
956
- const server = new MoltsPayServer(manifestPath, { port, host, facilitatorUrl });
957
- const manifestContent = await import("fs").then(
958
- (fs) => JSON.parse(fs.readFileSync(manifestPath, "utf-8"))
959
- );
960
- for (const service of manifestContent.services) {
961
- if (service.command) {
962
- const workdir = dirname(manifestPath);
963
- server.skill(service.id, async (params) => {
964
- return new Promise((resolve2, reject) => {
965
- const proc = spawn("sh", ["-c", service.command], {
966
- cwd: workdir,
967
- stdio: ["pipe", "pipe", "pipe"]
968
- });
969
- let stdout = "";
970
- let stderr = "";
971
- proc.stdout.on("data", (data) => {
972
- stdout += data.toString();
973
- });
974
- proc.stderr.on("data", (data) => {
975
- stderr += data.toString();
976
- process.stderr.write(data);
977
- });
978
- proc.stdin.write(JSON.stringify(params));
979
- proc.stdin.end();
980
- proc.on("close", (code) => {
981
- if (code !== 0) {
982
- reject(new Error(`Command failed (exit ${code}): ${stderr || "Unknown error"}`));
983
- return;
984
- }
985
- try {
986
- const result = JSON.parse(stdout.trim());
987
- resolve2(result);
988
- } catch {
989
- resolve2({ output: stdout.trim() });
990
- }
991
- });
992
- proc.on("error", (err) => {
993
- reject(new Error(`Failed to spawn command: ${err.message}`));
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
- const pidData = { pid: process.pid, port, manifest: manifestPath };
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
- unlinkSync(PID_FILE);
1008
- }
1124
+ if (existsSync3(PID_FILE)) unlinkSync(PID_FILE);
1125
+ if (existsSync3(tempManifestPath)) unlinkSync(tempManifestPath);
1009
1126
  } catch {
1010
1127
  }
1011
1128
  };