pancakeswap-swap.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import { MockUSDT__factory, ControlledERC20__factory } from "../typechain-types"
  2. import { IPancakeRouter02__factory } from "../typechain-types"
  3. import { config as dotenvConfig } from "dotenv"
  4. import { ethers } from "ethers"
  5. import fs from "fs"
  6. // BSC Testnet PancakeSwap V2 合约地址
  7. const PANCAKE_ROUTER_ADDRESS = "0xD99D1c33F9fC3444f8101754aBC46c52416550D1"
  8. const WBNB_ADDRESS = "0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd"
  9. /**
  10. * @dev PancakeSwap代币交换脚本
  11. * 在PancakeSwap V2上执行代币交换操作
  12. * 支持mUSDT和CTRL之间的双向交换
  13. * 包含滑点保护、价格计算、交易执行等功能
  14. */
  15. async function main() {
  16. dotenvConfig()
  17. const rpcUrl = process.env.BSC_TESTNET_RPC_URL
  18. const privateKey = process.env.PRIVATE_KEY
  19. if (!rpcUrl || !privateKey) {
  20. throw new Error("请在.env中配置BSC_TESTNET_RPC_URL和PRIVATE_KEY")
  21. }
  22. const provider = new ethers.JsonRpcProvider(rpcUrl)
  23. const wallet = new ethers.Wallet(privateKey, provider)
  24. console.log("🔄 开始PancakeSwap交易...")
  25. console.log("📝 操作账户:", wallet.address)
  26. console.log("💰 账户余额:", ethers.formatEther(await provider.getBalance(wallet.address)), "BNB")
  27. // 读取部署信息
  28. let deploymentInfo
  29. try {
  30. deploymentInfo = JSON.parse(fs.readFileSync("deployment-bsc-testnet.json", "utf8"))
  31. } catch (error) {
  32. console.error("❌ 无法读取部署信息文件,请先运行部署脚本")
  33. process.exit(1)
  34. }
  35. const mockUSDTAddress = deploymentInfo.contracts.mockUSDT
  36. const controlledERC20Address = deploymentInfo.contracts.controlledERC20
  37. console.log("📄 MockUSDT地址:", mockUSDTAddress)
  38. console.log("📄 ControlledERC20地址:", controlledERC20Address)
  39. // 获取合约实例
  40. const mockUSDT = MockUSDT__factory.connect(mockUSDTAddress, wallet)
  41. const controlledERC20 = ControlledERC20__factory.connect(controlledERC20Address, wallet)
  42. const pancakeRouter = IPancakeRouter02__factory.connect(PANCAKE_ROUTER_ADDRESS, wallet)
  43. // 检查代币余额
  44. const mockUSDTBalance = await mockUSDT.balanceOf(wallet.address)
  45. const controlledERC20Balance = await controlledERC20.balanceOf(wallet.address)
  46. console.log("\n💰 当前代币余额:")
  47. console.log("MockUSDT余额:", ethers.formatUnits(mockUSDTBalance, 6), "mUSDT")
  48. console.log("ControlledERC20余额:", ethers.formatEther(controlledERC20Balance), "CTRL")
  49. // 解析命令行参数(代币类型、数量、滑点)
  50. const args = process.argv.slice(2)
  51. if (args.length < 2) {
  52. console.log("\n📖 使用方法:")
  53. console.log("npm run swap <from_token> <amount> [slippage]")
  54. console.log("from_token: 'mUSDT' 或 'CTRL'")
  55. console.log("amount: 交易数量")
  56. console.log("slippage: 滑点百分比 (可选,默认1%)")
  57. console.log("\n示例:")
  58. console.log("npm run swap mUSDT 1000")
  59. console.log("npm run swap CTRL 100")
  60. process.exit(1)
  61. }
  62. let fromToken = args[0].toUpperCase()
  63. const amount = parseFloat(args[1])
  64. const slippage = args[2] ? parseFloat(args[2]) : 1 // 默认1%滑点
  65. console.log(`\n🔄 交易信息:`)
  66. console.log(`从: ${fromToken}`)
  67. console.log(`数量: ${amount}`)
  68. console.log(`滑点: ${slippage}%`)
  69. let tokenInAddress: string
  70. let tokenOutAddress: string
  71. let amountIn: bigint
  72. let tokenInContract: any
  73. let tokenOutContract: any
  74. if (fromToken === "MUSDT") fromToken = "mUSDT"
  75. if (fromToken === "mUSDT") {
  76. tokenInAddress = mockUSDTAddress
  77. tokenOutAddress = controlledERC20Address
  78. amountIn = ethers.parseUnits(amount.toString(), 6)
  79. tokenInContract = mockUSDT
  80. tokenOutContract = controlledERC20
  81. } else if (fromToken === "CTRL") {
  82. tokenInAddress = controlledERC20Address
  83. tokenOutAddress = mockUSDTAddress
  84. amountIn = ethers.parseEther(amount.toString())
  85. tokenInContract = controlledERC20
  86. tokenOutContract = mockUSDT
  87. } else {
  88. console.error("❌ 无效的代币名称,请使用 'mUSDT' 或 'CTRL'")
  89. process.exit(1)
  90. }
  91. // 检查用户余额是否足够进行交易
  92. const tokenInBalance = await tokenInContract.balanceOf(wallet.address)
  93. if (tokenInBalance < amountIn) {
  94. console.error(`❌ 余额不足! 需要 ${ethers.formatUnits(amountIn, fromToken === "mUSDT" ? 6 : 18)} ${fromToken}`)
  95. process.exit(1)
  96. }
  97. // 检查授权
  98. const allowance = await tokenInContract.allowance(wallet.address, PANCAKE_ROUTER_ADDRESS)
  99. if (allowance < amountIn) {
  100. console.log("🔐 授权Router使用代币...")
  101. const approveTx = await tokenInContract.approve(PANCAKE_ROUTER_ADDRESS, ethers.MaxUint256)
  102. await approveTx.wait()
  103. console.log("✅ 授权完成")
  104. }
  105. // 获取预期输出数量和设置滑点保护
  106. console.log("\n📊 获取交易报价...")
  107. const path = [tokenInAddress, tokenOutAddress]
  108. const amountsOut = await pancakeRouter.getAmountsOut(amountIn, path)
  109. const amountOutMin = (amountsOut[1] * BigInt(100 - slippage)) / 100n
  110. console.log(
  111. `预期输出: ${ethers.formatUnits(amountsOut[1], fromToken === "mUSDT" ? 18 : 6)} ${fromToken === "mUSDT" ? "CTRL" : "mUSDT"}`
  112. )
  113. console.log(
  114. `最小输出: ${ethers.formatUnits(amountOutMin, fromToken === "mUSDT" ? 18 : 6)} ${fromToken === "mUSDT" ? "CTRL" : "mUSDT"}`
  115. )
  116. // 执行代币交换交易
  117. console.log("\n🔄 执行交易...")
  118. const deadline = Math.floor(Date.now() / 1000) + 300 // 5分钟后过期
  119. const swapTx = await pancakeRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, wallet.address, deadline)
  120. console.log("⏳ 等待交易确认...")
  121. const receipt = await swapTx.wait()
  122. console.log("✅ 交易成功!")
  123. console.log("交易哈希:", receipt!.hash)
  124. // 检查交易后的代币余额变化
  125. const newMockUSDTBalance = await mockUSDT.balanceOf(wallet.address)
  126. const newControlledERC20Balance = await controlledERC20.balanceOf(wallet.address)
  127. console.log("\n💰 交易后余额:")
  128. console.log("MockUSDT余额:", ethers.formatUnits(newMockUSDTBalance, 6), "mUSDT")
  129. console.log("ControlledERC20余额:", ethers.formatEther(newControlledERC20Balance), "CTRL")
  130. // 计算实际收到的代币数量(用于验证交易结果)
  131. const actualReceived =
  132. fromToken === "mUSDT"
  133. ? newControlledERC20Balance - controlledERC20Balance
  134. : newMockUSDTBalance - mockUSDTBalance
  135. console.log(`\n📈 交易结果:`)
  136. console.log(
  137. `实际收到: ${ethers.formatUnits(actualReceived, fromToken === "mUSDT" ? 18 : 6)} ${fromToken === "mUSDT" ? "CTRL" : "mUSDT"}`
  138. )
  139. console.log(
  140. `价格影响: ${(((Number(amountsOut[1]) - Number(actualReceived)) / Number(amountsOut[1])) * 100).toFixed(2)}%`
  141. )
  142. // 保存交易信息
  143. const swapInfo = {
  144. fromToken: fromToken,
  145. toToken: fromToken === "mUSDT" ? "CTRL" : "mUSDT",
  146. amountIn: amountIn.toString(),
  147. amountOut: amountsOut[1].toString(),
  148. actualReceived: actualReceived.toString(),
  149. slippage: slippage,
  150. transactionHash: receipt!.hash,
  151. timestamp: new Date().toISOString(),
  152. }
  153. fs.writeFileSync("swap-info.json", JSON.stringify(swapInfo, null, 2))
  154. console.log("\n💾 交易信息已保存到 swap-info.json")
  155. }
  156. main()
  157. .then(() => process.exit(0))
  158. .catch((error) => {
  159. console.error("❌ 交易失败:", error)
  160. process.exit(1)
  161. })