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.
- package/dist/chains/bsc/four/utils.js +67 -345
- package/dist/chains/xlayer/eip7702/multi-hop-transfer.js +59 -144
- package/dist/contracts/tm-bundle-merkle/utils.js +75 -365
- package/dist/shared/constants/index.d.ts +1 -1
- package/dist/shared/constants/index.js +3 -1
- package/dist/shared/constants/profit.d.ts +20 -0
- package/dist/shared/constants/profit.js +35 -0
- package/dist/shared/flap/portal-bundle-merkle/utils.d.ts +2 -4
- package/dist/shared/flap/portal-bundle-merkle/utils.js +79 -185
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +3 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
// ✅
|
|
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
|
-
// ✅
|
|
152
|
+
// ✅ 按地址数量收取固定费用(原生代币)
|
|
357
153
|
let nativeProfitAmount = 0n;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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
|
-
// ✅
|
|
399
|
-
if (extractProfit &&
|
|
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
|
-
// ✅
|
|
482
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
// ✅
|
|
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
|
|
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
|
-
|
|
919
|
-
|
|
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
|
-
// ✅
|
|
777
|
+
// ✅ 按地址数量收取固定费用(原生代币支付,不受转账金额影响)
|
|
778
|
+
const activeAddressCountErc20 = sweepAmounts.filter(a => a > 0n).length;
|
|
1032
779
|
let nativeProfitAmount = 0n;
|
|
1033
|
-
if (extractProfit &&
|
|
1034
|
-
nativeProfitAmount =
|
|
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
|
-
// ✅
|
|
785
|
+
// ✅ 确定费用支付者:优先使用 targetWallet,否则使用归集金额最大的源钱包
|
|
1037
786
|
const useTargetAsPayerErc20 = targetWallet !== null;
|
|
1038
787
|
const profitPayerWalletErc20 = useTargetAsPayerErc20 ? targetWallet : (maxSweepIndex >= 0 ? wallets[maxSweepIndex] : null);
|
|
1039
|
-
// ✅
|
|
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
|
|
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
|
|
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
|
-
// ✅
|
|
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
|
-
|
|
1447
|
-
|
|
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
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
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;
|
|
1485
|
-
console.log(`[sweep with hops] targetWallet
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
1602
|
-
|
|
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;
|