moltspay 0.1.0

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/dist/cli.js ADDED
@@ -0,0 +1,881 @@
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.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/agent/PaymentAgent.ts
30
+ var import_ethers = require("ethers");
31
+
32
+ // src/chains/index.ts
33
+ var CHAINS = {
34
+ // ============ 主网 ============
35
+ base: {
36
+ name: "Base",
37
+ chainId: 8453,
38
+ rpc: "https://mainnet.base.org",
39
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
40
+ explorer: "https://basescan.org/address/",
41
+ explorerTx: "https://basescan.org/tx/",
42
+ avgBlockTime: 2
43
+ },
44
+ polygon: {
45
+ name: "Polygon",
46
+ chainId: 137,
47
+ rpc: "https://polygon-rpc.com",
48
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
49
+ explorer: "https://polygonscan.com/address/",
50
+ explorerTx: "https://polygonscan.com/tx/",
51
+ avgBlockTime: 2
52
+ },
53
+ ethereum: {
54
+ name: "Ethereum",
55
+ chainId: 1,
56
+ rpc: "https://eth.llamarpc.com",
57
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
58
+ explorer: "https://etherscan.io/address/",
59
+ explorerTx: "https://etherscan.io/tx/",
60
+ avgBlockTime: 12
61
+ },
62
+ // ============ 测试网 ============
63
+ base_sepolia: {
64
+ name: "Base Sepolia",
65
+ chainId: 84532,
66
+ rpc: "https://sepolia.base.org",
67
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
68
+ explorer: "https://sepolia.basescan.org/address/",
69
+ explorerTx: "https://sepolia.basescan.org/tx/",
70
+ avgBlockTime: 2
71
+ },
72
+ sepolia: {
73
+ name: "Sepolia",
74
+ chainId: 11155111,
75
+ rpc: "https://rpc.sepolia.org",
76
+ usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
77
+ explorer: "https://sepolia.etherscan.io/address/",
78
+ explorerTx: "https://sepolia.etherscan.io/tx/",
79
+ avgBlockTime: 12
80
+ }
81
+ };
82
+ function getChain(name) {
83
+ const config = CHAINS[name];
84
+ if (!config) {
85
+ throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(", ")}`);
86
+ }
87
+ return config;
88
+ }
89
+ function listChains() {
90
+ return Object.keys(CHAINS);
91
+ }
92
+ var ERC20_ABI = [
93
+ "function balanceOf(address owner) view returns (uint256)",
94
+ "function transfer(address to, uint256 amount) returns (bool)",
95
+ "function approve(address spender, uint256 amount) returns (bool)",
96
+ "function allowance(address owner, address spender) view returns (uint256)",
97
+ "function decimals() view returns (uint8)",
98
+ "function symbol() view returns (string)",
99
+ "function name() view returns (string)",
100
+ "function nonces(address owner) view returns (uint256)",
101
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
102
+ "event Transfer(address indexed from, address indexed to, uint256 value)",
103
+ "event Approval(address indexed owner, address indexed spender, uint256 value)"
104
+ ];
105
+
106
+ // src/agent/PaymentAgent.ts
107
+ var PaymentAgent = class _PaymentAgent {
108
+ chain;
109
+ chainConfig;
110
+ walletAddress;
111
+ provider;
112
+ usdcContract;
113
+ static PROTOCOL_VERSION = "1.0";
114
+ constructor(config = {}) {
115
+ this.chain = config.chain || "base_sepolia";
116
+ this.chainConfig = getChain(this.chain);
117
+ this.walletAddress = config.walletAddress || process.env.PAYMENT_AGENT_WALLET || "";
118
+ if (!this.walletAddress) {
119
+ throw new Error("walletAddress is required. Set via config or PAYMENT_AGENT_WALLET env var.");
120
+ }
121
+ const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
122
+ this.provider = new import_ethers.ethers.JsonRpcProvider(rpcUrl);
123
+ this.usdcContract = new import_ethers.ethers.Contract(
124
+ this.chainConfig.usdc,
125
+ ERC20_ABI,
126
+ this.provider
127
+ );
128
+ }
129
+ /**
130
+ * 生成支付请求(Invoice)
131
+ */
132
+ createInvoice(params) {
133
+ const expiresMinutes = params.expiresMinutes || 30;
134
+ const expiresAt = new Date(Date.now() + expiresMinutes * 60 * 1e3).toISOString();
135
+ const invoice = {
136
+ type: "payment_request",
137
+ version: _PaymentAgent.PROTOCOL_VERSION,
138
+ order_id: params.orderId,
139
+ service: params.service,
140
+ description: params.description || `${params.service} service`,
141
+ amount: params.amount.toFixed(2),
142
+ token: "USDC",
143
+ chain: this.chain,
144
+ chain_id: this.chainConfig.chainId,
145
+ recipient: this.walletAddress,
146
+ memo: params.orderId,
147
+ expires_at: expiresAt,
148
+ deep_link: this.generateDeepLink(params.amount, params.orderId),
149
+ explorer_url: `${this.chainConfig.explorer}${this.walletAddress}`
150
+ };
151
+ if (params.metadata) {
152
+ invoice.metadata = params.metadata;
153
+ }
154
+ return invoice;
155
+ }
156
+ /**
157
+ * 生成钱包深度链接(支持 MetaMask 等)
158
+ */
159
+ generateDeepLink(amount, memo) {
160
+ const amountWei = Math.floor(amount * 1e6);
161
+ return `https://metamask.app.link/send/${this.chainConfig.usdc}@${this.chainConfig.chainId}/transfer?address=${this.walletAddress}&uint256=${amountWei}`;
162
+ }
163
+ /**
164
+ * 验证链上支付
165
+ */
166
+ async verifyPayment(txHash, options = {}) {
167
+ try {
168
+ if (!txHash.startsWith("0x")) {
169
+ txHash = "0x" + txHash;
170
+ }
171
+ const receipt = await this.provider.getTransactionReceipt(txHash);
172
+ if (!receipt) {
173
+ return { verified: false, error: "Transaction not found", pending: true };
174
+ }
175
+ if (receipt.status !== 1) {
176
+ return { verified: false, error: "Transaction failed" };
177
+ }
178
+ const transferTopic = import_ethers.ethers.id("Transfer(address,address,uint256)");
179
+ const usdcAddress = this.chainConfig.usdc.toLowerCase();
180
+ for (const log of receipt.logs) {
181
+ if (log.address.toLowerCase() === usdcAddress && log.topics.length >= 3 && log.topics[0] === transferTopic) {
182
+ const from = import_ethers.ethers.getAddress("0x" + log.topics[1].slice(-40));
183
+ const to = import_ethers.ethers.getAddress("0x" + log.topics[2].slice(-40));
184
+ const amountWei = BigInt(log.data);
185
+ const amount = Number(amountWei) / 1e6;
186
+ if (to.toLowerCase() !== this.walletAddress.toLowerCase()) {
187
+ continue;
188
+ }
189
+ const tolerance = options.tolerance ?? 0.01;
190
+ if (options.expectedAmount) {
191
+ const diff = Math.abs(amount - options.expectedAmount);
192
+ if (diff > options.expectedAmount * tolerance) {
193
+ return {
194
+ verified: false,
195
+ error: `Amount mismatch: expected ${options.expectedAmount}, got ${amount}`
196
+ };
197
+ }
198
+ }
199
+ const currentBlock = await this.provider.getBlockNumber();
200
+ return {
201
+ verified: true,
202
+ tx_hash: txHash,
203
+ amount: amount.toFixed(2),
204
+ token: "USDC",
205
+ from,
206
+ to,
207
+ block_number: receipt.blockNumber,
208
+ confirmations: currentBlock - receipt.blockNumber,
209
+ explorer_url: `${this.chainConfig.explorerTx}${txHash}`
210
+ };
211
+ }
212
+ }
213
+ return { verified: false, error: "No USDC transfer to recipient found in transaction" };
214
+ } catch (error) {
215
+ return { verified: false, error: error.message };
216
+ }
217
+ }
218
+ /**
219
+ * 扫描最近转账(按金额匹配)
220
+ */
221
+ async scanRecentTransfers(expectedAmount, timeoutMinutes = 30) {
222
+ try {
223
+ const currentBlock = await this.provider.getBlockNumber();
224
+ const blocksPerMinute = Math.ceil(60 / this.chainConfig.avgBlockTime);
225
+ const fromBlock = currentBlock - timeoutMinutes * blocksPerMinute;
226
+ const transferTopic = import_ethers.ethers.id("Transfer(address,address,uint256)");
227
+ const recipientTopic = import_ethers.ethers.zeroPadValue(this.walletAddress, 32);
228
+ const logs = await this.provider.getLogs({
229
+ address: this.chainConfig.usdc,
230
+ topics: [transferTopic, null, recipientTopic],
231
+ fromBlock,
232
+ toBlock: "latest"
233
+ });
234
+ for (const log of logs) {
235
+ const amountWei = BigInt(log.data);
236
+ const amount = Number(amountWei) / 1e6;
237
+ if (Math.abs(amount - expectedAmount) < 0.01) {
238
+ const from = import_ethers.ethers.getAddress("0x" + log.topics[1].slice(-40));
239
+ return {
240
+ verified: true,
241
+ tx_hash: log.transactionHash,
242
+ amount: amount.toFixed(2),
243
+ token: "USDC",
244
+ from,
245
+ to: this.walletAddress,
246
+ block_number: log.blockNumber,
247
+ explorer_url: `${this.chainConfig.explorerTx}${log.transactionHash}`
248
+ };
249
+ }
250
+ }
251
+ return { verified: false, error: "No matching payment found" };
252
+ } catch (error) {
253
+ return { verified: false, error: error.message };
254
+ }
255
+ }
256
+ /**
257
+ * 获取钱包余额
258
+ */
259
+ async getBalance(address) {
260
+ const addr = address || this.walletAddress;
261
+ const [ethBalance, usdcBalance] = await Promise.all([
262
+ this.provider.getBalance(addr),
263
+ this.usdcContract.balanceOf(addr)
264
+ ]);
265
+ return {
266
+ address: addr,
267
+ eth: import_ethers.ethers.formatEther(ethBalance),
268
+ usdc: (Number(usdcBalance) / 1e6).toFixed(2),
269
+ chain: this.chain
270
+ };
271
+ }
272
+ /**
273
+ * 格式化 Invoice 为人类可读消息
274
+ */
275
+ formatInvoiceMessage(invoice, includeJson = true) {
276
+ let msg = `\u{1F3AC} **Payment Request**
277
+
278
+ **Service:** ${invoice.service}
279
+ **Price:** ${invoice.amount} USDC (${this.chainConfig.name})
280
+
281
+ **\u{1F4B3} Payment Options:**
282
+
283
+ 1\uFE0F\u20E3 **Direct Transfer:**
284
+ Send exactly \`${invoice.amount} USDC\` to:
285
+ \`${invoice.recipient}\`
286
+ (Network: ${this.chainConfig.name})
287
+
288
+ 2\uFE0F\u20E3 **One-Click Pay (MetaMask):**
289
+ ${invoice.deep_link}
290
+
291
+ \u23F1\uFE0F Expires: ${invoice.expires_at}`;
292
+ if (includeJson) {
293
+ msg += `
294
+
295
+ 3\uFE0F\u20E3 **For AI Agents:**
296
+ \`\`\`json
297
+ ${JSON.stringify(invoice, null, 2)}
298
+ \`\`\``;
299
+ }
300
+ msg += `
301
+
302
+ After payment, reply with your tx hash:
303
+ \`paid: 0x...\``;
304
+ return msg;
305
+ }
306
+ };
307
+
308
+ // src/wallet/Wallet.ts
309
+ var import_ethers2 = require("ethers");
310
+ var Wallet = class {
311
+ chain;
312
+ chainConfig;
313
+ address;
314
+ wallet;
315
+ provider;
316
+ usdcContract;
317
+ constructor(config = {}) {
318
+ this.chain = config.chain || "base_sepolia";
319
+ this.chainConfig = getChain(this.chain);
320
+ const privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
321
+ if (!privateKey) {
322
+ throw new Error("privateKey is required. Set via config or PAYMENT_AGENT_PRIVATE_KEY env var.");
323
+ }
324
+ const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
325
+ this.provider = new import_ethers2.ethers.JsonRpcProvider(rpcUrl);
326
+ this.wallet = new import_ethers2.ethers.Wallet(privateKey, this.provider);
327
+ this.address = this.wallet.address;
328
+ this.usdcContract = new import_ethers2.ethers.Contract(
329
+ this.chainConfig.usdc,
330
+ ERC20_ABI,
331
+ this.wallet
332
+ );
333
+ }
334
+ /**
335
+ * 获取钱包余额
336
+ */
337
+ async getBalance() {
338
+ const [ethBalance, usdcBalance] = await Promise.all([
339
+ this.provider.getBalance(this.address),
340
+ this.usdcContract.balanceOf(this.address)
341
+ ]);
342
+ return {
343
+ address: this.address,
344
+ eth: import_ethers2.ethers.formatEther(ethBalance),
345
+ usdc: (Number(usdcBalance) / 1e6).toFixed(2),
346
+ chain: this.chain
347
+ };
348
+ }
349
+ /**
350
+ * 发送 USDC 转账
351
+ */
352
+ async transfer(to, amount) {
353
+ try {
354
+ to = import_ethers2.ethers.getAddress(to);
355
+ const amountWei = BigInt(Math.floor(amount * 1e6));
356
+ const balance = await this.usdcContract.balanceOf(this.address);
357
+ if (BigInt(balance) < amountWei) {
358
+ return {
359
+ success: false,
360
+ error: `Insufficient USDC balance: ${Number(balance) / 1e6} < ${amount}`
361
+ };
362
+ }
363
+ const tx = await this.usdcContract.transfer(to, amountWei);
364
+ const receipt = await tx.wait();
365
+ if (receipt.status === 1) {
366
+ return {
367
+ success: true,
368
+ tx_hash: tx.hash,
369
+ from: this.address,
370
+ to,
371
+ amount,
372
+ gas_used: Number(receipt.gasUsed),
373
+ block_number: receipt.blockNumber,
374
+ explorer_url: `${this.chainConfig.explorerTx}${tx.hash}`
375
+ };
376
+ } else {
377
+ return {
378
+ success: false,
379
+ tx_hash: tx.hash,
380
+ error: "Transaction reverted"
381
+ };
382
+ }
383
+ } catch (error) {
384
+ return {
385
+ success: false,
386
+ error: error.message
387
+ };
388
+ }
389
+ }
390
+ /**
391
+ * 获取 ETH 余额
392
+ */
393
+ async getEthBalance() {
394
+ const balance = await this.provider.getBalance(this.address);
395
+ return import_ethers2.ethers.formatEther(balance);
396
+ }
397
+ /**
398
+ * 获取 USDC 余额
399
+ */
400
+ async getUsdcBalance() {
401
+ const balance = await this.usdcContract.balanceOf(this.address);
402
+ return (Number(balance) / 1e6).toFixed(2);
403
+ }
404
+ };
405
+
406
+ // src/audit/AuditLog.ts
407
+ var fs = __toESM(require("fs"));
408
+ var path = __toESM(require("path"));
409
+ var crypto = __toESM(require("crypto"));
410
+ var AuditLog = class {
411
+ basePath;
412
+ lastHash = "0000000000000000";
413
+ constructor(basePath) {
414
+ this.basePath = basePath || path.join(process.cwd(), "data", "audit");
415
+ this.ensureDir();
416
+ this.loadLastHash();
417
+ }
418
+ /**
419
+ * 记录审计日志
420
+ */
421
+ async log(params) {
422
+ const now = /* @__PURE__ */ new Date();
423
+ const entry = {
424
+ timestamp: now.getTime() / 1e3,
425
+ datetime: now.toISOString(),
426
+ action: params.action,
427
+ request_id: params.request_id,
428
+ from: params.from,
429
+ to: params.to,
430
+ amount: params.amount,
431
+ tx_hash: params.tx_hash,
432
+ reason: params.reason,
433
+ requester: params.requester,
434
+ prev_hash: this.lastHash,
435
+ hash: "",
436
+ // 计算后填充
437
+ metadata: params.metadata
438
+ };
439
+ entry.hash = this.calculateHash(entry);
440
+ this.lastHash = entry.hash;
441
+ const filePath = this.getFilePath(now);
442
+ const line = JSON.stringify(entry) + "\n";
443
+ fs.appendFileSync(filePath, line, "utf-8");
444
+ return entry;
445
+ }
446
+ /**
447
+ * 读取指定日期的日志
448
+ */
449
+ read(date) {
450
+ const filePath = this.getFilePath(date || /* @__PURE__ */ new Date());
451
+ if (!fs.existsSync(filePath)) {
452
+ return [];
453
+ }
454
+ const content = fs.readFileSync(filePath, "utf-8");
455
+ const lines = content.trim().split("\n").filter(Boolean);
456
+ return lines.map((line) => JSON.parse(line));
457
+ }
458
+ /**
459
+ * 验证日志完整性
460
+ */
461
+ verify(date) {
462
+ const entries = this.read(date);
463
+ const errors = [];
464
+ for (let i = 0; i < entries.length; i++) {
465
+ const entry = entries[i];
466
+ const expectedHash = this.calculateHash(entry);
467
+ if (entry.hash !== expectedHash) {
468
+ errors.push(`Entry ${i}: hash mismatch (expected ${expectedHash}, got ${entry.hash})`);
469
+ }
470
+ if (i > 0 && entry.prev_hash !== entries[i - 1].hash) {
471
+ errors.push(`Entry ${i}: prev_hash mismatch`);
472
+ }
473
+ }
474
+ return { valid: errors.length === 0, errors };
475
+ }
476
+ /**
477
+ * 搜索日志
478
+ */
479
+ search(filter) {
480
+ const results = [];
481
+ const startDate = filter.startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
482
+ const endDate = filter.endDate || /* @__PURE__ */ new Date();
483
+ const current = new Date(startDate);
484
+ while (current <= endDate) {
485
+ const entries = this.read(current);
486
+ for (const entry of entries) {
487
+ let match = true;
488
+ if (filter.action && entry.action !== filter.action) match = false;
489
+ if (filter.request_id && entry.request_id !== filter.request_id) match = false;
490
+ if (filter.from && entry.from?.toLowerCase() !== filter.from.toLowerCase()) match = false;
491
+ if (filter.to && entry.to?.toLowerCase() !== filter.to.toLowerCase()) match = false;
492
+ if (match) {
493
+ results.push(entry);
494
+ }
495
+ }
496
+ current.setDate(current.getDate() + 1);
497
+ }
498
+ return results;
499
+ }
500
+ /**
501
+ * 获取日志文件路径
502
+ */
503
+ getFilePath(date) {
504
+ const dateStr = date.toISOString().slice(0, 10);
505
+ return path.join(this.basePath, `audit_${dateStr}.jsonl`);
506
+ }
507
+ /**
508
+ * 计算条目哈希
509
+ */
510
+ calculateHash(entry) {
511
+ const data = {
512
+ timestamp: entry.timestamp,
513
+ action: entry.action,
514
+ request_id: entry.request_id,
515
+ from: entry.from,
516
+ to: entry.to,
517
+ amount: entry.amount,
518
+ tx_hash: entry.tx_hash,
519
+ prev_hash: entry.prev_hash
520
+ };
521
+ const str = JSON.stringify(data);
522
+ return crypto.createHash("sha256").update(str).digest("hex").slice(0, 16);
523
+ }
524
+ /**
525
+ * 加载最后一条日志的哈希
526
+ */
527
+ loadLastHash() {
528
+ const today = /* @__PURE__ */ new Date();
529
+ for (let i = 0; i < 2; i++) {
530
+ const date = new Date(today);
531
+ date.setDate(date.getDate() - i);
532
+ const entries = this.read(date);
533
+ if (entries.length > 0) {
534
+ this.lastHash = entries[entries.length - 1].hash;
535
+ return;
536
+ }
537
+ }
538
+ }
539
+ /**
540
+ * 确保目录存在
541
+ */
542
+ ensureDir() {
543
+ if (!fs.existsSync(this.basePath)) {
544
+ fs.mkdirSync(this.basePath, { recursive: true });
545
+ }
546
+ }
547
+ };
548
+
549
+ // src/wallet/SecureWallet.ts
550
+ var DEFAULT_LIMITS = {
551
+ singleMax: 100,
552
+ // 单笔最大 $100
553
+ dailyMax: 1e3,
554
+ // 日最大 $1000
555
+ requireWhitelist: true
556
+ };
557
+ var SecureWallet = class {
558
+ wallet;
559
+ limits;
560
+ whitelist;
561
+ auditLog;
562
+ dailyTotal = 0;
563
+ dailyDate = "";
564
+ pendingTransfers = /* @__PURE__ */ new Map();
565
+ constructor(config = {}) {
566
+ this.wallet = new Wallet({
567
+ chain: config.chain,
568
+ privateKey: config.privateKey
569
+ });
570
+ this.limits = { ...DEFAULT_LIMITS, ...config.limits };
571
+ this.whitelist = new Set((config.whitelist || []).map((a) => a.toLowerCase()));
572
+ this.auditLog = new AuditLog(config.auditPath);
573
+ }
574
+ /**
575
+ * 获取钱包地址
576
+ */
577
+ get address() {
578
+ return this.wallet.address;
579
+ }
580
+ /**
581
+ * 获取余额
582
+ */
583
+ async getBalance() {
584
+ return this.wallet.getBalance();
585
+ }
586
+ /**
587
+ * 安全转账(带限额和白名单检查)
588
+ */
589
+ async transfer(params) {
590
+ const { to, amount, reason, requester } = params;
591
+ const toAddress = to.toLowerCase();
592
+ const requestId = `tr_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
593
+ await this.auditLog.log({
594
+ action: "transfer_request",
595
+ request_id: requestId,
596
+ from: this.wallet.address,
597
+ to,
598
+ amount,
599
+ reason,
600
+ requester
601
+ });
602
+ if (this.limits.requireWhitelist && !this.whitelist.has(toAddress)) {
603
+ await this.auditLog.log({
604
+ action: "transfer_failed",
605
+ request_id: requestId,
606
+ metadata: { error: "Address not in whitelist" }
607
+ });
608
+ return { success: false, error: `Address not in whitelist: ${to}` };
609
+ }
610
+ if (amount > this.limits.singleMax) {
611
+ const pending = {
612
+ id: requestId,
613
+ to,
614
+ amount,
615
+ reason,
616
+ requester,
617
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
618
+ status: "pending"
619
+ };
620
+ this.pendingTransfers.set(requestId, pending);
621
+ await this.auditLog.log({
622
+ action: "transfer_request",
623
+ request_id: requestId,
624
+ metadata: { pending: true, reason: "Exceeds single limit" }
625
+ });
626
+ return {
627
+ success: false,
628
+ error: `Amount ${amount} exceeds single limit ${this.limits.singleMax}. Pending approval: ${requestId}`
629
+ };
630
+ }
631
+ this.updateDailyTotal();
632
+ if (this.dailyTotal + amount > this.limits.dailyMax) {
633
+ const pending = {
634
+ id: requestId,
635
+ to,
636
+ amount,
637
+ reason,
638
+ requester,
639
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
640
+ status: "pending"
641
+ };
642
+ this.pendingTransfers.set(requestId, pending);
643
+ await this.auditLog.log({
644
+ action: "transfer_request",
645
+ request_id: requestId,
646
+ metadata: { pending: true, reason: "Exceeds daily limit" }
647
+ });
648
+ return {
649
+ success: false,
650
+ error: `Daily limit would be exceeded (${this.dailyTotal} + ${amount} > ${this.limits.dailyMax}). Pending approval: ${requestId}`
651
+ };
652
+ }
653
+ const result = await this.wallet.transfer(to, amount);
654
+ if (result.success) {
655
+ this.dailyTotal += amount;
656
+ await this.auditLog.log({
657
+ action: "transfer_executed",
658
+ request_id: requestId,
659
+ from: this.wallet.address,
660
+ to,
661
+ amount,
662
+ tx_hash: result.tx_hash,
663
+ reason,
664
+ requester
665
+ });
666
+ } else {
667
+ await this.auditLog.log({
668
+ action: "transfer_failed",
669
+ request_id: requestId,
670
+ metadata: { error: result.error }
671
+ });
672
+ }
673
+ return result;
674
+ }
675
+ /**
676
+ * 审批待处理转账
677
+ */
678
+ async approve(requestId, approver) {
679
+ const pending = this.pendingTransfers.get(requestId);
680
+ if (!pending) {
681
+ return { success: false, error: `Pending transfer not found: ${requestId}` };
682
+ }
683
+ if (pending.status !== "pending") {
684
+ return { success: false, error: `Transfer already ${pending.status}` };
685
+ }
686
+ await this.auditLog.log({
687
+ action: "transfer_approved",
688
+ request_id: requestId,
689
+ metadata: { approver }
690
+ });
691
+ pending.status = "approved";
692
+ const result = await this.wallet.transfer(pending.to, pending.amount);
693
+ if (result.success) {
694
+ pending.status = "executed";
695
+ this.dailyTotal += pending.amount;
696
+ await this.auditLog.log({
697
+ action: "transfer_executed",
698
+ request_id: requestId,
699
+ from: this.wallet.address,
700
+ to: pending.to,
701
+ amount: pending.amount,
702
+ tx_hash: result.tx_hash,
703
+ reason: pending.reason,
704
+ requester: pending.requester,
705
+ metadata: { approved_by: approver }
706
+ });
707
+ } else {
708
+ await this.auditLog.log({
709
+ action: "transfer_failed",
710
+ request_id: requestId,
711
+ metadata: { error: result.error }
712
+ });
713
+ }
714
+ return result;
715
+ }
716
+ /**
717
+ * 拒绝待处理转账
718
+ */
719
+ async reject(requestId, rejecter, reason) {
720
+ const pending = this.pendingTransfers.get(requestId);
721
+ if (!pending) {
722
+ throw new Error(`Pending transfer not found: ${requestId}`);
723
+ }
724
+ pending.status = "rejected";
725
+ await this.auditLog.log({
726
+ action: "transfer_rejected",
727
+ request_id: requestId,
728
+ metadata: { rejected_by: rejecter, reason }
729
+ });
730
+ }
731
+ /**
732
+ * 添加白名单地址
733
+ */
734
+ async addToWhitelist(address, addedBy) {
735
+ const addr = address.toLowerCase();
736
+ this.whitelist.add(addr);
737
+ await this.auditLog.log({
738
+ action: "whitelist_add",
739
+ request_id: `wl_${Date.now()}`,
740
+ to: address,
741
+ metadata: { added_by: addedBy }
742
+ });
743
+ }
744
+ /**
745
+ * 移除白名单地址
746
+ */
747
+ async removeFromWhitelist(address, removedBy) {
748
+ const addr = address.toLowerCase();
749
+ this.whitelist.delete(addr);
750
+ await this.auditLog.log({
751
+ action: "whitelist_remove",
752
+ request_id: `wl_${Date.now()}`,
753
+ to: address,
754
+ metadata: { removed_by: removedBy }
755
+ });
756
+ }
757
+ /**
758
+ * 检查地址是否在白名单
759
+ */
760
+ isWhitelisted(address) {
761
+ return this.whitelist.has(address.toLowerCase());
762
+ }
763
+ /**
764
+ * 获取待处理转账列表
765
+ */
766
+ getPendingTransfers() {
767
+ return Array.from(this.pendingTransfers.values()).filter((p) => p.status === "pending");
768
+ }
769
+ /**
770
+ * 获取当前限额配置
771
+ */
772
+ getLimits() {
773
+ return { ...this.limits };
774
+ }
775
+ /**
776
+ * 获取今日已用额度
777
+ */
778
+ getDailyUsed() {
779
+ this.updateDailyTotal();
780
+ return this.dailyTotal;
781
+ }
782
+ /**
783
+ * 更新日限额计数器
784
+ */
785
+ updateDailyTotal() {
786
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
787
+ if (this.dailyDate !== today) {
788
+ this.dailyDate = today;
789
+ this.dailyTotal = 0;
790
+ }
791
+ }
792
+ };
793
+
794
+ // src/permit/Permit.ts
795
+ var import_ethers3 = require("ethers");
796
+
797
+ // src/cli.ts
798
+ var program = new import_commander.Command();
799
+ program.name("payment-agent").description("Blockchain payment infrastructure for AI Agents").version("0.1.0");
800
+ program.command("balance").description("Get wallet balance").option("-c, --chain <chain>", "Chain name", "base_sepolia").option("-a, --address <address>", "Wallet address").action(async (options) => {
801
+ try {
802
+ const agent = new PaymentAgent({
803
+ chain: options.chain,
804
+ walletAddress: options.address || process.env.PAYMENT_AGENT_WALLET
805
+ });
806
+ const balance = await agent.getBalance();
807
+ console.log(JSON.stringify(balance, null, 2));
808
+ } catch (error) {
809
+ console.error("Error:", error.message);
810
+ process.exit(1);
811
+ }
812
+ });
813
+ program.command("invoice").description("Generate payment invoice").requiredOption("-o, --order <orderId>", "Order ID").requiredOption("-a, --amount <amount>", "Amount in USDC").option("-s, --service <service>", "Service type", "payment").option("-c, --chain <chain>", "Chain name", "base_sepolia").option("--json", "Output raw JSON").action(async (options) => {
814
+ try {
815
+ const agent = new PaymentAgent({
816
+ chain: options.chain,
817
+ walletAddress: process.env.PAYMENT_AGENT_WALLET
818
+ });
819
+ const invoice = agent.createInvoice({
820
+ orderId: options.order,
821
+ amount: parseFloat(options.amount),
822
+ service: options.service
823
+ });
824
+ if (options.json) {
825
+ console.log(JSON.stringify(invoice, null, 2));
826
+ } else {
827
+ console.log(agent.formatInvoiceMessage(invoice));
828
+ }
829
+ } catch (error) {
830
+ console.error("Error:", error.message);
831
+ process.exit(1);
832
+ }
833
+ });
834
+ program.command("verify").description("Verify a payment transaction").requiredOption("-t, --tx <txHash>", "Transaction hash").option("-a, --amount <amount>", "Expected amount").option("-c, --chain <chain>", "Chain name", "base_sepolia").action(async (options) => {
835
+ try {
836
+ const agent = new PaymentAgent({
837
+ chain: options.chain,
838
+ walletAddress: process.env.PAYMENT_AGENT_WALLET
839
+ });
840
+ const result = await agent.verifyPayment(options.tx, {
841
+ expectedAmount: options.amount ? parseFloat(options.amount) : void 0
842
+ });
843
+ console.log(JSON.stringify(result, null, 2));
844
+ process.exit(result.verified ? 0 : 1);
845
+ } catch (error) {
846
+ console.error("Error:", error.message);
847
+ process.exit(1);
848
+ }
849
+ });
850
+ program.command("transfer").description("Transfer USDC (requires private key)").requiredOption("--to <address>", "Recipient address").requiredOption("-a, --amount <amount>", "Amount in USDC").option("-r, --reason <reason>", "Transfer reason").option("-c, --chain <chain>", "Chain name", "base_sepolia").option("--secure", "Use secure wallet with limits").action(async (options) => {
851
+ try {
852
+ if (options.secure) {
853
+ const wallet = new SecureWallet({ chain: options.chain });
854
+ const result = await wallet.transfer({
855
+ to: options.to,
856
+ amount: parseFloat(options.amount),
857
+ reason: options.reason
858
+ });
859
+ console.log(JSON.stringify(result, null, 2));
860
+ process.exit(result.success ? 0 : 1);
861
+ } else {
862
+ const wallet = new Wallet({ chain: options.chain });
863
+ const result = await wallet.transfer(options.to, parseFloat(options.amount));
864
+ console.log(JSON.stringify(result, null, 2));
865
+ process.exit(result.success ? 0 : 1);
866
+ }
867
+ } catch (error) {
868
+ console.error("Error:", error.message);
869
+ process.exit(1);
870
+ }
871
+ });
872
+ program.command("chains").description("List supported chains").action(() => {
873
+ const chains = listChains();
874
+ console.log("Supported chains:");
875
+ for (const name of chains) {
876
+ const config = CHAINS[name];
877
+ console.log(` ${name}: ${config.name} (chainId: ${config.chainId})`);
878
+ }
879
+ });
880
+ program.parse();
881
+ //# sourceMappingURL=cli.js.map