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