moltspay 0.8.11 → 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 +171 -57
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +172 -58
- 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 +50 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -3
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +2 -0
- package/dist/server/index.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -330,9 +330,67 @@ Client Agent Server Facilitator (Coinbase
|
|
|
330
330
|
- ❌ NO ETH/gas needed
|
|
331
331
|
- ❌ NO API credentials needed
|
|
332
332
|
|
|
333
|
+
## Server Setup (Selling Services)
|
|
334
|
+
|
|
335
|
+
To run a MoltsPay server that accepts x402 payments:
|
|
336
|
+
|
|
337
|
+
### 1. Configure CDP Credentials (Required for Mainnet)
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# Copy the example config
|
|
341
|
+
cp .env.example ~/.moltspay/.env
|
|
342
|
+
|
|
343
|
+
# Edit with your CDP credentials (get from https://portal.cdp.coinbase.com/)
|
|
344
|
+
nano ~/.moltspay/.env
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
```env
|
|
348
|
+
# ~/.moltspay/.env
|
|
349
|
+
USE_MAINNET=true
|
|
350
|
+
CDP_API_KEY_ID=your-key-id
|
|
351
|
+
CDP_API_KEY_SECRET=your-secret
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Important:** Server does NOT need a private key! The x402 facilitator handles all on-chain settlement.
|
|
355
|
+
|
|
356
|
+
### 2. Create Services Manifest
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"provider": {
|
|
361
|
+
"name": "My AI Service",
|
|
362
|
+
"wallet": "0xYOUR_WALLET_ADDRESS",
|
|
363
|
+
"chain": "base"
|
|
364
|
+
},
|
|
365
|
+
"services": [
|
|
366
|
+
{
|
|
367
|
+
"id": "my-service",
|
|
368
|
+
"name": "My Service",
|
|
369
|
+
"price": 0.99,
|
|
370
|
+
"currency": "USDC",
|
|
371
|
+
"command": "./handlers/my-service.sh",
|
|
372
|
+
"input": { "prompt": { "type": "string", "required": true } },
|
|
373
|
+
"output": { "result": { "type": "string" } }
|
|
374
|
+
}
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 3. Start Server
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
npx moltspay start ./moltspay.services.json --port 3000
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The server will:
|
|
386
|
+
- Return 402 with payment requirements for unpaid requests
|
|
387
|
+
- Verify signatures with CDP facilitator
|
|
388
|
+
- Execute service only after payment verification
|
|
389
|
+
- Settle payment on-chain (facilitator pays gas)
|
|
390
|
+
|
|
333
391
|
## CDP Wallet Support (Optional, Advanced)
|
|
334
392
|
|
|
335
|
-
CDP wallet is an **optional alternative** for
|
|
393
|
+
CDP wallet is an **optional alternative** for **client agents** who want Coinbase to host their wallet. Most users should use the simple local wallet above.
|
|
336
394
|
|
|
337
395
|
```bash
|
|
338
396
|
# Only if you have CDP credentials and want hosted wallet
|
|
@@ -445,8 +503,33 @@ const result = await permit.executePermitAndTransfer(
|
|
|
445
503
|
# Install globally
|
|
446
504
|
npm install -g moltspay
|
|
447
505
|
|
|
448
|
-
#
|
|
449
|
-
|
|
506
|
+
# === Client Commands ===
|
|
507
|
+
|
|
508
|
+
# Initialize wallet (first time setup)
|
|
509
|
+
npx moltspay init --chain base
|
|
510
|
+
|
|
511
|
+
# Check wallet status and balance
|
|
512
|
+
npx moltspay status
|
|
513
|
+
|
|
514
|
+
# Update spending limits
|
|
515
|
+
npx moltspay config # Interactive
|
|
516
|
+
npx moltspay config --max-per-tx 50 --max-per-day 500 # Direct
|
|
517
|
+
|
|
518
|
+
# Pay for a service
|
|
519
|
+
npx moltspay pay http://server:3000 service-id --prompt "your prompt"
|
|
520
|
+
|
|
521
|
+
# List services from a provider
|
|
522
|
+
npx moltspay services http://server:3000
|
|
523
|
+
|
|
524
|
+
# === Server Commands ===
|
|
525
|
+
|
|
526
|
+
# Start server from services manifest
|
|
527
|
+
npx moltspay start ./moltspay.services.json --port 3000
|
|
528
|
+
|
|
529
|
+
# Stop running server
|
|
530
|
+
npx moltspay stop
|
|
531
|
+
|
|
532
|
+
# === Legacy Commands ===
|
|
450
533
|
|
|
451
534
|
# Generate invoice
|
|
452
535
|
moltspay invoice --order order_123 --amount 2.0 --service video
|
package/dist/cli/index.js
CHANGED
|
@@ -116,6 +116,7 @@ var MoltsPayClient = class {
|
|
|
116
116
|
this.configDir = options.configDir || (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
|
|
117
117
|
this.config = this.loadConfig();
|
|
118
118
|
this.walletData = this.loadWallet();
|
|
119
|
+
this.loadSpending();
|
|
119
120
|
if (this.walletData) {
|
|
120
121
|
this.wallet = new import_ethers.Wallet(this.walletData.privateKey);
|
|
121
122
|
}
|
|
@@ -300,6 +301,7 @@ var MoltsPayClient = class {
|
|
|
300
301
|
if (today > this.lastSpendingReset) {
|
|
301
302
|
this.todaySpending = 0;
|
|
302
303
|
this.lastSpendingReset = today;
|
|
304
|
+
this.saveSpending();
|
|
303
305
|
}
|
|
304
306
|
if (this.todaySpending + amount > this.config.limits.maxPerDay) {
|
|
305
307
|
throw new Error(
|
|
@@ -308,10 +310,11 @@ var MoltsPayClient = class {
|
|
|
308
310
|
}
|
|
309
311
|
}
|
|
310
312
|
/**
|
|
311
|
-
* Record spending
|
|
313
|
+
* Record spending and persist to disk
|
|
312
314
|
*/
|
|
313
315
|
recordSpending(amount) {
|
|
314
316
|
this.todaySpending += amount;
|
|
317
|
+
this.saveSpending();
|
|
315
318
|
}
|
|
316
319
|
// --- Config & Wallet Management ---
|
|
317
320
|
loadConfig() {
|
|
@@ -327,9 +330,54 @@ var MoltsPayClient = class {
|
|
|
327
330
|
const configPath = (0, import_path.join)(this.configDir, "config.json");
|
|
328
331
|
(0, import_fs.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
|
|
329
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Load spending data from disk
|
|
335
|
+
*/
|
|
336
|
+
loadSpending() {
|
|
337
|
+
const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
|
|
338
|
+
if ((0, import_fs.existsSync)(spendingPath)) {
|
|
339
|
+
try {
|
|
340
|
+
const data = JSON.parse((0, import_fs.readFileSync)(spendingPath, "utf-8"));
|
|
341
|
+
const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
|
|
342
|
+
if (data.date && data.date === today) {
|
|
343
|
+
this.todaySpending = data.amount || 0;
|
|
344
|
+
this.lastSpendingReset = data.date;
|
|
345
|
+
} else {
|
|
346
|
+
this.todaySpending = 0;
|
|
347
|
+
this.lastSpendingReset = today;
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
this.todaySpending = 0;
|
|
351
|
+
this.lastSpendingReset = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Save spending data to disk
|
|
357
|
+
*/
|
|
358
|
+
saveSpending() {
|
|
359
|
+
(0, import_fs.mkdirSync)(this.configDir, { recursive: true });
|
|
360
|
+
const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
|
|
361
|
+
const data = {
|
|
362
|
+
date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
|
|
363
|
+
amount: this.todaySpending,
|
|
364
|
+
updatedAt: Date.now()
|
|
365
|
+
};
|
|
366
|
+
(0, import_fs.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
|
|
367
|
+
}
|
|
330
368
|
loadWallet() {
|
|
331
369
|
const walletPath = (0, import_path.join)(this.configDir, "wallet.json");
|
|
332
370
|
if ((0, import_fs.existsSync)(walletPath)) {
|
|
371
|
+
try {
|
|
372
|
+
const stats = (0, import_fs.statSync)(walletPath);
|
|
373
|
+
const mode = stats.mode & 511;
|
|
374
|
+
if (mode !== 384) {
|
|
375
|
+
console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
|
|
376
|
+
console.warn(`[MoltsPay] Fixing permissions to 0600...`);
|
|
377
|
+
(0, import_fs.chmodSync)(walletPath, 384);
|
|
378
|
+
}
|
|
379
|
+
} catch (err) {
|
|
380
|
+
}
|
|
333
381
|
const content = (0, import_fs.readFileSync)(walletPath, "utf-8");
|
|
334
382
|
return JSON.parse(content);
|
|
335
383
|
}
|
|
@@ -347,7 +395,7 @@ var MoltsPayClient = class {
|
|
|
347
395
|
createdAt: Date.now()
|
|
348
396
|
};
|
|
349
397
|
const walletPath = (0, import_path.join)(configDir, "wallet.json");
|
|
350
|
-
(0, import_fs.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2));
|
|
398
|
+
(0, import_fs.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
|
|
351
399
|
const config = {
|
|
352
400
|
chain: options.chain,
|
|
353
401
|
limits: {
|
|
@@ -963,75 +1011,141 @@ program.command("services <url>").description("List services from a provider").o
|
|
|
963
1011
|
console.error("\u274C Error:", err.message);
|
|
964
1012
|
}
|
|
965
1013
|
});
|
|
966
|
-
program.command("start <
|
|
967
|
-
const manifestPath = (0, import_path2.resolve)(manifest);
|
|
968
|
-
if (!(0, import_fs3.existsSync)(manifestPath)) {
|
|
969
|
-
console.error(`\u274C Manifest not found: ${manifestPath}`);
|
|
970
|
-
process.exit(1);
|
|
971
|
-
}
|
|
1014
|
+
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) => {
|
|
972
1015
|
const port = parseInt(options.port, 10);
|
|
973
1016
|
const host = options.host;
|
|
974
1017
|
const facilitatorUrl = options.facilitator;
|
|
1018
|
+
const allPaths = paths.flatMap((p) => p.split(",").map((s) => s.trim())).filter(Boolean);
|
|
975
1019
|
console.log(`
|
|
976
1020
|
\u{1F680} Starting MoltsPay Server (x402 protocol)
|
|
977
1021
|
`);
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1022
|
+
const allServices = [];
|
|
1023
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
1024
|
+
let provider = null;
|
|
1025
|
+
for (const inputPath of allPaths) {
|
|
1026
|
+
const resolvedPath = (0, import_path2.resolve)(inputPath);
|
|
1027
|
+
let manifestPath;
|
|
1028
|
+
let skillDir;
|
|
1029
|
+
let isSkillDir = false;
|
|
1030
|
+
if ((0, import_fs3.existsSync)((0, import_path2.join)(resolvedPath, "moltspay.services.json"))) {
|
|
1031
|
+
manifestPath = (0, import_path2.join)(resolvedPath, "moltspay.services.json");
|
|
1032
|
+
skillDir = resolvedPath;
|
|
1033
|
+
isSkillDir = true;
|
|
1034
|
+
} else if ((0, import_fs3.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
|
|
1035
|
+
manifestPath = resolvedPath;
|
|
1036
|
+
skillDir = (0, import_path2.dirname)(resolvedPath);
|
|
1037
|
+
} else if ((0, import_fs3.existsSync)(resolvedPath)) {
|
|
1038
|
+
console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
|
|
1039
|
+
continue;
|
|
1040
|
+
} else {
|
|
1041
|
+
console.error(`\u274C Path not found: ${resolvedPath}`);
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
console.log(`\u{1F4E6} Loading: ${manifestPath}`);
|
|
1045
|
+
try {
|
|
1046
|
+
const manifestContent = JSON.parse((0, import_fs3.readFileSync)(manifestPath, "utf-8"));
|
|
1047
|
+
if (!provider) {
|
|
1048
|
+
provider = manifestContent.provider;
|
|
1049
|
+
}
|
|
1050
|
+
let skillModule = null;
|
|
1051
|
+
if (isSkillDir) {
|
|
1052
|
+
const indexPath = (0, import_path2.join)(skillDir, "index.js");
|
|
1053
|
+
if ((0, import_fs3.existsSync)(indexPath)) {
|
|
1054
|
+
try {
|
|
1055
|
+
skillModule = await import(indexPath);
|
|
1056
|
+
console.log(` \u2705 Loaded module: ${indexPath}`);
|
|
1057
|
+
} catch (err) {
|
|
1058
|
+
console.error(` \u26A0\uFE0F Failed to load module: ${err.message}`);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
for (const service of manifestContent.services) {
|
|
1063
|
+
allServices.push(service);
|
|
1064
|
+
if (service.function && skillModule) {
|
|
1065
|
+
const fn = skillModule[service.function] || skillModule.default?.[service.function];
|
|
1066
|
+
if (fn && typeof fn === "function") {
|
|
1067
|
+
handlers.set(service.id, fn);
|
|
1068
|
+
console.log(` \u2705 ${service.id} \u2192 ${service.function}()`);
|
|
1069
|
+
} else {
|
|
1070
|
+
console.error(` \u274C Function '${service.function}' not found in index.js`);
|
|
1071
|
+
}
|
|
1072
|
+
} else if (service.command) {
|
|
1073
|
+
const workdir = skillDir;
|
|
1074
|
+
handlers.set(service.id, async (params) => {
|
|
1075
|
+
return new Promise((resolvePromise, reject) => {
|
|
1076
|
+
const proc = (0, import_child_process.spawn)("sh", ["-c", service.command], {
|
|
1077
|
+
cwd: workdir,
|
|
1078
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1079
|
+
});
|
|
1080
|
+
let stdout = "";
|
|
1081
|
+
let stderr = "";
|
|
1082
|
+
proc.stdout.on("data", (data) => {
|
|
1083
|
+
stdout += data.toString();
|
|
1084
|
+
});
|
|
1085
|
+
proc.stderr.on("data", (data) => {
|
|
1086
|
+
stderr += data.toString();
|
|
1087
|
+
process.stderr.write(data);
|
|
1088
|
+
});
|
|
1089
|
+
proc.stdin.write(JSON.stringify(params));
|
|
1090
|
+
proc.stdin.end();
|
|
1091
|
+
proc.on("close", (code) => {
|
|
1092
|
+
if (code !== 0) {
|
|
1093
|
+
reject(new Error(`Command failed (exit ${code}): ${stderr || "Unknown error"}`));
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
resolvePromise(JSON.parse(stdout.trim()));
|
|
1098
|
+
} catch {
|
|
1099
|
+
resolvePromise({ output: stdout.trim() });
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
proc.on("error", (err) => {
|
|
1103
|
+
reject(new Error(`Failed to spawn command: ${err.message}`));
|
|
1104
|
+
});
|
|
1020
1105
|
});
|
|
1021
1106
|
});
|
|
1022
|
-
|
|
1107
|
+
console.log(` \u2705 ${service.id} \u2192 command`);
|
|
1108
|
+
} else {
|
|
1109
|
+
console.warn(` \u26A0\uFE0F ${service.id}: no function or command defined`);
|
|
1110
|
+
}
|
|
1023
1111
|
}
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
console.error(`\u274C Failed to load ${manifestPath}: ${err.message}`);
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (allServices.length === 0) {
|
|
1118
|
+
console.error("\n\u274C No services loaded. Exiting.");
|
|
1119
|
+
process.exit(1);
|
|
1120
|
+
}
|
|
1121
|
+
if (!provider) {
|
|
1122
|
+
console.error("\n\u274C No provider config found. Exiting.");
|
|
1123
|
+
process.exit(1);
|
|
1124
|
+
}
|
|
1125
|
+
const combinedManifest = {
|
|
1126
|
+
provider,
|
|
1127
|
+
services: allServices
|
|
1128
|
+
};
|
|
1129
|
+
const tempManifestPath = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "combined-manifest.json");
|
|
1130
|
+
(0, import_fs3.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
|
|
1131
|
+
console.log(`
|
|
1132
|
+
\u{1F4CB} Combined manifest: ${allServices.length} services`);
|
|
1133
|
+
console.log(` Provider: ${provider.name}`);
|
|
1134
|
+
console.log(` Wallet: ${provider.wallet}`);
|
|
1135
|
+
console.log(` Port: ${port}`);
|
|
1136
|
+
console.log("");
|
|
1137
|
+
try {
|
|
1138
|
+
const server = new MoltsPayServer(tempManifestPath, { port, host, facilitatorUrl });
|
|
1139
|
+
for (const [serviceId, handler] of handlers) {
|
|
1140
|
+
server.skill(serviceId, handler);
|
|
1024
1141
|
}
|
|
1025
|
-
const pidData = { pid: process.pid, port,
|
|
1142
|
+
const pidData = { pid: process.pid, port, paths: allPaths };
|
|
1026
1143
|
(0, import_fs3.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
|
|
1027
|
-
console.log(` PID file: ${PID_FILE}`);
|
|
1028
|
-
console.log("");
|
|
1029
1144
|
server.listen(port);
|
|
1030
1145
|
const cleanup = () => {
|
|
1031
1146
|
try {
|
|
1032
|
-
if ((0, import_fs3.existsSync)(PID_FILE))
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1147
|
+
if ((0, import_fs3.existsSync)(PID_FILE)) (0, import_fs3.unlinkSync)(PID_FILE);
|
|
1148
|
+
if ((0, import_fs3.existsSync)(tempManifestPath)) (0, import_fs3.unlinkSync)(tempManifestPath);
|
|
1035
1149
|
} catch {
|
|
1036
1150
|
}
|
|
1037
1151
|
};
|