123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- 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 <from_token> <amount> [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)
- })
|