import { MockUSDT__factory, ControlledERC20__factory } from "../typechain-types" import { IPancakeRouter02__factory } from "../typechain-types" import { config as dotenvConfig } from "dotenv" import { ethers } from "ethers" import fs from "fs" // BSC Testnet PancakeSwap V2 合约地址 const PANCAKE_ROUTER_ADDRESS = "0xD99D1c33F9fC3444f8101754aBC46c52416550D1" const WBNB_ADDRESS = "0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd" /** * @dev PancakeSwap代币交换脚本 * 在PancakeSwap V2上执行代币交换操作 * 支持mUSDT和CTRL之间的双向交换 * 包含滑点保护、价格计算、交易执行等功能 */ async function main() { dotenvConfig() const rpcUrl = process.env.BSC_TESTNET_RPC_URL const privateKey = process.env.PRIVATE_KEY if (!rpcUrl || !privateKey) { throw new Error("请在.env中配置BSC_TESTNET_RPC_URL和PRIVATE_KEY") } const provider = new ethers.JsonRpcProvider(rpcUrl) const wallet = new ethers.Wallet(privateKey, provider) console.log("🔄 开始PancakeSwap交易...") console.log("📝 操作账户:", wallet.address) console.log("💰 账户余额:", ethers.formatEther(await provider.getBalance(wallet.address)), "BNB") // 读取部署信息 let deploymentInfo try { deploymentInfo = JSON.parse(fs.readFileSync("deployment-bsc-testnet.json", "utf8")) } catch (error) { console.error("❌ 无法读取部署信息文件,请先运行部署脚本") process.exit(1) } const mockUSDTAddress = deploymentInfo.contracts.mockUSDT const controlledERC20Address = deploymentInfo.contracts.controlledERC20 console.log("📄 MockUSDT地址:", mockUSDTAddress) console.log("📄 ControlledERC20地址:", controlledERC20Address) // 获取合约实例 const mockUSDT = MockUSDT__factory.connect(mockUSDTAddress, wallet) const controlledERC20 = ControlledERC20__factory.connect(controlledERC20Address, wallet) const pancakeRouter = IPancakeRouter02__factory.connect(PANCAKE_ROUTER_ADDRESS, wallet) // 检查代币余额 const mockUSDTBalance = await mockUSDT.balanceOf(wallet.address) const controlledERC20Balance = await controlledERC20.balanceOf(wallet.address) console.log("\n💰 当前代币余额:") console.log("MockUSDT余额:", ethers.formatUnits(mockUSDTBalance, 6), "mUSDT") console.log("ControlledERC20余额:", ethers.formatEther(controlledERC20Balance), "CTRL") // 解析命令行参数(代币类型、数量、滑点) const args = process.argv.slice(2) if (args.length < 2) { console.log("\n📖 使用方法:") console.log("npm run swap [slippage]") console.log("from_token: 'mUSDT' 或 'CTRL'") console.log("amount: 交易数量") console.log("slippage: 滑点百分比 (可选,默认1%)") console.log("\n示例:") console.log("npm run swap mUSDT 1000") console.log("npm run swap CTRL 100") process.exit(1) } let fromToken = args[0].toUpperCase() const amount = parseFloat(args[1]) const slippage = args[2] ? parseFloat(args[2]) : 1 // 默认1%滑点 console.log(`\n🔄 交易信息:`) console.log(`从: ${fromToken}`) console.log(`数量: ${amount}`) console.log(`滑点: ${slippage}%`) let tokenInAddress: string let tokenOutAddress: string let amountIn: bigint let tokenInContract: any let tokenOutContract: any if (fromToken === "MUSDT") fromToken = "mUSDT" if (fromToken === "mUSDT") { tokenInAddress = mockUSDTAddress tokenOutAddress = controlledERC20Address amountIn = ethers.parseUnits(amount.toString(), 6) tokenInContract = mockUSDT tokenOutContract = controlledERC20 } else if (fromToken === "CTRL") { tokenInAddress = controlledERC20Address tokenOutAddress = mockUSDTAddress amountIn = ethers.parseEther(amount.toString()) tokenInContract = controlledERC20 tokenOutContract = mockUSDT } else { console.error("❌ 无效的代币名称,请使用 'mUSDT' 或 'CTRL'") process.exit(1) } // 检查用户余额是否足够进行交易 const tokenInBalance = await tokenInContract.balanceOf(wallet.address) if (tokenInBalance < amountIn) { console.error(`❌ 余额不足! 需要 ${ethers.formatUnits(amountIn, fromToken === "mUSDT" ? 6 : 18)} ${fromToken}`) process.exit(1) } // 检查授权 const allowance = await tokenInContract.allowance(wallet.address, PANCAKE_ROUTER_ADDRESS) if (allowance < amountIn) { console.log("🔐 授权Router使用代币...") const approveTx = await tokenInContract.approve(PANCAKE_ROUTER_ADDRESS, ethers.MaxUint256) await approveTx.wait() console.log("✅ 授权完成") } // 获取预期输出数量和设置滑点保护 console.log("\n📊 获取交易报价...") const path = [tokenInAddress, tokenOutAddress] const amountsOut = await pancakeRouter.getAmountsOut(amountIn, path) const amountOutMin = (amountsOut[1] * BigInt(100 - slippage)) / 100n console.log( `预期输出: ${ethers.formatUnits(amountsOut[1], fromToken === "mUSDT" ? 18 : 6)} ${fromToken === "mUSDT" ? "CTRL" : "mUSDT"}` ) console.log( `最小输出: ${ethers.formatUnits(amountOutMin, fromToken === "mUSDT" ? 18 : 6)} ${fromToken === "mUSDT" ? "CTRL" : "mUSDT"}` ) // 执行代币交换交易 console.log("\n🔄 执行交易...") const deadline = Math.floor(Date.now() / 1000) + 300 // 5分钟后过期 const swapTx = await pancakeRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, wallet.address, deadline) console.log("⏳ 等待交易确认...") const receipt = await swapTx.wait() console.log("✅ 交易成功!") console.log("交易哈希:", receipt!.hash) // 检查交易后的代币余额变化 const newMockUSDTBalance = await mockUSDT.balanceOf(wallet.address) const newControlledERC20Balance = await controlledERC20.balanceOf(wallet.address) console.log("\n💰 交易后余额:") console.log("MockUSDT余额:", ethers.formatUnits(newMockUSDTBalance, 6), "mUSDT") console.log("ControlledERC20余额:", ethers.formatEther(newControlledERC20Balance), "CTRL") // 计算实际收到的代币数量(用于验证交易结果) const actualReceived = fromToken === "mUSDT" ? newControlledERC20Balance - controlledERC20Balance : newMockUSDTBalance - mockUSDTBalance console.log(`\n📈 交易结果:`) console.log( `实际收到: ${ethers.formatUnits(actualReceived, fromToken === "mUSDT" ? 18 : 6)} ${fromToken === "mUSDT" ? "CTRL" : "mUSDT"}` ) console.log( `价格影响: ${(((Number(amountsOut[1]) - Number(actualReceived)) / Number(amountsOut[1])) * 100).toFixed(2)}%` ) // 保存交易信息 const swapInfo = { fromToken: fromToken, toToken: fromToken === "mUSDT" ? "CTRL" : "mUSDT", amountIn: amountIn.toString(), amountOut: amountsOut[1].toString(), actualReceived: actualReceived.toString(), slippage: slippage, transactionHash: receipt!.hash, timestamp: new Date().toISOString(), } fs.writeFileSync("swap-info.json", JSON.stringify(swapInfo, null, 2)) console.log("\n💾 交易信息已保存到 swap-info.json") } main() .then(() => process.exit(0)) .catch((error) => { console.error("❌ 交易失败:", error) process.exit(1) })