UNPKG

four-flap-meme-sdk

Version:

SDK for Flap bonding curve and four.meme TokenManager

1,126 lines 56.7 kB
// ==================== 工具函数 ==================== function createPancakeContext(config) { const chainId = config.chainId ?? 56; const provider = new ethers.JsonRpcProvider(config.rpcUrl, { chainId, name: 'BSC' }); return { chainId, provider }; } async function quoteSellOutput({ routeParams, sellAmountWei, provider }) { console.log(`[quoteSellOutput] 开始报价, routeType=${routeParams.routeType}, sellAmount=${sellAmountWei} wei`); // ==================== V2 报价 ==================== if (routeParams.routeType === 'v2') { const { v2Path } = routeParams; const tokenIn = v2Path[0]; const tokenOut = v2Path[v2Path.length - 1]; console.log(`[quoteSellOutput] V2 路径: ${v2Path.join(' → ')}`); // ✅ 只用 V2 报价 const result = await quoteV2(provider, tokenIn, tokenOut, sellAmountWei, 'BSC'); if (result.amountOut > 0n) { console.log(`[quoteSellOutput] V2 报价成功: ${ethers.formatEther(result.amountOut)} BNB`); return { estimatedBNBOut: result.amountOut }; } throw new Error('V2 报价失败: 所有路径均无效'); } // ==================== V3 单跳报价 ==================== if (routeParams.routeType === 'v3-single') { const params = routeParams; console.log(`[quoteSellOutput] V3 单跳: ${params.v3TokenIn} → ${params.v3TokenOut}, fee=${params.v3Fee}`); // ✅ 只用 V3 报价,不 fallback 到 V2 const result = await quoteV3(provider, params.v3TokenIn, params.v3TokenOut, sellAmountWei, 'BSC', params.v3Fee); if (result.amountOut > 0n) { console.log(`[quoteSellOutput] V3 报价成功 (fee=${result.fee}): ${ethers.formatEther(result.amountOut)} BNB`); return { estimatedBNBOut: result.amountOut }; } // ❌ V3 报价失败时不再 fallback 到 V2,因为价格可能差异很大 throw new Error(`V3 单跳报价失败: tokenIn=${params.v3TokenIn}, tokenOut=${params.v3TokenOut}, fee=${params.v3Fee}`); } // ==================== V3 多跳报价 ==================== if (routeParams.routeType === 'v3-multi') { const params = routeParams; console.log(`[quoteSellOutput] V3 多跳: LPs=${params.v3LpAddresses?.join(', ')}`); // ✅ V3 多跳:只用 V3 报价,不 fallback 到 V2 if (params.v3LpAddresses && params.v3LpAddresses.length > 0) { const tokenIn = params.v3ExactTokenIn; const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; const result = await quoteV3(provider, tokenIn, WBNB, sellAmountWei, 'BSC'); if (result.amountOut > 0n) { console.log(`[quoteSellOutput] V3 多跳报价成功: ${ethers.formatEther(result.amountOut)} BNB`); return { estimatedBNBOut: result.amountOut }; } } // ❌ V3 多跳报价失败时不再 fallback 到 V2,因为价格可能差异很大 throw new Error(`V3 多跳报价失败: LPs=${params.v3LpAddresses?.join(', ')}, tokenIn=${params.v3ExactTokenIn}`); } throw new Error(`不支持的路由类型: ${routeParams.routeType}`); } async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress, useNativeToken = true }) { const deadline = getDeadline(); // ✅ ERC20 购买时,value = 0(通过代币授权支付) const buyValue = useNativeToken ? buyAmountBNB + FLAT_FEE : 0n; if (routeParams.routeType === 'v2') { const { v2Path } = routeParams; const reversePath = [...v2Path].reverse(); // ✅ 使用官方 V2 Router const v2RouterSeller = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller); const v2RouterBuyer = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer); let sellUnsigned; let buyUnsigned; if (useNativeToken) { // ✅ 原生代币模式(BNB) // 卖出:Token → WBNB → BNB sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline); // 买入:BNB → WBNB → Token buyUnsigned = await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, reversePath, buyer.address, deadline, { value: buyValue }); } else { // ✅ ERC20 模式(如 USDT) // 卖出:Token → USDT sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline); // 买入:USDT → Token buyUnsigned = await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmountBNB, // USDT 数量 0n, reversePath, buyer.address, deadline); } return { sellUnsigned, buyUnsigned }; } if (routeParams.routeType === 'v3-single') { const { v3TokenIn, v3TokenOut, v3Fee } = routeParams; const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI); const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller); const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer); // 卖出:Token → WBNB,需要 unwrapWETH9 const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{ tokenIn: v3TokenIn, tokenOut: v3TokenOut, fee: v3Fee, recipient: PANCAKE_V3_ROUTER_ADDRESS, // 先发给 Router amountIn: sellAmountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }]); const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]); const sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]); // 买入:WBNB → Token const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{ tokenIn: v3TokenOut, tokenOut: v3TokenIn, fee: v3Fee, recipient: buyer.address, amountIn: buyAmountBNB, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }]); const buyUnsigned = await v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue }); return { sellUnsigned, buyUnsigned }; } // V3 多跳暂不支持官方合约 throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳'); } async function calculateBuyerBudget({ buyer, quotedBNBOut, reserveGasBNB, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) { const reserveGas = ethers.parseEther((reserveGasBNB ?? 0.0005).toString()); // ✅ 已移除滑点保护:直接使用报价金额 const buyAmountBNB = quotedBNBOut; // ✅ 根据是否使用原生代币获取不同的余额 let buyerBalance; if (useNativeToken) { buyerBalance = await buyer.provider.getBalance(buyer.address); const requiredBalance = buyAmountBNB + FLAT_FEE + reserveGas; if (buyerBalance < requiredBalance) { throw new Error(`买方余额不足: 需要 ${ethers.formatEther(requiredBalance)} BNB, 实际 ${ethers.formatEther(buyerBalance)} BNB`); } return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB, useNativeToken }; } else { // ERC20 代币余额 const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider); buyerBalance = await erc20.balanceOf(buyer.address); const requiredBalance = buyAmountBNB + FLAT_FEE; // ERC20 不需要预留 Gas 在代币余额中 if (buyerBalance < requiredBalance) { throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(requiredBalance, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`); } // ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas const buyerBnbBalance = await buyer.provider.getBalance(buyer.address); if (buyerBnbBalance < reserveGas) { throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGas)} BNB, 实际 ${ethers.formatEther(buyerBnbBalance)} BNB`); } return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB, useNativeToken }; } } /** * ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化) * 顺序:[sellerBaseNonce, buyerNonce] */ function buildNoncePlanFromNonces(startNonces, sameAddress, approvalExists, profitNeeded, needBribeTx) { if (sameAddress) { let idx = 0; const bribeNonce = needBribeTx ? startNonces[0] + idx++ : undefined; if (approvalExists) idx++; const sellerNonce = startNonces[0] + idx++; const buyerNonce = startNonces[0] + idx++; const profitNonce = profitNeeded ? startNonces[0] + idx : undefined; return { sellerNonce, buyerNonce, bribeNonce, profitNonce }; } let sellerIdx = 0; const bribeNonce = needBribeTx ? startNonces[0] + sellerIdx++ : undefined; if (approvalExists) sellerIdx++; const sellerNonce = startNonces[0] + sellerIdx++; const profitNonce = profitNeeded ? startNonces[0] + sellerIdx : undefined; const buyerNonce = startNonces[1]; return { sellerNonce, buyerNonce, bribeNonce, profitNonce }; } /** * ✅ 规划 nonce * 交易顺序:贿赂 → 授权 → 卖出 → 买入 → 利润 * 贿赂和利润由卖方发送 */ async function planNonces({ seller, buyer, sameAddress, approvalExists, profitNeeded, needBribeTx, nonceManager }) { if (sameAddress) { // 同一地址:贿赂(可选) + 授权(可选) + 卖出 + 买入 + 利润(可选) const txCount = countTruthy([needBribeTx, approvalExists, true, true, profitNeeded]); const nonces = await nonceManager.getNextNonceBatch(buyer, txCount); let idx = 0; const bribeNonce = needBribeTx ? nonces[idx++] : undefined; if (approvalExists) idx++; const sellerNonce = nonces[idx++]; const buyerNonce = nonces[idx++]; const profitNonce = profitNeeded ? nonces[idx] : undefined; return { sellerNonce, buyerNonce, bribeNonce, profitNonce }; } if (needBribeTx || approvalExists || profitNeeded) { // 卖方需要多个 nonce:贿赂(可选) + 授权(可选) + 卖出 + 利润(可选) const sellerTxCount = countTruthy([needBribeTx, approvalExists, true, profitNeeded]); // ✅ 并行获取 seller 和 buyer 的 nonce const [sellerNonces, buyerNonce] = await Promise.all([ nonceManager.getNextNonceBatch(seller, sellerTxCount), nonceManager.getNextNonce(buyer) ]); let idx = 0; const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined; if (approvalExists) idx++; const sellerNonce = sellerNonces[idx++]; const profitNonce = profitNeeded ? sellerNonces[idx] : undefined; return { sellerNonce, buyerNonce, bribeNonce, profitNonce }; } // ✅ 并行获取 seller 和 buyer 的 nonce const [sellerNonce, buyerNonce] = await Promise.all([ nonceManager.getNextNonce(seller), nonceManager.getNextNonce(buyer) ]); return { sellerNonce, buyerNonce }; } function calculateProfitAmount(estimatedBNBOut) { if (estimatedBNBOut <= 0n) { return 0n; } return (estimatedBNBOut * BigInt(PROFIT_CONFIG.RATE_BPS_SWAP)) / 10000n; } /** * ✅ 获取 ERC20 代币 → 原生代币(BNB)的报价 * 用于将 ERC20 利润转换为 BNB * 使用共享的报价函数 */ async function getERC20ToNativeQuote(provider, tokenAddress, tokenAmount, version = 'v2', fee) { if (tokenAmount <= 0n) return 0n; const tokenLower = tokenAddress.toLowerCase(); const wbnbLower = WBNB_ADDRESS.toLowerCase(); // 如果代币本身就是 WBNB,直接返回 if (tokenLower === wbnbLower) { return tokenAmount; } // 使用共享的报价函数 if (version === 'v3') { const result = await quoteV3(provider, tokenAddress, WBNB_ADDRESS, tokenAmount, 'BSC', fee); if (result.amountOut > 0n) { console.log(`[getERC20ToNativeQuote] V3 报价成功: ${ethers.formatEther(result.amountOut)} BNB`); return result.amountOut; } console.warn(`[getERC20ToNativeQuote] V3 报价失败`); return 0n; } // V2 报价 const result = await quoteV2(provider, tokenAddress, WBNB_ADDRESS, tokenAmount, 'BSC'); if (result.amountOut > 0n) { console.log(`[getERC20ToNativeQuote] V2 报价成功: ${ethers.formatEther(result.amountOut)} BNB`); return result.amountOut; } console.warn(`[getERC20ToNativeQuote] V2 报价失败`); return 0n; } async function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress }) { const gasCost = gasLimit * gasPrice; if (sameAddress) { // 同一地址:需要足够的余额支付两笔交易 if (useNativeToken) { const requiredCombined = buyAmountBNB + FLAT_FEE * 2n + gasCost * 2n; if (buyerBalance < requiredCombined) { throw new Error(`账户余额不足:\n - 需要: ${ethers.formatEther(requiredCombined)} BNB(含两笔Gas与两笔手续费)\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`); } } else { // ERC20:检查代币余额 + BNB Gas 余额 const requiredToken = buyAmountBNB + FLAT_FEE * 2n; if (buyerBalance < requiredToken) { throw new Error(`账户代币余额不足:\n - 需要: ${ethers.formatUnits(requiredToken, quoteTokenDecimals)}\n - 实际: ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`); } // 检查 BNB Gas if (provider && buyerAddress) { const bnbBalance = await provider.getBalance(buyerAddress); const requiredGas = gasCost * 2n; if (bnbBalance < requiredGas) { throw new Error(`账户 BNB 余额不足 (用于支付 Gas):\n - 需要: ${ethers.formatEther(requiredGas)} BNB\n - 实际: ${ethers.formatEther(bnbBalance)} BNB`); } } } return; } // 不同地址 if (useNativeToken) { const requiredBuyer = buyAmountBNB + FLAT_FEE + reserveGas; if (buyerBalance < requiredBuyer) { throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(requiredBuyer)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`); } } // ERC20 余额已在 calculateBuyerBudget 中检查过 } function countTruthy(values) { return values.filter(Boolean).length; } /** * PancakeSwap V2/V3 通用捆绑换手(Merkle Bundle) * * 功能:钱包A卖出代币 → 钱包B买入相同数量 → 原子执行 * 适用于所有已迁移到外盘的代币(不区分Four/Flap) */ import { ethers, Contract, Wallet } from 'ethers'; import { calculateSellAmount } from '../utils/swap-helpers.js'; import { NonceManager, getDeadline, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../utils/bundle-helpers.js'; import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../utils/constants.js'; import { quoteV2, quoteV3 } from '../utils/quote-helpers.js'; import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_BALANCE_ABI } from '../abis/common.js'; /** * 获取 Gas Limit */ function getGasLimit(config, defaultGas = 800000) { if (config.gasLimit !== undefined) { return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit); } const multiplier = config.gasLimitMultiplier ?? 1.0; const calculatedGas = Math.ceil(defaultGas * multiplier); return BigInt(calculatedGas); } async function getGasPrice(provider, config) { const feeData = await provider.getFeeData(); let gasPrice = feeData.gasPrice || ethers.parseUnits('3', 'gwei'); if (config.minGasPriceGwei) { const minGas = ethers.parseUnits(config.minGasPriceGwei.toString(), 'gwei'); if (gasPrice < minGas) gasPrice = minGas; } if (config.maxGasPriceGwei) { const maxGas = ethers.parseUnits(config.maxGasPriceGwei.toString(), 'gwei'); if (gasPrice > maxGas) gasPrice = maxGas; } return gasPrice; } // ✅ ABI 别名(从公共模块导入) const PANCAKE_V2_ROUTER_ABI = V2_ROUTER_ABI; const PANCAKE_V3_ROUTER_ABI = V3_ROUTER02_ABI; const ERC20_BALANCE_OF_ABI = ERC20_BALANCE_ABI; // ✅ 使用官方 PancakeSwap Router 地址 const PANCAKE_V2_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV2Router; const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router; // 常量 const FLAT_FEE = 0n; const WBNB_ADDRESS = ADDRESSES.BSC.WBNB; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; // ✅ ERC20_BALANCE_OF_ABI 已在文件开头定义 /** * PancakeSwap捆绑换手(V2/V3通用) * ✅ 支持 quoteToken:传入 USDT 等地址时,卖出得到该代币,买入使用该代币 */ export async function pancakeBundleSwapMerkle(params) { const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces } = params; // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT) const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS; const context = createPancakeContext(config); const seller = new Wallet(sellerPrivateKey, context.provider); const buyer = new Wallet(buyerPrivateKey, context.provider); const sameAddress = seller.address.toLowerCase() === buyer.address.toLowerCase(); // ✅ 提前创建 NonceManager,供所有交易共享 const nonceManager = new NonceManager(context.provider); const finalGasLimit = getGasLimit(config); const txType = config.txType ?? 0; // ✅ 并行获取 gasPrice 和卖出数量 const [gasPrice, sellAmountResult] = await Promise.all([ getGasPrice(context.provider, config), calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage) ]); const { amount: sellAmountWei, decimals } = sellAmountResult; const quoteResult = await quoteSellOutput({ routeParams, sellAmountWei, provider: context.provider }); const buyerBudget = await calculateBuyerBudget({ buyer, quotedBNBOut: quoteResult.estimatedBNBOut, reserveGasBNB: config.reserveGasBNB, useNativeToken, quoteToken, quoteTokenDecimals, provider: context.provider }); const swapUnsigned = await buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB: buyerBudget.buyAmountBNB, buyer, seller, tokenAddress, useNativeToken }); // ✅ 修复:利润计算应基于 BNB 数量,不是 ERC20 数量 // 如果输出是原生代币(BNB),直接使用报价结果 // 如果输出是 ERC20(如 USDT),需要先转换为 BNB 等值 let profitAmount; if (useNativeToken) { // 输出是 BNB,直接计算利润 profitAmount = calculateProfitAmount(quoteResult.estimatedBNBOut); console.log(`[pancakeBundleSwapMerkle] 原生代币利润: ${ethers.formatEther(profitAmount)} BNB`); } else { // 输出是 ERC20,需要先获取 ERC20 → BNB 的报价 const version = routeParams.routeType === 'v2' ? 'v2' : 'v3'; const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined; const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, quoteResult.estimatedBNBOut, // 这实际上是 ERC20 数量 version, fee); profitAmount = calculateProfitAmount(estimatedBNBValue); console.log(`[pancakeBundleSwapMerkle] ERC20→BNB 报价: ${ethers.formatUnits(quoteResult.estimatedBNBOut, quoteTokenDecimals)} ${quoteToken?.slice(0, 10)}... → ${ethers.formatEther(estimatedBNBValue)} BNB, 利润: ${ethers.formatEther(profitAmount)} BNB`); } // ✅ 获取贿赂金额 const bribeAmount = config.bribeAmount && config.bribeAmount > 0 ? ethers.parseEther(String(config.bribeAmount)) : 0n; const needBribeTx = bribeAmount > 0n; // ✅ 使用共享的 NonceManager 规划 nonce // 如果前端传入了 startNonces,直接使用(性能优化) const noncePlan = startNonces && startNonces.length >= (sameAddress ? 1 : 2) ? buildNoncePlanFromNonces(startNonces, sameAddress, false, profitAmount > 0n, needBribeTx) : await planNonces({ seller, buyer, sameAddress, approvalExists: false, // ✅ 已移除授权 profitNeeded: profitAmount > 0n, needBribeTx, nonceManager }); // ✅ 并行签名所有交易 const signPromises = []; // 贿赂交易 if (needBribeTx && noncePlan.bribeNonce !== undefined) { signPromises.push(seller.signTransaction({ to: BLOCKRAZOR_BUILDER_EOA, value: bribeAmount, nonce: noncePlan.bribeNonce, gasPrice, gasLimit: 21000n, chainId: context.chainId, type: txType }).then(tx => ({ type: 'bribe', tx }))); } // 卖出交易 signPromises.push(seller.signTransaction({ ...swapUnsigned.sellUnsigned, from: seller.address, nonce: noncePlan.sellerNonce, gasLimit: finalGasLimit, gasPrice, chainId: context.chainId, type: txType }).then(tx => ({ type: 'sell', tx }))); // 买入交易 signPromises.push(buyer.signTransaction({ ...swapUnsigned.buyUnsigned, from: buyer.address, nonce: noncePlan.buyerNonce, gasLimit: finalGasLimit, gasPrice, chainId: context.chainId, type: txType }).then(tx => ({ type: 'buy', tx }))); // ✅ 并行执行所有签名 const signedResults = await Promise.all(signPromises); // 按类型提取结果 const bribeTx = signedResults.find(r => r.type === 'bribe')?.tx || null; const signedSell = signedResults.find(r => r.type === 'sell').tx; const signedBuy = signedResults.find(r => r.type === 'buy').tx; nonceManager.clearTemp(); validateFinalBalances({ sameAddress, buyerBalance: buyerBudget.buyerBalance, buyAmountBNB: buyerBudget.buyAmountBNB, reserveGas: buyerBudget.reserveGas, gasLimit: finalGasLimit, gasPrice, useNativeToken, quoteTokenDecimals, provider: context.provider, buyerAddress: buyer.address }); // ✅ 组装顺序:贿赂 → 卖出 → 买入 const signedTransactions = []; if (bribeTx) signedTransactions.push(bribeTx); signedTransactions.push(signedSell, signedBuy); // ✅ 利润多跳转账(强制 2 跳中转) if (profitAmount > 0n && noncePlan.profitNonce !== undefined) { const profitHopResult = await buildProfitHopTransactions({ provider: context.provider, payerWallet: seller, profitAmount, profitRecipient: PROFIT_CONFIG.RECIPIENT, hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: context.chainId, txType, startNonce: noncePlan.profitNonce }); signedTransactions.push(...profitHopResult.signedTransactions); } return { signedTransactions, metadata: { sellerAddress: seller.address, buyerAddress: buyer.address, sellAmount: ethers.formatUnits(sellAmountWei, decimals), buyAmount: useNativeToken ? ethers.formatEther(buyerBudget.buyAmountBNB) : ethers.formatUnits(buyerBudget.buyAmountBNB, quoteTokenDecimals), profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined } }; } /** * PancakeSwap 批量换手(一卖多买) * * 功能:主钱包一次卖出 → 多个子钱包同时买入 → 在同一个区块中完成 * 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易) * * 交易顺序:[授权(可选)] → [卖出] → [买入1, 买入2, ..., 买入N] → [利润] */ export async function pancakeBatchSwapMerkle(params) { const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces } = params; // ✅ 校验买方数量(最多 24 个) const MAX_BUYERS = 24; if (buyerPrivateKeys.length === 0) { throw new Error('至少需要一个买方钱包'); } if (buyerPrivateKeys.length > MAX_BUYERS) { throw new Error(`买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`); } const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS; const context = createPancakeContext(config); const seller = new Wallet(sellerPrivateKey, context.provider); const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider)); // ✅ 创建共享资源 const nonceManager = new NonceManager(context.provider); const finalGasLimit = getGasLimit(config); const txType = config.txType ?? 0; // ✅ 并行获取 gasPrice 和卖出数量 const [gasPrice, sellAmountResult] = await Promise.all([ getGasPrice(context.provider, config), calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage) ]); const { amount: sellAmountWei, decimals } = sellAmountResult; // ✅ 获取卖出报价 const quoteResult = await quoteSellOutput({ routeParams, sellAmountWei, provider: context.provider }); const estimatedBNBOut = quoteResult.estimatedBNBOut; // ✅ 计算每个买方的买入金额(已移除滑点保护:直接使用报价金额) let buyAmountsWei; const totalBuyAmount = estimatedBNBOut; if (buyerAmounts && buyerAmounts.length === buyers.length) { // 方式1:使用指定的买入金额(USDT) buyAmountsWei = buyerAmounts.map(amt => useNativeToken ? ethers.parseEther(amt) : ethers.parseUnits(amt, quoteTokenDecimals)); } else if (params.buyerRatios && params.buyerRatios.length === buyers.length) { // ✅ 方式2:按比例分配卖出所得 // buyerRatios 如 [0.3, 0.5, 0.2] 表示第一个买方分 30%,第二个 50%,第三个 20% buyAmountsWei = params.buyerRatios.map((ratio, index) => { // 按比例计算每个买方的金额 const amount = (totalBuyAmount * BigInt(Math.round(ratio * 10000))) / 10000n; return amount; }); } else { // 方式3:平均分配 const amountPerBuyer = totalBuyAmount / BigInt(buyers.length); buyAmountsWei = buyers.map(() => amountPerBuyer); } // ✅ 并行验证所有买方余额 const reserveGas = ethers.parseEther((config.reserveGasBNB ?? 0.0005).toString()); const erc20Contract = useNativeToken ? null : new Contract(quoteToken, ERC20_BALANCE_OF_ABI, context.provider); await Promise.all(buyers.map(async (buyer, i) => { const buyAmount = buyAmountsWei[i]; if (useNativeToken) { const buyerBalance = await buyer.provider.getBalance(buyer.address); const required = buyAmount + FLAT_FEE + reserveGas; if (buyerBalance < required) { throw new Error(`买方 ${i + 1} 余额不足: 需要 ${ethers.formatEther(required)}, 实际 ${ethers.formatEther(buyerBalance)}`); } } else { const buyerBalance = await erc20Contract.balanceOf(buyer.address); const required = buyAmount + FLAT_FEE; if (buyerBalance < required) { throw new Error(`买方 ${i + 1} 代币余额不足: 需要 ${ethers.formatUnits(required, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`); } } })); // ✅ 构建卖出交易(使用官方 Router) const deadline = getDeadline(); const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI); let sellUnsigned; if (routeParams.routeType === 'v2') { const { v2Path } = routeParams; const v2RouterSeller = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller); if (useNativeToken) { // ✅ 原生代币模式(BNB):Token → WBNB → BNB sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline); } else { // ✅ ERC20 模式(USDT):Token → USDT sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline); } } else if (routeParams.routeType === 'v3-single') { const { v3TokenIn, v3TokenOut, v3Fee } = routeParams; const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller); const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{ tokenIn: v3TokenIn, tokenOut: v3TokenOut, fee: v3Fee, recipient: useNativeToken ? PANCAKE_V3_ROUTER_ADDRESS : seller.address, amountIn: sellAmountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }]); if (useNativeToken) { // 原生代币:需要 unwrap WBNB const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]); sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]); } else { // ERC20:直接接收代币 sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData]); } } else { throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳'); } // ✅ 并行构建多个买入交易(使用官方 Router) const buyUnsignedList = await Promise.all(buyers.map(async (buyer, i) => { const buyAmount = buyAmountsWei[i]; // ✅ ERC20 模式:value = 0(通过代币授权支付) const buyValue = useNativeToken ? buyAmount + FLAT_FEE : 0n; if (routeParams.routeType === 'v2') { const { v2Path } = routeParams; const reversePath = [...v2Path].reverse(); const v2RouterBuyer = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer); if (useNativeToken) { // ✅ 原生代币模式(BNB):BNB → WBNB → Token return await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, reversePath, buyer.address, deadline, { value: buyValue }); } else { // ✅ ERC20 模式(USDT):USDT → Token return await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline); } } else if (routeParams.routeType === 'v3-single') { const { v3TokenIn, v3TokenOut, v3Fee } = routeParams; const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer); const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{ tokenIn: v3TokenOut, tokenOut: v3TokenIn, fee: v3Fee, recipient: buyer.address, amountIn: buyAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }]); return await v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue }); } else { throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳'); } })); // ✅ 获取贿赂金额 const bribeAmount = config.bribeAmount && config.bribeAmount > 0 ? ethers.parseEther(String(config.bribeAmount)) : 0n; // ✅ 规划 Nonce(贿赂和利润都由卖方发送) // 卖方: [贿赂(可选)] → [卖出] → [利润(可选)] let bribeNonce; let sellNonce; let buyerNonces; // ✅ 如果前端传入了 startNonces,直接使用(性能优化) if (startNonces && startNonces.length >= (1 + buyers.length)) { let sellerIdx = 0; if (bribeAmount > 0n) { bribeNonce = startNonces[0] + sellerIdx++; } sellNonce = startNonces[0] + sellerIdx; buyerNonces = startNonces.slice(1); } else { if (bribeAmount > 0n) { bribeNonce = await nonceManager.getNextNonce(seller); } sellNonce = await nonceManager.getNextNonce(seller); buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer))); } // ✅ 贿赂交易放在首位(由卖方发送) let bribeTx = null; if (bribeAmount > 0n && bribeNonce !== undefined) { bribeTx = await seller.signTransaction({ to: BLOCKRAZOR_BUILDER_EOA, value: bribeAmount, nonce: bribeNonce, gasPrice, gasLimit: 21000n, chainId: context.chainId, type: txType }); } // ✅ 修复:利润计算应基于 BNB 数量,不是 ERC20 数量 let profitAmount; if (useNativeToken) { profitAmount = calculateProfitAmount(estimatedBNBOut); console.log(`[pancakeBatchSwapMerkle] 原生代币利润: ${ethers.formatEther(profitAmount)} BNB`); } else { const version = routeParams.routeType === 'v2' ? 'v2' : 'v3'; const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined; const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedBNBOut, version, fee); profitAmount = calculateProfitAmount(estimatedBNBValue); console.log(`[pancakeBatchSwapMerkle] ERC20→BNB 报价: ${ethers.formatUnits(estimatedBNBOut, quoteTokenDecimals)} → ${ethers.formatEther(estimatedBNBValue)} BNB, 利润: ${ethers.formatEther(profitAmount)} BNB`); } // 计算利润 nonce const profitNonce = profitAmount > 0n ? (startNonces && startNonces.length >= 1 ? sellNonce + 1 : await nonceManager.getNextNonce(seller)) : undefined; nonceManager.clearTemp(); // ✅ 并行签名所有交易 // 1. 签名卖出交易 const signedSellPromise = seller.signTransaction({ ...sellUnsigned, from: seller.address, nonce: sellNonce, gasLimit: finalGasLimit, gasPrice, chainId: context.chainId, type: txType }); // 2. 并行签名所有买入交易 const signedBuyPromises = buyers.map((buyer, i) => buyer.signTransaction({ ...buyUnsignedList[i], from: buyer.address, nonce: buyerNonces[i], gasLimit: finalGasLimit, gasPrice, chainId: context.chainId, type: txType })); // 3. 等待所有签名完成 const [signedSell, ...signedBuys] = await Promise.all([ signedSellPromise, ...signedBuyPromises ]); // 4. 按顺序组装交易数组:贿赂 → 卖出 → 买入 const signedTransactions = []; if (bribeTx) signedTransactions.push(bribeTx); // 贿赂(首位) signedTransactions.push(signedSell); // 卖出 signedTransactions.push(...signedBuys); // 多个买入 // ✅ 利润多跳转账(强制 2 跳中转) if (profitAmount > 0n && profitNonce !== undefined) { const profitHopResult = await buildProfitHopTransactions({ provider: context.provider, payerWallet: seller, profitAmount, profitRecipient: PROFIT_CONFIG.RECIPIENT, hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: context.chainId, txType, startNonce: profitNonce }); signedTransactions.push(...profitHopResult.signedTransactions); } return { signedTransactions, metadata: { sellerAddress: seller.address, buyerAddresses: buyers.map(b => b.address), sellAmount: ethers.formatUnits(sellAmountWei, decimals), buyAmounts: buyAmountsWei.map(amt => useNativeToken ? ethers.formatEther(amt) : ethers.formatUnits(amt, quoteTokenDecimals)), profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined } }; } /** * PancakeSwap 快捷批量换手(资金自动流转) * * 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润] * * 特点: * - 子钱包不需要预先有余额 * - 资金来自主钱包卖出代币所得 * - 提升资金利用率 * - 支持 BNB 和 ERC20(如 USDT)两种模式 * * 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制) */ export async function pancakeQuickBatchSwapMerkle(params) { const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces } = params; // ✅ 判断是否使用原生代币 const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS; const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'.toLowerCase(); // ✅ 校验买方数量(子钱包已预留 BNB,不需要主钱包转 Gas) // BNB 模式:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23 // ERC20 模式:贿赂(1) + 卖出(1) + ERC20转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23 const MAX_BUYERS = 23; if (buyerPrivateKeys.length === 0) { throw new Error('至少需要一个买方钱包'); } if (buyerPrivateKeys.length > MAX_BUYERS) { const mode = useNativeToken ? 'BNB' : 'ERC20'; throw new Error(`资金利用率模式(${mode})买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`); } // ✅ 校验分配模式 if (!buyerRatios && !buyerAmounts) { throw new Error('必须提供 buyerRatios 或 buyerAmounts'); } if (buyerRatios && buyerRatios.length !== buyerPrivateKeys.length) { throw new Error(`buyerRatios 长度 (${buyerRatios.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`); } if (buyerAmounts && buyerAmounts.length !== buyerPrivateKeys.length) { throw new Error(`buyerAmounts 长度 (${buyerAmounts.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`); } const context = createPancakeContext(config); const seller = new Wallet(sellerPrivateKey, context.provider); const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider)); // ✅ 校验卖出路径输出代币是否匹配 let sellOutputToken; if (routeParams.routeType === 'v2') { const { v2Path } = routeParams; sellOutputToken = v2Path[v2Path.length - 1]; } else if (routeParams.routeType === 'v3-single') { const { v3TokenOut } = routeParams; sellOutputToken = v3TokenOut; } else if (routeParams.routeType === 'v3-multi') { const { v2Path } = routeParams; if (v2Path && v2Path.length > 0) { sellOutputToken = v2Path[v2Path.length - 1]; } } // 校验输出代币 if (useNativeToken) { // 原生代币模式:输出必须是 WBNB if (!sellOutputToken || sellOutputToken.toLowerCase() !== WBNB) { throw new Error(`原生代币模式要求卖出路径以 WBNB 结尾(当前输出: ${sellOutputToken || '未知'})。` + `请切换交易对或使用 ERC20 模式。`); } } else { // ERC20 模式:输出必须是指定的 quoteToken if (!sellOutputToken || sellOutputToken.toLowerCase() !== quoteToken.toLowerCase()) { throw new Error(`ERC20 模式要求卖出路径以 ${quoteToken} 结尾(当前输出: ${sellOutputToken || '未知'})。`); } } // ✅ 创建共享资源 const nonceManager = new NonceManager(context.provider); const finalGasLimit = getGasLimit(config); const txType = config.txType ?? 0; const ERC20_TRANSFER_GAS = 65000n; // ERC20 transfer 的 gas 限制 // ✅ 并行获取 gasPrice 和卖出数量 const [gasPrice, sellAmountResult] = await Promise.all([ getGasPrice(context.provider, config), calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage) ]); const { amount: sellAmountWei, decimals } = sellAmountResult; // ✅ 调试日志 console.log(`[pancakeQuickBatchSwapMerkle] 模式: ${useNativeToken ? 'BNB' : 'ERC20'}`); console.log(`[pancakeQuickBatchSwapMerkle] 卖出数量: ${ethers.formatUnits(sellAmountWei, decimals)}`); console.log(`[pancakeQuickBatchSwapMerkle] routeParams:`, JSON.stringify({ routeType: routeParams.routeType, v2Path: routeParams.v2Path, v3TokenIn: routeParams.v3TokenIn, v3TokenOut: routeParams.v3TokenOut, v3Fee: routeParams.v3Fee })); // ✅ 获取卖出报价 const quoteResult = await quoteSellOutput({ routeParams, sellAmountWei, provider: context.provider }); const estimatedOutput = quoteResult.estimatedBNBOut; const outputFormatted = useNativeToken ? ethers.formatEther(estimatedOutput) : ethers.formatUnits(estimatedOutput, quoteTokenDecimals); console.log(`[pancakeQuickBatchSwapMerkle] 预估卖出所得: ${outputFormatted} ${useNativeToken ? 'BNB' : 'ERC20'}`); // ✅ 计算利润(万分之六) let profitAmount; if (useNativeToken) { profitAmount = calculateProfitAmount(estimatedOutput); } else { // ERC20 模式:需要将 ERC20 价值转换为 BNB const version = routeParams.routeType === 'v2' ? 'v2' : 'v3'; const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined; const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedOutput, version, fee); profitAmount = calculateProfitAmount(estimatedBNBValue); console.log(`[pancakeQuickBatchSwapMerkle] ERC20→BNB 报价: ${outputFormatted} → ${ethers.formatEther(estimatedBNBValue)} BNB`); } const distributableAmount = estimatedOutput - (useNativeToken ? profitAmount : 0n); // ✅ 计算每个买方分到的金额 let transferAmountsWei; if (buyerAmounts && buyerAmounts.length === buyers.length) { // 数量模式 transferAmountsWei = buyerAmounts.map(amt => useNativeToken ? ethers.parseEther(amt) : ethers.parseUnits(amt, quoteTokenDecimals)); const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n); if (totalTransfer > distributableAmount) { const formatted = useNativeToken ? ethers.formatEther(distributableAmount) : ethers.formatUnits(distributableAmount, quoteTokenDecimals); throw new Error(`指定的买入总金额超过可分配金额 (${formatted})`); } } else if (buyerRatios && buyerRatios.length === buyers.length) { // 比例模式 transferAmountsWei = buyerRatios.map(ratio => { return (distributableAmount * BigInt(Math.round(ratio * 10000))) / 10000n; }); } else { throw new Error('必须提供 buyerRatios 或 buyerAmounts'); } // ✅ 获取贿赂金额 const bribeAmount = config.bribeAmount && config.bribeAmount > 0 ? ethers.parseEther(String(config.bribeAmount)) : 0n; // ✅ 验证主钱包余额 const sellerBalance = await seller.provider.getBalance(seller.address); let sellerGasCost; let sellerRequired; if (useNativeToken) { // BNB 模式:贿赂(21000) + 卖出(gasLimit) + N个原生转账(21000 each) + 利润(21000) sellerGasCost = gasPrice * (21000n + finalGasLimit + 21000n * BigInt(buyers.length) + 21000n); sellerRequired = bribeAmount + sellerGasCost; } else { // ERC20 模式:子钱包已预留 BNB,不需要主钱包转 Gas // 卖方 Gas: 贿赂(21000) + 卖出(gasLimit) + N个ERC20转账(65000 each) + 利润(21000) sellerGasCost = gasPrice * (21000n + finalGasLimit + ERC20_TRANSFER_GAS * BigInt(buyers.length) + 21000n); sellerRequired = bribeAmount + sellerGasCost; } if (sellerBalance < sellerRequired) { throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`); } // ==================== 规划 Nonce ==================== // ✅ 如果前端传入了 startNonces,直接使用(性能优化) let sellerNonce = startNonces && startNonces.length > 0 ? startNonces[0] : await nonceManager.getNextNonce(seller); const deadline = Math.floor(Date.now() / 1000) + 600; // ==================== 1. 贿赂交易 ==================== let bribeTx = null; if (bribeAmount > 0n) { bribeTx = await seller.signTransaction({ to: BLOCKRAZOR_BUILDER_EOA, value: bribeAmount, nonce: sellerNonce++, gasPrice, gasLimit: 21000n, chainId: context.chainId, type: txType }); console.log(`[pancakeQuickBatchSwapMerkle] 贿赂交易已签名`); } // ==================== 2. 卖出交易(使用官方 Router)==================== const v3RouterIface2 = new ethers.Interface(PANCAKE_V3_ROUTER_ABI); let sellUnsigned; if (routeParams.routeType === 'v2') { const { v2Path } = routeParams; const v2RouterSeller = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller); if (useNativeToken) { // ✅ 原生代币模式(BNB):Token → WBNB → BNB sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline); } else { // ✅ ERC20 模式(USDT):Token → USDT sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline); } } else if (routeParams.routeType === 'v3-single') { const { v3TokenIn, v3TokenOut, v3Fee } = routeParams; const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller); const sellSwapData = v3RouterIface2.encodeFunctionData('exactInputSingle', [{ tokenIn: v3TokenIn, tokenOut: v3TokenOut, fee: v3Fee, recipient: useNativeToken ? PANCAKE_V3_ROUTER_ADDRESS : seller.address, amountIn: sellAmountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }]); if (useNativeToken) { // 原生代币:需要 unwrap WBNB const sellUnwrapData = v3RouterIface2.encodeFunctionData('unwrapWETH9', [0n, seller.address]); sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]); } else { // ERC20:直接接收代币 sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData]); } } else { throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳'); } const signedSell = await seller.signTransaction({ ...sellUnsigned, from: seller.address, nonce: sellerNonce++, gasLimit: finalGasLimit, gasPrice, chainId: context.chainId, type: txType }); console.log(`[pancakeQuickBatchSwapMerkle] 卖出交易已签名`); // ==================== 3. 转账交易(并行签名)==================== const buyerGasCost = gasPrice * finalGasLimit; // ✅ 预分配 nonce,然后并行签名 const transferNonces = buyers.map((_, i) => sellerNonce + i); sellerNonce += buyers.length; // 更新 sellerNonce let transferTxs; if (useNativeToken) { // ✅ 原生代币模式:直接 BNB 转账(并行签名) transferTxs = await Promise.all(buyers.map((buyer, i) => { const transferValue = transferAmountsWei[i] + FLAT_FEE + buyerGasCost; return seller.signTransaction({ to: buyer.address, value: transferValue, nonce: transferNonces[i], gasPrice, gasLimit: 21000n, chainId: context.chainId, type: txType }); })); } else { // ✅ ERC20 模式:ERC20 transfer(并行签名) const erc20Interface = new ethers.Interface([ 'function transfer(address to, uint256 amount) returns (bool)' ]); transferTxs = await Promise.all(buyers.map((buyer, i) => { const transferData = erc20Interface.encodeFunctionData('transfer', [ buyer.address, transferAmountsWei[i] ]); return seller.signTransaction({ to: quoteToken, data: transferData, value: 0n, nonce: transferNonces[i], gasPrice, gasLimit: ERC20_TRANSFER_GAS, chainId: context.chainId, type: txType }); })); } console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`); // ==================== 4. 买入交易 ==================== // ✅ 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始) const buyerNonces = startNonces && startNonces.length > 1 ? startNonces.slice(1) : await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer))); const signedBuys = await Promise.all(buyers.map(async (buyer, i) => { const buyAmount = transferAmountsWei[i]; // ✅ ERC20 模式:value = 0(通过代币授权支付) const buyValue = useNativeToken ? buyAmount + FLAT_FEE : 0n; let buyUnsigned; if (routeParams.routeType === 'v2') { const { v2Path } = routeParams; const reversePath = [...v2Path].reverse(); const v2RouterBuyer = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer); if (useNativeToken) { // ✅ 原生代币模式(BNB):BNB → WBNB → Token buyUnsigned = await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, reversePath, buyer.address, deadline, { value: buyValue }); } else { // ✅ ERC20 模式(USDT):USDT → Token buyUnsigned = await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline); } } else if (routeParams.routeType === 'v3-single') { const { v3TokenIn, v3TokenOut, v3Fee } = routeParams; const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer); const buySwapData = v3RouterIface2.encodeFunctionData('exactInputSingle', [{ tokenIn: v3TokenOut, tokenOut: v3TokenIn, fee: v3Fee, recipient: buyer.address, amountIn: buyAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }]); buyUnsigned = await v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue }); } else { throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳'); } return buyer.signTransaction({ ...buyUnsigned, from: buyer.address, nonce: buyerNonces[i], gasLimit: finalGasLimit, gasPrice, chainId: context.chainId, type: txType }); })); console.log(`[pancakeQuickBatchSwapMerkle] ${signedBuys.length} 笔买入交易已签名`); nonceManager.clearTemp(); // ==================== 组装交易数组 ==================== // BNB 模式:贿赂 → 卖出 → 转账 → 买入 → 利润多跳 // ERC20 模式:贿赂 → 卖出 → ERC20转账 → BNB Gas转账 → 买入 → 利润多跳 const signedTransactions = []; if (bribeTx) signedTransactions.push(bribeTx); signedTransactions.push(signedSell); signedTransactions.push(...transfe