four-flap-meme-sdk
Version:
SDK for Flap bonding curve and four.meme TokenManager
1,114 lines • 67.2 kB
JavaScript
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