four-flap-meme-sdk 2.0.0 → 2.2.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.
Files changed (220) hide show
  1. package/dist/__tests__/subpath-exports.test.js +64 -0
  2. package/dist/chains/bsc/iro.d.ts +5 -0
  3. package/dist/chains/bsc/iro.js +4 -0
  4. package/dist/chains/eni/flat-aliases.d.ts +10 -0
  5. package/dist/chains/eni/flat-aliases.js +8 -0
  6. package/dist/chains/eni/index.d.ts +1 -0
  7. package/dist/chains/eni/index.js +1 -0
  8. package/dist/chains/index.d.ts +13 -0
  9. package/dist/chains/index.js +13 -0
  10. package/dist/chains/xlayer/eip7702/flat-aliases.d.ts +13 -0
  11. package/dist/chains/xlayer/eip7702/flat-aliases.js +10 -0
  12. package/dist/chains/xlayer/eip7702/index.d.ts +1 -0
  13. package/dist/chains/xlayer/eip7702/index.js +1 -0
  14. package/dist/chains/xlayer/index.d.ts +3 -2
  15. package/dist/chains/xlayer/index.js +4 -7
  16. package/dist/flap/index.d.ts +10 -0
  17. package/dist/flap/index.js +8 -0
  18. package/dist/merkle/index.d.ts +12 -0
  19. package/dist/merkle/index.js +11 -0
  20. package/dist/shared/constants/index.d.ts +2 -0
  21. package/dist/shared/index.d.ts +2 -0
  22. package/dist/vanity/index.d.ts +5 -0
  23. package/dist/vanity/index.js +5 -0
  24. package/package.json +93 -2
  25. package/dist/chains/bsc/four/disperse.d.ts +0 -12
  26. package/dist/chains/bsc/four/disperse.js +0 -470
  27. package/dist/chains/bsc/four/pairwise.d.ts +0 -7
  28. package/dist/chains/bsc/four/pairwise.js +0 -308
  29. package/dist/chains/bsc/four/submit/blockrazor.d.ts +0 -18
  30. package/dist/chains/bsc/four/submit/blockrazor.js +0 -86
  31. package/dist/chains/bsc/four/submit/direct.d.ts +0 -66
  32. package/dist/chains/bsc/four/submit/direct.js +0 -452
  33. package/dist/chains/bsc/four/submit/helpers.d.ts +0 -18
  34. package/dist/chains/bsc/four/submit/helpers.js +0 -57
  35. package/dist/chains/bsc/four/submit/index.d.ts +0 -12
  36. package/dist/chains/bsc/four/submit/index.js +0 -11
  37. package/dist/chains/bsc/four/submit/merkle.d.ts +0 -18
  38. package/dist/chains/bsc/four/submit/merkle.js +0 -74
  39. package/dist/chains/bsc/four/submit/types.d.ts +0 -143
  40. package/dist/chains/bsc/four/swap/index.d.ts +0 -32
  41. package/dist/chains/bsc/four/swap/index.js +0 -829
  42. package/dist/chains/bsc/four/swap/types.d.ts +0 -70
  43. package/dist/chains/bsc/four/swap/types.js +0 -1
  44. package/dist/chains/bsc/four/sweep.d.ts +0 -13
  45. package/dist/chains/bsc/four/sweep.js +0 -788
  46. package/dist/chains/bsc/four/utils/index.d.ts +0 -20
  47. package/dist/chains/bsc/four/utils/index.js +0 -1558
  48. package/dist/chains/bsc/four/utils/types.d.ts +0 -1
  49. package/dist/chains/bsc/four/utils/types.js +0 -1
  50. package/dist/chains/bsc/pancake/bundle-buy-first/index.d.ts +0 -8
  51. package/dist/chains/bsc/pancake/bundle-buy-first/index.js +0 -907
  52. package/dist/chains/bsc/pancake/bundle-buy-first/types.d.ts +0 -73
  53. package/dist/chains/bsc/pancake/bundle-buy-first/types.js +0 -1
  54. package/dist/chains/bsc/pancake/bundle-swap/helpers.d.ts +0 -102
  55. package/dist/chains/bsc/pancake/bundle-swap/helpers.js +0 -572
  56. package/dist/chains/bsc/pancake/bundle-swap/index.d.ts +0 -50
  57. package/dist/chains/bsc/pancake/bundle-swap/index.js +0 -1066
  58. package/dist/chains/bsc/pancake/bundle-swap/types.d.ts +0 -202
  59. package/dist/chains/bsc/pancake/bundle-swap/types.js +0 -3
  60. package/dist/chains/xlayer/eip7702/bundle-swap/index.d.ts +0 -72
  61. package/dist/chains/xlayer/eip7702/bundle-swap/index.js +0 -921
  62. package/dist/chains/xlayer/eip7702/bundle-swap/types.d.ts +0 -65
  63. package/dist/chains/xlayer/eip7702/bundle-swap/types.js +0 -1
  64. package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.d.ts +0 -128
  65. package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.js +0 -857
  66. package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.d.ts +0 -85
  67. package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.js +0 -1
  68. package/dist/chains/xlayer/eip7702/volume/index.d.ts +0 -96
  69. package/dist/chains/xlayer/eip7702/volume/index.js +0 -793
  70. package/dist/chains/xlayer/eip7702/volume/types.d.ts +0 -124
  71. package/dist/chains/xlayer/eip7702/volume/types.js +0 -1
  72. package/dist/chains/xlayer/eoa/types-core.d.ts +0 -363
  73. package/dist/chains/xlayer/eoa/types-core.js +0 -53
  74. package/dist/chains/xlayer/eoa/types-create.d.ts +0 -413
  75. package/dist/chains/xlayer/eoa/types-create.js +0 -9
  76. package/dist/chains/xlayer/eoa/types-volume.d.ts +0 -209
  77. package/dist/chains/xlayer/eoa/types-volume.js +0 -13
  78. package/dist/dex/direct-router/index.d.ts +0 -70
  79. package/dist/dex/direct-router/index.js +0 -1410
  80. package/dist/dex/direct-router/types.d.ts +0 -81
  81. package/dist/dex/direct-router/types.js +0 -1
  82. package/dist/shared/abis/TaxToken.json +0 -969
  83. package/dist/shared/abis/TokenManager.json +0 -836
  84. package/dist/shared/abis/TokenManager2.json +0 -136
  85. package/dist/shared/abis/TokenManagerHelper3.json +0 -993
  86. package/dist/shared/abis 2/TaxToken.json +0 -105
  87. package/dist/shared/abis 2/TokenManager.json +0 -836
  88. package/dist/shared/abis 2/TokenManager2.json +0 -60
  89. package/dist/shared/abis 2/TokenManagerHelper3.json +0 -993
  90. package/dist/shared/abis 2/common.d.ts +0 -85
  91. package/dist/shared/abis 2/common.js +0 -254
  92. package/dist/shared/abis 2/index.d.ts +0 -8
  93. package/dist/shared/abis 2/index.js +0 -8
  94. package/dist/shared/clients 2/blockrazor.d.ts +0 -314
  95. package/dist/shared/clients 2/blockrazor.js +0 -596
  96. package/dist/shared/clients 2/club48.d.ts +0 -154
  97. package/dist/shared/clients 2/club48.js +0 -331
  98. package/dist/shared/clients 2/emitservice.d.ts +0 -47
  99. package/dist/shared/clients 2/emitservice.js +0 -44
  100. package/dist/shared/clients 2/four.d.ts +0 -132
  101. package/dist/shared/clients 2/four.js +0 -281
  102. package/dist/shared/clients 2/merkle.d.ts +0 -210
  103. package/dist/shared/clients 2/merkle.js +0 -400
  104. package/dist/shared/flap/__tests__/curve.test.d.ts +0 -1
  105. package/dist/shared/flap/__tests__/curve.test.js +0 -85
  106. package/dist/shared/flap/portal/index.d.ts +0 -12
  107. package/dist/shared/flap/portal/index.js +0 -11
  108. package/dist/shared/flap/portal/portal.d.ts +0 -47
  109. package/dist/shared/flap/portal/portal.js +0 -218
  110. package/dist/shared/flap/portal/types.d.ts +0 -227
  111. package/dist/shared/flap/portal/types.js +0 -80
  112. package/dist/shared/flap/portal/writer.d.ts +0 -121
  113. package/dist/shared/flap/portal/writer.js +0 -265
  114. package/dist/shared/flap/portal-bundle-merkle/core/index.d.ts +0 -18
  115. package/dist/shared/flap/portal-bundle-merkle/core/index.js +0 -938
  116. package/dist/shared/flap/portal-bundle-merkle/core/types.d.ts +0 -1
  117. package/dist/shared/flap/portal-bundle-merkle/core/types.js +0 -1
  118. package/dist/shared/flap/portal-bundle-merkle/swap/index.d.ts +0 -42
  119. package/dist/shared/flap/portal-bundle-merkle/swap/index.js +0 -1448
  120. package/dist/shared/flap/portal-bundle-merkle/swap/types.d.ts +0 -84
  121. package/dist/shared/flap/portal-bundle-merkle/swap/types.js +0 -1
  122. package/dist/shared/flap/portal-bundle-merkle/utils/index.d.ts +0 -17
  123. package/dist/shared/flap/portal-bundle-merkle/utils/index.js +0 -1024
  124. package/dist/shared/flap/portal-bundle-merkle/utils/types.d.ts +0 -16
  125. package/dist/shared/flap/portal-bundle-merkle/utils/types.js +0 -1
  126. package/dist/shared/flap/portal-bundle-merkle/utils-helpers.d.ts +0 -100
  127. package/dist/shared/flap/portal-bundle-merkle/utils-helpers.js +0 -133
  128. package/dist/shared/flap 2/__tests__/curve.test.d.ts +0 -1
  129. package/dist/shared/flap 2/__tests__/curve.test.js +0 -85
  130. package/dist/shared/flap 2/abi.d.ts +0 -4
  131. package/dist/shared/flap 2/abi.js +0 -4
  132. package/dist/shared/flap 2/constants.d.ts +0 -128
  133. package/dist/shared/flap 2/constants.js +0 -143
  134. package/dist/shared/flap 2/curve.d.ts +0 -33
  135. package/dist/shared/flap 2/curve.js +0 -84
  136. package/dist/shared/flap 2/errors.d.ts +0 -37
  137. package/dist/shared/flap 2/errors.js +0 -114
  138. package/dist/shared/flap 2/index.d.ts +0 -22
  139. package/dist/shared/flap 2/index.js +0 -33
  140. package/dist/shared/flap 2/ipfs.d.ts +0 -21
  141. package/dist/shared/flap 2/ipfs.js +0 -38
  142. package/dist/shared/flap 2/meta.d.ts +0 -30
  143. package/dist/shared/flap 2/meta.js +0 -195
  144. package/dist/shared/flap 2/permit.d.ts +0 -16
  145. package/dist/shared/flap 2/permit.js +0 -67
  146. package/dist/shared/flap 2/pinata.d.ts +0 -40
  147. package/dist/shared/flap 2/pinata.js +0 -106
  148. package/dist/shared/flap 2/portal-bundle-merkle/config.d.ts +0 -79
  149. package/dist/shared/flap 2/portal-bundle-merkle/config.js +0 -133
  150. package/dist/shared/flap 2/portal-bundle-merkle/core.d.ts +0 -18
  151. package/dist/shared/flap 2/portal-bundle-merkle/core.js +0 -938
  152. package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.d.ts +0 -125
  153. package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.js +0 -665
  154. package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.d.ts +0 -88
  155. package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.js +0 -446
  156. package/dist/shared/flap 2/portal-bundle-merkle/index.d.ts +0 -14
  157. package/dist/shared/flap 2/portal-bundle-merkle/index.js +0 -26
  158. package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.d.ts +0 -28
  159. package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.js +0 -701
  160. package/dist/shared/flap 2/portal-bundle-merkle/private.d.ts +0 -17
  161. package/dist/shared/flap 2/portal-bundle-merkle/private.js +0 -549
  162. package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.d.ts +0 -65
  163. package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.js +0 -831
  164. package/dist/shared/flap 2/portal-bundle-merkle/swap.d.ts +0 -201
  165. package/dist/shared/flap 2/portal-bundle-merkle/swap.js +0 -1359
  166. package/dist/shared/flap 2/portal-bundle-merkle/types.d.ts +0 -358
  167. package/dist/shared/flap 2/portal-bundle-merkle/types.js +0 -1
  168. package/dist/shared/flap 2/portal-bundle-merkle/utils.d.ts +0 -89
  169. package/dist/shared/flap 2/portal-bundle-merkle/utils.js +0 -963
  170. package/dist/shared/flap 2/portal-bundle.d.ts +0 -119
  171. package/dist/shared/flap 2/portal-bundle.js +0 -584
  172. package/dist/shared/flap 2/portal.d.ts +0 -392
  173. package/dist/shared/flap 2/portal.js +0 -559
  174. package/dist/shared/flap 2/vanity.d.ts +0 -48
  175. package/dist/shared/flap 2/vanity.js +0 -110
  176. package/dist/shared/flap 2/vault.d.ts +0 -240
  177. package/dist/shared/flap 2/vault.js +0 -366
  178. package/dist/shared/four 2/index.d.ts +0 -7
  179. package/dist/shared/four 2/index.js +0 -22
  180. package/dist/shared/four 2/tax-token.d.ts +0 -176
  181. package/dist/shared/four 2/tax-token.js +0 -302
  182. package/dist/shared/index 2.js +0 -10
  183. package/dist/shared/index.d 2.ts +0 -10
  184. package/dist/types/distribute.d.ts +0 -72
  185. package/dist/types/distribute.js +0 -1
  186. package/dist/types 2/errors.d.ts +0 -27
  187. package/dist/types 2/errors.js +0 -34
  188. package/dist/utils/__tests__/errors.test.d.ts +0 -1
  189. package/dist/utils/__tests__/errors.test.js +0 -76
  190. package/dist/utils/airdrop-sweep-types.d.ts +0 -1
  191. package/dist/utils/airdrop-sweep-types.js +0 -1
  192. package/dist/utils/erc20/index.d.ts +0 -242
  193. package/dist/utils/erc20/index.js +0 -645
  194. package/dist/utils/erc20/types.d.ts +0 -77
  195. package/dist/utils/erc20/types.js +0 -1
  196. package/dist/utils/erc20-types.d.ts +0 -1
  197. package/dist/utils/erc20-types.js +0 -1
  198. package/dist/utils/holders-maker/helpers.d.ts +0 -43
  199. package/dist/utils/holders-maker/helpers.js +0 -371
  200. package/dist/utils/holders-maker/index.d.ts +0 -26
  201. package/dist/utils/holders-maker/index.js +0 -218
  202. package/dist/utils/holders-maker/types.d.ts +0 -72
  203. package/dist/utils/holders-maker/types.js +0 -4
  204. package/dist/utils/holders-maker-types.d.ts +0 -1
  205. package/dist/utils/holders-maker-types.js +0 -1
  206. package/dist/utils/lp-inspect/index.d.ts +0 -44
  207. package/dist/utils/lp-inspect/index.js +0 -937
  208. package/dist/utils/lp-inspect/types.d.ts +0 -100
  209. package/dist/utils/lp-inspect/types.js +0 -1
  210. package/dist/utils/lp-inspect-types.d.ts +0 -1
  211. package/dist/utils/lp-inspect-types.js +0 -1
  212. package/dist/utils/private-sale-types.d.ts +0 -1
  213. package/dist/utils/private-sale-types.js +0 -1
  214. package/dist/utils/quote-helpers/index.d.ts +0 -107
  215. package/dist/utils/quote-helpers/index.js +0 -346
  216. package/dist/utils/quote-helpers/types.d.ts +0 -16
  217. package/dist/utils/quote-helpers/types.js +0 -1
  218. package/dist/utils/quote-helpers-types.d.ts +0 -1
  219. package/dist/utils/quote-helpers-types.js +0 -1
  220. /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
- }