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