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/README.md +212 -0
- package/dist/chains/index.d.mts +25 -0
- package/dist/chains/index.d.ts +25 -0
- package/dist/chains/index.js +113 -0
- package/dist/chains/index.js.map +1 -0
- package/dist/chains/index.mjs +84 -0
- package/dist/chains/index.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +881 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +858 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index-CZzgdtin.d.mts +161 -0
- package/dist/index-CZzgdtin.d.ts +161 -0
- package/dist/index.d.mts +118 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.js +996 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +950 -0
- package/dist/index.mjs.map +1 -0
- package/dist/permit/index.d.mts +49 -0
- package/dist/permit/index.d.ts +49 -0
- package/dist/permit/index.js +273 -0
- package/dist/permit/index.js.map +1 -0
- package/dist/permit/index.mjs +246 -0
- package/dist/permit/index.mjs.map +1 -0
- package/dist/wallet/index.d.mts +101 -0
- package/dist/wallet/index.d.ts +101 -0
- package/dist/wallet/index.js +601 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/index.mjs +563 -0
- package/dist/wallet/index.mjs.map +1 -0
- package/package.json +70 -0
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
|