four-flap-meme-sdk 1.8.3 → 1.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { ethers, Wallet } from 'ethers';
2
2
  import { getOptimizedGasPrice, NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../utils/bundle-helpers.js';
3
- import { PROFIT_CONFIG, ZERO_ADDRESS, BLOCKRAZOR_BUILDER_EOA } from '../../../utils/constants.js';
3
+ import { BLOCKRAZOR_BUILDER_EOA, calculateTransferFee } from '../../../utils/constants.js';
4
4
  import { GAS_LIMITS, CHAINS } from '../../../shared/constants/index.js';
5
5
  import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient, getBribeAmount } from './config.js';
6
6
  // ==================== Gas 常量(使用统一常量) ====================
@@ -9,211 +9,14 @@ const ERC20_TRANSFER_GAS_LIMIT = GAS_LIMITS.ERC20_TRANSFER;
9
9
  const BRIBE_GAS_LIMIT = GAS_LIMITS.BRIBE;
10
10
  const PROFIT_HOP_GAS_LIMIT = GAS_LIMITS.NATIVE_TRANSFER; // 利润多跳使用 NATIVE_TRANSFER
11
11
  import { getErc20DecimalsMerkle as _getErc20DecimalsMerkle, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, batchGetBalances as _batchGetBalances, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress } from './internal.js';
12
- /**
13
- * 根据用户类型获取利润费率(bps)
14
- * - v0: 万分之6 (6 bps = 0.06%)
15
- * - v1: 万分之5 (5 bps = 0.05%)
16
- */
17
- function getProfitRateBps(userType) {
18
- if (userType === 'v1')
19
- return PROFIT_CONFIG.RATE_BPS_V1_DOUBLE; // v1 用户:万分之5
20
- return PROFIT_CONFIG.RATE_BPS_V0_DOUBLE; // v0 用户(默认):万分之6
21
- }
22
- /**
23
- * 计算利润金额
24
- * ✅ 归集和分散专用:根据 userType 使用不同费率
25
- */
26
- function calculateProfit(amount, userType) {
27
- const rateBps = getProfitRateBps(userType);
28
- const profit = (amount * BigInt(rateBps)) / 10000n;
29
- const remaining = amount - profit;
30
- return { profit, remaining };
31
- }
32
- // ==================== ERC20 → 原生代币报价 ====================
33
- import { quote, QUOTE_CONFIG } from '../../../utils/quote-helpers.js';
34
- import { Helper3 } from '../../../contracts/helper3.js';
35
- import { FlapPortal } from '../../../shared/flap/portal.js';
36
- /** ✅ 不设置最低利润阈值,报价是多少就是多少 */
37
- /** 链 ID → 链名称 映射 */
38
- const CHAIN_ID_TO_NAME = {
39
- 56: 'BSC',
40
- 143: 'MONAD',
41
- 196: 'XLAYER',
42
- };
43
- /** 各链的稳定币地址 */
44
- const STABLE_COINS = {
45
- BSC: {
46
- usdt: '0x55d398326f99059fF775485246999027B3197955',
47
- usdc: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d',
48
- },
49
- MONAD: {
50
- // TODO: 添加 Monad 链的稳定币地址
51
- },
52
- XLAYER: {
53
- usdt: '0x1E4a5963aBFD975d8c9021ce480b42188849D41d',
54
- },
55
- };
56
- /**
57
- * 获取 FOUR 内盘代币 → BNB 的报价
58
- */
59
- async function getFourInnerQuote(rpcUrl, tokenAddress, tokenAmount) {
60
- try {
61
- const helper = Helper3.connectByChain('BSC', rpcUrl);
62
- const result = await helper.trySell(tokenAddress, tokenAmount);
63
- // result = { tokenManager, quote, funds, fee }
64
- // funds 是卖出代币能获得的 BNB 数量
65
- const funds = result.funds;
66
- console.log(`[getFourInnerQuote] FOUR 内盘报价成功: ${funds} wei`);
67
- return funds;
68
- }
69
- catch (error) {
70
- console.warn(`[getFourInnerQuote] FOUR 内盘报价失败:`, error);
71
- return 0n;
72
- }
73
- }
74
- // ✅ ZERO_ADDRESS 从公共模块导入
75
- /**
76
- * 获取 FLAP 内盘代币 → 原生代币的报价
77
- * ✅ 与 portal-bundle-merkle/core.ts 使用相同的 quoteExactInput 方法
78
- */
79
- async function getFlapInnerQuote(rpcUrl, chainId, tokenAddress, tokenAmount) {
80
- try {
81
- // 根据链 ID 确定链名称
82
- const chainName = chainId === CHAINS.BSC.chainId ? 'BSC'
83
- : chainId === CHAINS.MONAD.chainId ? 'MONAD'
84
- : chainId === CHAINS.XLAYER.chainId ? 'XLAYER'
85
- : null;
86
- if (!chainName) {
87
- console.warn(`[getFlapInnerQuote] 不支持的链 ID: ${chainId}`);
88
- return 0n;
89
- }
90
- const portal = new FlapPortal({ chain: chainName, rpcUrl });
91
- // ✅ 使用 quoteExactInput 与 core.ts 保持一致
92
- const funds = await portal.quoteExactInput({
93
- inputToken: tokenAddress,
94
- outputToken: ZERO_ADDRESS, // 输出原生代币
95
- inputAmount: tokenAmount
96
- });
97
- console.log(`[getFlapInnerQuote] FLAP 内盘报价成功: ${funds} wei`);
98
- return funds;
99
- }
100
- catch (error) {
101
- console.warn(`[getFlapInnerQuote] FLAP 内盘报价失败:`, error);
102
- return 0n;
103
- }
104
- }
105
- /**
106
- * 获取 ERC20 代币 → 原生代币的报价
107
- *
108
- * @param provider - Provider 实例
109
- * @param tokenAddress - ERC20 代币地址
110
- * @param tokenAmount - 代币数量(wei)
111
- * @param chainId - 链 ID(56=BSC, 143=Monad)
112
- * @param poolType - 池子类型:'flap'/'four' 内盘,'v2'/'v3' 外盘
113
- * @param quoteToken - 报价代币:'native'(BNB/MON),'usdt','usdc'
114
- * @param rpcUrl - RPC URL(内盘报价需要)
115
- * @returns 等值的原生代币数量(wei),失败返回 0n
116
- */
117
- async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId, poolType = 'v2', quoteToken = 'native', rpcUrl) {
118
- if (tokenAmount <= 0n)
119
- return 0n;
120
- // ✅ FOUR 内盘:通过 Helper3.trySell 获取真实报价
121
- if (poolType === 'four') {
122
- const url = rpcUrl || provider._getConnection?.()?.url || '';
123
- if (!url) {
124
- console.warn(`[getTokenToNativeQuote] FOUR 内盘需要 rpcUrl,报价失败`);
125
- return 0n;
126
- }
127
- const funds = await getFourInnerQuote(url, tokenAddress, tokenAmount);
128
- return funds > 0n ? funds : 0n;
129
- }
130
- // ✅ FLAP 内盘:通过 FlapPortal.previewSell 获取真实报价
131
- if (poolType === 'flap') {
132
- const url = rpcUrl || provider._getConnection?.()?.url || '';
133
- if (!url) {
134
- console.warn(`[getTokenToNativeQuote] FLAP 内盘需要 rpcUrl,报价失败`);
135
- return 0n;
136
- }
137
- const funds = await getFlapInnerQuote(url, chainId, tokenAddress, tokenAmount);
138
- return funds > 0n ? funds : 0n;
139
- }
140
- const chainName = CHAIN_ID_TO_NAME[chainId];
141
- if (!chainName) {
142
- console.warn(`[getTokenToNativeQuote] 不支持的链 ID: ${chainId},报价失败`);
143
- return 0n;
144
- }
145
- const chainConfig = QUOTE_CONFIG[chainName];
146
- if (!chainConfig) {
147
- console.warn(`[getTokenToNativeQuote] 不支持的链: ${chainName},报价失败`);
148
- return 0n;
149
- }
150
- try {
151
- const version = poolType === 'v3' ? 'v3' : 'v2';
152
- let nativeAmount = 0n;
153
- if (quoteToken === 'native') {
154
- // ✅ 直接路径:TOKEN → WBNB
155
- console.log(`[getTokenToNativeQuote] 使用 ${version} 直接路径报价 (TOKEN → WBNB)`);
156
- const result = await quote({
157
- provider,
158
- tokenIn: tokenAddress,
159
- tokenOut: chainConfig.wrappedNative,
160
- amountIn: tokenAmount,
161
- chain: chainName,
162
- version
163
- });
164
- nativeAmount = result.amountOut;
165
- }
166
- else {
167
- // ✅ 稳定币路径:TOKEN → USDT/USDC → WBNB(两步报价)
168
- const stableCoins = STABLE_COINS[chainName];
169
- const stableCoinAddress = stableCoins?.[quoteToken];
170
- if (!stableCoinAddress) {
171
- console.warn(`[getTokenToNativeQuote] 链 ${chainName} 不支持 ${quoteToken},报价失败`);
172
- return 0n;
173
- }
174
- console.log(`[getTokenToNativeQuote] 使用 ${version} 稳定币路径报价 (TOKEN → ${quoteToken.toUpperCase()} → WBNB)`);
175
- // 第一步:TOKEN → USDT/USDC
176
- const step1 = await quote({
177
- provider,
178
- tokenIn: tokenAddress,
179
- tokenOut: stableCoinAddress,
180
- amountIn: tokenAmount,
181
- chain: chainName,
182
- version
183
- });
184
- if (step1.amountOut <= 0n) {
185
- console.warn(`[getTokenToNativeQuote] TOKEN → ${quoteToken.toUpperCase()} 报价失败`);
186
- return 0n;
187
- }
188
- // 第二步:USDT/USDC → WBNB
189
- const step2 = await quote({
190
- provider,
191
- tokenIn: stableCoinAddress,
192
- tokenOut: chainConfig.wrappedNative,
193
- amountIn: step1.amountOut,
194
- chain: chainName,
195
- version
196
- });
197
- nativeAmount = step2.amountOut;
198
- }
199
- if (nativeAmount > 0n) {
200
- console.log(`[getTokenToNativeQuote] 报价成功: ${nativeAmount} wei`);
201
- return nativeAmount;
202
- }
203
- }
204
- catch (error) {
205
- console.warn(`[getTokenToNativeQuote] 报价失败:`, error);
206
- }
207
- // 报价失败,返回 0
208
- return 0n;
209
- }
12
+ // ==================== 钱包转账按地址收费 ====================
210
13
  /**
211
14
  * 分发(仅签名版本 - 不依赖 Merkle)
212
15
  * ✅ 精简版:只负责签名交易,不提交到 Merkle
213
16
  * ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突
214
17
  */
215
18
  export async function disperseWithBundleMerkle(params) {
216
- const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native', userType = 'v0' } = params;
19
+ const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce } = params;
217
20
  // 快速返回空结果
218
21
  if (!recipients || recipients.length === 0) {
219
22
  return {
@@ -297,17 +100,15 @@ export async function disperseWithBundleMerkle(params) {
297
100
  console.log(`[disperse] 贿赂交易已添加: ${ethers.formatEther(bribeAmount)} BNB`);
298
101
  }
299
102
  if (isNative) {
300
- // ✅ 原生币:先计算所有金额和利润(同步),再并行签名
103
+ // ✅ 原生币:按地址数量收取固定费用,全额转账
104
+ if (extractProfit) {
105
+ totalProfit = calculateTransferFee(chainIdNum, recipients.length);
106
+ console.log(`[disperse Native] 按地址收费: ${recipients.length} 个地址 × ${ethers.formatEther(calculateTransferFee(chainIdNum, 1))} = ${ethers.formatEther(totalProfit)} 原生币`);
107
+ }
301
108
  const txDataList = recipients.map((to, i) => {
302
109
  const originalAmount = ethers.parseEther(normalizedAmounts[i]);
303
110
  totalAmountBeforeProfit += originalAmount;
304
- let actualAmount = originalAmount;
305
- if (extractProfit) {
306
- const { profit, remaining } = calculateProfit(originalAmount, userType);
307
- actualAmount = remaining;
308
- totalProfit += profit;
309
- }
310
- return { to, value: actualAmount, nonce: nonces[nonceOffset + i] };
111
+ return { to, value: originalAmount, nonce: nonces[nonceOffset + i] };
311
112
  });
312
113
  // ✅ 并行签名所有交易
313
114
  const txPromises = txDataList.map(({ to, value, nonce }) => mainWallet.signTransaction({
@@ -341,46 +142,24 @@ export async function disperseWithBundleMerkle(params) {
341
142
  // ✅ ERC20:并行获取 decimals(传入 chainIdNum 避免 NETWORK_ERROR)
342
143
  const decimals = tokenDecimals ?? (await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum));
343
144
  const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
344
- // ✅ 先计算所有原始金额和预估 ERC20 利润
345
- let totalTokenProfit = 0n;
145
+ // ✅ 先计算所有原始金额
346
146
  const originalAmounts = [];
347
147
  for (let i = 0; i < recipients.length; i++) {
348
148
  const originalAmount = ethers.parseUnits(normalizedAmounts[i], decimals);
349
149
  originalAmounts.push(originalAmount);
350
150
  totalAmountBeforeProfit += originalAmount;
351
- if (extractProfit) {
352
- const { profit } = calculateProfit(originalAmount, userType);
353
- totalTokenProfit += profit; // 累计 ERC20 代币利润(用于报价)
354
- }
355
151
  }
356
- // ✅ 获取 ERC20 利润等值的原生代币(OKB/BNB)报价
152
+ // ✅ 按地址数量收取固定费用(原生代币)
357
153
  let nativeProfitAmount = 0n;
358
- let profitIsNative = false; // 标记利润是否以原生代币扣除
359
- if (extractProfit && totalTokenProfit > 0n) {
360
- nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
361
- // 判断报价是否成功(> 0n 即可,不设最低阈值)
362
- profitIsNative = nativeProfitAmount > 0n;
363
- if (profitIsNative) {
364
- totalProfit = nativeProfitAmount;
365
- console.log(`[disperse ERC20] 报价成功,以原生代币扣除利润: ${ethers.formatEther(nativeProfitAmount)} 原生币`);
366
- }
367
- else {
368
- console.log(`[disperse ERC20] 报价失败,以 ERC20 扣除利润: ${ethers.formatUnits(totalTokenProfit, decimals)} Token`);
369
- }
154
+ if (extractProfit) {
155
+ nativeProfitAmount = calculateTransferFee(chainIdNum, recipients.length);
156
+ totalProfit = nativeProfitAmount;
157
+ console.log(`[disperse ERC20] 按地址收费: ${recipients.length} 个地址 × ${ethers.formatEther(calculateTransferFee(chainIdNum, 1))} = ${ethers.formatEther(nativeProfitAmount)} 原生币`);
370
158
  }
371
- // ✅ 根据报价结果决定分发金额
372
- // 报价成功:全额分发 ERC20,只扣 OKB
373
- // 报价失败:扣 ERC20 利润
159
+ // ✅ ERC20 全额分发,费用以原生币单独支付
374
160
  const txDataList = recipients.map((to, i) => {
375
161
  const originalAmount = originalAmounts[i];
376
- let actualAmount = originalAmount;
377
- if (extractProfit && !profitIsNative) {
378
- // 报价失败:从 ERC20 中扣除利润
379
- const { remaining } = calculateProfit(originalAmount, userType);
380
- actualAmount = remaining;
381
- }
382
- // 报价成功:全额分发 ERC20(不扣 ERC20)
383
- const data = iface.encodeFunctionData('transfer', [to, actualAmount]);
162
+ const data = iface.encodeFunctionData('transfer', [to, originalAmount]);
384
163
  return { data, nonce: nonces[nonceOffset + i] };
385
164
  });
386
165
  // ✅ 并行签名所有交易
@@ -395,8 +174,8 @@ export async function disperseWithBundleMerkle(params) {
395
174
  type: txType
396
175
  }));
397
176
  signedTxs.push(...(await Promise.all(txPromises)));
398
- // ✅ 利润多跳转账(强制 2 跳中转)- 仅在报价成功时扣 OKB/BNB
399
- if (extractProfit && profitIsNative && nativeProfitAmount > 0n) {
177
+ // ✅ 费用多跳转账(强制 2 跳中转)
178
+ if (extractProfit && nativeProfitAmount > 0n) {
400
179
  const profitHopResult = await buildProfitHopTransactions({
401
180
  provider,
402
181
  payerWallet: mainWallet,
@@ -478,25 +257,18 @@ export async function disperseWithBundleMerkle(params) {
478
257
  console.log(`[disperse with hops] 贿赂交易已添加: ${ethers.formatEther(bribeAmountHop)} BNB`);
479
258
  }
480
259
  const txsToSign = [];
481
- // ✅ ERC20 多跳:累计代币利润,最后统一转换为原生代币
482
- let totalTokenProfit = 0n;
260
+ // ✅ 按地址数量收取固定费用(原生代币),不再扣除转账金额
261
+ if (extractProfit) {
262
+ totalProfit = calculateTransferFee(chainIdNum, recipients.length);
263
+ console.log(`[disperse with hops] 按地址收费: ${recipients.length} 个地址 × ${ethers.formatEther(calculateTransferFee(chainIdNum, 1))} = ${ethers.formatEther(totalProfit)} 原生币`);
264
+ }
483
265
  for (let i = 0; i < recipients.length; i++) {
484
266
  const finalRecipient = recipients[i];
485
267
  const originalAmountWei = isNative
486
268
  ? ethers.parseEther(normalizedAmounts[i])
487
269
  : ethers.parseUnits(normalizedAmounts[i], decimals);
488
270
  totalAmountBeforeProfit += originalAmountWei;
489
- let amountWei = originalAmountWei;
490
- if (extractProfit) {
491
- const { profit, remaining } = calculateProfit(originalAmountWei, userType);
492
- amountWei = remaining;
493
- if (isNative) {
494
- totalProfit += profit; // 原生币直接累加
495
- }
496
- else {
497
- totalTokenProfit += profit; // ERC20 累计代币利润
498
- }
499
- }
271
+ const amountWei = originalAmountWei; // 全额转账
500
272
  const hopChain = preparedHops[i];
501
273
  if (hopChain.length === 0) {
502
274
  // 无跳转:直接转账
@@ -662,11 +434,6 @@ export async function disperseWithBundleMerkle(params) {
662
434
  }
663
435
  }
664
436
  }
665
- // ✅ ERC20 多跳:获取代币利润等值的原生代币报价
666
- if (!isNative && extractProfit && totalTokenProfit > 0n) {
667
- const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
668
- totalProfit = nativeProfitAmount; // 更新为原生代币利润
669
- }
670
437
  // ✅ 并行签名所有交易
671
438
  const signedTxsResult = await Promise.all(txsToSign.map(({ wallet, tx }) => wallet.signTransaction(tx)));
672
439
  signedTxs.push(...signedTxsResult);
@@ -709,7 +476,7 @@ export async function disperseWithBundleMerkle(params) {
709
476
  * ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突
710
477
  */
711
478
  export async function sweepWithBundleMerkle(params) {
712
- const { sourcePrivateKeys, target, targetPrivateKey, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native', userType = 'v0' } = params;
479
+ const { sourcePrivateKeys, target, targetPrivateKey, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce } = params;
713
480
  // 快速返回空结果
714
481
  if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) {
715
482
  return {
@@ -860,29 +627,22 @@ export async function sweepWithBundleMerkle(params) {
860
627
  maxSweepAmount = toSend;
861
628
  maxSweepIndex = i;
862
629
  }
863
- // 累计总利润
864
- if (extractProfit && toSend > 0n) {
865
- const { profit } = calculateProfit(toSend, userType);
866
- totalProfit += profit;
867
- }
868
630
  }
869
- // ✅ 如果需要提取利润,检查支付者是否有足够余额支付利润转账的额外 gas
631
+ // ✅ 按地址数量收取固定费用(原生代币)
632
+ const activeNativeAddressCount = sweepAmounts.filter(a => a > 0n).length;
633
+ if (extractProfit && activeNativeAddressCount > 0) {
634
+ totalProfit = calculateTransferFee(chainIdNum, activeNativeAddressCount);
635
+ console.log(`[sweep Native] 按地址收费: ${activeNativeAddressCount} 个地址 × ${ethers.formatEther(calculateTransferFee(chainIdNum, 1))} = ${ethers.formatEther(totalProfit)} 原生币`);
636
+ }
637
+ // ✅ 如果需要提取费用,检查支付者是否有足够余额
870
638
  if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) {
871
639
  const payerBalance = balances[maxSweepIndex];
872
640
  const payerSweepAmount = sweepAmounts[maxSweepIndex];
873
- const payerNeedGas = gasCostBase + profitTxGas; // 支付者需要 2 笔交易的 gas
874
- // 如果支付者余额不足以支付 2 笔交易的 gas,减少其归集金额
641
+ const payerNeedGas = gasCostBase + profitTxGas + totalProfit;
875
642
  if (payerBalance < payerSweepAmount + payerNeedGas) {
876
643
  const maxPayerSweep = payerBalance > payerNeedGas ? payerBalance - payerNeedGas : 0n;
877
644
  sweepAmounts[maxSweepIndex] = maxPayerSweep;
878
- // 重新计算总金额和总利润
879
645
  totalAmountBeforeProfit = sweepAmounts.reduce((sum, amt) => sum + amt, 0n);
880
- totalProfit = 0n;
881
- for (let i = 0; i < sweepAmounts.length; i++) {
882
- if (sweepAmounts[i] > 0n) {
883
- totalProfit += calculateProfit(sweepAmounts[i], userType).profit;
884
- }
885
- }
886
646
  }
887
647
  }
888
648
  // ✅ 第二步:生成归集交易
@@ -915,17 +675,8 @@ export async function sweepWithBundleMerkle(params) {
915
675
  const toSend = sweepAmounts[i];
916
676
  if (toSend <= 0n)
917
677
  return null;
918
- let actualToSend = toSend;
919
- if (extractProfit && !useTargetAsPayer) {
920
- // ✅ 如果由源钱包支付利润,支付者扣除所有利润总和;其他钱包不扣利润,归集干净
921
- if (i === maxSweepIndex && totalProfit > 0n) {
922
- actualToSend = toSend - totalProfit; // 支付者扣除所有利润总和
923
- }
924
- else {
925
- actualToSend = toSend; // 其他钱包不扣利润,归集全部
926
- }
927
- }
928
- // ✅ 如果由 targetWallet 支付利润,所有源钱包都归集全部金额
678
+ // 全额归集,费用由支付者额外支付原生代币
679
+ const actualToSend = toSend;
929
680
  // ✅ 支付者使用预留的第一个 nonce,其他钱包使用批量获取的 nonce
930
681
  let nonce;
931
682
  if (!useTargetAsPayer && i === maxSweepIndex && payerProfitNonce !== undefined) {
@@ -1022,42 +773,37 @@ export async function sweepWithBundleMerkle(params) {
1022
773
  maxSweepAmount = toSend;
1023
774
  maxSweepIndex = i;
1024
775
  }
1025
- // 累计总代币利润(ERC20 归集)
1026
- if (extractProfit && toSend > 0n) {
1027
- const { profit } = calculateProfit(toSend, userType);
1028
- totalProfit += profit; // 先累计代币利润
1029
- }
1030
776
  }
1031
- // ✅ ERC20 归集:获取代币利润等值的原生代币(BNB)报价
777
+ // ✅ 按地址数量收取固定费用(原生代币支付,不受转账金额影响)
778
+ const activeAddressCountErc20 = sweepAmounts.filter(a => a > 0n).length;
1032
779
  let nativeProfitAmount = 0n;
1033
- if (extractProfit && totalProfit > 0n) {
1034
- nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
780
+ if (extractProfit && activeAddressCountErc20 > 0) {
781
+ nativeProfitAmount = calculateTransferFee(chainIdNum, activeAddressCountErc20);
782
+ totalProfit = nativeProfitAmount;
783
+ console.log(`[sweep ERC20] 按地址收费: ${activeAddressCountErc20} 个地址 × ${ethers.formatEther(calculateTransferFee(chainIdNum, 1))} = ${ethers.formatEther(nativeProfitAmount)} 原生币`);
1035
784
  }
1036
- // ✅ 确定利润支付者:优先使用 targetWallet,否则使用归集金额最大的源钱包
785
+ // ✅ 确定费用支付者:优先使用 targetWallet,否则使用归集金额最大的源钱包
1037
786
  const useTargetAsPayerErc20 = targetWallet !== null;
1038
787
  const profitPayerWalletErc20 = useTargetAsPayerErc20 ? targetWallet : (maxSweepIndex >= 0 ? wallets[maxSweepIndex] : null);
1039
- // ✅ 如果需要提取利润,检查支付者是否有足够 BNB 支付利润转账的额外 gas
788
+ // ✅ 如果需要提取费用,检查支付者是否有足够原生代币支付
1040
789
  if (extractProfit && nativeProfitAmount > 0n && profitPayerWalletErc20 && config.checkBnbForErc20NoHop) {
1041
790
  const payerBnbBalance = useTargetAsPayerErc20
1042
791
  ? await provider.getBalance(target)
1043
792
  : (bnbBalances[maxSweepIndex] ?? 0n);
1044
793
  const payerNeedGas = (useTargetAsPayerErc20 ? 0n : finalGasLimit * gasPrice) + profitTxGas + nativeProfitAmount;
1045
- // 如果支付者 BNB 余额不足,跳过利润提取
1046
794
  if (payerBnbBalance < payerNeedGas) {
1047
- nativeProfitAmount = 0n; // 跳过利润提取
795
+ nativeProfitAmount = 0n;
796
+ totalProfit = 0n;
1048
797
  }
1049
798
  }
1050
- // ✅ 第二步:生成归集交易(ERC20 归集不扣除代币,利润从 BNB 支付)
1051
- // ✅ 修复 nonce 冲突:统一管理 targetWallet 的 nonce
799
+ // ✅ 第二步:生成归集交易(ERC20 全额归集,费用以原生币单独支付)
1052
800
  let payerProfitNonce;
1053
801
  if (extractProfit && nativeProfitAmount > 0n && profitPayerWalletErc20) {
1054
802
  if (useTargetAsPayerErc20 && targetWalletNonceStart !== undefined) {
1055
- // ✅ targetWallet:使用统一管理的 nonce(贿赂交易已使用 targetWalletNonceUsed 个)
1056
803
  payerProfitNonce = targetWalletNonceStart + targetWalletNonceUsed;
1057
- console.log(`[sweep ERC20] targetWallet 利润交易 nonce: ${payerProfitNonce} (起点=${targetWalletNonceStart}, 已用=${targetWalletNonceUsed})`);
804
+ console.log(`[sweep ERC20] targetWallet 费用交易 nonce: ${payerProfitNonce} (起点=${targetWalletNonceStart}, 已用=${targetWalletNonceUsed})`);
1058
805
  }
1059
806
  else {
1060
- // ✅ 源钱包支付利润:使用 NonceManager 获取 2 个 nonce(归集 + 利润)
1061
807
  const nonces = await nonceManager.getNextNonceBatch(profitPayerWalletErc20, 2);
1062
808
  payerProfitNonce = nonces[1];
1063
809
  }
@@ -1098,9 +844,8 @@ export async function sweepWithBundleMerkle(params) {
1098
844
  });
1099
845
  const allTxs = (await Promise.all(txPromises)).filter(tx => tx !== null);
1100
846
  signedTxs.push(...allTxs);
1101
- // ✅ 第三步:利润多跳转账(强制 2 跳中转)- ERC20 利润转等值原生代币
847
+ // ✅ 第三步:费用多跳转账(强制 2 跳中转)
1102
848
  if (extractProfit && nativeProfitAmount > 0n && profitPayerWalletErc20 && payerProfitNonce !== undefined) {
1103
- totalProfit = nativeProfitAmount; // 更新为原生代币利润
1104
849
  const profitHopResult = await buildProfitHopTransactions({
1105
850
  provider,
1106
851
  payerWallet: profitPayerWalletErc20,
@@ -1442,9 +1187,10 @@ export async function sweepWithBundleMerkle(params) {
1442
1187
  const hopSignedTxs = await Promise.all(hopTxsToSign.map(({ wallet, tx }) => wallet.signTransaction(tx)));
1443
1188
  signedTxs.push(...hopSignedTxs);
1444
1189
  }
1445
- // ========== 第7步:计算利润并添加利润转账 ==========
1446
- if (extractProfit && totalAmountBeforeProfit > 0n) {
1447
- // 确定利润支付者:优先使用 targetWallet,否则使用归集金额最大的源钱包
1190
+ // ========== 第7步:按地址数量收取固定费用(原生代币) ==========
1191
+ const activeHopAddressCount = sweepAmounts.filter(a => a > 0n).length;
1192
+ if (extractProfit && activeHopAddressCount > 0) {
1193
+ // ✅ 确定费用支付者:优先使用 targetWallet,否则使用归集金额最大的源钱包
1448
1194
  let maxSweepIndex = -1;
1449
1195
  let maxSweepAmount = 0n;
1450
1196
  for (let i = 0; i < sweepAmounts.length; i++) {
@@ -1455,37 +1201,19 @@ export async function sweepWithBundleMerkle(params) {
1455
1201
  }
1456
1202
  const useTargetAsPayerHop = targetWallet !== null;
1457
1203
  const profitPayerWalletHop = useTargetAsPayerHop ? targetWallet : (maxSweepIndex >= 0 ? sourceWallets[maxSweepIndex] : null);
1458
- // 计算总代币利润
1459
- let totalTokenProfit = 0n;
1460
- for (let i = 0; i < sweepAmounts.length; i++) {
1461
- if (sweepAmounts[i] > 0n) {
1462
- const { profit } = calculateProfit(sweepAmounts[i], userType);
1463
- if (isNative) {
1464
- totalProfit += profit;
1465
- }
1466
- else {
1467
- totalTokenProfit += profit;
1468
- }
1469
- }
1470
- }
1471
- // ✅ ERC20:获取代币利润等值的原生代币报价
1472
- let nativeProfitAmount = isNative ? totalProfit : 0n;
1473
- if (!isNative && totalTokenProfit > 0n) {
1474
- nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
1475
- totalProfit = nativeProfitAmount;
1476
- }
1477
- // ✅ 利润多跳转账(强制 2 跳中转)
1204
+ // ✅ 按地址数量收取固定费用
1205
+ const nativeProfitAmount = calculateTransferFee(chainIdNum, activeHopAddressCount);
1206
+ totalProfit = nativeProfitAmount;
1207
+ console.log(`[sweep with hops] 按地址收费: ${activeHopAddressCount} 个地址 × ${ethers.formatEther(calculateTransferFee(chainIdNum, 1))} = ${ethers.formatEther(nativeProfitAmount)} 原生币`);
1208
+ // 费用多跳转账(强制 2 跳中转)
1478
1209
  if (nativeProfitAmount > 0n && profitPayerWalletHop) {
1479
- // ✅ 修复 nonce 冲突:如果利润支付者是 targetWallet,使用统一管理的 nonce
1480
1210
  let profitNonce;
1481
1211
  if (useTargetAsPayerHop && targetWalletNonceStart !== undefined) {
1482
- // targetWallet:使用统一管理的 nonce(贿赂交易已使用 targetWalletNonceUsed 个)
1483
1212
  profitNonce = targetWalletNonceStart + targetWalletNonceUsed;
1484
- targetWalletNonceUsed += 1; // ✅ 递增 nonce 计数
1485
- console.log(`[sweep with hops] targetWallet 利润交易 nonce: ${profitNonce} (起点=${targetWalletNonceStart}, 已用=${targetWalletNonceUsed})`);
1213
+ targetWalletNonceUsed += 1;
1214
+ console.log(`[sweep with hops] targetWallet 费用交易 nonce: ${profitNonce} (起点=${targetWalletNonceStart}, 已用=${targetWalletNonceUsed})`);
1486
1215
  }
1487
1216
  else {
1488
- // 源钱包支付利润:使用 NonceManager 获取 nonce
1489
1217
  profitNonce = await nonceManager.getNextNonce(profitPayerWalletHop);
1490
1218
  }
1491
1219
  const profitHopResult = await buildProfitHopTransactions({
@@ -1500,7 +1228,7 @@ export async function sweepWithBundleMerkle(params) {
1500
1228
  startNonce: profitNonce
1501
1229
  });
1502
1230
  signedTxs.push(...profitHopResult.signedTransactions);
1503
- profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
1231
+ profitHopWallets = profitHopResult.hopWallets;
1504
1232
  }
1505
1233
  }
1506
1234
  }
@@ -1528,7 +1256,7 @@ export async function sweepWithBundleMerkle(params) {
1528
1256
  * - 支持利润刮取:利润由第一个 sender 支付(作为额外费用,不扣减每对转账金额)
1529
1257
  */
1530
1258
  export async function pairwiseTransferWithBundleMerkle(params) {
1531
- const { senderPrivateKeys, receiverAddresses, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native', userType = 'v0', } = params;
1259
+ const { senderPrivateKeys, receiverAddresses, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, config, startNonce, } = params;
1532
1260
  if (!senderPrivateKeys || senderPrivateKeys.length === 0) {
1533
1261
  return { signedTransactions: [], hopWallets: undefined };
1534
1262
  }
@@ -1581,25 +1309,19 @@ export async function pairwiseTransferWithBundleMerkle(params) {
1581
1309
  isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum))
1582
1310
  ]);
1583
1311
  const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
1584
- // 利润:按每对金额计算,但由第一个 sender 支付(额外费用)
1312
+ // 费用:按地址数量收取固定费用(原生代币),由第一个 sender 支付
1585
1313
  const extractProfit = shouldExtractProfit(config);
1586
1314
  let totalAmountBeforeProfit = 0n;
1587
- let totalTokenProfit = 0n;
1588
1315
  let totalProfitNative = 0n;
1589
1316
  const parsedAmountsWei = normalizedAmounts.map(v => {
1590
1317
  const amt = isNative ? ethers.parseEther(v) : ethers.parseUnits(v, decimals);
1591
1318
  totalAmountBeforeProfit += amt;
1592
- if (extractProfit && amt > 0n) {
1593
- const { profit } = calculateProfit(amt, userType);
1594
- if (isNative)
1595
- totalProfitNative += profit;
1596
- else
1597
- totalTokenProfit += profit;
1598
- }
1599
1319
  return amt;
1600
1320
  });
1601
- if (!isNative && extractProfit && totalTokenProfit > 0n) {
1602
- totalProfitNative = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
1321
+ // 按地址数量收取固定费用
1322
+ if (extractProfit && pairCount > 0) {
1323
+ totalProfitNative = calculateTransferFee(chainIdNum, pairCount);
1324
+ console.log(`[pairwise] 按地址收费: ${pairCount} 个地址 × ${ethers.formatEther(calculateTransferFee(chainIdNum, 1))} = ${ethers.formatEther(totalProfitNative)} 原生币`);
1603
1325
  }
1604
1326
  // ✅ BSC 链贿赂支持(由第一个 sender 支付)
1605
1327
  const bribeAmount = chainIdNum === CHAINS.BSC.chainId ? getBribeAmount(config) : 0n;