four-flap-meme-sdk
Version:
SDK for Flap bonding curve and four.meme TokenManager
1,126 lines • 56.7 kB
JavaScript
// ==================== 工具函数 ====================
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