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,831 +0,0 @@
1
- /**
2
- * Flap Protocol 内盘捆绑换手(Merkle Bundle)- 先买后卖
3
- *
4
- * 功能:钱包B先买入代币 → 钱包A卖出相同数量 → 原子执行
5
- */
6
- import { ethers, Contract, Wallet } from 'ethers';
7
- import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../utils/bundle-helpers.js';
8
- import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
9
- import { PROFIT_CONFIG, ADDRESSES, ZERO_ADDRESS } from '../../../utils/constants.js';
10
- import { GAS_LIMITS, CHAINS } from '../../constants/index.js';
11
- import { ERC20_BALANCE_ABI, V2_ROUTER_QUOTE_ABI } from '../../abis/common.js';
12
- import { getGasPriceConfig, getTxType, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI } from './config.js';
13
- // ==================== 链常量 ====================
14
- const BSC_PANCAKE_V2_ROUTER = ADDRESSES.BSC.PancakeV2Router;
15
- const BSC_WBNB = ADDRESSES.BSC.WBNB;
16
- const MONAD_PANCAKE_V2_ROUTER = '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9';
17
- const MONAD_WMON = ADDRESSES.MONAD.WMON;
18
- const ROUTER_ABI = V2_ROUTER_QUOTE_ABI;
19
- /**
20
- * 获取 ERC20 代币 → 原生代币的报价
21
- * @param provider - Provider 实例
22
- * @param tokenAddress - ERC20 代币地址
23
- * @param tokenAmount - 代币数量(wei)
24
- * @param chainId - 链 ID(56=BSC, 143=Monad)
25
- * @returns 等值的原生代币数量(wei),失败返回 0n
26
- */
27
- async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
28
- if (tokenAmount <= 0n)
29
- return 0n;
30
- try {
31
- if (chainId === CHAINS.BSC.chainId) {
32
- // BSC: 使用 PancakeSwap V2 Router
33
- const router = new Contract(BSC_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
34
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, BSC_WBNB]);
35
- return amounts[1];
36
- }
37
- if (chainId === CHAINS.MONAD.chainId) {
38
- // ✅ Monad: 使用 PancakeSwap V2 Router
39
- const router = new Contract(MONAD_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
40
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, MONAD_WMON]);
41
- return amounts[1];
42
- }
43
- // ✅ Base (8453) / XLayer (196) 等链暂不支持 V2 报价,返回 0
44
- return 0n;
45
- }
46
- catch {
47
- // 报价失败返回 0(可能是流动性不足或路径不存在)
48
- return 0n;
49
- }
50
- }
51
- /**
52
- * 获取 Gas Limit(支持 FlapAnyConfig)
53
- */
54
- function getGasLimit(config, defaultGas = 800000) {
55
- if (config.gasLimit !== undefined) {
56
- return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
57
- }
58
- const multiplier = config.gasLimitMultiplier ?? 1.0;
59
- return BigInt(Math.ceil(defaultGas * multiplier));
60
- }
61
- // ==================== 多笔买卖常量 ====================
62
- /** 最大 Bundle 签名数 */
63
- const MAX_BUNDLE_SIGNATURES = 50;
64
- /** 贿赂交易数 */
65
- const BRIBE_TX_COUNT = 1;
66
- /** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
67
- const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
68
- /** 最大买卖交易数 */
69
- const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
70
- /**
71
- * 根据 userType 获取每笔交易的利润率
72
- */
73
- function getProfitRatePerTxBps(userType) {
74
- if (userType === 'v1') {
75
- return PROFIT_CONFIG.RATE_BPS_V1_DOUBLE;
76
- }
77
- return PROFIT_CONFIG.RATE_BPS_V0_DOUBLE; // v0 或默认
78
- }
79
- /**
80
- * 验证买卖笔数
81
- */
82
- function validateSwapCounts(buyCount, sellCount) {
83
- if (buyCount < 1) {
84
- throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
85
- }
86
- if (sellCount < 1) {
87
- throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
88
- }
89
- const totalSwaps = buyCount + sellCount;
90
- if (totalSwaps > MAX_SWAP_TX_COUNT) {
91
- throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
92
- `最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
93
- }
94
- }
95
- function getChainId(chain) {
96
- switch (chain) {
97
- case 'bsc': return 56;
98
- case 'xlayer': return 196;
99
- case 'base': return 8453;
100
- }
101
- }
102
- function getNativeTokenName(chain) {
103
- switch (chain) {
104
- case 'bsc': return 'BNB';
105
- case 'xlayer': return 'OKB';
106
- case 'base': return 'ETH';
107
- }
108
- }
109
- // 使用公共工具的 getGasLimit,移除本地重复实现
110
- export async function flapBundleBuyFirstMerkle(params) {
111
- const { chain, buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount } = params;
112
- // ✅ 判断是否为多钱包模式
113
- const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
114
- !!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
115
- const buyCount = _buyCount ?? 1;
116
- const sellCount = _sellCount ?? 1;
117
- // ✅ 多钱包模式:使用单独的处理逻辑
118
- if (isMultiWalletMode) {
119
- return await flapBundleBuyFirstMultiWallet({
120
- chain,
121
- buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
122
- sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
123
- tokenAddress,
124
- buyerFunds,
125
- config,
126
- quoteToken,
127
- quoteTokenDecimals
128
- });
129
- }
130
- // ✅ 单钱包模式(向后兼容)
131
- if (!buyerPrivateKey || !sellerPrivateKey) {
132
- throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
133
- }
134
- validateSwapCounts(buyCount, sellCount);
135
- // ✅ 计算利润比例(根据 userType 动态调整)
136
- const totalTxCount = buyCount + sellCount;
137
- const profitRateBps = getProfitRatePerTxBps(config.userType) * totalTxCount;
138
- const chainContext = createChainContext(chain, config.rpcUrl);
139
- const buyer = new Wallet(buyerPrivateKey, chainContext.provider);
140
- const seller = new Wallet(sellerPrivateKey, chainContext.provider);
141
- const sameAddress = buyer.address.toLowerCase() === seller.address.toLowerCase();
142
- const finalGasLimit = getGasLimit(config);
143
- const txType = getTxType(config);
144
- const nonceManager = new NonceManager(chainContext.provider);
145
- // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
146
- const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
147
- const inputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
148
- // 卖出时的输出代币:与买入时的输入代币相同
149
- const outputToken = inputToken;
150
- // ✅ 优化:第一批并行 - buyerFunds、gasPrice
151
- const [buyerFundsResult, gasPrice] = await Promise.all([
152
- calculateBuyerFunds({
153
- buyer,
154
- buyerFunds,
155
- buyerFundsPercentage,
156
- reserveGasEth: config.reserveGasETH,
157
- nativeToken: chainContext.nativeToken,
158
- useNativeToken,
159
- quoteToken,
160
- quoteTokenDecimals,
161
- provider: chainContext.provider
162
- }),
163
- getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config))
164
- ]);
165
- const { buyerFundsWei, buyerBalance } = buyerFundsResult;
166
- const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
167
- // ✅ 获取报价
168
- const quoteResult = await quoteBuyerOutput({
169
- portalAddress: chainContext.portalAddress,
170
- tokenAddress,
171
- buyerFundsWei,
172
- provider: chainContext.provider,
173
- skipQuoteOnError: config.skipQuoteOnError,
174
- inputToken // ✅ 传递输入代币
175
- });
176
- const sellAmountWei = quoteResult.sellAmountWei;
177
- // ✅ 验证卖方余额
178
- const sellerInfo = await ensureSellerBalance({
179
- tokenAddress,
180
- provider: chainContext.provider,
181
- seller,
182
- sellAmountWei,
183
- skipBalanceCheck: sameAddress
184
- });
185
- const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
186
- const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
187
- // ✅ 拆分买入和卖出金额
188
- const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
189
- const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
190
- // ✅ 第三批并行 - 构建多笔买入和卖出交易、estimatedSellFunds
191
- const buyUnsignedPromises = buyAmountsWei.map(amount => portalBuyer.swapExactInput.populateTransaction({
192
- inputToken,
193
- outputToken: tokenAddress,
194
- inputAmount: amount,
195
- minOutputAmount: 0n,
196
- permitData: '0x'
197
- }, useNativeToken ? { value: amount } : {}));
198
- const sellUnsignedPromises = sellAmountsWei.map(amount => portalSeller.swapExactInput.populateTransaction({
199
- inputToken: tokenAddress,
200
- outputToken,
201
- inputAmount: amount,
202
- minOutputAmount: 0,
203
- permitData: '0x'
204
- }));
205
- const [buyUnsignedArray, sellUnsignedArray, estimatedSellFunds] = await Promise.all([
206
- Promise.all(buyUnsignedPromises),
207
- Promise.all(sellUnsignedPromises),
208
- estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken)
209
- ]);
210
- // ✅ 获取贿赂金额
211
- const bribeAmount = getBribeAmount(config);
212
- const needBribeTx = bribeAmount > 0n;
213
- // ✅ 规划多笔交易 nonce
214
- const multiNoncePlan = await planMultiNonces({
215
- buyer,
216
- seller,
217
- buyCount,
218
- sellCount,
219
- extractProfit: true,
220
- needBribeTx,
221
- sameAddress,
222
- nonceManager
223
- });
224
- // ✅ 修复:基于卖出收益估算利润(使用动态利润比例)
225
- const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : buyerFundsWei;
226
- const tokenProfitAmount = calculateMultiProfitAmount(profitBase, profitRateBps);
227
- // ✅ ERC20 购买:获取代币利润等值的原生代币(BNB)报价
228
- let nativeProfitAmount = tokenProfitAmount; // 原生代币购买时直接使用
229
- if (!useNativeToken && tokenProfitAmount > 0n) {
230
- // 将代币利润转换为等值 BNB
231
- nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, inputToken, tokenProfitAmount, chainContext.chainId);
232
- }
233
- // ✅ 构建多笔买入交易
234
- const buyTxs = buyUnsignedArray.map((unsigned, i) => buildTransactionRequest(unsigned, {
235
- from: buyer.address,
236
- nonce: multiNoncePlan.buyerNonces[i],
237
- gasLimit: finalGasLimit,
238
- gasPrice,
239
- priorityFee,
240
- chainId: chainContext.chainId,
241
- txType,
242
- value: useNativeToken ? buyAmountsWei[i] : 0n
243
- }));
244
- // ✅ 构建多笔卖出交易
245
- const sellTxs = sellUnsignedArray.map((unsigned, i) => buildTransactionRequest(unsigned, {
246
- from: seller.address,
247
- nonce: multiNoncePlan.sellerNonces[i],
248
- gasLimit: finalGasLimit,
249
- gasPrice,
250
- priorityFee,
251
- chainId: chainContext.chainId,
252
- txType,
253
- value: 0n
254
- }));
255
- // ✅ 贿赂交易放在首位
256
- let bribeTx = null;
257
- if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
258
- bribeTx = await seller.signTransaction({
259
- to: BLOCKRAZOR_BUILDER_EOA,
260
- value: bribeAmount,
261
- nonce: multiNoncePlan.bribeNonce,
262
- gasPrice,
263
- gasLimit: GAS_LIMITS.BRIBE,
264
- chainId: chainContext.chainId,
265
- type: txType
266
- });
267
- }
268
- // ✅ 并行签名所有买入和卖出交易
269
- const [signedBuys, signedSells] = await Promise.all([
270
- Promise.all(buyTxs.map(tx => buyer.signTransaction(tx))),
271
- Promise.all(sellTxs.map(tx => seller.signTransaction(tx)))
272
- ]);
273
- nonceManager.clearTemp();
274
- // ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)
275
- const allTransactions = [];
276
- if (bribeTx)
277
- allTransactions.push(bribeTx);
278
- allTransactions.push(...signedBuys, ...signedSells);
279
- // ✅ 利润多跳转账(强制 2 跳中转)
280
- const profitResult = await buildProfitTransaction({
281
- provider: chainContext.provider,
282
- seller,
283
- profitAmount: nativeProfitAmount,
284
- profitNonce: multiNoncePlan.profitNonce,
285
- gasPrice,
286
- chainId: chainContext.chainId,
287
- txType
288
- });
289
- let profitHopWallets;
290
- if (profitResult) {
291
- allTransactions.push(...profitResult.signedTransactions);
292
- profitHopWallets = profitResult.hopWallets; // ✅ 收集利润多跳钱包
293
- }
294
- return {
295
- signedTransactions: allTransactions,
296
- profitHopWallets, // ✅ 返回利润多跳钱包
297
- metadata: {
298
- buyerAddress: buyer.address,
299
- sellerAddress: seller.address,
300
- buyAmount: ethers.formatEther(buyerFundsWei),
301
- sellAmount: ethers.formatUnits(sellAmountWei, sellerInfo.decimals),
302
- profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
303
- // ✅ 返回多笔买卖信息
304
- buyCount,
305
- sellCount,
306
- // ✅ 返回每笔交易的具体金额(随机分配后的数组)
307
- buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
308
- sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, sellerInfo.decimals))
309
- }
310
- };
311
- }
312
- // ✅ 使用公共模块中的 ERC20_BALANCE_ABI
313
- const ERC20_BALANCE_OF_ABI = ERC20_BALANCE_ABI;
314
- async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, reserveGasEth, nativeToken, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
315
- const reserveGas = ethers.parseEther((reserveGasEth || 0.0005).toString());
316
- // ✅ 根据是否使用原生代币获取不同的余额
317
- let buyerBalance;
318
- if (useNativeToken) {
319
- buyerBalance = await buyer.provider.getBalance(buyer.address);
320
- }
321
- else {
322
- // ERC20 代币余额
323
- const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
324
- buyerBalance = await erc20.balanceOf(buyer.address);
325
- }
326
- let buyerFundsWei;
327
- if (buyerFunds !== undefined) {
328
- // ✅ 根据代币精度解析金额
329
- buyerFundsWei = useNativeToken
330
- ? ethers.parseEther(String(buyerFunds))
331
- : ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
332
- }
333
- else if (buyerFundsPercentage !== undefined) {
334
- const pct = Math.max(0, Math.min(100, buyerFundsPercentage));
335
- // ✅ 原生代币需要预留 Gas,ERC20 不需要
336
- const spendable = useNativeToken
337
- ? (buyerBalance > reserveGas ? buyerBalance - reserveGas : 0n)
338
- : buyerBalance;
339
- buyerFundsWei = (spendable * BigInt(Math.floor(pct * 100))) / 10000n;
340
- }
341
- else {
342
- throw new Error('必须提供 buyerFunds 或 buyerFundsPercentage');
343
- }
344
- if (buyerFundsWei <= 0n) {
345
- throw new Error('buyerFunds 需要大于 0');
346
- }
347
- // ✅ 余额检查
348
- if (useNativeToken) {
349
- if (buyerBalance < buyerFundsWei + reserveGas) {
350
- throw new Error(`买方余额不足: 需要 ${ethers.formatEther(buyerFundsWei + reserveGas)} ${nativeToken},实际 ${ethers.formatEther(buyerBalance)} ${nativeToken}`);
351
- }
352
- }
353
- else {
354
- // ERC20 购买:检查代币余额
355
- if (buyerBalance < buyerFundsWei) {
356
- throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(buyerFundsWei, quoteTokenDecimals)},实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
357
- }
358
- // ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas
359
- const buyerBnbBalance = await buyer.provider.getBalance(buyer.address);
360
- if (buyerBnbBalance < reserveGas) {
361
- throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGas)} ${nativeToken},实际 ${ethers.formatEther(buyerBnbBalance)} ${nativeToken}`);
362
- }
363
- }
364
- return { buyerFundsWei, buyerBalance };
365
- }
366
- async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
367
- }) {
368
- let quotedToken = 0n;
369
- const portal = new Contract(portalAddress, PORTAL_ABI, provider);
370
- try {
371
- quotedToken = await portal.quoteExactInput.staticCall({
372
- inputToken, // ✅ 使用动态输入代币
373
- outputToken: tokenAddress,
374
- inputAmount: buyerFundsWei
375
- });
376
- }
377
- catch (error) {
378
- if (skipQuoteOnError ?? true) {
379
- quotedToken = 0n;
380
- }
381
- else {
382
- throw new Error(`买入报价失败: ${error}`);
383
- }
384
- }
385
- const sellAmountWei = quotedToken;
386
- if (sellAmountWei <= 0n) {
387
- throw new Error('卖方卖出数量为 0:报价失败');
388
- }
389
- return { quotedToken, sellAmountWei };
390
- }
391
- async function ensureSellerBalance({ tokenAddress, provider, seller, sellAmountWei, skipBalanceCheck }) {
392
- const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, provider);
393
- const [sellerTokenBal, decimals] = await Promise.all([
394
- erc20.balanceOf(seller.address),
395
- erc20.decimals()
396
- ]);
397
- if (!skipBalanceCheck && sellerTokenBal < sellAmountWei) {
398
- throw new Error(`卖方代币余额不足: 需要 ${ethers.formatUnits(sellAmountWei, decimals)},实际 ${ethers.formatUnits(sellerTokenBal, decimals)}`);
399
- }
400
- return { decimals };
401
- }
402
- async function estimateSellFunds(portal, tokenAddress, sellAmountWei, outputToken = ZERO_ADDRESS // ✅ 新增:输出代币地址
403
- ) {
404
- try {
405
- return await portal.quoteExactInput.staticCall({
406
- inputToken: tokenAddress,
407
- outputToken, // ✅ 使用动态输出代币
408
- inputAmount: sellAmountWei
409
- });
410
- }
411
- catch {
412
- return 0n;
413
- }
414
- }
415
- /**
416
- * 根据 userType 获取利润率(基点)
417
- */
418
- function getProfitRateBps(userType) {
419
- if (userType === 'v1') {
420
- return PROFIT_CONFIG.RATE_BPS_V1; // 0.05%
421
- }
422
- return PROFIT_CONFIG.RATE_BPS_V0; // v0 或默认 0.06%
423
- }
424
- function calculateProfitAmount(expectedFunds, userType) {
425
- if (expectedFunds <= 0n) {
426
- return 0n;
427
- }
428
- const rateBps = getProfitRateBps(userType);
429
- return (expectedFunds * BigInt(rateBps)) / 10000n;
430
- }
431
- /**
432
- * 计算多笔交易的利润金额
433
- * @param expectedFunds - 预期收益
434
- * @param rateBps - 利润比例(基点)
435
- */
436
- function calculateMultiProfitAmount(expectedFunds, rateBps) {
437
- if (expectedFunds <= 0n || rateBps <= 0) {
438
- return 0n;
439
- }
440
- return (expectedFunds * BigInt(rateBps)) / 10000n;
441
- }
442
- /**
443
- * ✅ 将金额随机拆分成多份(更自然,不像机器人)
444
- * @param totalAmount - 总金额
445
- * @param count - 拆分份数
446
- * @returns 拆分后的金额数组(随机分配,总和精确)
447
- */
448
- function splitAmount(totalAmount, count) {
449
- if (count <= 0) {
450
- throw new Error('拆分份数必须大于 0');
451
- }
452
- if (count === 1) {
453
- return [totalAmount];
454
- }
455
- // 生成随机权重(0.5 - 1.5 之间)
456
- const weights = [];
457
- for (let i = 0; i < count; i++) {
458
- // 随机值在 0.5 - 1.5 之间,使每份金额有 ±50% 的浮动
459
- weights.push(0.5 + Math.random());
460
- }
461
- // 计算权重总和
462
- const totalWeight = weights.reduce((a, b) => a + b, 0);
463
- // 根据权重分配金额
464
- const amounts = [];
465
- let allocated = 0n;
466
- for (let i = 0; i < count - 1; i++) {
467
- // 按权重比例分配
468
- const ratio = weights[i] / totalWeight;
469
- const amount = BigInt(Math.floor(Number(totalAmount) * ratio));
470
- amounts.push(amount);
471
- allocated += amount;
472
- }
473
- // 最后一份分配剩余的全部(确保总和精确)
474
- amounts.push(totalAmount - allocated);
475
- // 随机打乱顺序(让大小金额交错出现)
476
- for (let i = amounts.length - 1; i > 0; i--) {
477
- const j = Math.floor(Math.random() * (i + 1));
478
- [amounts[i], amounts[j]] = [amounts[j], amounts[i]];
479
- }
480
- return amounts;
481
- }
482
- /**
483
- * ✅ 规划 nonce
484
- * 交易顺序:贿赂 → 买入 → 卖出 → 利润
485
- */
486
- async function planNonces({ buyer, seller, extractProfit, needBribeTx, sameAddress, nonceManager }) {
487
- if (sameAddress) {
488
- // 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
489
- const txCount = countTruthy([needBribeTx, true, true, extractProfit]);
490
- const nonces = await nonceManager.getNextNonceBatch(buyer, txCount);
491
- let idx = 0;
492
- const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
493
- const buyerNonce = nonces[idx++];
494
- const sellerNonce = nonces[idx++];
495
- const profitNonce = extractProfit ? nonces[idx] : undefined;
496
- return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
497
- }
498
- if (needBribeTx || extractProfit) {
499
- // 卖方需要多个 nonce:贿赂(可选) + 卖出 + 利润(可选)
500
- const sellerTxCount = countTruthy([needBribeTx, true, extractProfit]);
501
- // ✅ 并行获取 buyer 和 seller 的 nonce
502
- const [sellerNonces, buyerNonces] = await Promise.all([
503
- nonceManager.getNextNonceBatch(seller, sellerTxCount),
504
- nonceManager.getNextNoncesForWallets([buyer])
505
- ]);
506
- let idx = 0;
507
- const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined;
508
- const sellerNonce = sellerNonces[idx++];
509
- const profitNonce = extractProfit ? sellerNonces[idx] : undefined;
510
- const buyerNonce = buyerNonces[0];
511
- return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
512
- }
513
- // ✅ 使用 getNextNoncesForWallets 批量获取
514
- const nonces = await nonceManager.getNextNoncesForWallets([buyer, seller]);
515
- return { buyerNonce: nonces[0], sellerNonce: nonces[1] };
516
- }
517
- /**
518
- * ✅ 规划多笔交易 nonce
519
- * 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
520
- *
521
- * 利润中转需要 PROFIT_HOP_COUNT + 1 个 nonce(支付者→跳1→跳2→利润地址)
522
- */
523
- async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, sameAddress, nonceManager }) {
524
- // 利润中转交易数(支付者只需要 1 个 nonce,中转钱包的 nonce 由 buildProfitHopTransactions 内部处理)
525
- const profitNonceCount = extractProfit ? 1 : 0;
526
- if (sameAddress) {
527
- // 同一地址:贿赂(可选) + 买入(多笔) + 卖出(多笔) + 利润(可选)
528
- const bribeTxCount = needBribeTx ? 1 : 0;
529
- const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
530
- const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
531
- let idx = 0;
532
- const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
533
- const buyerNonces = nonces.slice(idx, idx + buyCount);
534
- idx += buyCount;
535
- const sellerNonces = nonces.slice(idx, idx + sellCount);
536
- idx += sellCount;
537
- const profitNonce = extractProfit ? nonces[idx] : undefined;
538
- return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
539
- }
540
- // 不同地址
541
- // 买方:buyCount 个 nonce
542
- // 卖方:贿赂(可选) + sellCount + 利润(可选) 个 nonce
543
- const bribeTxCount = needBribeTx ? 1 : 0;
544
- const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
545
- const [buyerNonces, sellerNoncesAll] = await Promise.all([
546
- nonceManager.getNextNonceBatch(buyer, buyCount),
547
- nonceManager.getNextNonceBatch(seller, sellerTxCount)
548
- ]);
549
- let idx = 0;
550
- const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
551
- const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
552
- idx += sellCount;
553
- const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
554
- return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
555
- }
556
- function buildTransactionRequest(unsigned, { from, nonce, gasLimit, gasPrice, priorityFee, chainId, txType, value }) {
557
- const tx = {
558
- ...unsigned,
559
- from,
560
- nonce,
561
- gasLimit,
562
- chainId,
563
- type: txType
564
- };
565
- if (value !== undefined) {
566
- tx.value = value;
567
- }
568
- if (txType === 2) {
569
- tx.maxFeePerGas = gasPrice;
570
- tx.maxPriorityFeePerGas = priorityFee;
571
- }
572
- else {
573
- tx.gasPrice = gasPrice;
574
- }
575
- return tx;
576
- }
577
- /**
578
- * 构建利润多跳转账交易(强制 2 跳中转)
579
- */
580
- async function buildProfitTransaction({ provider, seller, profitAmount, profitNonce, gasPrice, chainId, txType }) {
581
- if (profitNonce === undefined || profitAmount === 0n) {
582
- return null;
583
- }
584
- const profitHopResult = await buildProfitHopTransactions({
585
- provider,
586
- payerWallet: seller,
587
- profitAmount,
588
- profitRecipient: getProfitRecipient(),
589
- hopCount: PROFIT_HOP_COUNT,
590
- gasPrice,
591
- chainId,
592
- txType,
593
- startNonce: profitNonce
594
- });
595
- return { signedTransactions: profitHopResult.signedTransactions, hopWallets: profitHopResult.hopWallets };
596
- }
597
- function countTruthy(values) {
598
- return values.filter(Boolean).length;
599
- }
600
- // ✅ Provider 缓存(复用连接,减少初始化开销)
601
- const providerCache = new Map();
602
- const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
603
- function createChainContext(chain, rpcUrl) {
604
- const chainId = getChainId(chain);
605
- const nativeToken = getNativeTokenName(chain);
606
- const portalAddress = FLAP_PORTAL_ADDRESSES[chain.toUpperCase()];
607
- // ✅ Provider 缓存
608
- const cacheKey = `${chain}-${rpcUrl}`;
609
- const now = Date.now();
610
- const cached = providerCache.get(cacheKey);
611
- let provider;
612
- if (cached && cached.expireAt > now) {
613
- provider = cached.provider;
614
- }
615
- else {
616
- provider = new ethers.JsonRpcProvider(rpcUrl, {
617
- chainId,
618
- name: chain.toUpperCase()
619
- });
620
- providerCache.set(cacheKey, { provider, expireAt: now + PROVIDER_CACHE_TTL_MS });
621
- }
622
- return { chainId, nativeToken, portalAddress, provider };
623
- }
624
- /**
625
- * ✅ Flap 多钱包捆绑换手
626
- * - 多个买方钱包执行买入(每个钱包1笔)
627
- * - 多个卖方钱包执行卖出(每个钱包1笔)
628
- * - 买入总价值 = 卖出总价值
629
- * - ✅ 优化:最大化并行操作
630
- */
631
- async function flapBundleBuyFirstMultiWallet(params) {
632
- const { chain, buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config, quoteToken, quoteTokenDecimals = 18 } = params;
633
- const buyCount = buyerPrivateKeys.length;
634
- const sellCount = sellerPrivateKeys.length;
635
- if (buyCount === 0)
636
- throw new Error('买方钱包数量不能为0');
637
- if (sellCount === 0)
638
- throw new Error('卖方钱包数量不能为0');
639
- // 验证总交易数不超过限制
640
- validateSwapCounts(buyCount, sellCount);
641
- // ✅ 计算利润比例(根据 userType 动态调整)
642
- const totalTxCount = buyCount + sellCount;
643
- const profitRateBps = getProfitRatePerTxBps(config.userType) * totalTxCount;
644
- const chainContext = createChainContext(chain, config.rpcUrl);
645
- const nonceManager = new NonceManager(chainContext.provider);
646
- // 创建所有钱包实例
647
- const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
648
- const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
649
- // 使用第一个卖方作为主卖方(支付贿赂和利润)
650
- const mainSeller = sellers[0];
651
- // ✅ 判断是否使用原生代币
652
- const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
653
- const inputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
654
- const outputToken = inputToken;
655
- // ✅ 计算总交易金额
656
- if (!buyerFunds) {
657
- throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
658
- }
659
- const totalFundsWei = useNativeToken
660
- ? ethers.parseEther(String(buyerFunds))
661
- : ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
662
- if (totalFundsWei <= 0n) {
663
- throw new Error('交易金额必须大于0');
664
- }
665
- const finalGasLimit = getGasLimit(config);
666
- const txType = getTxType(config);
667
- const bribeAmount = getBribeAmount(config);
668
- const needBribeTx = bribeAmount > 0n;
669
- // ✅ 第一批并行:报价 + Gas价格 + 代币精度 + 所有钱包 nonces
670
- // 预先计算所有唯一钱包地址(去重)
671
- const allWallets = [...sellers, ...buyers];
672
- const uniqueWallets = allWallets.filter((w, i) => {
673
- const addr = w.address.toLowerCase();
674
- const firstIdx = allWallets.findIndex(x => x.address.toLowerCase() === addr);
675
- return firstIdx === i;
676
- });
677
- const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, chainContext.provider);
678
- const [quoteResult, gasPrice, decimals, noncesArray] = await Promise.all([
679
- quoteBuyerOutput({
680
- portalAddress: chainContext.portalAddress,
681
- tokenAddress,
682
- buyerFundsWei: totalFundsWei,
683
- provider: chainContext.provider,
684
- skipQuoteOnError: config.skipQuoteOnError,
685
- inputToken
686
- }),
687
- getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config)),
688
- erc20.decimals(),
689
- nonceManager.getNextNoncesForWallets(uniqueWallets)
690
- ]);
691
- // 构建 nonces Map
692
- const noncesMap = new Map();
693
- uniqueWallets.forEach((wallet, i) => {
694
- noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
695
- });
696
- const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
697
- // ✅ 将总金额平均分配给买方
698
- const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
699
- // ✅ 将代币平均分配给卖方
700
- const sellAmountsWei = splitAmount(quoteResult.sellAmountWei, sellCount);
701
- // ✅ 第二批并行:估算利润 + ERC20 转原生代币报价(如需要)
702
- const portal = new Contract(chainContext.portalAddress, PORTAL_ABI, chainContext.provider);
703
- const estimatedSellFunds = await estimateSellFunds(portal, tokenAddress, quoteResult.sellAmountWei, outputToken);
704
- const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : totalFundsWei;
705
- const tokenProfitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
706
- // ERC20 购买:获取代币利润等值的原生代币报价
707
- let nativeProfitAmount = tokenProfitAmount;
708
- if (!useNativeToken && tokenProfitAmount > 0n) {
709
- nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, inputToken, tokenProfitAmount, chainContext.chainId);
710
- }
711
- // ✅ 第三批并行:构建并签名所有交易
712
- const allTransactions = [];
713
- // 1. 贿赂交易(由主卖方支付)- 先处理以确定 nonce 偏移
714
- let bribeTxPromise = null;
715
- if (needBribeTx) {
716
- const mainSellerAddr = mainSeller.address.toLowerCase();
717
- const bribeNonce = noncesMap.get(mainSellerAddr);
718
- noncesMap.set(mainSellerAddr, bribeNonce + 1);
719
- bribeTxPromise = mainSeller.signTransaction({
720
- to: BLOCKRAZOR_BUILDER_EOA,
721
- value: bribeAmount,
722
- nonce: bribeNonce,
723
- gasPrice,
724
- gasLimit: GAS_LIMITS.BRIBE,
725
- chainId: chainContext.chainId,
726
- type: txType
727
- });
728
- }
729
- // 2. 预分配所有 nonces(同步操作,避免竞态)
730
- const buyerNonces = [];
731
- const sellerNonces = [];
732
- buyers.forEach(buyer => {
733
- const addr = buyer.address.toLowerCase();
734
- const nonce = noncesMap.get(addr);
735
- buyerNonces.push(nonce);
736
- noncesMap.set(addr, nonce + 1);
737
- });
738
- sellers.forEach(seller => {
739
- const addr = seller.address.toLowerCase();
740
- const nonce = noncesMap.get(addr);
741
- sellerNonces.push(nonce);
742
- noncesMap.set(addr, nonce + 1);
743
- });
744
- // 3. 并行构建所有买入交易
745
- const buyTxPromises = buyers.map(async (buyer, i) => {
746
- const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
747
- const unsigned = await portalBuyer.swapExactInput.populateTransaction({
748
- inputToken,
749
- outputToken: tokenAddress,
750
- inputAmount: buyAmountsWei[i],
751
- minOutputAmount: 0n,
752
- permitData: '0x'
753
- }, useNativeToken ? { value: buyAmountsWei[i] } : {});
754
- return buyer.signTransaction(buildTransactionRequest(unsigned, {
755
- from: buyer.address,
756
- nonce: buyerNonces[i],
757
- gasLimit: finalGasLimit,
758
- gasPrice,
759
- priorityFee,
760
- chainId: chainContext.chainId,
761
- txType,
762
- value: useNativeToken ? buyAmountsWei[i] : 0n
763
- }));
764
- });
765
- // 4. 并行构建所有卖出交易
766
- const sellTxPromises = sellers.map(async (seller, i) => {
767
- const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
768
- const unsigned = await portalSeller.swapExactInput.populateTransaction({
769
- inputToken: tokenAddress,
770
- outputToken,
771
- inputAmount: sellAmountsWei[i],
772
- minOutputAmount: 0n,
773
- permitData: '0x'
774
- });
775
- return seller.signTransaction(buildTransactionRequest(unsigned, {
776
- from: seller.address,
777
- nonce: sellerNonces[i],
778
- gasLimit: finalGasLimit,
779
- gasPrice,
780
- priorityFee,
781
- chainId: chainContext.chainId,
782
- txType,
783
- value: 0n
784
- }));
785
- });
786
- // ✅ 并行签名所有交易(贿赂 + 买入 + 卖出)
787
- const [bribeTx, signedBuys, signedSells] = await Promise.all([
788
- bribeTxPromise,
789
- Promise.all(buyTxPromises),
790
- Promise.all(sellTxPromises)
791
- ]);
792
- // 组装交易顺序:贿赂 → 买入 → 卖出
793
- if (bribeTx)
794
- allTransactions.push(bribeTx);
795
- allTransactions.push(...signedBuys, ...signedSells);
796
- // 5. 利润多跳转账(由主卖方支付)
797
- let profitHopWallets;
798
- if (nativeProfitAmount > 0n) {
799
- const mainSellerAddr = mainSeller.address.toLowerCase();
800
- const profitNonce = noncesMap.get(mainSellerAddr);
801
- const profitResult = await buildProfitTransaction({
802
- provider: chainContext.provider,
803
- seller: mainSeller,
804
- profitAmount: nativeProfitAmount,
805
- profitNonce,
806
- gasPrice,
807
- chainId: chainContext.chainId,
808
- txType
809
- });
810
- if (profitResult) {
811
- allTransactions.push(...profitResult.signedTransactions);
812
- profitHopWallets = profitResult.hopWallets;
813
- }
814
- }
815
- nonceManager.clearTemp();
816
- return {
817
- signedTransactions: allTransactions,
818
- profitHopWallets,
819
- metadata: {
820
- buyerAddress: buyers.map(b => b.address).join(','),
821
- sellerAddress: sellers.map(s => s.address).join(','),
822
- buyAmount: ethers.formatEther(totalFundsWei),
823
- sellAmount: ethers.formatUnits(quoteResult.sellAmountWei, decimals),
824
- profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
825
- buyCount,
826
- sellCount,
827
- buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
828
- sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, decimals))
829
- }
830
- };
831
- }