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