UNPKG

four-flap-meme-sdk

Version:

SDK for Flap bonding curve and four.meme TokenManager

1,114 lines 67.2 kB
import { ethers, Wallet } from 'ethers'; import { getOptimizedGasPrice, NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../utils/bundle-helpers.js'; import { PROFIT_CONFIG, ZERO_ADDRESS } from '../../utils/constants.js'; import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient } from './config.js'; import { getErc20DecimalsMerkle as _getErc20DecimalsMerkle, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, batchGetBalances as _batchGetBalances, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress } from './internal.js'; // ==================== 本地利润计算(万分之三)==================== /** * 计算利润金额(万分之三) * ✅ 归集和分散专用:3 bps = 0.03% */ function calculateProfit(amount) { const profit = (amount * BigInt(PROFIT_CONFIG.RATE_BPS_CAPITAL)) / 10000n; const remaining = amount - profit; return { profit, remaining }; } // ==================== ERC20 → 原生代币报价 ==================== import { quote, QUOTE_CONFIG } from '../../utils/quote-helpers.js'; import { Helper3 } from '../helper3.js'; import { FlapPortal } from '../../flap/portal.js'; /** 最低利润(Wei):无法获取报价时使用,0.0001 BNB */ const MIN_PROFIT_WEI = 100000000000000n; // 0.0001 BNB = 10^14 wei /** 链 ID → 链名称 映射 */ const CHAIN_ID_TO_NAME = { 56: 'BSC', 143: 'MONAD', 196: 'XLAYER', }; /** 各链的稳定币地址 */ const STABLE_COINS = { BSC: { usdt: '0x55d398326f99059fF775485246999027B3197955', usdc: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', }, MONAD: { // TODO: 添加 Monad 链的稳定币地址 }, XLAYER: { usdt: '0x1E4a5963aBFD975d8c9021ce480b42188849D41d', }, }; /** * 获取 FOUR 内盘代币 → BNB 的报价 */ async function getFourInnerQuote(rpcUrl, tokenAddress, tokenAmount) { try { const helper = Helper3.connectByChain('BSC', rpcUrl); const result = await helper.trySell(tokenAddress, tokenAmount); // result = { tokenManager, quote, funds, fee } // funds 是卖出代币能获得的 BNB 数量 const funds = result.funds; console.log(`[getFourInnerQuote] FOUR 内盘报价成功: ${funds} wei`); return funds; } catch (error) { console.warn(`[getFourInnerQuote] FOUR 内盘报价失败:`, error); return 0n; } } // ✅ ZERO_ADDRESS 从公共模块导入 /** * 获取 FLAP 内盘代币 → 原生代币的报价 * ✅ 与 portal-bundle-merkle/core.ts 使用相同的 quoteExactInput 方法 */ async function getFlapInnerQuote(rpcUrl, chainId, tokenAddress, tokenAmount) { try { // 根据链 ID 确定链名称 const chainName = chainId === 56 ? 'BSC' : chainId === 143 ? 'MONAD' : chainId === 196 ? 'XLAYER' : null; if (!chainName) { console.warn(`[getFlapInnerQuote] 不支持的链 ID: ${chainId}`); return 0n; } const portal = new FlapPortal({ chain: chainName, rpcUrl }); // ✅ 使用 quoteExactInput 与 core.ts 保持一致 const funds = await portal.quoteExactInput({ inputToken: tokenAddress, outputToken: ZERO_ADDRESS, // 输出原生代币 inputAmount: tokenAmount }); console.log(`[getFlapInnerQuote] FLAP 内盘报价成功: ${funds} wei`); return funds; } catch (error) { console.warn(`[getFlapInnerQuote] FLAP 内盘报价失败:`, error); return 0n; } } /** * 获取 ERC20 代币 → 原生代币的报价 * * @param provider - Provider 实例 * @param tokenAddress - ERC20 代币地址 * @param tokenAmount - 代币数量(wei) * @param chainId - 链 ID(56=BSC, 143=Monad) * @param poolType - 池子类型:'flap'/'four' 内盘,'v2'/'v3' 外盘 * @param quoteToken - 报价代币:'native'(BNB/MON),'usdt','usdc' * @param rpcUrl - RPC URL(内盘报价需要) * @returns 等值的原生代币数量(wei),失败返回 MIN_PROFIT_WEI */ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId, poolType = 'v2', quoteToken = 'native', rpcUrl) { if (tokenAmount <= 0n) return MIN_PROFIT_WEI; // ✅ FOUR 内盘:通过 Helper3.trySell 获取真实报价 if (poolType === 'four') { const url = rpcUrl || provider._getConnection?.()?.url || ''; if (!url) { console.warn(`[getTokenToNativeQuote] FOUR 内盘需要 rpcUrl,使用最低利润`); return MIN_PROFIT_WEI; } const funds = await getFourInnerQuote(url, tokenAddress, tokenAmount); return funds > 0n ? funds : MIN_PROFIT_WEI; } // ✅ FLAP 内盘:通过 FlapPortal.previewSell 获取真实报价 if (poolType === 'flap') { const url = rpcUrl || provider._getConnection?.()?.url || ''; if (!url) { console.warn(`[getTokenToNativeQuote] FLAP 内盘需要 rpcUrl,使用最低利润`); return MIN_PROFIT_WEI; } const funds = await getFlapInnerQuote(url, chainId, tokenAddress, tokenAmount); return funds > 0n ? funds : MIN_PROFIT_WEI; } const chainName = CHAIN_ID_TO_NAME[chainId]; if (!chainName) { console.warn(`[getTokenToNativeQuote] 不支持的链 ID: ${chainId},使用最低利润`); return MIN_PROFIT_WEI; } const chainConfig = QUOTE_CONFIG[chainName]; if (!chainConfig) { console.warn(`[getTokenToNativeQuote] 不支持的链: ${chainName},使用最低利润`); return MIN_PROFIT_WEI; } try { const version = poolType === 'v3' ? 'v3' : 'v2'; let nativeAmount = 0n; if (quoteToken === 'native') { // ✅ 直接路径:TOKEN → WBNB console.log(`[getTokenToNativeQuote] 使用 ${version} 直接路径报价 (TOKEN → WBNB)`); const result = await quote({ provider, tokenIn: tokenAddress, tokenOut: chainConfig.wrappedNative, amountIn: tokenAmount, chain: chainName, version }); nativeAmount = result.amountOut; } else { // ✅ 稳定币路径:TOKEN → USDT/USDC → WBNB(两步报价) const stableCoins = STABLE_COINS[chainName]; const stableCoinAddress = stableCoins?.[quoteToken]; if (!stableCoinAddress) { console.warn(`[getTokenToNativeQuote] 链 ${chainName} 不支持 ${quoteToken},使用最低利润`); return MIN_PROFIT_WEI; } console.log(`[getTokenToNativeQuote] 使用 ${version} 稳定币路径报价 (TOKEN → ${quoteToken.toUpperCase()} → WBNB)`); // 第一步:TOKEN → USDT/USDC const step1 = await quote({ provider, tokenIn: tokenAddress, tokenOut: stableCoinAddress, amountIn: tokenAmount, chain: chainName, version }); if (step1.amountOut <= 0n) { console.warn(`[getTokenToNativeQuote] TOKEN → ${quoteToken.toUpperCase()} 报价失败`); return MIN_PROFIT_WEI; } // 第二步:USDT/USDC → WBNB const step2 = await quote({ provider, tokenIn: stableCoinAddress, tokenOut: chainConfig.wrappedNative, amountIn: step1.amountOut, chain: chainName, version }); nativeAmount = step2.amountOut; } if (nativeAmount > 0n) { console.log(`[getTokenToNativeQuote] 报价成功: ${nativeAmount} wei`); return nativeAmount; } } catch (error) { console.warn(`[getTokenToNativeQuote] 报价失败:`, error); } // ✅ 报价失败,返回最低利润(0.0001 BNB) console.log(`[getTokenToNativeQuote] 无法获取报价,使用最低利润: ${MIN_PROFIT_WEI}`); return MIN_PROFIT_WEI; } /** * 分发(仅签名版本 - 不依赖 Merkle) * ✅ 精简版:只负责签名交易,不提交到 Merkle * ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突 */ export async function disperseWithBundleMerkle(params) { const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native' } = params; // 快速返回空结果 if (!recipients || recipients.length === 0) { return { signedTransactions: [], hopWallets: undefined }; } // 初始化 const chainIdNum = config.chainId ?? 56; const chainName = chainIdNum === 143 ? 'Monad' : chainIdNum === 56 ? 'BSC' : `Chain-${chainIdNum}`; const provider = new ethers.JsonRpcProvider(config.rpcUrl, { chainId: chainIdNum, name: chainName }); const mainWallet = new Wallet(fromPrivateKey, provider); const isNative = _isNativeTokenAddress(tokenAddress); const txType = getTxType(config); // ✅ 优化:预处理数据(不需要 RPC) const normalizedAmounts = items && items.length > 0 ? items.map(it => (typeof it.amount === 'bigint' ? it.amount.toString() : String(it.amount))) : _normalizeAmounts(recipients, amount, amounts); const providedHops = (() => { if (hopPrivateKeys && hopPrivateKeys.length > 0) { if (hopPrivateKeys.length !== recipients.length) { throw new Error(`hopPrivateKeys length (${hopPrivateKeys.length}) must match recipients length (${recipients.length})`); } return hopPrivateKeys.every(h => h.length === 0) ? null : hopPrivateKeys; } if (items && items.length > 0) { const hops = items.map(it => it.hopPrivateKeys ?? []); return hops.every(h => h.length === 0) ? null : hops; } return null; })(); const hopCountInput = (() => { if (items && items.length > 0) { const baseArray = Array.isArray(hopCount) ? hopCount : new Array(recipients.length).fill(hopCount); return items.map((it, i) => (typeof it.hopCount === 'number' ? it.hopCount : (baseArray[i] ?? 0))); } return hopCount; })(); const preparedHops = providedHops ?? _generateHopWallets(recipients.length, hopCountInput); const hasHops = preparedHops !== null; const maxHopCount = hasHops ? Math.max(...preparedHops.map(h => h.length)) : 0; const finalGasLimit = _calculateGasLimit(config, isNative, hasHops, maxHopCount); const nativeGasLimit = (config.prefer21000ForNative ?? false) ? 21000n : finalGasLimit; const signedTxs = []; const extractProfit = shouldExtractProfit(config); let totalProfit = 0n; let totalAmountBeforeProfit = 0n; // ✅ 优化:并行获取 gasPrice 和 nonces(或使用传入的 startNonce) const nonceManager = new NonceManager(provider); if (!preparedHops) { // ========== 无多跳:直接批量转账 ========== const extraTxCount = extractProfit ? 1 : 0; const totalTxCount = recipients.length + extraTxCount; // ✅ 优化:并行获取 gasPrice 和 nonces const [gasPrice, nonces] = await Promise.all([ getOptimizedGasPrice(provider, getGasPriceConfig(config)), startNonce !== undefined ? Promise.resolve(Array.from({ length: totalTxCount }, (_, i) => startNonce + i)) : nonceManager.getNextNonceBatch(mainWallet, totalTxCount) ]); if (isNative) { // ✅ 原生币:先计算所有金额和利润(同步),再并行签名 const txDataList = recipients.map((to, i) => { const originalAmount = ethers.parseEther(normalizedAmounts[i]); totalAmountBeforeProfit += originalAmount; let actualAmount = originalAmount; if (extractProfit) { const { profit, remaining } = calculateProfit(originalAmount); actualAmount = remaining; totalProfit += profit; } return { to, value: actualAmount, nonce: nonces[i] }; }); // ✅ 并行签名所有交易 const txPromises = txDataList.map(({ to, value, nonce }) => mainWallet.signTransaction({ to, value, nonce, gasPrice, gasLimit: nativeGasLimit, chainId: chainIdNum, type: txType })); signedTxs.push(...(await Promise.all(txPromises))); // ✅ 利润多跳转账(强制 2 跳中转) if (extractProfit && totalProfit > 0n) { const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet: mainWallet, profitAmount: totalProfit, profitRecipient: getProfitRecipient(), hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: chainIdNum, txType, startNonce: nonces[recipients.length] }); signedTxs.push(...profitHopResult.signedTransactions); } } else { // ✅ ERC20:并行获取 decimals(传入 chainIdNum 避免 NETWORK_ERROR) const decimals = tokenDecimals ?? (await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum)); const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']); // 先计算所有金额和利润(同步)- ERC20 利润以代币计 let totalTokenProfit = 0n; const txDataList = recipients.map((to, i) => { const originalAmount = ethers.parseUnits(normalizedAmounts[i], decimals); totalAmountBeforeProfit += originalAmount; let actualAmount = originalAmount; if (extractProfit) { const { profit, remaining } = calculateProfit(originalAmount); actualAmount = remaining; totalTokenProfit += profit; // 累计 ERC20 代币利润 } const data = iface.encodeFunctionData('transfer', [to, actualAmount]); return { data, nonce: nonces[i] }; }); // ✅ 获取 ERC20 利润等值的原生代币(BNB)报价 let nativeProfitAmount = 0n; if (extractProfit && totalTokenProfit > 0n) { nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl); totalProfit = nativeProfitAmount; // 更新为原生代币利润 } // ✅ 并行签名所有交易 const txPromises = txDataList.map(({ data, nonce }) => mainWallet.signTransaction({ to: tokenAddress, data, value: 0n, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType })); signedTxs.push(...(await Promise.all(txPromises))); // ✅ 利润多跳转账(强制 2 跳中转)- ERC20 利润转等值原生代币 if (extractProfit && nativeProfitAmount > 0n) { const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet: mainWallet, profitAmount: nativeProfitAmount, profitRecipient: getProfitRecipient(), hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: chainIdNum, txType, startNonce: nonces[recipients.length] }); signedTxs.push(...profitHopResult.signedTransactions); } } } else { // ========== 有多跳:构建跳转链 ========== // ✅ 优化:并行获取 gasPrice 和 decimals const [gasPrice, decimals] = await Promise.all([ getOptimizedGasPrice(provider, getGasPriceConfig(config)), isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum)) ]); const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']); // ✅ Gas limit 设置 // - 原生代币转账:21000(固定) // - ERC20 转账:65000(固定,因为 gas 波动较大,不再使用模拟预估) const nativeTransferGasLimit = 21000n; const erc20TransferGasLimit = 65000n; // ✅ 原生代币多跳的 gas 费(每跳只需要 21000) const nativeHopGasFee = nativeTransferGasLimit * gasPrice; // ✅ ERC20 多跳时,中转钱包需要执行 2 笔交易(转 gas + 转 ERC20) const erc20HopGasFee = erc20TransferGasLimit * gasPrice; const nativeHopGasFeeForErc20 = nativeTransferGasLimit * gasPrice; // ✅ 优化:预先计算主钱包需要的总 nonce 数量 // - 原生代币多跳:主钱包只需要 1 个 nonce(一笔转账包含后续所有 gas) // - ERC20 多跳:主钱包需要 2 个 nonce(转 gas + 转 ERC20) let mainWalletNonceCount = 0; for (let i = 0; i < recipients.length; i++) { const hopChain = preparedHops[i]; if (hopChain.length === 0) { // 无跳转:1 个 nonce mainWalletNonceCount += 1; } else { // 有跳转: // - Native: 1(一笔转账) // - ERC20: 2(转 gas + 转 ERC20) mainWalletNonceCount += isNative ? 1 : 2; } } // 利润交易需要额外 1 个 nonce if (extractProfit) mainWalletNonceCount += 1; // ✅ 一次性获取所有主钱包的 nonces const allMainNonces = startNonce !== undefined ? Array.from({ length: mainWalletNonceCount }, (_, i) => startNonce + i) : await nonceManager.getNextNonceBatch(mainWallet, mainWalletNonceCount); let mainNonceIdx = 0; const txsToSign = []; // ✅ ERC20 多跳:累计代币利润,最后统一转换为原生代币 let totalTokenProfit = 0n; for (let i = 0; i < recipients.length; i++) { const finalRecipient = recipients[i]; const originalAmountWei = isNative ? ethers.parseEther(normalizedAmounts[i]) : ethers.parseUnits(normalizedAmounts[i], decimals); totalAmountBeforeProfit += originalAmountWei; let amountWei = originalAmountWei; if (extractProfit) { const { profit, remaining } = calculateProfit(originalAmountWei); amountWei = remaining; if (isNative) { totalProfit += profit; // 原生币直接累加 } else { totalTokenProfit += profit; // ERC20 累计代币利润 } } const hopChain = preparedHops[i]; if (hopChain.length === 0) { // 无跳转:直接转账 const nonce = allMainNonces[mainNonceIdx++]; if (isNative) { txsToSign.push({ wallet: mainWallet, tx: { to: finalRecipient, value: amountWei, nonce, gasPrice, gasLimit: nativeGasLimit, chainId: chainIdNum, type: txType } }); } else { const data = iface.encodeFunctionData('transfer', [finalRecipient, amountWei]); txsToSign.push({ wallet: mainWallet, tx: { to: tokenAddress, data, value: 0n, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType } }); } continue; } // 有跳转 const fullChain = [mainWallet, ...hopChain.map(pk => new Wallet(pk, provider))]; const addresses = [...fullChain.map(w => w.address), finalRecipient]; if (isNative) { // ========== 原生代币多跳:gas 包含在转账金额中逐层传递 ========== for (let j = 0; j < addresses.length - 1; j++) { const fromWallet = fullChain[j]; const toAddress = addresses[j + 1]; const nonce = j === 0 ? allMainNonces[mainNonceIdx++] : 0; const remainingHops = addresses.length - 2 - j; const additionalGas = nativeHopGasFee * BigInt(remainingHops); const transferValue = amountWei + additionalGas; txsToSign.push({ wallet: fromWallet, tx: { to: toAddress, value: transferValue, nonce, gasPrice, gasLimit: nativeTransferGasLimit, chainId: chainIdNum, type: txType } }); } } else { // ========== ERC20 多跳:gas 也逐层传递(保护隐私)========== // 计算每个中转钱包需要的 gas(从后往前) // - 最后一个中转钱包:只需要 ERC20 转账的 gas // - 其他中转钱包:需要 转 gas 的 gas + ERC20 转账的 gas + 后续所有的 gas const hopGasNeeds = []; for (let j = hopChain.length - 1; j >= 0; j--) { if (j === hopChain.length - 1) { // 最后一个中转钱包:只需要 ERC20 转账的 gas hopGasNeeds.unshift(erc20HopGasFee); } else { // 其他中转钱包:转 gas(21000) + 转 ERC20 + 后续的 gas const nextHopGas = hopGasNeeds[0]; hopGasNeeds.unshift(nativeHopGasFeeForErc20 + erc20HopGasFee + nextHopGas); } } // 第一步:主钱包给第一个中转钱包转 gas(包含所有后续的 gas) const totalGasForFirstHop = hopGasNeeds[0]; txsToSign.push({ wallet: mainWallet, tx: { to: fullChain[1].address, value: totalGasForFirstHop, nonce: allMainNonces[mainNonceIdx++], gasPrice, gasLimit: nativeTransferGasLimit, chainId: chainIdNum, type: txType } }); // 第二步:主钱包给第一个中转钱包转 ERC20 const mainToFirstHopData = iface.encodeFunctionData('transfer', [fullChain[1].address, amountWei]); txsToSign.push({ wallet: mainWallet, tx: { to: tokenAddress, data: mainToFirstHopData, value: 0n, nonce: allMainNonces[mainNonceIdx++], gasPrice, gasLimit: erc20TransferGasLimit, chainId: chainIdNum, type: txType } }); // 第三步:中转钱包逐层传递(gas 和 ERC20) for (let j = 1; j < fullChain.length; j++) { const fromWallet = fullChain[j]; const toAddress = addresses[j + 1]; // 下一个地址(可能是中转钱包或最终接收者) const isLastHop = j === fullChain.length - 1; if (!isLastHop) { // 非最后一个中转钱包:先转 gas 给下一个中转钱包 const gasToTransfer = hopGasNeeds[j]; // 下一个中转钱包需要的 gas txsToSign.push({ wallet: fromWallet, tx: { to: toAddress, value: gasToTransfer, nonce: 0, // 中转钱包的第一笔交易 gasPrice, gasLimit: nativeTransferGasLimit, chainId: chainIdNum, type: txType } }); // 再转 ERC20 const erc20Data = iface.encodeFunctionData('transfer', [toAddress, amountWei]); txsToSign.push({ wallet: fromWallet, tx: { to: tokenAddress, data: erc20Data, value: 0n, nonce: 1, // 中转钱包的第二笔交易 gasPrice, gasLimit: erc20TransferGasLimit, chainId: chainIdNum, type: txType } }); } else { // 最后一个中转钱包:只转 ERC20 给最终接收者 const erc20Data = iface.encodeFunctionData('transfer', [toAddress, amountWei]); txsToSign.push({ wallet: fromWallet, tx: { to: tokenAddress, data: erc20Data, value: 0n, nonce: 0, // 最后一个中转钱包只有一笔交易 gasPrice, gasLimit: erc20TransferGasLimit, chainId: chainIdNum, type: txType } }); } } } } // ✅ ERC20 多跳:获取代币利润等值的原生代币报价 if (!isNative && extractProfit && totalTokenProfit > 0n) { const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl); totalProfit = nativeProfitAmount; // 更新为原生代币利润 } // ✅ 并行签名所有交易 const signedTxsResult = await Promise.all(txsToSign.map(({ wallet, tx }) => wallet.signTransaction(tx))); signedTxs.push(...signedTxsResult); // ✅ 利润多跳转账(强制 2 跳中转) if (extractProfit && totalProfit > 0n) { const profitNonce = allMainNonces[mainNonceIdx++]; const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet: mainWallet, profitAmount: totalProfit, profitRecipient: getProfitRecipient(), hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: chainIdNum, txType, startNonce: profitNonce }); signedTxs.push(...profitHopResult.signedTransactions); } } // ✅ 简化返回:只返回签名交易、多跳钱包和元数据 return { signedTransactions: signedTxs, hopWallets: preparedHops || undefined, metadata: extractProfit ? { totalAmount: ethers.formatEther(totalAmountBeforeProfit), profitAmount: ethers.formatEther(totalProfit), profitRecipient: getProfitRecipient(), recipientCount: recipients.length, isNative, tokenAddress: isNative ? undefined : tokenAddress } : undefined }; } /** * 归集(仅签名版本 - 不依赖 Merkle) * ✅ 精简版:只负责签名交易,不提交到 Merkle * ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突 */ export async function sweepWithBundleMerkle(params) { const { sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native' } = params; // 快速返回空结果 if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) { return { signedTransactions: [], hopWallets: undefined }; } // 初始化 const chainIdNum = config.chainId ?? 56; const chainName = chainIdNum === 143 ? 'Monad' : chainIdNum === 56 ? 'BSC' : `Chain-${chainIdNum}`; const provider = new ethers.JsonRpcProvider(config.rpcUrl, { chainId: chainIdNum, name: chainName }); const isNative = _isNativeTokenAddress(tokenAddress); const txType = getTxType(config); // 归集比例处理 const clamp = (n) => (typeof n === 'number' && Number.isFinite(n) ? Math.max(0, Math.min(100, Math.floor(n))) : undefined); const ratio = clamp(ratioPct); // 生成跳转钱包 // 统一 sources 输入 const actualKeys = (() => { if (sources && sources.length > 0) return sources.map(s => s.privateKey); return sourcePrivateKeys; })(); // hop 私钥链优先使用传入 const providedHops = (() => { if (hopPrivateKeys && hopPrivateKeys.length > 0) { if (hopPrivateKeys.length !== actualKeys.length) { throw new Error(`hopPrivateKeys length (${hopPrivateKeys.length}) must match sourcePrivateKeys length (${actualKeys.length})`); } return hopPrivateKeys.every(h => h.length === 0) ? null : hopPrivateKeys; } if (sources && sources.length > 0) { const hops = sources.map(s => s.hopPrivateKeys ?? []); return hops.every(h => h.length === 0) ? null : hops; } return null; })(); // hop 数量数组 const hopCountInput = (() => { if (sources && sources.length > 0) { const baseArray = Array.isArray(hopCount) ? hopCount : new Array(actualKeys.length).fill(hopCount); return sources.map((s, i) => (typeof s.hopCount === 'number' ? s.hopCount : (baseArray[i] ?? 0))); } return hopCount; })(); const preparedHops = providedHops ?? _generateHopWallets(actualKeys.length, hopCountInput); const hasHops = preparedHops !== null; const maxHopCount = hasHops ? Math.max(...preparedHops.map(h => h.length)) : 0; // 智能计算 Gas Limit const finalGasLimit = _calculateGasLimit(config, isNative, hasHops, maxHopCount); const nativeGasLimit = (config.prefer21000ForNative ?? false) ? 21000n : finalGasLimit; const signedTxs = []; // ✅ 利润提取配置 const extractProfit = shouldExtractProfit(config); let totalProfit = 0n; let totalAmountBeforeProfit = 0n; if (!preparedHops) { // ========== 无多跳:直接批量归集 ========== const wallets = actualKeys.map(pk => new Wallet(pk, provider)); const addresses = wallets.map(w => w.address); const nonceManager = new NonceManager(provider); // ✅ 优化:并行获取 gasPrice 和余额 if (isNative) { // ✅ 原生币:并行获取 gasPrice 和余额 const [gasPrice, balances] = await Promise.all([ getOptimizedGasPrice(provider, getGasPriceConfig(config)), _batchGetBalances(provider, addresses) ]); const gasCostBase = nativeGasLimit * gasPrice; const profitTxGas = nativeGasLimit * gasPrice; // ✅ 第一步:计算所有钱包的归集金额和利润,找出归集金额最大的钱包 const sweepAmounts = []; let maxSweepIndex = -1; let maxSweepAmount = 0n; for (let i = 0; i < wallets.length; i++) { const bal = balances[i]; let toSend = 0n; const ratioForI = (() => { if (sources && sources[i] && sources[i].ratioPct !== undefined) return clamp(sources[i].ratioPct); if (ratios && ratios[i] !== undefined) return clamp(ratios[i]); return ratio; })(); const amountStrForI = (() => { if (sources && sources[i] && sources[i].amount !== undefined) return String(sources[i].amount); if (amounts && amounts[i] !== undefined) return String(amounts[i]); return amount !== undefined ? String(amount) : undefined; })(); // ✅ 只有支付者需要 2 笔交易的 gas,其他钱包只需要 1 笔 const gasCost = gasCostBase; // 所有钱包都只计算主转账的 gas if (ratioForI !== undefined) { const want = (bal * BigInt(ratioForI)) / 100n; const maxSendable = bal > gasCost ? (bal - gasCost) : 0n; toSend = want > maxSendable ? maxSendable : want; } else if (amountStrForI && amountStrForI.trim().length > 0) { const amt = ethers.parseEther(amountStrForI); const need = amt + gasCost; if (!skipIfInsufficient || bal >= need) toSend = amt; } sweepAmounts.push(toSend); totalAmountBeforeProfit += toSend; // 找出归集金额最大的钱包 if (toSend > maxSweepAmount) { maxSweepAmount = toSend; maxSweepIndex = i; } // 累计总利润 if (extractProfit && toSend > 0n) { const { profit } = calculateProfit(toSend); totalProfit += profit; } } // ✅ 如果需要提取利润,检查支付者是否有足够余额支付利润转账的额外 gas if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) { const payerBalance = balances[maxSweepIndex]; const payerSweepAmount = sweepAmounts[maxSweepIndex]; const payerNeedGas = gasCostBase + profitTxGas; // 支付者需要 2 笔交易的 gas // 如果支付者余额不足以支付 2 笔交易的 gas,减少其归集金额 if (payerBalance < payerSweepAmount + payerNeedGas) { const maxPayerSweep = payerBalance > payerNeedGas ? payerBalance - payerNeedGas : 0n; sweepAmounts[maxSweepIndex] = maxPayerSweep; // 重新计算总金额和总利润 totalAmountBeforeProfit = sweepAmounts.reduce((sum, amt) => sum + amt, 0n); totalProfit = 0n; for (let i = 0; i < sweepAmounts.length; i++) { if (sweepAmounts[i] > 0n) { totalProfit += calculateProfit(sweepAmounts[i]).profit; } } } } // ✅ 第二步:生成归集交易(扣除利润后的金额) // ✅ 先为支付者预留 nonce,确保 nonce 连续 let payerProfitNonce; if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) { const payerWallet = wallets[maxSweepIndex]; const nonces = await nonceManager.getNextNonceBatch(payerWallet, 2); payerProfitNonce = nonces[1]; // 利润交易的 nonce } // ✅ 优化:批量获取所有钱包的 nonce(JSON-RPC 批量请求) // 过滤出需要归集的钱包 const walletsToSweep = wallets.filter((_, i) => sweepAmounts[i] > 0n && i !== maxSweepIndex); const nonces = walletsToSweep.length > 0 ? await nonceManager.getNextNoncesForWallets(walletsToSweep) : []; let nonceIdx = 0; const txPromises = wallets.map(async (w, i) => { const toSend = sweepAmounts[i]; if (toSend <= 0n) return null; let actualToSend = toSend; if (extractProfit) { // ✅ 如果是支付者,扣除所有利润总和;其他钱包不扣利润,归集干净 if (i === maxSweepIndex && totalProfit > 0n) { actualToSend = toSend - totalProfit; // 支付者扣除所有利润总和 } else { actualToSend = toSend; // 其他钱包不扣利润,归集全部 } } // ✅ 支付者使用预留的第一个 nonce,其他钱包使用批量获取的 nonce let nonce; if (i === maxSweepIndex && payerProfitNonce !== undefined) { nonce = payerProfitNonce - 1; // 使用预留的第一个 nonce } else { nonce = nonces[nonceIdx++]; } const mainTx = await w.signTransaction({ to: target, value: actualToSend, nonce, gasPrice, gasLimit: nativeGasLimit, chainId: chainIdNum, type: txType }); return mainTx; }); const allTxs = (await Promise.all(txPromises)).filter(tx => tx !== null); signedTxs.push(...allTxs); // ✅ 第三步:利润多跳转账(强制 2 跳中转) if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0 && payerProfitNonce !== undefined) { const payerWallet = wallets[maxSweepIndex]; const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet, profitAmount: totalProfit, profitRecipient: getProfitRecipient(), hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: chainIdNum, txType, startNonce: payerProfitNonce }); signedTxs.push(...profitHopResult.signedTransactions); } } else { // ✅ ERC20:并行获取 gasPrice、decimals、余额(传入 chainIdNum 避免 NETWORK_ERROR) const [gasPrice, decimals, balances, bnbBalances] = await Promise.all([ getOptimizedGasPrice(provider, getGasPriceConfig(config)), Promise.resolve(tokenDecimals ?? await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum)), _batchGetBalances(provider, addresses, tokenAddress), config.checkBnbForErc20NoHop ? _batchGetBalances(provider, addresses) : Promise.resolve([]) ]); const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']); const profitTxGas = finalGasLimit * gasPrice; // ✅ 第一步:计算所有钱包的归集金额和利润,找出归集金额最大的钱包 const sweepAmounts = []; let maxSweepIndex = -1; let maxSweepAmount = 0n; for (let i = 0; i < wallets.length; i++) { const bal = balances[i]; let toSend = 0n; const ratioForI = (() => { if (sources && sources[i] && sources[i].ratioPct !== undefined) return clamp(sources[i].ratioPct); if (ratios && ratios[i] !== undefined) return clamp(ratios[i]); return ratio; })(); const amountStrForI = (() => { if (sources && sources[i] && sources[i].amount !== undefined) return String(sources[i].amount); if (amounts && amounts[i] !== undefined) return String(amounts[i]); return amount !== undefined ? String(amount) : undefined; })(); if (ratioForI !== undefined) { toSend = (bal * BigInt(ratioForI)) / 100n; } else if (amountStrForI && amountStrForI.trim().length > 0) { toSend = ethers.parseUnits(amountStrForI, decimals); } if (toSend <= 0n || (skipIfInsufficient && bal < toSend)) { sweepAmounts.push(0n); continue; } // ✅ 检查 BNB 余额(只需主转账的 gas,支付者后面单独检查) const totalGasNeeded = finalGasLimit * gasPrice; if (config.checkBnbForErc20NoHop) { const bnb = bnbBalances[i] ?? 0n; if (skipIfInsufficient && bnb < totalGasNeeded) { sweepAmounts.push(0n); continue; } } sweepAmounts.push(toSend); totalAmountBeforeProfit += toSend; // 找出归集金额最大的钱包 if (toSend > maxSweepAmount) { maxSweepAmount = toSend; maxSweepIndex = i; } // 累计总代币利润(ERC20 归集) if (extractProfit && toSend > 0n) { const { profit } = calculateProfit(toSend); totalProfit += profit; // 先累计代币利润 } } // ✅ ERC20 归集:获取代币利润等值的原生代币(BNB)报价 let nativeProfitAmount = 0n; if (extractProfit && totalProfit > 0n) { nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl); } // ✅ 如果需要提取利润,检查支付者是否有足够 BNB 支付利润转账的额外 gas if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && config.checkBnbForErc20NoHop) { const payerBnbBalance = bnbBalances[maxSweepIndex] ?? 0n; const payerNeedGas = finalGasLimit * gasPrice + profitTxGas + nativeProfitAmount; // 支付者需要 gas + 利润 // 如果支付者 BNB 余额不足,跳过利润提取 if (payerBnbBalance < payerNeedGas) { nativeProfitAmount = 0n; // 跳过利润提取 } } // ✅ 第二步:生成归集交易(ERC20 归集不扣除代币,利润从 BNB 支付) // ✅ 先为支付者预留 nonce,确保 nonce 连续 let payerProfitNonce; if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0) { const payerWallet = wallets[maxSweepIndex]; const nonces = await nonceManager.getNextNonceBatch(payerWallet, 2); payerProfitNonce = nonces[1]; // 利润交易的 nonce } // ✅ 优化:批量获取所有钱包的 nonce(JSON-RPC 批量请求) // 过滤出需要归集的钱包(排除支付者,因为支付者已经预留了 nonce) const walletsToSweepErc20 = wallets.filter((_, i) => sweepAmounts[i] > 0n && i !== maxSweepIndex); const noncesErc20 = walletsToSweepErc20.length > 0 ? await nonceManager.getNextNoncesForWallets(walletsToSweepErc20) : []; let nonceIdxErc20 = 0; const txPromises = wallets.map(async (w, i) => { const toSend = sweepAmounts[i]; if (toSend <= 0n) return null; // ✅ ERC20 归集:全部归集,不扣除代币(利润从 BNB 支付) const actualToSend = toSend; // ✅ 支付者使用预留的第一个 nonce,其他钱包使用批量获取的 nonce let nonce; if (i === maxSweepIndex && payerProfitNonce !== undefined) { nonce = payerProfitNonce - 1; // 使用预留的第一个 nonce } else { nonce = noncesErc20[nonceIdxErc20++]; } const data = iface.encodeFunctionData('transfer', [target, actualToSend]); const mainTx = await w.signTransaction({ to: tokenAddress, data, value: 0n, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType }); return mainTx; }); const allTxs = (await Promise.all(txPromises)).filter(tx => tx !== null); signedTxs.push(...allTxs); // ✅ 第三步:利润多跳转账(强制 2 跳中转)- ERC20 利润转等值原生代币 if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && payerProfitNonce !== undefined) { const payerWallet = wallets[maxSweepIndex]; totalProfit = nativeProfitAmount; // 更新为原生代币利润 const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet, profitAmount: nativeProfitAmount, profitRecipient: getProfitRecipient(), hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: chainIdNum, txType, startNonce: payerProfitNonce }); signedTxs.push(...profitHopResult.signedTransactions); } } } else { // ========== 有多跳:构建跳转链归集 ========== // ✅ 优化版:批量计算 + 批量获取 nonce + 并行签名 const sourceWallets = actualKeys.map(pk => new Wallet(pk, provider)); const withHopIndexes = []; const withoutHopIndexes = []; for (let i = 0; i < preparedHops.length; i++) { if (preparedHops[i].length > 0) { withHopIndexes.push(i); } else { withoutHopIndexes.push(i); } } const sourceAddresses = sourceWallets.map(w => w.address); // ✅ 优化:并行获取 gasPrice、decimals、余额 const [gasPrice, decimals, balances, bnbBalances] = await Promise.all([ getOptimizedGasPrice(provider, getGasPriceConfig(config)), isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum)), _batchGetBalances(provider, sourceAddresses, tokenAddress), isNative ? Promise.resolve([]) : _batchGetBalances(provider, sourceAddresses) ]); const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']); // ✅ Gas limit 设置(与分散函数保持一致) // - 原生代币转账:21000(固定) // - ERC20 转账:65000(固定,因为 gas 波动较大,不再使用模拟预估) const nativeTransferGasLimit = 21000n; const erc20TransferGasLimit = 65000n; // ✅ 原生代币多跳的 gas 费(每跳只需要 21000) const nativeHopGasFee = nativeTransferGasLimit * gasPrice; // ✅ ERC20 多跳时,中转钱包需要执行 2 笔交易(转 gas + 转 ERC20) const erc20HopGasFee = erc20TransferGasLimit * gasPrice; const nativeHopGasFeeForErc20 = nativeTransferGasLimit * gasPrice; const nonceManager = new NonceManager(provider); // ✅ 用于记录每个钱包的归集金额 const sweepAmounts = new Array(sourceWallets.length).fill(0n); // ✅ 辅助函数:获取比例和金额 const getRatioForI = (i) => { if (sources && sources[i] && sources[i].ratioPct !== undefined) return clamp(sources[i].ratioPct); if (ratios && ratios[i] !== undefined) return clamp(ratios[i]); return ratio; }; const getAmountStrForI = (i) => { if (sources && sources[i] && sources[i].amount !== undefined) return String(sources[i].amount); if (amounts && amounts[i] !== undefined) return String(amounts[i]); return amount !== undefined ? String(amount) : undefined; }; const noHopTxDataList = []; for (const i of withoutHopIndexes) { const bal = balances[i]; let toSend = 0n; const ratioForI = getRatioForI(i); const amountStrForI = getAmountStrForI(i); if (isNative) { const gasCost = nativeGasLimit * gasPrice; if (ratioForI !== undefined) { const want = (bal * BigInt(ratioForI)) / 100n; const maxSendable = bal > gasCost ? (bal - gasCost) : 0n; toSend = want > maxSendable ? maxSendable : want; } else if (amountStrForI && amountStrForI.trim().length > 0) { const amt = ethers.parseEther(amountStrForI); const need = amt + gasCost; if (!skipIfInsufficient || bal >= need) toSend = amt; } } else { if (ratioForI !== undefined) { toSend = (bal * BigInt(ratioForI)) / 100n; } else if (amountStrForI && amountStrForI.trim().length > 0) { toSend = ethers.parseUnits(amountStrForI, decimals); } if (skipIfInsufficient && bal < toSend) toSend = 0n; } if (toSend > 0n) { sweepAmounts[i] = toSend; totalAmountBeforeProfit += toSend; noHopTxDataList.push({ index: i, wallet: sourceWallets[i], toSend }); } } // ========== 第2步:批量获取无跳转钱包的 nonces ========== const noHopWallets = noHopTxDataList.map(d => d.wallet); const noHopNonces = noHopWallets.length > 0 ? await nonceManager.getNextNoncesForWallets(noHopWallets) : []; // ========== 第3步:并行签名无跳转交易 ========== const noHopTxPromises = noHopTxDataList.map((data, idx) => { const { wallet, toSend } = data; const nonce = noHopNonces[idx]; if (isNative) { return wallet.signTransaction({ to: target, value: toSend, nonce, gasPrice, gasLimit: nativeGasLimit, chainId: chainIdNum, type: txType }); } else { const txData = iface.encodeFunctionData('transfer', [target, toSend]); return wallet.signTransaction({ to: tokenAddress, data: txData, value: 0n, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType }); } }); if (noHopTxPromises.length > 0) { const noHopSignedTxs = await Promise.all(noHopTxPromises); signedTxs.push(...noHopSignedTxs); } const hopTxDataList = []; for (const i of withHopIndexes) { const sourceWallet = sourceWallets[i]; const hopChain = preparedHops[i]; const bal = balances[i]; let toSend = 0n; const ratioForI = getRatioForI(i); const amountStrForI = getAmountStrForI(i); if (isNative) { // 原生代币多跳每跳只需要 21000 gas const totalGasCost = nativeTransferGasLimit * gasPrice * BigInt(hopChain.length + 1); if (ratioForI !== undefined) { const want = (bal