four-flap-meme-sdk 2.0.0 → 2.1.0
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/__tests__/subpath-exports.test.js +34 -0
- package/dist/chains/index.d.ts +13 -0
- package/dist/chains/index.js +13 -0
- package/dist/chains/xlayer/eip7702/index.d.ts +2 -0
- package/dist/flap/index.d.ts +10 -0
- package/dist/flap/index.js +8 -0
- package/dist/shared/constants/index.d.ts +2 -0
- package/dist/shared/index.d.ts +2 -0
- package/package.json +66 -1
- package/dist/chains/bsc/four/disperse.d.ts +0 -12
- package/dist/chains/bsc/four/disperse.js +0 -470
- package/dist/chains/bsc/four/pairwise.d.ts +0 -7
- package/dist/chains/bsc/four/pairwise.js +0 -308
- package/dist/chains/bsc/four/submit/blockrazor.d.ts +0 -18
- package/dist/chains/bsc/four/submit/blockrazor.js +0 -86
- package/dist/chains/bsc/four/submit/direct.d.ts +0 -66
- package/dist/chains/bsc/four/submit/direct.js +0 -452
- package/dist/chains/bsc/four/submit/helpers.d.ts +0 -18
- package/dist/chains/bsc/four/submit/helpers.js +0 -57
- package/dist/chains/bsc/four/submit/index.d.ts +0 -12
- package/dist/chains/bsc/four/submit/index.js +0 -11
- package/dist/chains/bsc/four/submit/merkle.d.ts +0 -18
- package/dist/chains/bsc/four/submit/merkle.js +0 -74
- package/dist/chains/bsc/four/submit/types.d.ts +0 -143
- package/dist/chains/bsc/four/swap/index.d.ts +0 -32
- package/dist/chains/bsc/four/swap/index.js +0 -829
- package/dist/chains/bsc/four/swap/types.d.ts +0 -70
- package/dist/chains/bsc/four/swap/types.js +0 -1
- package/dist/chains/bsc/four/sweep.d.ts +0 -13
- package/dist/chains/bsc/four/sweep.js +0 -788
- package/dist/chains/bsc/four/utils/index.d.ts +0 -20
- package/dist/chains/bsc/four/utils/index.js +0 -1558
- package/dist/chains/bsc/four/utils/types.d.ts +0 -1
- package/dist/chains/bsc/four/utils/types.js +0 -1
- package/dist/chains/bsc/pancake/bundle-buy-first/index.d.ts +0 -8
- package/dist/chains/bsc/pancake/bundle-buy-first/index.js +0 -907
- package/dist/chains/bsc/pancake/bundle-buy-first/types.d.ts +0 -73
- package/dist/chains/bsc/pancake/bundle-buy-first/types.js +0 -1
- package/dist/chains/bsc/pancake/bundle-swap/helpers.d.ts +0 -102
- package/dist/chains/bsc/pancake/bundle-swap/helpers.js +0 -572
- package/dist/chains/bsc/pancake/bundle-swap/index.d.ts +0 -50
- package/dist/chains/bsc/pancake/bundle-swap/index.js +0 -1066
- package/dist/chains/bsc/pancake/bundle-swap/types.d.ts +0 -202
- package/dist/chains/bsc/pancake/bundle-swap/types.js +0 -3
- package/dist/chains/xlayer/eip7702/bundle-swap/index.d.ts +0 -72
- package/dist/chains/xlayer/eip7702/bundle-swap/index.js +0 -921
- package/dist/chains/xlayer/eip7702/bundle-swap/types.d.ts +0 -65
- package/dist/chains/xlayer/eip7702/bundle-swap/types.js +0 -1
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.d.ts +0 -128
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.js +0 -857
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.d.ts +0 -85
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.js +0 -1
- package/dist/chains/xlayer/eip7702/volume/index.d.ts +0 -96
- package/dist/chains/xlayer/eip7702/volume/index.js +0 -793
- package/dist/chains/xlayer/eip7702/volume/types.d.ts +0 -124
- package/dist/chains/xlayer/eip7702/volume/types.js +0 -1
- package/dist/chains/xlayer/eoa/types-core.d.ts +0 -363
- package/dist/chains/xlayer/eoa/types-core.js +0 -53
- package/dist/chains/xlayer/eoa/types-create.d.ts +0 -413
- package/dist/chains/xlayer/eoa/types-create.js +0 -9
- package/dist/chains/xlayer/eoa/types-volume.d.ts +0 -209
- package/dist/chains/xlayer/eoa/types-volume.js +0 -13
- package/dist/dex/direct-router/index.d.ts +0 -70
- package/dist/dex/direct-router/index.js +0 -1410
- package/dist/dex/direct-router/types.d.ts +0 -81
- package/dist/dex/direct-router/types.js +0 -1
- package/dist/shared/abis/TaxToken.json +0 -969
- package/dist/shared/abis/TokenManager.json +0 -836
- package/dist/shared/abis/TokenManager2.json +0 -136
- package/dist/shared/abis/TokenManagerHelper3.json +0 -993
- package/dist/shared/abis 2/TaxToken.json +0 -105
- package/dist/shared/abis 2/TokenManager.json +0 -836
- package/dist/shared/abis 2/TokenManager2.json +0 -60
- package/dist/shared/abis 2/TokenManagerHelper3.json +0 -993
- package/dist/shared/abis 2/common.d.ts +0 -85
- package/dist/shared/abis 2/common.js +0 -254
- package/dist/shared/abis 2/index.d.ts +0 -8
- package/dist/shared/abis 2/index.js +0 -8
- package/dist/shared/clients 2/blockrazor.d.ts +0 -314
- package/dist/shared/clients 2/blockrazor.js +0 -596
- package/dist/shared/clients 2/club48.d.ts +0 -154
- package/dist/shared/clients 2/club48.js +0 -331
- package/dist/shared/clients 2/emitservice.d.ts +0 -47
- package/dist/shared/clients 2/emitservice.js +0 -44
- package/dist/shared/clients 2/four.d.ts +0 -132
- package/dist/shared/clients 2/four.js +0 -281
- package/dist/shared/clients 2/merkle.d.ts +0 -210
- package/dist/shared/clients 2/merkle.js +0 -400
- package/dist/shared/flap/__tests__/curve.test.d.ts +0 -1
- package/dist/shared/flap/__tests__/curve.test.js +0 -85
- package/dist/shared/flap/portal/index.d.ts +0 -12
- package/dist/shared/flap/portal/index.js +0 -11
- package/dist/shared/flap/portal/portal.d.ts +0 -47
- package/dist/shared/flap/portal/portal.js +0 -218
- package/dist/shared/flap/portal/types.d.ts +0 -227
- package/dist/shared/flap/portal/types.js +0 -80
- package/dist/shared/flap/portal/writer.d.ts +0 -121
- package/dist/shared/flap/portal/writer.js +0 -265
- package/dist/shared/flap/portal-bundle-merkle/core/index.d.ts +0 -18
- package/dist/shared/flap/portal-bundle-merkle/core/index.js +0 -938
- package/dist/shared/flap/portal-bundle-merkle/core/types.d.ts +0 -1
- package/dist/shared/flap/portal-bundle-merkle/core/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/swap/index.d.ts +0 -42
- package/dist/shared/flap/portal-bundle-merkle/swap/index.js +0 -1448
- package/dist/shared/flap/portal-bundle-merkle/swap/types.d.ts +0 -84
- package/dist/shared/flap/portal-bundle-merkle/swap/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/utils/index.d.ts +0 -17
- package/dist/shared/flap/portal-bundle-merkle/utils/index.js +0 -1024
- package/dist/shared/flap/portal-bundle-merkle/utils/types.d.ts +0 -16
- package/dist/shared/flap/portal-bundle-merkle/utils/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/utils-helpers.d.ts +0 -100
- package/dist/shared/flap/portal-bundle-merkle/utils-helpers.js +0 -133
- package/dist/shared/flap 2/__tests__/curve.test.d.ts +0 -1
- package/dist/shared/flap 2/__tests__/curve.test.js +0 -85
- package/dist/shared/flap 2/abi.d.ts +0 -4
- package/dist/shared/flap 2/abi.js +0 -4
- package/dist/shared/flap 2/constants.d.ts +0 -128
- package/dist/shared/flap 2/constants.js +0 -143
- package/dist/shared/flap 2/curve.d.ts +0 -33
- package/dist/shared/flap 2/curve.js +0 -84
- package/dist/shared/flap 2/errors.d.ts +0 -37
- package/dist/shared/flap 2/errors.js +0 -114
- package/dist/shared/flap 2/index.d.ts +0 -22
- package/dist/shared/flap 2/index.js +0 -33
- package/dist/shared/flap 2/ipfs.d.ts +0 -21
- package/dist/shared/flap 2/ipfs.js +0 -38
- package/dist/shared/flap 2/meta.d.ts +0 -30
- package/dist/shared/flap 2/meta.js +0 -195
- package/dist/shared/flap 2/permit.d.ts +0 -16
- package/dist/shared/flap 2/permit.js +0 -67
- package/dist/shared/flap 2/pinata.d.ts +0 -40
- package/dist/shared/flap 2/pinata.js +0 -106
- package/dist/shared/flap 2/portal-bundle-merkle/config.d.ts +0 -79
- package/dist/shared/flap 2/portal-bundle-merkle/config.js +0 -133
- package/dist/shared/flap 2/portal-bundle-merkle/core.d.ts +0 -18
- package/dist/shared/flap 2/portal-bundle-merkle/core.js +0 -938
- package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.d.ts +0 -125
- package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.js +0 -665
- package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.d.ts +0 -88
- package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.js +0 -446
- package/dist/shared/flap 2/portal-bundle-merkle/index.d.ts +0 -14
- package/dist/shared/flap 2/portal-bundle-merkle/index.js +0 -26
- package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.d.ts +0 -28
- package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.js +0 -701
- package/dist/shared/flap 2/portal-bundle-merkle/private.d.ts +0 -17
- package/dist/shared/flap 2/portal-bundle-merkle/private.js +0 -549
- package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.d.ts +0 -65
- package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.js +0 -831
- package/dist/shared/flap 2/portal-bundle-merkle/swap.d.ts +0 -201
- package/dist/shared/flap 2/portal-bundle-merkle/swap.js +0 -1359
- package/dist/shared/flap 2/portal-bundle-merkle/types.d.ts +0 -358
- package/dist/shared/flap 2/portal-bundle-merkle/types.js +0 -1
- package/dist/shared/flap 2/portal-bundle-merkle/utils.d.ts +0 -89
- package/dist/shared/flap 2/portal-bundle-merkle/utils.js +0 -963
- package/dist/shared/flap 2/portal-bundle.d.ts +0 -119
- package/dist/shared/flap 2/portal-bundle.js +0 -584
- package/dist/shared/flap 2/portal.d.ts +0 -392
- package/dist/shared/flap 2/portal.js +0 -559
- package/dist/shared/flap 2/vanity.d.ts +0 -48
- package/dist/shared/flap 2/vanity.js +0 -110
- package/dist/shared/flap 2/vault.d.ts +0 -240
- package/dist/shared/flap 2/vault.js +0 -366
- package/dist/shared/four 2/index.d.ts +0 -7
- package/dist/shared/four 2/index.js +0 -22
- package/dist/shared/four 2/tax-token.d.ts +0 -176
- package/dist/shared/four 2/tax-token.js +0 -302
- package/dist/shared/index 2.js +0 -10
- package/dist/shared/index.d 2.ts +0 -10
- package/dist/types/distribute.d.ts +0 -72
- package/dist/types/distribute.js +0 -1
- package/dist/types 2/errors.d.ts +0 -27
- package/dist/types 2/errors.js +0 -34
- package/dist/utils/__tests__/errors.test.d.ts +0 -1
- package/dist/utils/__tests__/errors.test.js +0 -76
- package/dist/utils/airdrop-sweep-types.d.ts +0 -1
- package/dist/utils/airdrop-sweep-types.js +0 -1
- package/dist/utils/erc20/index.d.ts +0 -242
- package/dist/utils/erc20/index.js +0 -645
- package/dist/utils/erc20/types.d.ts +0 -77
- package/dist/utils/erc20/types.js +0 -1
- package/dist/utils/erc20-types.d.ts +0 -1
- package/dist/utils/erc20-types.js +0 -1
- package/dist/utils/holders-maker/helpers.d.ts +0 -43
- package/dist/utils/holders-maker/helpers.js +0 -371
- package/dist/utils/holders-maker/index.d.ts +0 -26
- package/dist/utils/holders-maker/index.js +0 -218
- package/dist/utils/holders-maker/types.d.ts +0 -72
- package/dist/utils/holders-maker/types.js +0 -4
- package/dist/utils/holders-maker-types.d.ts +0 -1
- package/dist/utils/holders-maker-types.js +0 -1
- package/dist/utils/lp-inspect/index.d.ts +0 -44
- package/dist/utils/lp-inspect/index.js +0 -937
- package/dist/utils/lp-inspect/types.d.ts +0 -100
- package/dist/utils/lp-inspect/types.js +0 -1
- package/dist/utils/lp-inspect-types.d.ts +0 -1
- package/dist/utils/lp-inspect-types.js +0 -1
- package/dist/utils/private-sale-types.d.ts +0 -1
- package/dist/utils/private-sale-types.js +0 -1
- package/dist/utils/quote-helpers/index.d.ts +0 -107
- package/dist/utils/quote-helpers/index.js +0 -346
- package/dist/utils/quote-helpers/types.d.ts +0 -16
- package/dist/utils/quote-helpers/types.js +0 -1
- package/dist/utils/quote-helpers-types.d.ts +0 -1
- package/dist/utils/quote-helpers-types.js +0 -1
- /package/dist/{chains/bsc/four/submit/types.js → __tests__/subpath-exports.test.d.ts} +0 -0
|
@@ -1,963 +0,0 @@
|
|
|
1
|
-
import { ethers, Wallet } from 'ethers';
|
|
2
|
-
import { getOptimizedGasPrice, NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../utils/bundle-helpers.js';
|
|
3
|
-
import { ADDRESSES } from '../../../utils/constants.js';
|
|
4
|
-
import { GAS_LIMITS, PROFIT_CONFIG, calculateTransferFee } from '../../constants/index.js';
|
|
5
|
-
import { MULTICALL3_ABI, ERC20_BALANCE_ABI } from '../../abis/common.js';
|
|
6
|
-
import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient } from './config.js';
|
|
7
|
-
// ==================== 链配置 ====================
|
|
8
|
-
const CHAIN_ID_MAP = {
|
|
9
|
-
'BSC': 56,
|
|
10
|
-
'MONAD': 143,
|
|
11
|
-
'XLAYER': 196
|
|
12
|
-
};
|
|
13
|
-
// ==================== 链常量 ====================
|
|
14
|
-
// ✅ 使用公共模块
|
|
15
|
-
const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
|
|
16
|
-
// ==================== 内部辅助函数 ====================
|
|
17
|
-
const decimalsCache = new Map();
|
|
18
|
-
async function getErc20Decimals(provider, token) {
|
|
19
|
-
const network = await provider.getNetwork();
|
|
20
|
-
const key = `${network.chainId}_${token.toLowerCase()}`;
|
|
21
|
-
if (decimalsCache.has(key))
|
|
22
|
-
return decimalsCache.get(key);
|
|
23
|
-
try {
|
|
24
|
-
const erc20 = new ethers.Contract(token, ['function decimals() view returns (uint8)'], provider);
|
|
25
|
-
const d = await erc20.decimals();
|
|
26
|
-
if (!Number.isFinite(d) || d < 0 || d > 36)
|
|
27
|
-
return 18;
|
|
28
|
-
decimalsCache.set(key, d);
|
|
29
|
-
return d;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return 18;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* ✅ 生成多跳中间钱包(返回完整钱包信息:地址 + 私钥)
|
|
37
|
-
*/
|
|
38
|
-
function generateHopWallets(recipientCount, hopCount) {
|
|
39
|
-
const hopCounts = Array.isArray(hopCount) ? hopCount : new Array(recipientCount).fill(hopCount);
|
|
40
|
-
if (hopCounts.every(h => h <= 0))
|
|
41
|
-
return null;
|
|
42
|
-
const result = [];
|
|
43
|
-
for (let i = 0; i < recipientCount; i++) {
|
|
44
|
-
const chain = [];
|
|
45
|
-
for (let j = 0; j < hopCounts[i]; j++) {
|
|
46
|
-
const wallet = Wallet.createRandom();
|
|
47
|
-
chain.push({
|
|
48
|
-
address: wallet.address,
|
|
49
|
-
privateKey: wallet.privateKey
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
result.push(chain);
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
function normalizeAmounts(recipients, amount, amounts) {
|
|
57
|
-
if (amounts && amounts.length > 0) {
|
|
58
|
-
if (amounts.length !== recipients.length) {
|
|
59
|
-
throw new Error(`amounts length (${amounts.length}) must match recipients length (${recipients.length})`);
|
|
60
|
-
}
|
|
61
|
-
return amounts;
|
|
62
|
-
}
|
|
63
|
-
if (amount !== undefined && amount.trim().length > 0) {
|
|
64
|
-
return new Array(recipients.length).fill(amount);
|
|
65
|
-
}
|
|
66
|
-
throw new Error('Either amount or amounts must be provided');
|
|
67
|
-
}
|
|
68
|
-
async function batchGetBalances(provider, addresses, tokenAddress) {
|
|
69
|
-
if (addresses.length === 0)
|
|
70
|
-
return [];
|
|
71
|
-
if (!tokenAddress) {
|
|
72
|
-
return Promise.all(addresses.map(addr => provider.getBalance(addr).catch(() => 0n)));
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// ✅ 使用公共模块
|
|
76
|
-
const multicall = new ethers.Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
77
|
-
const iface = new ethers.Interface(ERC20_BALANCE_ABI);
|
|
78
|
-
const calls = addresses.map(addr => ({
|
|
79
|
-
target: tokenAddress,
|
|
80
|
-
allowFailure: true,
|
|
81
|
-
callData: iface.encodeFunctionData('balanceOf', [addr])
|
|
82
|
-
}));
|
|
83
|
-
try {
|
|
84
|
-
const results = await multicall.aggregate3(calls);
|
|
85
|
-
return results.map((result) => {
|
|
86
|
-
if (result.success) {
|
|
87
|
-
return iface.decodeFunctionResult('balanceOf', result.returnData)[0];
|
|
88
|
-
}
|
|
89
|
-
return 0n;
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
const fallbackCalls = addresses.map(addr => provider
|
|
94
|
-
.call({ to: tokenAddress, data: iface.encodeFunctionData('balanceOf', [addr]) })
|
|
95
|
-
.then(raw => iface.decodeFunctionResult('balanceOf', raw)[0])
|
|
96
|
-
.catch(() => 0n));
|
|
97
|
-
return Promise.all(fallbackCalls);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* 计算 Gas Limit
|
|
103
|
-
* - 原生代币转账:21000 gas(固定)
|
|
104
|
-
* - ERC20 标准 transfer:约 45000-55000 gas,使用 55000 作为安全值
|
|
105
|
-
*
|
|
106
|
-
* ✅ 优化:降低 ERC20 gas limit,减少中转钱包 BNB 残留
|
|
107
|
-
*/
|
|
108
|
-
function calculateGasLimit(config, isNative, hasHops, _hopCount = 0) {
|
|
109
|
-
if (config.gasLimit !== undefined) {
|
|
110
|
-
return BigInt(config.gasLimit);
|
|
111
|
-
}
|
|
112
|
-
// ✅ 原生代币: 21000, ERC20 标准 transfer: 48000(USDT 最低约 46815)
|
|
113
|
-
const baseGas = isNative ? 21000 : 48000;
|
|
114
|
-
// ✅ 多跳时每个中转钱包只执行一笔 transfer,使用较小的安全系数
|
|
115
|
-
const multiplier = config.gasLimitMultiplier ?? 1.1;
|
|
116
|
-
return BigInt(Math.ceil(baseGas * multiplier));
|
|
117
|
-
}
|
|
118
|
-
function isNativeTokenAddress(tokenAddress) {
|
|
119
|
-
if (!tokenAddress)
|
|
120
|
-
return true;
|
|
121
|
-
const v = tokenAddress.trim().toLowerCase();
|
|
122
|
-
if (!v)
|
|
123
|
-
return true;
|
|
124
|
-
if (v === 'native')
|
|
125
|
-
return true;
|
|
126
|
-
if (v === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee')
|
|
127
|
-
return true;
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
// ==================== 分散函数 ====================
|
|
131
|
-
/**
|
|
132
|
-
* Flap Protocol: 分散(仅签名版本)
|
|
133
|
-
* ✅ 支持利润提取(按地址数量收取固定费用)
|
|
134
|
-
* ✅ 支持多跳
|
|
135
|
-
* ✅ 支持 Multicall3 批量获取余额
|
|
136
|
-
*/
|
|
137
|
-
export async function flapDisperseWithBundleMerkle(params) {
|
|
138
|
-
const { chain, fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce } = params;
|
|
139
|
-
if (!recipients || recipients.length === 0) {
|
|
140
|
-
return { signedTransactions: [], hopWallets: undefined };
|
|
141
|
-
}
|
|
142
|
-
const chainIdNum = CHAIN_ID_MAP[chain] ?? 56;
|
|
143
|
-
const provider = new ethers.JsonRpcProvider(config.rpcUrl, { chainId: chainIdNum, name: chain });
|
|
144
|
-
const mainWallet = new Wallet(fromPrivateKey, provider);
|
|
145
|
-
const isNative = isNativeTokenAddress(tokenAddress);
|
|
146
|
-
const txType = getTxType(config);
|
|
147
|
-
// 预处理数据
|
|
148
|
-
const normalizedAmounts = items && items.length > 0
|
|
149
|
-
? items.map(it => (typeof it.amount === 'bigint' ? it.amount.toString() : String(it.amount)))
|
|
150
|
-
: normalizeAmounts(recipients, amount, amounts);
|
|
151
|
-
const providedHops = (() => {
|
|
152
|
-
if (hopPrivateKeys && hopPrivateKeys.length > 0) {
|
|
153
|
-
if (hopPrivateKeys.length !== recipients.length) {
|
|
154
|
-
throw new Error(`hopPrivateKeys length (${hopPrivateKeys.length}) must match recipients length (${recipients.length})`);
|
|
155
|
-
}
|
|
156
|
-
return hopPrivateKeys.every(h => h.length === 0) ? null : hopPrivateKeys;
|
|
157
|
-
}
|
|
158
|
-
if (items && items.length > 0) {
|
|
159
|
-
const hops = items.map(it => it.hopPrivateKeys ?? []);
|
|
160
|
-
return hops.every(h => h.length === 0) ? null : hops;
|
|
161
|
-
}
|
|
162
|
-
return null;
|
|
163
|
-
})();
|
|
164
|
-
const hopCountInput = (() => {
|
|
165
|
-
if (items && items.length > 0) {
|
|
166
|
-
const baseArray = Array.isArray(hopCount) ? hopCount : new Array(recipients.length).fill(hopCount);
|
|
167
|
-
return items.map((it, i) => (typeof it.hopCount === 'number' ? it.hopCount : (baseArray[i] ?? 0)));
|
|
168
|
-
}
|
|
169
|
-
return hopCount;
|
|
170
|
-
})();
|
|
171
|
-
const preparedHops = providedHops ?? generateHopWallets(recipients.length, hopCountInput);
|
|
172
|
-
const hasHops = preparedHops !== null;
|
|
173
|
-
const maxHopCount = hasHops ? Math.max(...preparedHops.map(h => h.length)) : 0;
|
|
174
|
-
const finalGasLimit = calculateGasLimit(config, isNative, hasHops, maxHopCount);
|
|
175
|
-
const nativeGasLimit = GAS_LIMITS.BRIBE;
|
|
176
|
-
const signedTxs = [];
|
|
177
|
-
const extractProfit = shouldExtractProfit(config);
|
|
178
|
-
const profitAddr = getProfitRecipient();
|
|
179
|
-
let totalProfit = 0n;
|
|
180
|
-
let totalAmountBeforeProfit = 0n;
|
|
181
|
-
let profitHopWallets; // ✅ 收集利润多跳钱包
|
|
182
|
-
const nonceManager = new NonceManager(provider);
|
|
183
|
-
if (!preparedHops) {
|
|
184
|
-
// ========== 无多跳:直接批量转账 ==========
|
|
185
|
-
const extraTxCount = extractProfit ? 1 : 0;
|
|
186
|
-
const totalTxCount = recipients.length + extraTxCount;
|
|
187
|
-
const [gasPrice, nonces] = await Promise.all([
|
|
188
|
-
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
189
|
-
startNonce !== undefined
|
|
190
|
-
? Promise.resolve(Array.from({ length: totalTxCount }, (_, i) => startNonce + i))
|
|
191
|
-
: nonceManager.getNextNonceBatch(mainWallet, totalTxCount)
|
|
192
|
-
]);
|
|
193
|
-
// ✅ 按地址数量收取固定费用(原生代币)
|
|
194
|
-
if (extractProfit) {
|
|
195
|
-
totalProfit = calculateTransferFee(chainIdNum, recipients.length);
|
|
196
|
-
console.log(`[flap disperse] 利润按地址数量收取: ${ethers.formatEther(totalProfit)} 原生币 (${recipients.length} 地址)`);
|
|
197
|
-
}
|
|
198
|
-
if (isNative) {
|
|
199
|
-
const txDataList = recipients.map((to, i) => {
|
|
200
|
-
const originalAmount = ethers.parseEther(normalizedAmounts[i]);
|
|
201
|
-
totalAmountBeforeProfit += originalAmount;
|
|
202
|
-
// ✅ 不再从转账金额中扣除利润,全额转账
|
|
203
|
-
return { to, value: originalAmount, nonce: nonces[i] };
|
|
204
|
-
});
|
|
205
|
-
const txPromises = txDataList.map(({ to, value, nonce }) => mainWallet.signTransaction({
|
|
206
|
-
to,
|
|
207
|
-
value,
|
|
208
|
-
nonce,
|
|
209
|
-
gasPrice,
|
|
210
|
-
gasLimit: nativeGasLimit,
|
|
211
|
-
chainId: chainIdNum,
|
|
212
|
-
type: txType
|
|
213
|
-
}));
|
|
214
|
-
signedTxs.push(...(await Promise.all(txPromises)));
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
const decimals = tokenDecimals ?? (await getErc20Decimals(provider, tokenAddress));
|
|
218
|
-
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
219
|
-
const txDataList = recipients.map((to, i) => {
|
|
220
|
-
const originalAmount = ethers.parseUnits(normalizedAmounts[i], decimals);
|
|
221
|
-
totalAmountBeforeProfit += originalAmount;
|
|
222
|
-
// ✅ 不再从转账金额中扣除利润,全额转账
|
|
223
|
-
const data = iface.encodeFunctionData('transfer', [to, originalAmount]);
|
|
224
|
-
return { data, nonce: nonces[i] };
|
|
225
|
-
});
|
|
226
|
-
const txPromises = txDataList.map(({ data, nonce }) => mainWallet.signTransaction({
|
|
227
|
-
to: tokenAddress,
|
|
228
|
-
data,
|
|
229
|
-
value: 0n,
|
|
230
|
-
nonce,
|
|
231
|
-
gasPrice,
|
|
232
|
-
gasLimit: finalGasLimit,
|
|
233
|
-
chainId: chainIdNum,
|
|
234
|
-
type: txType
|
|
235
|
-
}));
|
|
236
|
-
signedTxs.push(...(await Promise.all(txPromises)));
|
|
237
|
-
}
|
|
238
|
-
// ✅ 利润多跳转账(固定费用,原生代币)
|
|
239
|
-
if (extractProfit && totalProfit > 0n) {
|
|
240
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
241
|
-
provider,
|
|
242
|
-
payerWallet: mainWallet,
|
|
243
|
-
profitAmount: totalProfit,
|
|
244
|
-
profitRecipient: profitAddr,
|
|
245
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
246
|
-
gasPrice,
|
|
247
|
-
chainId: chainIdNum,
|
|
248
|
-
txType,
|
|
249
|
-
startNonce: nonces[recipients.length]
|
|
250
|
-
});
|
|
251
|
-
signedTxs.push(...profitHopResult.signedTransactions);
|
|
252
|
-
profitHopWallets = profitHopResult.hopWallets;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
// ========== 有多跳:构建跳转链 ==========
|
|
257
|
-
const [gasPrice, decimals] = await Promise.all([
|
|
258
|
-
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
259
|
-
isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await getErc20Decimals(provider, tokenAddress))
|
|
260
|
-
]);
|
|
261
|
-
const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
262
|
-
const gasFeePerHop = finalGasLimit * gasPrice;
|
|
263
|
-
let mainWalletNonceCount = 0;
|
|
264
|
-
for (let i = 0; i < recipients.length; i++) {
|
|
265
|
-
const hopChain = preparedHops[i];
|
|
266
|
-
if (hopChain.length === 0) {
|
|
267
|
-
mainWalletNonceCount += 1;
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
const nonceNeed = isNative ? 1 : (hopChain.length + 1);
|
|
271
|
-
mainWalletNonceCount += nonceNeed;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (extractProfit)
|
|
275
|
-
mainWalletNonceCount += 1;
|
|
276
|
-
const allMainNonces = startNonce !== undefined
|
|
277
|
-
? Array.from({ length: mainWalletNonceCount }, (_, i) => startNonce + i)
|
|
278
|
-
: await nonceManager.getNextNonceBatch(mainWallet, mainWalletNonceCount);
|
|
279
|
-
let mainNonceIdx = 0;
|
|
280
|
-
const txsToSign = [];
|
|
281
|
-
// ✅ 按地址数量收取固定费用(原生代币)
|
|
282
|
-
if (extractProfit) {
|
|
283
|
-
totalProfit = calculateTransferFee(chainIdNum, recipients.length);
|
|
284
|
-
console.log(`[flap disperse hops] 利润按地址数量收取: ${ethers.formatEther(totalProfit)} 原生币 (${recipients.length} 地址)`);
|
|
285
|
-
}
|
|
286
|
-
for (let i = 0; i < recipients.length; i++) {
|
|
287
|
-
const finalRecipient = recipients[i];
|
|
288
|
-
const originalAmountWei = isNative
|
|
289
|
-
? ethers.parseEther(normalizedAmounts[i])
|
|
290
|
-
: ethers.parseUnits(normalizedAmounts[i], decimals);
|
|
291
|
-
totalAmountBeforeProfit += originalAmountWei;
|
|
292
|
-
// ✅ 不再从转账金额中扣除利润,全额转账
|
|
293
|
-
const amountWei = originalAmountWei;
|
|
294
|
-
const hopChain = preparedHops[i];
|
|
295
|
-
if (hopChain.length === 0) {
|
|
296
|
-
const nonce = allMainNonces[mainNonceIdx++];
|
|
297
|
-
if (isNative) {
|
|
298
|
-
txsToSign.push({
|
|
299
|
-
wallet: mainWallet,
|
|
300
|
-
tx: { to: finalRecipient, value: amountWei, nonce, gasPrice, gasLimit: nativeGasLimit, chainId: chainIdNum, type: txType }
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
const data = iface.encodeFunctionData('transfer', [finalRecipient, amountWei]);
|
|
305
|
-
txsToSign.push({
|
|
306
|
-
wallet: mainWallet,
|
|
307
|
-
tx: { to: tokenAddress, data, value: 0n, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType }
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
// ✅ 支持 string 和 GeneratedWallet 两种类型
|
|
313
|
-
const fullChain = [mainWallet, ...hopChain.map(w => new Wallet(typeof w === 'string' ? w : w.privateKey, provider))];
|
|
314
|
-
const addresses = [...fullChain.map(w => w.address), finalRecipient];
|
|
315
|
-
if (!isNative) {
|
|
316
|
-
for (let j = 0; j < hopChain.length; j++) {
|
|
317
|
-
const nonce = allMainNonces[mainNonceIdx++];
|
|
318
|
-
txsToSign.push({
|
|
319
|
-
wallet: mainWallet,
|
|
320
|
-
tx: { to: fullChain[j + 1].address, value: gasFeePerHop, nonce, gasPrice, gasLimit: nativeGasLimit, chainId: chainIdNum, type: txType }
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
for (let j = 0; j < addresses.length - 1; j++) {
|
|
325
|
-
const fromWallet = fullChain[j];
|
|
326
|
-
const toAddress = addresses[j + 1];
|
|
327
|
-
const nonce = j === 0 ? allMainNonces[mainNonceIdx++] : 0;
|
|
328
|
-
if (isNative) {
|
|
329
|
-
const remainingHops = addresses.length - 2 - j;
|
|
330
|
-
const additionalGas = gasFeePerHop * BigInt(remainingHops);
|
|
331
|
-
const transferValue = amountWei + additionalGas;
|
|
332
|
-
txsToSign.push({
|
|
333
|
-
wallet: fromWallet,
|
|
334
|
-
tx: { to: toAddress, value: transferValue, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType }
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
const data = iface.encodeFunctionData('transfer', [toAddress, amountWei]);
|
|
339
|
-
txsToSign.push({
|
|
340
|
-
wallet: fromWallet,
|
|
341
|
-
tx: { to: tokenAddress, data, value: 0n, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType }
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
const signedTxsResult = await Promise.all(txsToSign.map(({ wallet, tx }) => wallet.signTransaction(tx)));
|
|
347
|
-
signedTxs.push(...signedTxsResult);
|
|
348
|
-
// ✅ 利润多跳转账(固定费用,原生代币)
|
|
349
|
-
if (extractProfit && totalProfit > 0n) {
|
|
350
|
-
const profitNonce = allMainNonces[mainNonceIdx++];
|
|
351
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
352
|
-
provider,
|
|
353
|
-
payerWallet: mainWallet,
|
|
354
|
-
profitAmount: totalProfit,
|
|
355
|
-
profitRecipient: profitAddr,
|
|
356
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
357
|
-
gasPrice,
|
|
358
|
-
chainId: chainIdNum,
|
|
359
|
-
txType,
|
|
360
|
-
startNonce: profitNonce
|
|
361
|
-
});
|
|
362
|
-
signedTxs.push(...profitHopResult.signedTransactions);
|
|
363
|
-
profitHopWallets = profitHopResult.hopWallets;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
signedTransactions: signedTxs,
|
|
368
|
-
hopWallets: preparedHops || undefined,
|
|
369
|
-
profitHopWallets, // ✅ 返回利润多跳钱包
|
|
370
|
-
metadata: extractProfit ? {
|
|
371
|
-
totalAmount: ethers.formatEther(totalAmountBeforeProfit),
|
|
372
|
-
profitAmount: ethers.formatEther(totalProfit),
|
|
373
|
-
profitRecipient: profitAddr,
|
|
374
|
-
recipientCount: recipients.length,
|
|
375
|
-
isNative,
|
|
376
|
-
tokenAddress: isNative ? undefined : tokenAddress
|
|
377
|
-
} : undefined
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
// ==================== 归集函数 ====================
|
|
381
|
-
/**
|
|
382
|
-
* Flap Protocol: 归集(仅签名版本)
|
|
383
|
-
* ✅ 支持利润提取(按地址数量收取固定费用)
|
|
384
|
-
* ✅ 查询余额最大的钱包支付利润
|
|
385
|
-
* ✅ 支持多跳
|
|
386
|
-
* ✅ 支持 Multicall3 批量获取余额
|
|
387
|
-
*/
|
|
388
|
-
export async function flapSweepWithBundleMerkle(params) {
|
|
389
|
-
const { chain, sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config } = params;
|
|
390
|
-
if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) {
|
|
391
|
-
return { signedTransactions: [], hopWallets: undefined };
|
|
392
|
-
}
|
|
393
|
-
const chainIdNum = CHAIN_ID_MAP[chain] ?? 56;
|
|
394
|
-
const provider = new ethers.JsonRpcProvider(config.rpcUrl, { chainId: chainIdNum, name: chain });
|
|
395
|
-
const isNative = isNativeTokenAddress(tokenAddress);
|
|
396
|
-
const txType = getTxType(config);
|
|
397
|
-
const clamp = (n) => (typeof n === 'number' && Number.isFinite(n)
|
|
398
|
-
? Math.max(0, Math.min(100, Math.floor(n)))
|
|
399
|
-
: undefined);
|
|
400
|
-
const ratio = clamp(ratioPct);
|
|
401
|
-
const actualKeys = (() => {
|
|
402
|
-
if (sources && sources.length > 0)
|
|
403
|
-
return sources.map(s => s.privateKey);
|
|
404
|
-
return sourcePrivateKeys;
|
|
405
|
-
})();
|
|
406
|
-
const providedHops = (() => {
|
|
407
|
-
if (hopPrivateKeys && hopPrivateKeys.length > 0) {
|
|
408
|
-
if (hopPrivateKeys.length !== actualKeys.length) {
|
|
409
|
-
throw new Error(`hopPrivateKeys length (${hopPrivateKeys.length}) must match sourcePrivateKeys length (${actualKeys.length})`);
|
|
410
|
-
}
|
|
411
|
-
return hopPrivateKeys.every(h => h.length === 0) ? null : hopPrivateKeys;
|
|
412
|
-
}
|
|
413
|
-
if (sources && sources.length > 0) {
|
|
414
|
-
const hops = sources.map(s => s.hopPrivateKeys ?? []);
|
|
415
|
-
return hops.every(h => h.length === 0) ? null : hops;
|
|
416
|
-
}
|
|
417
|
-
return null;
|
|
418
|
-
})();
|
|
419
|
-
const hopCountInput = (() => {
|
|
420
|
-
if (sources && sources.length > 0) {
|
|
421
|
-
const baseArray = Array.isArray(hopCount) ? hopCount : new Array(actualKeys.length).fill(hopCount);
|
|
422
|
-
return sources.map((s, i) => (typeof s.hopCount === 'number' ? s.hopCount : (baseArray[i] ?? 0)));
|
|
423
|
-
}
|
|
424
|
-
return hopCount;
|
|
425
|
-
})();
|
|
426
|
-
const preparedHops = providedHops ?? generateHopWallets(actualKeys.length, hopCountInput);
|
|
427
|
-
const hasHops = preparedHops !== null;
|
|
428
|
-
const maxHopCount = hasHops ? Math.max(...preparedHops.map(h => h.length)) : 0;
|
|
429
|
-
const finalGasLimit = calculateGasLimit(config, isNative, hasHops, maxHopCount);
|
|
430
|
-
const nativeGasLimit = GAS_LIMITS.BRIBE;
|
|
431
|
-
const signedTxs = [];
|
|
432
|
-
const extractProfit = shouldExtractProfit(config);
|
|
433
|
-
const profitAddr = getProfitRecipient();
|
|
434
|
-
let totalProfit = 0n;
|
|
435
|
-
let totalAmountBeforeProfit = 0n;
|
|
436
|
-
let profitHopWallets; // ✅ 收集利润多跳钱包
|
|
437
|
-
if (!preparedHops) {
|
|
438
|
-
// ========== 无多跳:直接批量归集 ==========
|
|
439
|
-
const wallets = actualKeys.map(pk => new Wallet(pk, provider));
|
|
440
|
-
const addresses = wallets.map(w => w.address);
|
|
441
|
-
const nonceManager = new NonceManager(provider);
|
|
442
|
-
// ✅ 按地址数量收取固定费用(原生代币)
|
|
443
|
-
if (extractProfit) {
|
|
444
|
-
totalProfit = calculateTransferFee(chainIdNum, wallets.length);
|
|
445
|
-
console.log(`[flap sweep] 利润按地址数量收取: ${ethers.formatEther(totalProfit)} 原生币 (${wallets.length} 地址)`);
|
|
446
|
-
}
|
|
447
|
-
if (isNative) {
|
|
448
|
-
const [gasPrice, balances] = await Promise.all([
|
|
449
|
-
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
450
|
-
batchGetBalances(provider, addresses)
|
|
451
|
-
]);
|
|
452
|
-
const gasCostBase = nativeGasLimit * gasPrice;
|
|
453
|
-
const profitTxGas = PROFIT_CONFIG.HOP.GAS_LIMIT * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
454
|
-
// 第一步:计算所有钱包的归集金额,找出余额最大的钱包
|
|
455
|
-
const sweepAmounts = [];
|
|
456
|
-
let maxSweepIndex = -1;
|
|
457
|
-
let maxSweepAmount = 0n;
|
|
458
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
459
|
-
const bal = balances[i];
|
|
460
|
-
let toSend = 0n;
|
|
461
|
-
const ratioForI = (() => {
|
|
462
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
463
|
-
return clamp(sources[i].ratioPct);
|
|
464
|
-
if (ratios && ratios[i] !== undefined)
|
|
465
|
-
return clamp(ratios[i]);
|
|
466
|
-
return ratio;
|
|
467
|
-
})();
|
|
468
|
-
const amountStrForI = (() => {
|
|
469
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
470
|
-
return String(sources[i].amount);
|
|
471
|
-
if (amounts && amounts[i] !== undefined)
|
|
472
|
-
return String(amounts[i]);
|
|
473
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
474
|
-
})();
|
|
475
|
-
const gasCost = gasCostBase;
|
|
476
|
-
if (ratioForI !== undefined) {
|
|
477
|
-
const want = (bal * BigInt(ratioForI)) / 100n;
|
|
478
|
-
const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
|
|
479
|
-
toSend = want > maxSendable ? maxSendable : want;
|
|
480
|
-
}
|
|
481
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
482
|
-
const amt = ethers.parseEther(amountStrForI);
|
|
483
|
-
const need = amt + gasCost;
|
|
484
|
-
if (!skipIfInsufficient || bal >= need)
|
|
485
|
-
toSend = amt;
|
|
486
|
-
}
|
|
487
|
-
sweepAmounts.push(toSend);
|
|
488
|
-
totalAmountBeforeProfit += toSend;
|
|
489
|
-
// ✅ 找出余额最大的钱包作为利润支付者
|
|
490
|
-
if (toSend > maxSweepAmount) {
|
|
491
|
-
maxSweepAmount = toSend;
|
|
492
|
-
maxSweepIndex = i;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
// 检查支付者余额是否足够支付利润
|
|
496
|
-
if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) {
|
|
497
|
-
const payerBalance = balances[maxSweepIndex];
|
|
498
|
-
const payerSweepAmount = sweepAmounts[maxSweepIndex];
|
|
499
|
-
const payerNeedGas = gasCostBase + profitTxGas;
|
|
500
|
-
if (payerBalance < payerSweepAmount + payerNeedGas + totalProfit) {
|
|
501
|
-
// 余额不足支付利润,减少归集金额
|
|
502
|
-
const maxPayerSweep = payerBalance > (payerNeedGas + totalProfit) ? payerBalance - payerNeedGas - totalProfit : 0n;
|
|
503
|
-
sweepAmounts[maxSweepIndex] = maxPayerSweep;
|
|
504
|
-
totalAmountBeforeProfit = sweepAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
// 第二步:生成归集交易
|
|
508
|
-
let payerProfitNonce;
|
|
509
|
-
if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) {
|
|
510
|
-
const payerWallet = wallets[maxSweepIndex];
|
|
511
|
-
const nonces = await nonceManager.getNextNonceBatch(payerWallet, 2);
|
|
512
|
-
payerProfitNonce = nonces[1];
|
|
513
|
-
}
|
|
514
|
-
const walletsToSweep = wallets.filter((_, i) => sweepAmounts[i] > 0n && i !== maxSweepIndex);
|
|
515
|
-
const nonces = walletsToSweep.length > 0
|
|
516
|
-
? await nonceManager.getNextNoncesForWallets(walletsToSweep)
|
|
517
|
-
: [];
|
|
518
|
-
let nonceIdx = 0;
|
|
519
|
-
const txPromises = wallets.map(async (w, i) => {
|
|
520
|
-
const toSend = sweepAmounts[i];
|
|
521
|
-
if (toSend <= 0n)
|
|
522
|
-
return null;
|
|
523
|
-
// ✅ 全额归集,不从转账金额中扣除利润
|
|
524
|
-
let nonce;
|
|
525
|
-
if (i === maxSweepIndex && payerProfitNonce !== undefined) {
|
|
526
|
-
nonce = payerProfitNonce - 1;
|
|
527
|
-
}
|
|
528
|
-
else {
|
|
529
|
-
nonce = nonces[nonceIdx++];
|
|
530
|
-
}
|
|
531
|
-
const mainTx = await w.signTransaction({
|
|
532
|
-
to: target,
|
|
533
|
-
value: toSend,
|
|
534
|
-
nonce,
|
|
535
|
-
gasPrice,
|
|
536
|
-
gasLimit: nativeGasLimit,
|
|
537
|
-
chainId: chainIdNum,
|
|
538
|
-
type: txType
|
|
539
|
-
});
|
|
540
|
-
return mainTx;
|
|
541
|
-
});
|
|
542
|
-
const allTxs = (await Promise.all(txPromises)).filter(tx => tx !== null);
|
|
543
|
-
signedTxs.push(...allTxs);
|
|
544
|
-
// ✅ 利润多跳转账(固定费用,原生代币)
|
|
545
|
-
if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0 && payerProfitNonce !== undefined) {
|
|
546
|
-
const payerWallet = wallets[maxSweepIndex];
|
|
547
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
548
|
-
provider,
|
|
549
|
-
payerWallet,
|
|
550
|
-
profitAmount: totalProfit,
|
|
551
|
-
profitRecipient: profitAddr,
|
|
552
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
553
|
-
gasPrice,
|
|
554
|
-
chainId: chainIdNum,
|
|
555
|
-
txType,
|
|
556
|
-
startNonce: payerProfitNonce
|
|
557
|
-
});
|
|
558
|
-
signedTxs.push(...profitHopResult.signedTransactions);
|
|
559
|
-
profitHopWallets = profitHopResult.hopWallets;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
// ERC20 归集
|
|
564
|
-
const [gasPrice, decimals, balances, nativeBalances] = await Promise.all([
|
|
565
|
-
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
566
|
-
Promise.resolve(tokenDecimals ?? await getErc20Decimals(provider, tokenAddress)),
|
|
567
|
-
batchGetBalances(provider, addresses, tokenAddress),
|
|
568
|
-
batchGetBalances(provider, addresses)
|
|
569
|
-
]);
|
|
570
|
-
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
571
|
-
const profitTxGas = PROFIT_CONFIG.HOP.GAS_LIMIT * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
572
|
-
const sweepAmounts = [];
|
|
573
|
-
let maxSweepIndex = -1;
|
|
574
|
-
let maxSweepAmount = 0n;
|
|
575
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
576
|
-
const bal = balances[i];
|
|
577
|
-
let toSend = 0n;
|
|
578
|
-
const ratioForI = (() => {
|
|
579
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
580
|
-
return clamp(sources[i].ratioPct);
|
|
581
|
-
if (ratios && ratios[i] !== undefined)
|
|
582
|
-
return clamp(ratios[i]);
|
|
583
|
-
return ratio;
|
|
584
|
-
})();
|
|
585
|
-
const amountStrForI = (() => {
|
|
586
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
587
|
-
return String(sources[i].amount);
|
|
588
|
-
if (amounts && amounts[i] !== undefined)
|
|
589
|
-
return String(amounts[i]);
|
|
590
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
591
|
-
})();
|
|
592
|
-
if (ratioForI !== undefined) {
|
|
593
|
-
toSend = (bal * BigInt(ratioForI)) / 100n;
|
|
594
|
-
}
|
|
595
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
596
|
-
toSend = ethers.parseUnits(amountStrForI, decimals);
|
|
597
|
-
}
|
|
598
|
-
if (toSend <= 0n || (skipIfInsufficient && bal < toSend)) {
|
|
599
|
-
sweepAmounts.push(0n);
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
const totalGasNeeded = finalGasLimit * gasPrice;
|
|
603
|
-
const nativeBal = nativeBalances[i] ?? 0n;
|
|
604
|
-
if (skipIfInsufficient && nativeBal < totalGasNeeded) {
|
|
605
|
-
sweepAmounts.push(0n);
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
sweepAmounts.push(toSend);
|
|
609
|
-
totalAmountBeforeProfit += toSend;
|
|
610
|
-
// ✅ 找出原生代币余额最大的钱包作为利润支付者
|
|
611
|
-
if (nativeBal > maxSweepAmount) {
|
|
612
|
-
maxSweepAmount = nativeBal;
|
|
613
|
-
maxSweepIndex = i;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
// 检查支付者原生代币余额是否足够支付固定费用
|
|
617
|
-
if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) {
|
|
618
|
-
const payerNativeBalance = nativeBalances[maxSweepIndex] ?? 0n;
|
|
619
|
-
const payerNeedGas = finalGasLimit * gasPrice + profitTxGas + totalProfit;
|
|
620
|
-
if (payerNativeBalance < payerNeedGas) {
|
|
621
|
-
// 余额不足,跳过利润收取
|
|
622
|
-
totalProfit = 0n;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
let payerProfitNonce;
|
|
626
|
-
if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) {
|
|
627
|
-
const payerWallet = wallets[maxSweepIndex];
|
|
628
|
-
const nonces = await nonceManager.getNextNonceBatch(payerWallet, 2);
|
|
629
|
-
payerProfitNonce = nonces[1];
|
|
630
|
-
}
|
|
631
|
-
const walletsToSweepErc20 = wallets.filter((_, i) => sweepAmounts[i] > 0n && i !== maxSweepIndex);
|
|
632
|
-
const noncesErc20 = walletsToSweepErc20.length > 0
|
|
633
|
-
? await nonceManager.getNextNoncesForWallets(walletsToSweepErc20)
|
|
634
|
-
: [];
|
|
635
|
-
let nonceIdxErc20 = 0;
|
|
636
|
-
const txPromises = wallets.map(async (w, i) => {
|
|
637
|
-
const toSend = sweepAmounts[i];
|
|
638
|
-
if (toSend <= 0n)
|
|
639
|
-
return null;
|
|
640
|
-
let nonce;
|
|
641
|
-
if (i === maxSweepIndex && payerProfitNonce !== undefined) {
|
|
642
|
-
nonce = payerProfitNonce - 1;
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
nonce = noncesErc20[nonceIdxErc20++];
|
|
646
|
-
}
|
|
647
|
-
// ✅ 全额归集 ERC20,不从转账金额中扣除利润
|
|
648
|
-
const data = iface.encodeFunctionData('transfer', [target, toSend]);
|
|
649
|
-
const mainTx = await w.signTransaction({
|
|
650
|
-
to: tokenAddress,
|
|
651
|
-
data,
|
|
652
|
-
value: 0n,
|
|
653
|
-
nonce,
|
|
654
|
-
gasPrice,
|
|
655
|
-
gasLimit: finalGasLimit,
|
|
656
|
-
chainId: chainIdNum,
|
|
657
|
-
type: txType
|
|
658
|
-
});
|
|
659
|
-
return mainTx;
|
|
660
|
-
});
|
|
661
|
-
const allTxs = (await Promise.all(txPromises)).filter(tx => tx !== null);
|
|
662
|
-
signedTxs.push(...allTxs);
|
|
663
|
-
// ✅ 利润多跳转账(固定费用,原生代币)
|
|
664
|
-
if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0 && payerProfitNonce !== undefined) {
|
|
665
|
-
const payerWallet = wallets[maxSweepIndex];
|
|
666
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
667
|
-
provider,
|
|
668
|
-
payerWallet,
|
|
669
|
-
profitAmount: totalProfit,
|
|
670
|
-
profitRecipient: profitAddr,
|
|
671
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
672
|
-
gasPrice,
|
|
673
|
-
chainId: chainIdNum,
|
|
674
|
-
txType,
|
|
675
|
-
startNonce: payerProfitNonce
|
|
676
|
-
});
|
|
677
|
-
signedTxs.push(...profitHopResult.signedTransactions);
|
|
678
|
-
profitHopWallets = profitHopResult.hopWallets;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
else {
|
|
683
|
-
// ========== 有多跳:构建跳转链归集 ==========
|
|
684
|
-
const sourceWallets = actualKeys.map(pk => new Wallet(pk, provider));
|
|
685
|
-
// ✅ 按地址数量收取固定费用(原生代币)
|
|
686
|
-
if (extractProfit) {
|
|
687
|
-
totalProfit = calculateTransferFee(chainIdNum, sourceWallets.length);
|
|
688
|
-
console.log(`[flap sweep hops] 利润按地址数量收取: ${ethers.formatEther(totalProfit)} 原生币 (${sourceWallets.length} 地址)`);
|
|
689
|
-
}
|
|
690
|
-
const withHopIndexes = [];
|
|
691
|
-
const withoutHopIndexes = [];
|
|
692
|
-
for (let i = 0; i < preparedHops.length; i++) {
|
|
693
|
-
if (preparedHops[i].length > 0) {
|
|
694
|
-
withHopIndexes.push(i);
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
withoutHopIndexes.push(i);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
const sourceAddresses = sourceWallets.map(w => w.address);
|
|
701
|
-
const [gasPrice, decimals, balances, nativeBalances] = await Promise.all([
|
|
702
|
-
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
703
|
-
isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await getErc20Decimals(provider, tokenAddress)),
|
|
704
|
-
batchGetBalances(provider, sourceAddresses, tokenAddress),
|
|
705
|
-
isNative ? Promise.resolve([]) : batchGetBalances(provider, sourceAddresses)
|
|
706
|
-
]);
|
|
707
|
-
const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
708
|
-
const gasFeePerHop = finalGasLimit * gasPrice;
|
|
709
|
-
const nonceManager = new NonceManager(provider);
|
|
710
|
-
const sweepAmounts = new Array(sourceWallets.length).fill(0n);
|
|
711
|
-
// 处理无跳转的地址
|
|
712
|
-
for (const i of withoutHopIndexes) {
|
|
713
|
-
const sourceWallet = sourceWallets[i];
|
|
714
|
-
const bal = balances[i];
|
|
715
|
-
let toSend = 0n;
|
|
716
|
-
if (isNative) {
|
|
717
|
-
const gasCost = nativeGasLimit * gasPrice;
|
|
718
|
-
const ratioForI = (() => {
|
|
719
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
720
|
-
return clamp(sources[i].ratioPct);
|
|
721
|
-
if (ratios && ratios[i] !== undefined)
|
|
722
|
-
return clamp(ratios[i]);
|
|
723
|
-
return ratio;
|
|
724
|
-
})();
|
|
725
|
-
const amountStrForI = (() => {
|
|
726
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
727
|
-
return String(sources[i].amount);
|
|
728
|
-
if (amounts && amounts[i] !== undefined)
|
|
729
|
-
return String(amounts[i]);
|
|
730
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
731
|
-
})();
|
|
732
|
-
if (ratioForI !== undefined) {
|
|
733
|
-
const want = (bal * BigInt(ratioForI)) / 100n;
|
|
734
|
-
const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
|
|
735
|
-
toSend = want > maxSendable ? maxSendable : want;
|
|
736
|
-
}
|
|
737
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
738
|
-
const amt = ethers.parseEther(amountStrForI);
|
|
739
|
-
const need = amt + gasCost;
|
|
740
|
-
if (!skipIfInsufficient || bal >= need)
|
|
741
|
-
toSend = amt;
|
|
742
|
-
}
|
|
743
|
-
if (toSend > 0n) {
|
|
744
|
-
sweepAmounts[i] = toSend;
|
|
745
|
-
totalAmountBeforeProfit += toSend;
|
|
746
|
-
const nonce = await nonceManager.getNextNonce(sourceWallet);
|
|
747
|
-
const tx = await sourceWallet.signTransaction({
|
|
748
|
-
to: target,
|
|
749
|
-
value: toSend,
|
|
750
|
-
nonce,
|
|
751
|
-
gasPrice,
|
|
752
|
-
gasLimit: nativeGasLimit,
|
|
753
|
-
chainId: chainIdNum,
|
|
754
|
-
type: txType
|
|
755
|
-
});
|
|
756
|
-
signedTxs.push(tx);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
const ratioForI = (() => {
|
|
761
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
762
|
-
return clamp(sources[i].ratioPct);
|
|
763
|
-
if (ratios && ratios[i] !== undefined)
|
|
764
|
-
return clamp(ratios[i]);
|
|
765
|
-
return ratio;
|
|
766
|
-
})();
|
|
767
|
-
const amountStrForI = (() => {
|
|
768
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
769
|
-
return String(sources[i].amount);
|
|
770
|
-
if (amounts && amounts[i] !== undefined)
|
|
771
|
-
return String(amounts[i]);
|
|
772
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
773
|
-
})();
|
|
774
|
-
if (ratioForI !== undefined) {
|
|
775
|
-
toSend = (bal * BigInt(ratioForI)) / 100n;
|
|
776
|
-
}
|
|
777
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
778
|
-
toSend = ethers.parseUnits(amountStrForI, decimals);
|
|
779
|
-
}
|
|
780
|
-
if (toSend > 0n && (!skipIfInsufficient || bal >= toSend)) {
|
|
781
|
-
sweepAmounts[i] = toSend;
|
|
782
|
-
totalAmountBeforeProfit += toSend;
|
|
783
|
-
const nonce = await nonceManager.getNextNonce(sourceWallet);
|
|
784
|
-
const data = iface.encodeFunctionData('transfer', [target, toSend]);
|
|
785
|
-
const tx = await sourceWallet.signTransaction({
|
|
786
|
-
to: tokenAddress,
|
|
787
|
-
data,
|
|
788
|
-
value: 0n,
|
|
789
|
-
nonce,
|
|
790
|
-
gasPrice,
|
|
791
|
-
gasLimit: finalGasLimit,
|
|
792
|
-
chainId: chainIdNum,
|
|
793
|
-
type: txType
|
|
794
|
-
});
|
|
795
|
-
signedTxs.push(tx);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
// 处理有跳转的地址
|
|
800
|
-
for (const i of withHopIndexes) {
|
|
801
|
-
const sourceWallet = sourceWallets[i];
|
|
802
|
-
const hopChain = preparedHops[i];
|
|
803
|
-
let toSend = 0n;
|
|
804
|
-
const bal = balances[i];
|
|
805
|
-
if (isNative) {
|
|
806
|
-
const totalGasCost = finalGasLimit * gasPrice * BigInt(hopChain.length + 1);
|
|
807
|
-
const ratioForI = (() => {
|
|
808
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
809
|
-
return clamp(sources[i].ratioPct);
|
|
810
|
-
if (ratios && ratios[i] !== undefined)
|
|
811
|
-
return clamp(ratios[i]);
|
|
812
|
-
return ratio;
|
|
813
|
-
})();
|
|
814
|
-
const amountStrForI = (() => {
|
|
815
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
816
|
-
return String(sources[i].amount);
|
|
817
|
-
if (amounts && amounts[i] !== undefined)
|
|
818
|
-
return String(amounts[i]);
|
|
819
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
820
|
-
})();
|
|
821
|
-
if (ratioForI !== undefined) {
|
|
822
|
-
const want = (bal * BigInt(ratioForI)) / 100n;
|
|
823
|
-
const maxSendable = bal > totalGasCost ? (bal - totalGasCost) : 0n;
|
|
824
|
-
toSend = want > maxSendable ? maxSendable : want;
|
|
825
|
-
}
|
|
826
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
827
|
-
const amt = ethers.parseEther(amountStrForI);
|
|
828
|
-
const need = amt + totalGasCost;
|
|
829
|
-
if (!skipIfInsufficient || bal >= need)
|
|
830
|
-
toSend = amt;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
else {
|
|
834
|
-
const nativeBal = nativeBalances[i];
|
|
835
|
-
const nativeNeeded = (gasFeePerHop * BigInt(hopChain.length)) + (finalGasLimit * gasPrice);
|
|
836
|
-
if (nativeBal < nativeNeeded && skipIfInsufficient)
|
|
837
|
-
continue;
|
|
838
|
-
const ratioForI = (() => {
|
|
839
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
840
|
-
return clamp(sources[i].ratioPct);
|
|
841
|
-
if (ratios && ratios[i] !== undefined)
|
|
842
|
-
return clamp(ratios[i]);
|
|
843
|
-
return ratio;
|
|
844
|
-
})();
|
|
845
|
-
const amountStrForI = (() => {
|
|
846
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
847
|
-
return String(sources[i].amount);
|
|
848
|
-
if (amounts && amounts[i] !== undefined)
|
|
849
|
-
return String(amounts[i]);
|
|
850
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
851
|
-
})();
|
|
852
|
-
if (ratioForI !== undefined) {
|
|
853
|
-
toSend = (bal * BigInt(ratioForI)) / 100n;
|
|
854
|
-
}
|
|
855
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
856
|
-
toSend = ethers.parseUnits(amountStrForI, decimals);
|
|
857
|
-
}
|
|
858
|
-
if (skipIfInsufficient && bal < toSend)
|
|
859
|
-
toSend = 0n;
|
|
860
|
-
}
|
|
861
|
-
if (toSend <= 0n)
|
|
862
|
-
continue;
|
|
863
|
-
sweepAmounts[i] = toSend;
|
|
864
|
-
totalAmountBeforeProfit += toSend;
|
|
865
|
-
// ✅ 支持 string 和 GeneratedWallet 两种类型
|
|
866
|
-
const fullChain = [sourceWallet, ...hopChain.map(w => new Wallet(typeof w === 'string' ? w : w.privateKey, provider))];
|
|
867
|
-
const addresses = [...fullChain.map(w => w.address), target];
|
|
868
|
-
if (!isNative) {
|
|
869
|
-
const gasNonces = await nonceManager.getNextNonceBatch(sourceWallet, hopChain.length);
|
|
870
|
-
for (let j = 0; j < hopChain.length; j++) {
|
|
871
|
-
const tx = await sourceWallet.signTransaction({
|
|
872
|
-
to: fullChain[j + 1].address,
|
|
873
|
-
value: gasFeePerHop,
|
|
874
|
-
nonce: gasNonces[j],
|
|
875
|
-
gasPrice,
|
|
876
|
-
gasLimit: nativeGasLimit,
|
|
877
|
-
chainId: chainIdNum,
|
|
878
|
-
type: txType
|
|
879
|
-
});
|
|
880
|
-
signedTxs.push(tx);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
for (let j = 0; j < addresses.length - 1; j++) {
|
|
884
|
-
const fromWallet = fullChain[j];
|
|
885
|
-
const toAddress = addresses[j + 1];
|
|
886
|
-
const nonce = j === 0
|
|
887
|
-
? await nonceManager.getNextNonce(sourceWallet)
|
|
888
|
-
: 0;
|
|
889
|
-
if (isNative) {
|
|
890
|
-
const remainingHops = addresses.length - 2 - j;
|
|
891
|
-
const additionalGas = gasFeePerHop * BigInt(remainingHops);
|
|
892
|
-
const valueToTransfer = toSend + additionalGas;
|
|
893
|
-
const tx = await fromWallet.signTransaction({
|
|
894
|
-
to: toAddress,
|
|
895
|
-
value: valueToTransfer,
|
|
896
|
-
nonce,
|
|
897
|
-
gasPrice,
|
|
898
|
-
gasLimit: finalGasLimit,
|
|
899
|
-
chainId: chainIdNum,
|
|
900
|
-
type: txType
|
|
901
|
-
});
|
|
902
|
-
signedTxs.push(tx);
|
|
903
|
-
}
|
|
904
|
-
else {
|
|
905
|
-
const data = iface.encodeFunctionData('transfer', [toAddress, toSend]);
|
|
906
|
-
const tx = await fromWallet.signTransaction({
|
|
907
|
-
to: tokenAddress,
|
|
908
|
-
data,
|
|
909
|
-
value: 0n,
|
|
910
|
-
nonce,
|
|
911
|
-
gasPrice,
|
|
912
|
-
gasLimit: finalGasLimit,
|
|
913
|
-
chainId: chainIdNum,
|
|
914
|
-
type: txType
|
|
915
|
-
});
|
|
916
|
-
signedTxs.push(tx);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
// ✅ 利润多跳转账(固定费用,原生代币)- totalProfit 已在函数开头计算
|
|
921
|
-
if (extractProfit && totalProfit > 0n && totalAmountBeforeProfit > 0n) {
|
|
922
|
-
// 找出余额最大的钱包作为利润支付者
|
|
923
|
-
let maxSweepIndex = -1;
|
|
924
|
-
let maxSweepAmount = 0n;
|
|
925
|
-
for (let i = 0; i < sweepAmounts.length; i++) {
|
|
926
|
-
if (sweepAmounts[i] > maxSweepAmount) {
|
|
927
|
-
maxSweepAmount = sweepAmounts[i];
|
|
928
|
-
maxSweepIndex = i;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (maxSweepIndex >= 0) {
|
|
932
|
-
const payerWallet = sourceWallets[maxSweepIndex];
|
|
933
|
-
const profitNonce = await nonceManager.getNextNonce(payerWallet);
|
|
934
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
935
|
-
provider,
|
|
936
|
-
payerWallet,
|
|
937
|
-
profitAmount: totalProfit,
|
|
938
|
-
profitRecipient: profitAddr,
|
|
939
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
940
|
-
gasPrice,
|
|
941
|
-
chainId: chainIdNum,
|
|
942
|
-
txType,
|
|
943
|
-
startNonce: profitNonce
|
|
944
|
-
});
|
|
945
|
-
signedTxs.push(...profitHopResult.signedTransactions);
|
|
946
|
-
profitHopWallets = profitHopResult.hopWallets;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
return {
|
|
951
|
-
signedTransactions: signedTxs,
|
|
952
|
-
hopWallets: preparedHops || undefined,
|
|
953
|
-
profitHopWallets, // ✅ 返回利润多跳钱包
|
|
954
|
-
metadata: extractProfit ? {
|
|
955
|
-
totalAmount: isNative ? ethers.formatEther(totalAmountBeforeProfit) : ethers.formatUnits(totalAmountBeforeProfit, tokenDecimals ?? 18),
|
|
956
|
-
profitAmount: ethers.formatEther(totalProfit),
|
|
957
|
-
profitRecipient: profitAddr,
|
|
958
|
-
sourceCount: actualKeys.length,
|
|
959
|
-
isNative,
|
|
960
|
-
tokenAddress: isNative ? undefined : tokenAddress
|
|
961
|
-
} : undefined
|
|
962
|
-
};
|
|
963
|
-
}
|