moltspay 0.5.4 → 0.7.1

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.
Files changed (68) hide show
  1. package/README.md +0 -124
  2. package/dist/cdp/index.d.mts +111 -0
  3. package/dist/cdp/index.d.ts +111 -0
  4. package/dist/cdp/index.js +30655 -0
  5. package/dist/cdp/index.js.map +1 -0
  6. package/dist/cdp/index.mjs +30631 -0
  7. package/dist/cdp/index.mjs.map +1 -0
  8. package/dist/chains/index.d.mts +1 -1
  9. package/dist/chains/index.d.ts +1 -1
  10. package/dist/cli/index.js +990 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/cli/index.mjs +967 -0
  13. package/dist/cli/index.mjs.map +1 -0
  14. package/dist/client/index.d.mts +134 -0
  15. package/dist/client/index.d.ts +134 -0
  16. package/dist/client/index.js +331 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/client/index.mjs +296 -0
  19. package/dist/client/index.mjs.map +1 -0
  20. package/dist/createWallet-D53qu7ie.d.mts +77 -0
  21. package/dist/createWallet-D53qu7ie.d.ts +77 -0
  22. package/dist/index-Dg8n6wdW.d.mts +32 -0
  23. package/dist/index-Dg8n6wdW.d.ts +32 -0
  24. package/dist/index.d.mts +6 -1483
  25. package/dist/index.d.ts +6 -1483
  26. package/dist/index.js +31039 -4254
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +31042 -4203
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/server/index.d.mts +120 -0
  31. package/dist/server/index.d.ts +120 -0
  32. package/dist/server/index.js +418 -0
  33. package/dist/server/index.js.map +1 -0
  34. package/dist/server/index.mjs +393 -0
  35. package/dist/server/index.mjs.map +1 -0
  36. package/dist/wallet/index.d.mts +3 -451
  37. package/dist/wallet/index.d.ts +3 -451
  38. package/dist/wallet/index.js +5 -1021
  39. package/dist/wallet/index.js.map +1 -1
  40. package/dist/wallet/index.mjs +16 -1015
  41. package/dist/wallet/index.mjs.map +1 -1
  42. package/package.json +19 -19
  43. package/dist/cli.js +0 -1984
  44. package/dist/cli.js.map +0 -1
  45. package/dist/cli.mjs +0 -1969
  46. package/dist/cli.mjs.map +0 -1
  47. package/dist/guide/index.d.mts +0 -39
  48. package/dist/guide/index.d.ts +0 -39
  49. package/dist/guide/index.js +0 -181
  50. package/dist/guide/index.js.map +0 -1
  51. package/dist/guide/index.mjs +0 -152
  52. package/dist/guide/index.mjs.map +0 -1
  53. package/dist/index-CyFg9s2m.d.mts +0 -161
  54. package/dist/index-CyFg9s2m.d.ts +0 -161
  55. package/dist/orders/index.d.mts +0 -97
  56. package/dist/orders/index.d.ts +0 -97
  57. package/dist/orders/index.js +0 -162
  58. package/dist/orders/index.js.map +0 -1
  59. package/dist/orders/index.mjs +0 -136
  60. package/dist/orders/index.mjs.map +0 -1
  61. package/dist/permit/index.d.mts +0 -49
  62. package/dist/permit/index.d.ts +0 -49
  63. package/dist/permit/index.js +0 -273
  64. package/dist/permit/index.js.map +0 -1
  65. package/dist/permit/index.mjs +0 -246
  66. package/dist/permit/index.mjs.map +0 -1
  67. /package/dist/{cli.d.mts → cli/index.d.mts} +0 -0
  68. /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
@@ -0,0 +1,990 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var import_commander = require("commander");
28
+ var import_os2 = require("os");
29
+ var import_path2 = require("path");
30
+ var import_fs3 = require("fs");
31
+ var import_child_process = require("child_process");
32
+
33
+ // src/client/index.ts
34
+ var import_fs = require("fs");
35
+ var import_os = require("os");
36
+ var import_path = require("path");
37
+ var import_ethers = require("ethers");
38
+
39
+ // src/chains/index.ts
40
+ var CHAINS = {
41
+ // ============ Mainnet ============
42
+ base: {
43
+ name: "Base",
44
+ chainId: 8453,
45
+ rpc: "https://mainnet.base.org",
46
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
47
+ explorer: "https://basescan.org/address/",
48
+ explorerTx: "https://basescan.org/tx/",
49
+ avgBlockTime: 2
50
+ },
51
+ polygon: {
52
+ name: "Polygon",
53
+ chainId: 137,
54
+ rpc: "https://polygon-rpc.com",
55
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
56
+ explorer: "https://polygonscan.com/address/",
57
+ explorerTx: "https://polygonscan.com/tx/",
58
+ avgBlockTime: 2
59
+ },
60
+ ethereum: {
61
+ name: "Ethereum",
62
+ chainId: 1,
63
+ rpc: "https://eth.llamarpc.com",
64
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
65
+ explorer: "https://etherscan.io/address/",
66
+ explorerTx: "https://etherscan.io/tx/",
67
+ avgBlockTime: 12
68
+ },
69
+ // ============ Testnet ============
70
+ base_sepolia: {
71
+ name: "Base Sepolia",
72
+ chainId: 84532,
73
+ rpc: "https://sepolia.base.org",
74
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
75
+ explorer: "https://sepolia.basescan.org/address/",
76
+ explorerTx: "https://sepolia.basescan.org/tx/",
77
+ avgBlockTime: 2
78
+ },
79
+ sepolia: {
80
+ name: "Sepolia",
81
+ chainId: 11155111,
82
+ rpc: "https://rpc.sepolia.org",
83
+ usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
84
+ explorer: "https://sepolia.etherscan.io/address/",
85
+ explorerTx: "https://sepolia.etherscan.io/tx/",
86
+ avgBlockTime: 12
87
+ }
88
+ };
89
+ function getChain(name) {
90
+ const config = CHAINS[name];
91
+ if (!config) {
92
+ throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(", ")}`);
93
+ }
94
+ return config;
95
+ }
96
+ function getChainById(chainId) {
97
+ return Object.values(CHAINS).find((c) => c.chainId === chainId);
98
+ }
99
+
100
+ // src/client/index.ts
101
+ var DEFAULT_CONFIG = {
102
+ chain: "base",
103
+ limits: {
104
+ maxPerTx: 100,
105
+ maxPerDay: 1e3
106
+ }
107
+ };
108
+ var MoltsPayClient = class {
109
+ configDir;
110
+ config;
111
+ walletData = null;
112
+ wallet = null;
113
+ todaySpending = 0;
114
+ lastSpendingReset = 0;
115
+ constructor(options = {}) {
116
+ this.configDir = options.configDir || (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
117
+ this.config = this.loadConfig();
118
+ this.walletData = this.loadWallet();
119
+ if (this.walletData) {
120
+ this.wallet = new import_ethers.Wallet(this.walletData.privateKey);
121
+ }
122
+ }
123
+ /**
124
+ * Check if client is initialized (has wallet)
125
+ */
126
+ get isInitialized() {
127
+ return this.wallet !== null;
128
+ }
129
+ /**
130
+ * Get wallet address
131
+ */
132
+ get address() {
133
+ return this.wallet?.address || null;
134
+ }
135
+ /**
136
+ * Get current config
137
+ */
138
+ getConfig() {
139
+ return { ...this.config };
140
+ }
141
+ /**
142
+ * Update config
143
+ */
144
+ updateConfig(updates) {
145
+ if (updates.maxPerTx !== void 0) {
146
+ this.config.limits.maxPerTx = updates.maxPerTx;
147
+ }
148
+ if (updates.maxPerDay !== void 0) {
149
+ this.config.limits.maxPerDay = updates.maxPerDay;
150
+ }
151
+ this.saveConfig();
152
+ }
153
+ /**
154
+ * Get services from a provider
155
+ */
156
+ async getServices(serverUrl) {
157
+ const res = await fetch(`${serverUrl}/services`);
158
+ if (!res.ok) {
159
+ throw new Error(`Failed to get services: ${res.statusText}`);
160
+ }
161
+ return res.json();
162
+ }
163
+ /**
164
+ * Pay for a service and get the result
165
+ */
166
+ async pay(serverUrl, service, params) {
167
+ if (!this.wallet) {
168
+ throw new Error("Client not initialized. Run: npx moltspay init");
169
+ }
170
+ const payRes = await fetch(`${serverUrl}/pay`, {
171
+ method: "POST",
172
+ headers: { "Content-Type": "application/json" },
173
+ body: JSON.stringify({ service, params })
174
+ });
175
+ if (payRes.status !== 402) {
176
+ const err = await payRes.json();
177
+ throw new Error(err.error || "Unexpected response");
178
+ }
179
+ const paymentReq = await payRes.json();
180
+ const { payment } = paymentReq;
181
+ this.checkLimits(payment.amount);
182
+ console.log(`[MoltsPay] Paying $${payment.amount} ${payment.currency} to ${payment.wallet}`);
183
+ const txHash = await this.executePayment(payment);
184
+ console.log(`[MoltsPay] Payment tx: ${txHash}`);
185
+ const verifyRes = await fetch(`${serverUrl}/verify`, {
186
+ method: "POST",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify({
189
+ chargeId: payment.chargeId,
190
+ txHash
191
+ })
192
+ });
193
+ if (!verifyRes.ok) {
194
+ const err = await verifyRes.json();
195
+ throw new Error(err.error || "Verification failed");
196
+ }
197
+ const result = await verifyRes.json();
198
+ this.recordSpending(payment.amount);
199
+ return result.result;
200
+ }
201
+ /**
202
+ * Check spending limits
203
+ */
204
+ checkLimits(amount) {
205
+ if (amount > this.config.limits.maxPerTx) {
206
+ throw new Error(
207
+ `Amount $${amount} exceeds max per transaction ($${this.config.limits.maxPerTx})`
208
+ );
209
+ }
210
+ const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
211
+ if (today > this.lastSpendingReset) {
212
+ this.todaySpending = 0;
213
+ this.lastSpendingReset = today;
214
+ }
215
+ if (this.todaySpending + amount > this.config.limits.maxPerDay) {
216
+ throw new Error(
217
+ `Would exceed daily limit ($${this.todaySpending} + $${amount} > $${this.config.limits.maxPerDay})`
218
+ );
219
+ }
220
+ }
221
+ /**
222
+ * Record spending
223
+ */
224
+ recordSpending(amount) {
225
+ this.todaySpending += amount;
226
+ }
227
+ /**
228
+ * Execute payment on-chain
229
+ */
230
+ async executePayment(payment) {
231
+ let chain;
232
+ try {
233
+ chain = getChain(payment.chain);
234
+ } catch {
235
+ throw new Error(`Unknown chain: ${payment.chain}`);
236
+ }
237
+ const { ethers: ethers2 } = await import("ethers");
238
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
239
+ const signer = new ethers2.Wallet(this.walletData.privateKey, provider);
240
+ const usdcAddress = chain.usdc;
241
+ const usdcAbi = [
242
+ "function transfer(address to, uint256 amount) returns (bool)",
243
+ "function balanceOf(address account) view returns (uint256)"
244
+ ];
245
+ const usdc = new ethers2.Contract(usdcAddress, usdcAbi, signer);
246
+ const amountInUnits = ethers2.parseUnits(payment.amount.toString(), 6);
247
+ const balance = await usdc.balanceOf(this.wallet.address);
248
+ if (balance < amountInUnits) {
249
+ throw new Error(
250
+ `Insufficient USDC balance: ${ethers2.formatUnits(balance, 6)} < ${payment.amount}`
251
+ );
252
+ }
253
+ const tx = await usdc.transfer(payment.wallet, amountInUnits);
254
+ const receipt = await tx.wait();
255
+ return receipt.hash;
256
+ }
257
+ // --- Config & Wallet Management ---
258
+ loadConfig() {
259
+ const configPath = (0, import_path.join)(this.configDir, "config.json");
260
+ if ((0, import_fs.existsSync)(configPath)) {
261
+ const content = (0, import_fs.readFileSync)(configPath, "utf-8");
262
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
263
+ }
264
+ return { ...DEFAULT_CONFIG };
265
+ }
266
+ saveConfig() {
267
+ (0, import_fs.mkdirSync)(this.configDir, { recursive: true });
268
+ const configPath = (0, import_path.join)(this.configDir, "config.json");
269
+ (0, import_fs.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
270
+ }
271
+ loadWallet() {
272
+ const walletPath = (0, import_path.join)(this.configDir, "wallet.json");
273
+ if ((0, import_fs.existsSync)(walletPath)) {
274
+ const content = (0, import_fs.readFileSync)(walletPath, "utf-8");
275
+ return JSON.parse(content);
276
+ }
277
+ return null;
278
+ }
279
+ /**
280
+ * Initialize a new wallet (called by CLI)
281
+ */
282
+ static init(configDir, options) {
283
+ (0, import_fs.mkdirSync)(configDir, { recursive: true });
284
+ const wallet = import_ethers.Wallet.createRandom();
285
+ const walletData = {
286
+ address: wallet.address,
287
+ privateKey: wallet.privateKey,
288
+ createdAt: Date.now()
289
+ };
290
+ const walletPath = (0, import_path.join)(configDir, "wallet.json");
291
+ (0, import_fs.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2));
292
+ const config = {
293
+ chain: options.chain,
294
+ limits: {
295
+ maxPerTx: options.maxPerTx,
296
+ maxPerDay: options.maxPerDay
297
+ }
298
+ };
299
+ const configPath = (0, import_path.join)(configDir, "config.json");
300
+ (0, import_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2));
301
+ return { address: wallet.address, configDir };
302
+ }
303
+ /**
304
+ * Get wallet balance
305
+ */
306
+ async getBalance() {
307
+ if (!this.wallet) {
308
+ throw new Error("Client not initialized");
309
+ }
310
+ let chain;
311
+ try {
312
+ chain = getChain(this.config.chain);
313
+ } catch {
314
+ throw new Error(`Unknown chain: ${this.config.chain}`);
315
+ }
316
+ const { ethers: ethers2 } = await import("ethers");
317
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
318
+ const nativeBalance = await provider.getBalance(this.wallet.address);
319
+ const usdcAbi = ["function balanceOf(address) view returns (uint256)"];
320
+ const usdc = new ethers2.Contract(chain.usdc, usdcAbi, provider);
321
+ const usdcBalance = await usdc.balanceOf(this.wallet.address);
322
+ return {
323
+ usdc: parseFloat(ethers2.formatUnits(usdcBalance, 6)),
324
+ native: parseFloat(ethers2.formatEther(nativeBalance))
325
+ };
326
+ }
327
+ };
328
+
329
+ // src/server/index.ts
330
+ var import_fs2 = require("fs");
331
+ var import_http = require("http");
332
+
333
+ // src/verify/index.ts
334
+ var import_ethers2 = require("ethers");
335
+ var TRANSFER_EVENT_TOPIC = import_ethers2.ethers.id("Transfer(address,address,uint256)");
336
+ async function verifyPayment(params) {
337
+ const { txHash, expectedAmount, expectedTo } = params;
338
+ let chain;
339
+ try {
340
+ if (typeof params.chain === "number") {
341
+ chain = getChainById(params.chain);
342
+ } else {
343
+ chain = getChain(params.chain || "base");
344
+ }
345
+ if (!chain) {
346
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
347
+ }
348
+ } catch (e) {
349
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
350
+ }
351
+ try {
352
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
353
+ const receipt = await provider.getTransactionReceipt(txHash);
354
+ if (!receipt) {
355
+ return { verified: false, error: "Transaction not found or not confirmed" };
356
+ }
357
+ if (receipt.status !== 1) {
358
+ return { verified: false, error: "Transaction failed" };
359
+ }
360
+ const usdcAddress = chain.usdc?.toLowerCase();
361
+ if (!usdcAddress) {
362
+ return { verified: false, error: `Chain ${chain.name} USDC address not configured` };
363
+ }
364
+ for (const log of receipt.logs) {
365
+ if (log.address.toLowerCase() !== usdcAddress) {
366
+ continue;
367
+ }
368
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
369
+ continue;
370
+ }
371
+ const from = "0x" + log.topics[1].slice(-40);
372
+ const to = "0x" + log.topics[2].slice(-40);
373
+ const amountRaw = BigInt(log.data);
374
+ const amount = Number(amountRaw) / 1e6;
375
+ if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
376
+ continue;
377
+ }
378
+ if (amount < expectedAmount) {
379
+ return {
380
+ verified: false,
381
+ error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
382
+ amount,
383
+ from,
384
+ to,
385
+ txHash,
386
+ blockNumber: receipt.blockNumber
387
+ };
388
+ }
389
+ return {
390
+ verified: true,
391
+ amount,
392
+ from,
393
+ to,
394
+ txHash,
395
+ blockNumber: receipt.blockNumber
396
+ };
397
+ }
398
+ return { verified: false, error: "No USDC transfer found" };
399
+ } catch (e) {
400
+ return { verified: false, error: e.message || String(e) };
401
+ }
402
+ }
403
+
404
+ // src/server/index.ts
405
+ function generateChargeId() {
406
+ return "ch_" + Math.random().toString(36).substring(2, 15);
407
+ }
408
+ var MoltsPayServer = class {
409
+ manifest;
410
+ skills = /* @__PURE__ */ new Map();
411
+ charges = /* @__PURE__ */ new Map();
412
+ options;
413
+ constructor(servicesPath, options = {}) {
414
+ const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
415
+ this.manifest = JSON.parse(content);
416
+ this.options = {
417
+ port: options.port || 3e3,
418
+ host: options.host || "0.0.0.0",
419
+ chargeExpirySecs: options.chargeExpirySecs || 300
420
+ // 5 minutes
421
+ };
422
+ console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
423
+ console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
424
+ console.log(`[MoltsPay] Wallet: ${this.manifest.provider.wallet}`);
425
+ }
426
+ /**
427
+ * Register a skill handler for a service
428
+ */
429
+ skill(serviceId, handler) {
430
+ const config = this.manifest.services.find((s) => s.id === serviceId);
431
+ if (!config) {
432
+ throw new Error(`Service '${serviceId}' not found in manifest`);
433
+ }
434
+ this.skills.set(serviceId, {
435
+ id: serviceId,
436
+ config,
437
+ handler
438
+ });
439
+ console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);
440
+ return this;
441
+ }
442
+ /**
443
+ * Start the server
444
+ */
445
+ listen(port) {
446
+ const p = port || this.options.port;
447
+ const server = (0, import_http.createServer)((req, res) => this.handleRequest(req, res));
448
+ server.listen(p, this.options.host, () => {
449
+ console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
450
+ console.log(`[MoltsPay] Endpoints:`);
451
+ console.log(` GET /services - List available services`);
452
+ console.log(` POST /pay - Create payment & execute service`);
453
+ console.log(` POST /verify - Verify payment & get result`);
454
+ console.log(` GET /status/:id - Check charge status`);
455
+ });
456
+ }
457
+ async handleRequest(req, res) {
458
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
459
+ const path = url.pathname;
460
+ const method = req.method || "GET";
461
+ res.setHeader("Access-Control-Allow-Origin", "*");
462
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
463
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
464
+ if (method === "OPTIONS") {
465
+ res.writeHead(204);
466
+ res.end();
467
+ return;
468
+ }
469
+ try {
470
+ if (method === "GET" && path === "/services") {
471
+ return this.handleGetServices(res);
472
+ }
473
+ if (method === "POST" && path === "/pay") {
474
+ const body = await this.readBody(req);
475
+ return this.handlePay(body, res);
476
+ }
477
+ if (method === "POST" && path === "/verify") {
478
+ const body = await this.readBody(req);
479
+ return this.handleVerify(body, res);
480
+ }
481
+ if (method === "GET" && path.startsWith("/status/")) {
482
+ const chargeId = path.replace("/status/", "");
483
+ return this.handleStatus(chargeId, res);
484
+ }
485
+ this.sendJson(res, 404, { error: "Not found" });
486
+ } catch (err) {
487
+ console.error("[MoltsPay] Error:", err);
488
+ this.sendJson(res, 500, { error: err.message || "Internal error" });
489
+ }
490
+ }
491
+ /**
492
+ * GET /services - List available services
493
+ */
494
+ handleGetServices(res) {
495
+ const services = this.manifest.services.map((s) => ({
496
+ id: s.id,
497
+ name: s.name,
498
+ description: s.description,
499
+ price: s.price,
500
+ currency: s.currency,
501
+ input: s.input,
502
+ output: s.output,
503
+ available: this.skills.has(s.id)
504
+ }));
505
+ this.sendJson(res, 200, {
506
+ provider: this.manifest.provider,
507
+ services
508
+ });
509
+ }
510
+ /**
511
+ * POST /pay - Create payment request
512
+ * Body: { service: string, params: object }
513
+ */
514
+ handlePay(body, res) {
515
+ const { service, params } = body;
516
+ if (!service) {
517
+ return this.sendJson(res, 400, { error: "Missing service" });
518
+ }
519
+ const skill = this.skills.get(service);
520
+ if (!skill) {
521
+ return this.sendJson(res, 404, { error: `Service '${service}' not found or not registered` });
522
+ }
523
+ for (const [key, field] of Object.entries(skill.config.input)) {
524
+ if (field.required && (!params || params[key] === void 0)) {
525
+ return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
526
+ }
527
+ }
528
+ const chargeId = generateChargeId();
529
+ const now = Date.now();
530
+ const charge = {
531
+ id: chargeId,
532
+ service,
533
+ params: params || {},
534
+ amount: skill.config.price,
535
+ currency: skill.config.currency,
536
+ status: "pending",
537
+ createdAt: now,
538
+ expiresAt: now + this.options.chargeExpirySecs * 1e3
539
+ };
540
+ this.charges.set(chargeId, charge);
541
+ const paymentRequest = {
542
+ chargeId,
543
+ service,
544
+ amount: charge.amount,
545
+ currency: charge.currency,
546
+ wallet: this.manifest.provider.wallet,
547
+ chain: this.manifest.provider.chain,
548
+ expiresAt: charge.expiresAt
549
+ };
550
+ this.sendJson(res, 402, {
551
+ message: "Payment required",
552
+ payment: paymentRequest
553
+ });
554
+ }
555
+ /**
556
+ * POST /verify - Verify payment and execute skill
557
+ * Body: { chargeId: string, txHash: string }
558
+ */
559
+ async handleVerify(body, res) {
560
+ const { chargeId, txHash } = body;
561
+ if (!chargeId || !txHash) {
562
+ return this.sendJson(res, 400, { error: "Missing chargeId or txHash" });
563
+ }
564
+ const charge = this.charges.get(chargeId);
565
+ if (!charge) {
566
+ return this.sendJson(res, 404, { error: "Charge not found" });
567
+ }
568
+ if (Date.now() > charge.expiresAt) {
569
+ charge.status = "expired";
570
+ return this.sendJson(res, 400, { error: "Charge expired" });
571
+ }
572
+ if (charge.status === "completed") {
573
+ return this.sendJson(res, 200, {
574
+ status: "completed",
575
+ result: charge.result
576
+ });
577
+ }
578
+ try {
579
+ const verification = await verifyPayment({
580
+ txHash,
581
+ expectedTo: this.manifest.provider.wallet,
582
+ expectedAmount: charge.amount,
583
+ chain: this.manifest.provider.chain
584
+ });
585
+ if (!verification.verified) {
586
+ charge.status = "failed";
587
+ return this.sendJson(res, 400, {
588
+ error: "Payment verification failed",
589
+ reason: verification.error
590
+ });
591
+ }
592
+ charge.status = "paid";
593
+ charge.txHash = txHash;
594
+ charge.paidAt = Date.now();
595
+ const skill = this.skills.get(charge.service);
596
+ console.log(`[MoltsPay] Executing skill: ${charge.service}`);
597
+ const result = await skill.handler(charge.params);
598
+ charge.status = "completed";
599
+ charge.result = result;
600
+ charge.completedAt = Date.now();
601
+ this.sendJson(res, 200, {
602
+ status: "completed",
603
+ chargeId,
604
+ txHash,
605
+ result
606
+ });
607
+ } catch (err) {
608
+ console.error("[MoltsPay] Skill execution error:", err);
609
+ charge.status = "failed";
610
+ this.sendJson(res, 500, {
611
+ error: "Skill execution failed",
612
+ message: err.message
613
+ });
614
+ }
615
+ }
616
+ /**
617
+ * GET /status/:chargeId - Check charge status
618
+ */
619
+ handleStatus(chargeId, res) {
620
+ const charge = this.charges.get(chargeId);
621
+ if (!charge) {
622
+ return this.sendJson(res, 404, { error: "Charge not found" });
623
+ }
624
+ this.sendJson(res, 200, {
625
+ chargeId: charge.id,
626
+ service: charge.service,
627
+ amount: charge.amount,
628
+ currency: charge.currency,
629
+ status: charge.status,
630
+ txHash: charge.txHash,
631
+ result: charge.status === "completed" ? charge.result : void 0,
632
+ createdAt: charge.createdAt,
633
+ expiresAt: charge.expiresAt
634
+ });
635
+ }
636
+ async readBody(req) {
637
+ return new Promise((resolve2, reject) => {
638
+ let body = "";
639
+ req.on("data", (chunk) => body += chunk);
640
+ req.on("end", () => {
641
+ try {
642
+ resolve2(body ? JSON.parse(body) : {});
643
+ } catch {
644
+ reject(new Error("Invalid JSON"));
645
+ }
646
+ });
647
+ req.on("error", reject);
648
+ });
649
+ }
650
+ sendJson(res, status, data) {
651
+ res.writeHead(status, { "Content-Type": "application/json" });
652
+ res.end(JSON.stringify(data, null, 2));
653
+ }
654
+ };
655
+
656
+ // src/cli/index.ts
657
+ var readline = __toESM(require("readline"));
658
+ var program = new import_commander.Command();
659
+ var DEFAULT_CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
660
+ var PID_FILE = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "server.pid");
661
+ if (!(0, import_fs3.existsSync)(DEFAULT_CONFIG_DIR)) {
662
+ (0, import_fs3.mkdirSync)(DEFAULT_CONFIG_DIR, { recursive: true });
663
+ }
664
+ function prompt(question) {
665
+ const rl = readline.createInterface({
666
+ input: process.stdin,
667
+ output: process.stdout
668
+ });
669
+ return new Promise((resolve2) => {
670
+ rl.question(question, (answer) => {
671
+ rl.close();
672
+ resolve2(answer.trim());
673
+ });
674
+ });
675
+ }
676
+ program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version("1.0.0");
677
+ 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) => {
678
+ console.log("\n\u{1F510} MoltsPay Client Setup\n");
679
+ if ((0, import_fs3.existsSync)((0, import_path2.join)(options.configDir, "wallet.json"))) {
680
+ console.log('\u26A0\uFE0F Already initialized. Use "moltspay config" to update settings.');
681
+ console.log(` Config dir: ${options.configDir}`);
682
+ return;
683
+ }
684
+ let chain = options.chain;
685
+ let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
686
+ let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
687
+ if (!maxPerTx) {
688
+ const answer = await prompt("Max per transaction (USD) [100]: ");
689
+ maxPerTx = answer ? parseFloat(answer) : 100;
690
+ }
691
+ if (!maxPerDay) {
692
+ const answer = await prompt("Max per day (USD) [1000]: ");
693
+ maxPerDay = answer ? parseFloat(answer) : 1e3;
694
+ }
695
+ console.log("\nCreating wallet...");
696
+ const result = MoltsPayClient.init(options.configDir, {
697
+ chain,
698
+ maxPerTx,
699
+ maxPerDay
700
+ });
701
+ console.log(`
702
+ \u2705 Wallet created: ${result.address}`);
703
+ console.log(`
704
+ \u{1F4C1} Config saved to: ${result.configDir}`);
705
+ console.log(`
706
+ \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path2.join)(result.configDir, "wallet.json")}`);
707
+ console.log(` This file contains your private key!
708
+ `);
709
+ console.log(`\u{1F4B0} Fund your wallet with USDC on ${chain} to start using services.
710
+ `);
711
+ });
712
+ program.command("config").description("Update MoltsPay settings").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) => {
713
+ const client = new MoltsPayClient({ configDir: options.configDir });
714
+ if (!client.isInitialized) {
715
+ console.log("\u274C Not initialized. Run: npx moltspay init");
716
+ return;
717
+ }
718
+ const currentConfig = client.getConfig();
719
+ if (!options.maxPerTx && !options.maxPerDay) {
720
+ console.log("\n\u{1F4CB} Current Settings:\n");
721
+ console.log(` Wallet: ${client.address}`);
722
+ console.log(` Chain: ${currentConfig.chain}`);
723
+ console.log(` Max per tx: $${currentConfig.limits.maxPerTx}`);
724
+ console.log(` Max per day: $${currentConfig.limits.maxPerDay}`);
725
+ console.log("");
726
+ const maxPerTxAnswer = await prompt(`New max per tx (USD) [${currentConfig.limits.maxPerTx}]: `);
727
+ const maxPerDayAnswer = await prompt(`New max per day (USD) [${currentConfig.limits.maxPerDay}]: `);
728
+ if (maxPerTxAnswer) {
729
+ client.updateConfig({ maxPerTx: parseFloat(maxPerTxAnswer) });
730
+ console.log(`\u2705 Updated max per tx to $${maxPerTxAnswer}`);
731
+ }
732
+ if (maxPerDayAnswer) {
733
+ client.updateConfig({ maxPerDay: parseFloat(maxPerDayAnswer) });
734
+ console.log(`\u2705 Updated max per day to $${maxPerDayAnswer}`);
735
+ }
736
+ } else {
737
+ if (options.maxPerTx) {
738
+ client.updateConfig({ maxPerTx: parseFloat(options.maxPerTx) });
739
+ console.log(`\u2705 Updated max per tx to $${options.maxPerTx}`);
740
+ }
741
+ if (options.maxPerDay) {
742
+ client.updateConfig({ maxPerDay: parseFloat(options.maxPerDay) });
743
+ console.log(`\u2705 Updated max per day to $${options.maxPerDay}`);
744
+ }
745
+ }
746
+ });
747
+ program.command("status").description("Show wallet status and balance").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).option("--json", "Output as JSON").action(async (options) => {
748
+ const client = new MoltsPayClient({ configDir: options.configDir });
749
+ if (!client.isInitialized) {
750
+ if (options.json) {
751
+ console.log(JSON.stringify({ error: "Not initialized" }));
752
+ } else {
753
+ console.log("\u274C Not initialized. Run: npx moltspay init");
754
+ }
755
+ return;
756
+ }
757
+ const config = client.getConfig();
758
+ let balance = { usdc: 0, native: 0 };
759
+ try {
760
+ balance = await client.getBalance();
761
+ } catch (err) {
762
+ console.error("Warning: Could not fetch balance:", err.message);
763
+ }
764
+ if (options.json) {
765
+ console.log(JSON.stringify({
766
+ address: client.address,
767
+ chain: config.chain,
768
+ balance,
769
+ limits: config.limits
770
+ }, null, 2));
771
+ } else {
772
+ console.log("\n\u{1F4CA} MoltsPay Status\n");
773
+ console.log(` Wallet: ${client.address}`);
774
+ console.log(` Chain: ${config.chain}`);
775
+ console.log(` Balance: ${balance.usdc.toFixed(2)} USDC`);
776
+ console.log(` Native: ${balance.native.toFixed(6)} ETH`);
777
+ console.log("");
778
+ console.log(" Limits:");
779
+ console.log(` Max per tx: $${config.limits.maxPerTx}`);
780
+ console.log(` Max per day: $${config.limits.maxPerDay}`);
781
+ console.log("");
782
+ }
783
+ });
784
+ program.command("services <url>").description("List services from a provider").option("--json", "Output as JSON").action(async (url, options) => {
785
+ try {
786
+ const client = new MoltsPayClient();
787
+ const services = await client.getServices(url);
788
+ if (options.json) {
789
+ console.log(JSON.stringify(services, null, 2));
790
+ } else {
791
+ console.log(`
792
+ \u{1F3EA} ${services.provider.name}
793
+ `);
794
+ console.log(` ${services.provider.description || ""}`);
795
+ console.log(` Wallet: ${services.provider.wallet}`);
796
+ console.log(` Chain: ${services.provider.chain}`);
797
+ console.log("\n\u{1F4E6} Services:\n");
798
+ for (const svc of services.services) {
799
+ const status = svc.available ? "\u2705" : "\u274C";
800
+ console.log(` ${status} ${svc.id}`);
801
+ console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
802
+ if (svc.description) {
803
+ console.log(` ${svc.description}`);
804
+ }
805
+ console.log("");
806
+ }
807
+ }
808
+ } catch (err) {
809
+ console.error("\u274C Error:", err.message);
810
+ }
811
+ });
812
+ 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").action(async (manifest, options) => {
813
+ const manifestPath = (0, import_path2.resolve)(manifest);
814
+ if (!(0, import_fs3.existsSync)(manifestPath)) {
815
+ console.error(`\u274C Manifest not found: ${manifestPath}`);
816
+ process.exit(1);
817
+ }
818
+ const port = parseInt(options.port, 10);
819
+ const host = options.host;
820
+ console.log(`
821
+ \u{1F680} Starting MoltsPay Server
822
+ `);
823
+ console.log(` Manifest: ${manifestPath}`);
824
+ console.log(` Port: ${port}`);
825
+ console.log("");
826
+ try {
827
+ const server = new MoltsPayServer(manifestPath, { port, host });
828
+ const manifestContent = await import("fs").then(
829
+ (fs) => JSON.parse(fs.readFileSync(manifestPath, "utf-8"))
830
+ );
831
+ for (const service of manifestContent.services) {
832
+ if (service.command) {
833
+ const workdir = (0, import_path2.dirname)(manifestPath);
834
+ server.skill(service.id, async (params) => {
835
+ return new Promise((resolve2, reject) => {
836
+ const proc = (0, import_child_process.spawn)("sh", ["-c", service.command], {
837
+ cwd: workdir,
838
+ stdio: ["pipe", "pipe", "pipe"]
839
+ });
840
+ let stdout = "";
841
+ let stderr = "";
842
+ proc.stdout.on("data", (data) => {
843
+ stdout += data.toString();
844
+ });
845
+ proc.stderr.on("data", (data) => {
846
+ stderr += data.toString();
847
+ process.stderr.write(data);
848
+ });
849
+ proc.stdin.write(JSON.stringify(params));
850
+ proc.stdin.end();
851
+ proc.on("close", (code) => {
852
+ if (code !== 0) {
853
+ reject(new Error(`Command failed (exit ${code}): ${stderr || "Unknown error"}`));
854
+ return;
855
+ }
856
+ try {
857
+ const result = JSON.parse(stdout.trim());
858
+ resolve2(result);
859
+ } catch {
860
+ resolve2({ output: stdout.trim() });
861
+ }
862
+ });
863
+ proc.on("error", (err) => {
864
+ reject(new Error(`Failed to spawn command: ${err.message}`));
865
+ });
866
+ });
867
+ });
868
+ }
869
+ }
870
+ const pidData = { pid: process.pid, port, manifest: manifestPath };
871
+ (0, import_fs3.writeFileSync)(PID_FILE, JSON.stringify(pidData, null, 2));
872
+ console.log(` PID file: ${PID_FILE}`);
873
+ console.log("");
874
+ server.listen(port);
875
+ const cleanup = () => {
876
+ try {
877
+ if ((0, import_fs3.existsSync)(PID_FILE)) {
878
+ (0, import_fs3.unlinkSync)(PID_FILE);
879
+ }
880
+ } catch {
881
+ }
882
+ };
883
+ process.on("SIGINT", () => {
884
+ console.log("\n\n\u{1F44B} Shutting down...");
885
+ cleanup();
886
+ process.exit(0);
887
+ });
888
+ process.on("SIGTERM", () => {
889
+ console.log("\n\n\u{1F44B} Shutting down...");
890
+ cleanup();
891
+ process.exit(0);
892
+ });
893
+ process.on("exit", cleanup);
894
+ } catch (err) {
895
+ console.error(`\u274C Failed to start server: ${err.message}`);
896
+ process.exit(1);
897
+ }
898
+ });
899
+ program.command("stop").description("Stop the running MoltsPay server").action(async () => {
900
+ if (!(0, import_fs3.existsSync)(PID_FILE)) {
901
+ console.log("\u274C No running server found (no PID file)");
902
+ process.exit(1);
903
+ }
904
+ try {
905
+ const pidData = JSON.parse((0, import_fs3.readFileSync)(PID_FILE, "utf-8"));
906
+ const { pid, port, manifest } = pidData;
907
+ console.log(`
908
+ \u{1F6D1} Stopping MoltsPay Server
909
+ `);
910
+ console.log(` PID: ${pid}`);
911
+ console.log(` Port: ${port}`);
912
+ console.log(` Manifest: ${manifest}`);
913
+ console.log("");
914
+ try {
915
+ process.kill(pid, 0);
916
+ } catch {
917
+ console.log("\u26A0\uFE0F Process not running, cleaning up PID file...");
918
+ (0, import_fs3.unlinkSync)(PID_FILE);
919
+ process.exit(0);
920
+ }
921
+ process.kill(pid, "SIGTERM");
922
+ console.log("\u2705 Sent SIGTERM to server");
923
+ await new Promise((resolve2) => setTimeout(resolve2, 1e3));
924
+ try {
925
+ process.kill(pid, 0);
926
+ console.log("\u26A0\uFE0F Server still running, sending SIGKILL...");
927
+ process.kill(pid, "SIGKILL");
928
+ } catch {
929
+ }
930
+ if ((0, import_fs3.existsSync)(PID_FILE)) {
931
+ (0, import_fs3.unlinkSync)(PID_FILE);
932
+ }
933
+ console.log("\u2705 Server stopped\n");
934
+ } catch (err) {
935
+ console.error(`\u274C Failed to stop server: ${err.message}`);
936
+ process.exit(1);
937
+ }
938
+ });
939
+ program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <url>", "Image URL (for image-to-video)").option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
940
+ const client = new MoltsPayClient();
941
+ if (!client.isInitialized) {
942
+ console.error("\u274C Wallet not initialized. Run: npx moltspay init");
943
+ process.exit(1);
944
+ }
945
+ let params = {};
946
+ if (paramsJson) {
947
+ try {
948
+ params = JSON.parse(paramsJson);
949
+ } catch {
950
+ console.error("\u274C Invalid JSON params");
951
+ process.exit(1);
952
+ }
953
+ }
954
+ if (options.prompt) params.prompt = options.prompt;
955
+ if (options.image) params.image_url = options.image;
956
+ if (!params.prompt) {
957
+ console.error("\u274C Missing prompt. Use --prompt or pass JSON params");
958
+ process.exit(1);
959
+ }
960
+ if (!options.json) {
961
+ console.log(`
962
+ \u{1F4B3} MoltsPay - Paying for service
963
+ `);
964
+ console.log(` Server: ${server}`);
965
+ console.log(` Service: ${service}`);
966
+ console.log(` Prompt: ${params.prompt}`);
967
+ if (params.image_url) console.log(` Image: ${params.image_url}`);
968
+ console.log(` Wallet: ${client.address}`);
969
+ console.log("");
970
+ }
971
+ try {
972
+ const result = await client.pay(server, service, params);
973
+ if (options.json) {
974
+ console.log(JSON.stringify(result));
975
+ } else {
976
+ console.log("\u2705 Success!\n");
977
+ console.log(JSON.stringify(result, null, 2));
978
+ console.log("");
979
+ }
980
+ } catch (err) {
981
+ if (options.json) {
982
+ console.log(JSON.stringify({ error: err.message }));
983
+ } else {
984
+ console.error(`\u274C Error: ${err.message}`);
985
+ }
986
+ process.exit(1);
987
+ }
988
+ });
989
+ program.parse();
990
+ //# sourceMappingURL=index.js.map