#!/usr/bin/env node const { ethers } = require("hardhat"); const fs = require("fs"); const path = require("path"); // 颜色输出工具 const colors = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m' }; function log(color, message) { console.log(`${colors[color]}${message}${colors.reset}`); } class MiniSwapCLI { constructor() { this.contracts = {}; this.signer = null; this.signers = []; } async init() { try { // 连接到网络 await this.connectToNetwork(); // 加载合约 await this.loadContracts(); log('green', '✅ CLI 工具初始化成功!'); log('cyan', `当前账户: ${this.signer.address}`); } catch (error) { log('red', `❌ 初始化失败: ${error.message}`); process.exit(1); } } async connectToNetwork() { try { this.signers = await ethers.getSigners(); this.signer = this.signers[0]; // 获取网络信息 const network = await this.signer.provider.getNetwork(); log('blue', `连接到网络: ${network.name || 'localhost'} (Chain ID: ${network.chainId})`); log('blue', `找到 ${this.signers.length} 个账户`); // 验证网络连接 const balance = await this.signer.provider.getBalance(this.signer.address); if (balance === 0n) { log('yellow', '⚠️ 警告: 当前账户余额为 0 ETH'); } } catch (error) { throw new Error(`网络连接失败: ${error.message}`); } } async loadContracts() { const addressesPath = path.join(__dirname, '..', 'contract-addresses.json'); if (!fs.existsSync(addressesPath)) { throw new Error('合约地址文件不存在,请先运行 npm run deploy'); } const addresses = JSON.parse(fs.readFileSync(addressesPath, 'utf8')); log('blue', '正在加载合约...'); log('white', `TokenA: ${addresses.TokenA}`); log('white', `TokenB: ${addresses.TokenB}`); log('white', `MiniSwapAMM: ${addresses.MiniSwapAMM}`); // 加载合约 const TokenA = await ethers.getContractFactory("TokenA"); const TokenB = await ethers.getContractFactory("TokenB"); const MiniSwapAMM = await ethers.getContractFactory("MiniSwapAMM"); this.contracts.tokenA = TokenA.attach(addresses.TokenA); this.contracts.tokenB = TokenB.attach(addresses.TokenB); this.contracts.amm = MiniSwapAMM.attach(addresses.MiniSwapAMM); // 验证合约连接 try { await this.contracts.tokenA.name(); await this.contracts.tokenB.name(); await this.contracts.amm.getReserves(); log('green', '✅ 合约加载并验证成功'); } catch (error) { throw new Error(`合约验证失败,请确保合约已正确部署到当前网络: ${error.message}`); } } // 切换账户 async switchAccount(index) { if (index < 0 || index >= this.signers.length) { log('red', '❌ 无效的账户索引'); return; } this.signer = this.signers[index]; // 重新连接合约到新的签名者 this.contracts.tokenA = this.contracts.tokenA.connect(this.signer); this.contracts.tokenB = this.contracts.tokenB.connect(this.signer); this.contracts.amm = this.contracts.amm.connect(this.signer); log('green', `✅ 切换到账户 ${index}: ${this.signer.address}`); } // 查看所有账户 async showAccounts() { log('cyan', '\n=== 账户列表 ==='); for (let i = 0; i < this.signers.length; i++) { const current = this.signers[i].address === this.signer.address ? ' (当前)' : ''; log('white', `[${i}] ${this.signers[i].address}${current}`); } } // 查看余额 async showBalances() { try { const address = this.signer.address; // 获取 ETH 余额 const ethBalance = await this.signer.provider.getBalance(address); // 获取代币余额 const tokenABalance = await this.contracts.tokenA.balanceOf(address); const tokenBBalance = await this.contracts.tokenB.balanceOf(address); const lpBalance = await this.contracts.amm.balanceOf(address); log('cyan', '\n=== 账户余额 ==='); log('white', `地址: ${address}`); log('white', `ETH: ${ethers.formatEther(ethBalance)}`); log('white', `TokenA: ${ethers.formatEther(tokenABalance)} TKA`); log('white', `TokenB: ${ethers.formatEther(tokenBBalance)} TKB`); log('white', `LP Token: ${ethers.formatEther(lpBalance)} MSLP`); } catch (error) { log('red', `❌ 查询余额失败: ${error.message}`); } } // 查看 AMM 储备量 async showReserves() { try { const [reserveA, reserveB] = await this.contracts.amm.getReserves(); const totalSupply = await this.contracts.amm.totalSupply(); log('cyan', '\n=== AMM 储备量 ==='); log('white', `TokenA 储备: ${ethers.formatEther(reserveA)} TKA`); log('white', `TokenB 储备: ${ethers.formatEther(reserveB)} TKB`); log('white', `LP Token 总供应: ${ethers.formatEther(totalSupply)} MSLP`); if (reserveA > 0 && reserveB > 0) { const priceAtoB = Number(ethers.formatEther(reserveB)) / Number(ethers.formatEther(reserveA)); const priceBtoA = Number(ethers.formatEther(reserveA)) / Number(ethers.formatEther(reserveB)); log('white', `价格 (A->B): ${priceAtoB.toFixed(6)} TKB/TKA`); log('white', `价格 (B->A): ${priceBtoA.toFixed(6)} TKA/TKB`); } } catch (error) { log('red', `❌ 查询储备量失败: ${error.message}`); } } // 铸造代币 async mintTokens(amount = "1000") { try { log('yellow', '🔄 铸造代币中...'); const amountWei = ethers.parseEther(amount); const txA = await this.contracts.tokenA.faucet(amountWei); const txB = await this.contracts.tokenB.faucet(amountWei); await txA.wait(); await txB.wait(); log('green', `✅ 成功铸造 ${amount} TKA 和 ${amount} TKB`); } catch (error) { log('red', `❌ 铸造代币失败: ${error.message}`); } } // 授权代币 async approveTokens(amount = "10000") { try { log('yellow', '🔄 授权代币中...'); const amountWei = ethers.parseEther(amount); const ammAddress = await this.contracts.amm.getAddress(); const txA = await this.contracts.tokenA.approve(ammAddress, amountWei); const txB = await this.contracts.tokenB.approve(ammAddress, amountWei); await txA.wait(); await txB.wait(); log('green', `✅ 成功授权 ${amount} TKA 和 ${amount} TKB 给 AMM 合约`); } catch (error) { log('red', `❌ 授权代币失败: ${error.message}`); } } // 添加流动性 async addLiquidity(amountA, amountB, slippage = "1") { try { log('yellow', '🔄 添加流动性中...'); const amountAWei = ethers.parseEther(amountA); const amountBWei = ethers.parseEther(amountB); // 计算滑点保护 const slippagePercent = parseFloat(slippage) / 100; const amountAMin = amountAWei * BigInt(Math.floor((1 - slippagePercent) * 1000)) / 1000n; const amountBMin = amountBWei * BigInt(Math.floor((1 - slippagePercent) * 1000)) / 1000n; const tx = await this.contracts.amm.addLiquidity( amountAWei, amountBWei, amountAMin, amountBMin ); const receipt = await tx.wait(); log('green', `✅ 流动性添加成功!`); log('blue', `交易哈希: ${receipt.hash}`); } catch (error) { log('red', `❌ 添加流动性失败: ${error.message}`); } } // 移除流动性 async removeLiquidity(liquidity, slippage = "1") { try { log('yellow', '🔄 移除流动性中...'); const liquidityWei = ethers.parseEther(liquidity); // 计算最小输出 const [reserveA, reserveB] = await this.contracts.amm.getReserves(); const totalSupply = await this.contracts.amm.totalSupply(); const amountA = liquidityWei * reserveA / totalSupply; const amountB = liquidityWei * reserveB / totalSupply; const slippagePercent = parseFloat(slippage) / 100; const amountAMin = amountA * BigInt(Math.floor((1 - slippagePercent) * 1000)) / 1000n; const amountBMin = amountB * BigInt(Math.floor((1 - slippagePercent) * 1000)) / 1000n; const tx = await this.contracts.amm.removeLiquidity( liquidityWei, amountAMin, amountBMin ); const receipt = await tx.wait(); log('green', `✅ 流动性移除成功!`); log('blue', `交易哈希: ${receipt.hash}`); } catch (error) { log('red', `❌ 移除流动性失败: ${error.message}`); } } // 交换 A -> B async swapAForB(amountIn, slippage = "1") { try { log('yellow', '🔄 交换 TokenA -> TokenB 中...'); const amountInWei = ethers.parseEther(amountIn); const [reserveA, reserveB] = await this.contracts.amm.getReserves(); const amountOut = await this.contracts.amm.getAmountOut(amountInWei, reserveA, reserveB); const slippagePercent = parseFloat(slippage) / 100; const amountOutMin = amountOut * BigInt(Math.floor((1 - slippagePercent) * 1000)) / 1000n; log('blue', `预期输出: ${ethers.formatEther(amountOut)} TKB`); log('blue', `最小输出: ${ethers.formatEther(amountOutMin)} TKB`); const tx = await this.contracts.amm.swapAForB(amountInWei, amountOutMin); const receipt = await tx.wait(); log('green', `✅ 交换成功!`); log('blue', `交易哈希: ${receipt.hash}`); } catch (error) { log('red', `❌ 交换失败: ${error.message}`); } } // 交换 B -> A async swapBForA(amountIn, slippage = "1") { try { log('yellow', '🔄 交换 TokenB -> TokenA 中...'); const amountInWei = ethers.parseEther(amountIn); const [reserveA, reserveB] = await this.contracts.amm.getReserves(); const amountOut = await this.contracts.amm.getAmountOut(amountInWei, reserveB, reserveA); const slippagePercent = parseFloat(slippage) / 100; const amountOutMin = amountOut * BigInt(Math.floor((1 - slippagePercent) * 1000)) / 1000n; log('blue', `预期输出: ${ethers.formatEther(amountOut)} TKA`); log('blue', `最小输出: ${ethers.formatEther(amountOutMin)} TKA`); const tx = await this.contracts.amm.swapBForA(amountInWei, amountOutMin); const receipt = await tx.wait(); log('green', `✅ 交换成功!`); log('blue', `交易哈希: ${receipt.hash}`); } catch (error) { log('red', `❌ 交换失败: ${error.message}`); } } // 计算交换价格 async calculateSwap(amountIn, direction = "a2b") { try { const amountInWei = ethers.parseEther(amountIn); const [reserveA, reserveB] = await this.contracts.amm.getReserves(); let amountOut; if (direction === "a2b") { amountOut = await this.contracts.amm.getAmountOut(amountInWei, reserveA, reserveB); log('cyan', '\n=== 交换计算 (A -> B) ==='); log('white', `输入: ${amountIn} TKA`); log('white', `输出: ${ethers.formatEther(amountOut)} TKB`); } else { amountOut = await this.contracts.amm.getAmountOut(amountInWei, reserveB, reserveA); log('cyan', '\n=== 交换计算 (B -> A) ==='); log('white', `输入: ${amountIn} TKB`); log('white', `输出: ${ethers.formatEther(amountOut)} TKA`); } const rate = Number(ethers.formatEther(amountOut)) / Number(amountIn); log('white', `汇率: ${rate.toFixed(6)}`); } catch (error) { log('red', `❌ 计算失败: ${error.message}`); } } // 显示帮助信息 showHelp() { log('cyan', '\n=== MiniSwap CLI 工具 ==='); log('white', '可用命令:'); log('yellow', ' accounts - 显示所有账户'); log('yellow', ' switch - 切换到指定账户'); log('yellow', ' balances - 查看当前账户余额'); log('yellow', ' reserves - 查看 AMM 储备量'); log('yellow', ' mint [amount] - 铸造代币 (默认 1000)'); log('yellow', ' approve [amount] - 授权代币 (默认 10000)'); log('yellow', ' add [slippage] - 添加流动性'); log('yellow', ' remove [slippage] - 移除流动性'); log('yellow', ' swapAB [slippage] - 交换 A->B'); log('yellow', ' swapBA [slippage] - 交换 B->A'); log('yellow', ' calc - 计算交换价格'); log('yellow', ' help - 显示帮助信息'); log('yellow', ' exit - 退出程序'); log('white', '\n滑点参数单位为百分比,例如 1 表示 1%'); } // 主循环 async run() { await this.init(); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); this.showHelp(); const prompt = () => { rl.question('\n> ', async (input) => { const args = input.trim().split(' '); const command = args[0]; try { switch (command) { case 'accounts': await this.showAccounts(); break; case 'switch': await this.switchAccount(parseInt(args[1])); break; case 'balances': await this.showBalances(); break; case 'reserves': await this.showReserves(); break; case 'mint': await this.mintTokens(args[1] || "1000"); break; case 'approve': await this.approveTokens(args[1] || "10000"); break; case 'add': if (args.length < 3) { log('red', '❌ 用法: add [slippage]'); } else { await this.addLiquidity(args[1], args[2], args[3] || "1"); } break; case 'remove': if (args.length < 2) { log('red', '❌ 用法: remove [slippage]'); } else { await this.removeLiquidity(args[1], args[2] || "1"); } break; case 'swapAB': if (args.length < 2) { log('red', '❌ 用法: swapAB [slippage]'); } else { await this.swapAForB(args[1], args[2] || "1"); } break; case 'swapBA': if (args.length < 2) { log('red', '❌ 用法: swapBA [slippage]'); } else { await this.swapBForA(args[1], args[2] || "1"); } break; case 'calc': if (args.length < 3) { log('red', '❌ 用法: calc '); } else { await this.calculateSwap(args[1], args[2]); } break; case 'help': this.showHelp(); break; case 'exit': log('green', '👋 再见!'); rl.close(); return; case '': break; default: log('red', `❌ 未知命令: ${command}`); log('yellow', '输入 "help" 查看可用命令'); } } catch (error) { log('red', `❌ 执行命令时出错: ${error.message}`); } prompt(); }); }; prompt(); } } // 如果直接运行此文件 if (require.main === module) { const cli = new MiniSwapCLI(); cli.run().catch(console.error); } module.exports = MiniSwapCLI;