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,1410 +0,0 @@
1
- /**
2
- * 直接 Router 交易(不走代理合约)
3
- *
4
- * 支持:
5
- * - BSC: PancakeSwap V2/V3
6
- * - Monad: PancakeSwap V2, Uniswap V2/V3
7
- *
8
- * 收费方式:交易末尾附加利润提取交易(和内盘一致)
9
- */
10
- import { ethers, Wallet, JsonRpcProvider, Contract } from 'ethers';
11
- import { NonceManager, getOptimizedGasPrice, getDeadline as _getDeadline, buildProfitHopTransactions, PROFIT_HOP_COUNT, buildGasFields } from '../../utils/bundle-helpers.js';
12
- import { PROFIT_CONFIG, ZERO_ADDRESS, BLOCKRAZOR_BUILDER_EOA, ADDRESSES, getProfitRecipient } from '../../utils/constants.js';
13
- import { GAS_LIMITS } from '../../shared/constants/index.js';
14
- import { V2_ROUTER_ABI as _V2_ROUTER_ABI, V3_ROUTER02_ABI as _V3_ROUTER02_ABI, V3_ROUTER_LEGACY_ABI as _V3_ROUTER_LEGACY_ABI, SWAP_ROUTER02_V2_ABI as _SWAP_ROUTER02_V2_ABI, ERC20_ABI } from '../../shared/abis/common.js';
15
- import { getTokenToNativeQuote, quoteV2 } from '../../utils/quote-helpers.js';
16
- // ============================================================================
17
- // ✅ 方案 C:Provider 缓存(避免重复创建)
18
- // ============================================================================
19
- const providerCache = new Map();
20
- const PROVIDER_CACHE_TTL = 60000; // 60秒后过期
21
- const providerCacheTimestamps = new Map();
22
- /**
23
- * 获取缓存的 Provider 实例
24
- * - 根据 rpcUrl + chainId 作为缓存 key
25
- * - 60秒后自动失效,避免长连接问题
26
- */
27
- function getCachedProvider(rpcUrl, chainId, chainName) {
28
- const cacheKey = `${rpcUrl}_${chainId}`;
29
- const now = Date.now();
30
- // 检查缓存是否存在且未过期
31
- const cachedProvider = providerCache.get(cacheKey);
32
- const timestamp = providerCacheTimestamps.get(cacheKey) || 0;
33
- if (cachedProvider && (now - timestamp) < PROVIDER_CACHE_TTL) {
34
- return cachedProvider;
35
- }
36
- // ✅ 禁用 ENS: 避免 "network does not support ENS" 错误
37
- const provider = new JsonRpcProvider(rpcUrl, { chainId, name: chainName, ensAddress: undefined });
38
- providerCache.set(cacheKey, provider);
39
- providerCacheTimestamps.set(cacheKey, now);
40
- return provider;
41
- }
42
- const DEFAULT_GAS_LIMIT = 300000;
43
- const DEADLINE_MINUTES = 20;
44
- // ✅ BLOCKRAZOR_BUILDER_EOA 从公共模块导入
45
- /**
46
- * 截断小数位数,避免超过代币精度导致 parseUnits 报错
47
- * 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
48
- */
49
- function truncateDecimals(value, decimals) {
50
- if (!value || decimals < 0)
51
- return value;
52
- const parts = value.split('.');
53
- if (parts.length === 1)
54
- return value; // 没有小数点
55
- const integerPart = parts[0];
56
- const decimalPart = parts[1];
57
- if (decimals === 0)
58
- return integerPart;
59
- // 截断到指定小数位数
60
- const truncatedDecimal = decimalPart.slice(0, decimals);
61
- // 如果截断后没有小数部分,只返回整数部分
62
- if (!truncatedDecimal || truncatedDecimal === '')
63
- return integerPart;
64
- return `${integerPart}.${truncatedDecimal}`;
65
- }
66
- /** Router 地址配置 - ✅ 使用公共模块中的地址 */
67
- export const DIRECT_ROUTERS = {
68
- BSC: {
69
- PANCAKESWAP_V2: ADDRESSES.BSC.PancakeV2Router,
70
- PANCAKESWAP_V3: ADDRESSES.BSC.PancakeV3Router,
71
- IROSWAP_V2: ADDRESSES.BSC.IroSwapV2Router,
72
- WBNB: ADDRESSES.BSC.WBNB,
73
- },
74
- MONAD: {
75
- // PancakeSwap
76
- PANCAKESWAP_V2: '0xB1Bc24c34e88f7D43D5923034E3a14B24DaACfF9',
77
- PANCAKESWAP_V3: '0x1b81D678ffb9C0263b24A97847620C99d213eB14',
78
- // Uniswap
79
- UNISWAP_V2: '0x4b2ab38dbf28d31d467aa8993f6c2585981d6804',
80
- UNISWAP_V3: '0xd6145b2d3f379919e8cdeda7b97e37c4b2ca9c40',
81
- // Wrapped Native
82
- WMON: ADDRESSES.MONAD.WMON,
83
- },
84
- // XLayer (PotatoSwap + DYORSwap)
85
- XLAYER: {
86
- POTATOSWAP_V2: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e',
87
- POTATOSWAP_V3: '0xB45D0149249488333E3F3f9F359807F4b810C1FC',
88
- DYORSWAP_ROUTER: '0xfb001fbbace32f09cb6d3c449b935183de53ee96',
89
- DYORSWAP_FACTORY: '0x2CcaDb1e437AA9cDc741574bDa154686B1F04C09',
90
- V3_ROUTER: '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41',
91
- V3_FACTORY: '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5',
92
- WOKB: '0xe538905cf8410324e03a5a23c1c177a474d59b2b',
93
- },
94
- ENI: {
95
- DSWAP_V2: ADDRESSES.ENI.DswapV2Router,
96
- DSWAP_V3: ADDRESSES.ENI.DswapV3Router02,
97
- IROSWAP_V2: ADDRESSES.ENI.IroSwapV2Router,
98
- WEGAS: ADDRESSES.ENI.WEGAS,
99
- },
100
- };
101
- /** 链 ID 映射 */
102
- const CHAIN_IDS = {
103
- BSC: 56,
104
- MONAD: 143,
105
- XLAYER: 196,
106
- ENI: 173,
107
- };
108
- /** 获取原生包装代币地址 */
109
- function getWrappedNative(chain) {
110
- const chainUpper = chain.toUpperCase();
111
- if (chainUpper === 'BSC')
112
- return DIRECT_ROUTERS.BSC.WBNB;
113
- if (chainUpper === 'MONAD')
114
- return DIRECT_ROUTERS.MONAD.WMON;
115
- if (chainUpper === 'XLAYER')
116
- return DIRECT_ROUTERS.XLAYER.WOKB;
117
- if (chainUpper === 'ENI')
118
- return DIRECT_ROUTERS.ENI.WEGAS;
119
- throw new Error(`Unsupported chain: ${chain}`);
120
- }
121
- // ============================================================================
122
- // ABI - ✅ 使用公共模块
123
- // ============================================================================
124
- /** V2 Router ABI */
125
- const V2_ROUTER_ABI = _V2_ROUTER_ABI;
126
- /** SwapRouter02 的 V2 方法 ABI - ✅ 从公共模块导入 */
127
- const SWAP_ROUTER02_V2_ABI = _SWAP_ROUTER02_V2_ABI;
128
- /**
129
- * DYORSwap SwapRouter02 的 V2 方法
130
- *
131
- * ⚠️ 重要:DYORSwap 使用自定义的函数签名,无法通过标准 ABI 编码
132
- * 必须手动构造 calldata
133
- *
134
- * swapExactTokensForTokens selector: 0x3234fe0d
135
- * unwrapWETH9 selector: 0x7342292e
136
- * refundETH selector: 0x6d29fcf4
137
- */
138
- const DYORSWAP_SELECTORS = {
139
- swapExactTokensForTokens: '0x3234fe0d',
140
- unwrapWETH9: '0x7342292e',
141
- refundETH: '0x6d29fcf4',
142
- multicall: '0x5ae401dc',
143
- };
144
- /**
145
- * 手动编码 DYORSwap 的 swapExactTokensForTokens calldata
146
- *
147
- * 参数格式 (根据成功交易分析):
148
- * - amountIn: uint256
149
- * - amountOutMin: uint256
150
- * - path: address[] (动态) - offset from start
151
- * - pools: address[] (动态) - offset from start
152
- * - to: uint256 (address(2) = 2)
153
- * - flag: uint256 (1)
154
- * - factory: address
155
- *
156
- * 成功交易的布局:
157
- * [0] amountIn
158
- * [1] amountOutMin
159
- * [2] offset to path = 0xa0 = 160 = 5 * 32 (从参数区域开始)
160
- * [3] offset to pools = 0xe0 = 224 = 7 * 32
161
- * [4] to = 2
162
- * [5] flag = 1
163
- * [6] factory
164
- * [7] path.length
165
- * [8+] path elements
166
- * [N] pools.length
167
- * [N+1+] pools elements
168
- */
169
- function encodeDYORSwapExactTokensForTokens(amountIn, amountOutMin, path, pools, to, // 可以是地址 (string) 或 address(2) 这样的特殊值 (bigint)
170
- flag, factory) {
171
- const pathOffset = 5n * 32n; // 160
172
- // poolsOffset = pathOffset + (1 + path.length) * 32 = 160 + 96 = 256?
173
- // 不对,成功交易是 224 = 160 + 64 = pathOffset + path.length * 32
174
- const poolsOffset = pathOffset + BigInt(path.length) * 32n; // 160 + 2*32 = 224
175
- // 手动构建数据
176
- const pad32 = (hex) => hex.replace('0x', '').padStart(64, '0');
177
- const toHex = (n) => n.toString(16).padStart(64, '0');
178
- // 处理 to 参数:可以是地址 (string) 或特殊值 (bigint)
179
- const toValue = typeof to === 'string' ? pad32(to) : toHex(to);
180
- let data = DYORSWAP_SELECTORS.swapExactTokensForTokens;
181
- data += toHex(amountIn);
182
- data += toHex(amountOutMin);
183
- data += toHex(pathOffset);
184
- data += toHex(poolsOffset);
185
- data += toValue;
186
- data += toHex(flag);
187
- data += pad32(factory);
188
- // path 数据
189
- data += toHex(BigInt(path.length));
190
- for (const addr of path) {
191
- data += pad32(addr);
192
- }
193
- // ⚠️ 不包含 pools 数据!成功交易中 pools 是空的
194
- // poolsOffset 指向的位置没有数据
195
- return '0x' + data.replace('0x', '');
196
- }
197
- /**
198
- * 手动编码 DYORSwap 的 unwrapWETH9 calldata
199
- *
200
- * 参数格式 (根据成功交易分析):
201
- * - amountMinimum: uint256
202
- * - recipient: address
203
- * - pools: bytes (动态) - 包含特殊的 pool 数据
204
- *
205
- * 成功交易的布局:
206
- * [0] amountMinimum
207
- * [1] recipient
208
- * [2] offset to pools = 0x60 = 96 = 3 * 32
209
- * [3] pools 数据 (bytes)
210
- */
211
- function encodeDYORUnwrapWETH9(amountMinimum, recipient, poolsData = '' // hex bytes,可以为空
212
- ) {
213
- const pad32 = (hex) => hex.slice(2).padStart(64, '0');
214
- const toHex = (n) => n.toString(16).padStart(64, '0');
215
- const poolsOffset = 3n * 32n; // 96
216
- const poolsBytes = poolsData.startsWith('0x') ? poolsData.slice(2) : poolsData;
217
- const poolsLength = poolsBytes.length / 2;
218
- let data = DYORSWAP_SELECTORS.unwrapWETH9;
219
- data += toHex(amountMinimum);
220
- data += pad32(recipient);
221
- data += toHex(poolsOffset);
222
- data += toHex(BigInt(poolsLength));
223
- // pools 数据需要 padding 到 32 字节的倍数
224
- if (poolsBytes.length > 0) {
225
- const paddedLength = Math.ceil(poolsBytes.length / 64) * 64;
226
- data += poolsBytes.padEnd(paddedLength, '0');
227
- }
228
- return '0x' + data.replace('0x', '');
229
- }
230
- /**
231
- * DYORSwap 的 ETHBack_Dividend 合约地址
232
- * 这是 refundETH 中使用的固定地址,不是 LP 池地址
233
- */
234
- const DYORSWAP_ETHBACK_DIVIDEND = '0x2e61b0b4410f6fb941d47205191cbb8963d44bcc';
235
- /**
236
- * 手动编码 DYORSwap 的 refundETH calldata
237
- *
238
- * 根据成功交易分析,refundETH 的格式是:
239
- * refundETH(bytes pools)
240
- *
241
- * 成功交易的 refundETH (0x6d29fcf4):
242
- * [0] offset to pools = 0x20 = 32
243
- * [1] pools 数组长度 = 1
244
- * [2] fee = 30 (0x1e)
245
- * [3] ETHBack_Dividend 合约地址
246
- */
247
- function encodeDYORRefundETH(fee = 30 // 默认 fee = 30 (0x1e)
248
- ) {
249
- const toHex = (n) => n.toString(16).padStart(64, '0');
250
- const pad32 = (hex) => hex.replace('0x', '').padStart(64, '0');
251
- // 使用固定的 ETHBack_Dividend 合约地址
252
- let data = DYORSWAP_SELECTORS.refundETH;
253
- data += toHex(32n); // offset = 0x20
254
- data += toHex(1n); // 数组长度 = 1
255
- data += toHex(BigInt(fee)); // fee = 30
256
- data += pad32(DYORSWAP_ETHBACK_DIVIDEND); // ETHBack_Dividend 地址
257
- return '0x' + data.replace('0x', '');
258
- }
259
- /**
260
- * 手动编码 DYORSwap 的 multicall calldata
261
- *
262
- * multicall(uint256 deadline, bytes[] data)
263
- * 这个函数使用标准 ABI 编码
264
- */
265
- function encodeDYORMulticall(deadline, data) {
266
- const abiCoder = ethers.AbiCoder.defaultAbiCoder();
267
- // multicall(uint256 deadline, bytes[] data)
268
- // 确保每个 data 元素都是正确的 hex 格式
269
- const formattedData = data.map(d => d.startsWith('0x') ? d : '0x' + d);
270
- const encoded = abiCoder.encode(['uint256', 'bytes[]'], [deadline, formattedData]).slice(2);
271
- return '0x' + DYORSWAP_SELECTORS.multicall.slice(2) + encoded;
272
- }
273
- /** V3 SwapRouter02 ABI (PancakeSwap V3, 新版 Uniswap) */
274
- const V3_ROUTER02_ABI = _V3_ROUTER02_ABI;
275
- /** V3 SwapRouter(旧版)ABI (Monad Uniswap V3) */
276
- const V3_ROUTER_LEGACY_ABI = _V3_ROUTER_LEGACY_ABI;
277
- /**
278
- * 判断是否使用旧版 SwapRouter
279
- *
280
- * 旧版特征:
281
- * - exactInputSingle 包含 deadline 字段
282
- * - multicall 只有 bytes[] 参数
283
- *
284
- * Monad 上的 V3 DEX 都使用旧版:
285
- * - Uniswap V3: 0xd6145b2d3f379919e8cdeda7b97e37c4b2ca9c40
286
- * - PancakeSwap V3: 0x1b81D678ffb9C0263b24A97847620C99d213eB14
287
- *
288
- * XLayer:
289
- * - V3 Router (0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41) - 旧版(exactInputSingle 包含 deadline)
290
- * - SwapRouter02 (0xB45D0149249488333E3F3f9F359807F4b810C1FC) - 新版(V2+V3 混合)
291
- *
292
- * BSC PancakeSwap V3 使用新版 SwapRouter02
293
- */
294
- function isLegacySwapRouter(chain, routerAddress) {
295
- const chainUpper = chain.toUpperCase();
296
- const routerLower = routerAddress.toLowerCase();
297
- // ✅ Monad 上的所有 V3 Router 都是旧版
298
- if (chainUpper === 'MONAD') {
299
- if (routerLower === DIRECT_ROUTERS.MONAD.UNISWAP_V3.toLowerCase()) {
300
- return true;
301
- }
302
- if (routerLower === DIRECT_ROUTERS.MONAD.PANCAKESWAP_V3.toLowerCase()) {
303
- return true;
304
- }
305
- }
306
- // ✅ XLayer V3 专用 Router 是旧版(exactInputSingle 包含 deadline)
307
- if (chainUpper === 'XLAYER') {
308
- if (routerLower === DIRECT_ROUTERS.XLAYER.V3_ROUTER.toLowerCase()) {
309
- return true; // V3 Router 是旧版
310
- }
311
- // SwapRouter02 是新版
312
- return false;
313
- }
314
- // ✅ BSC PancakeSwap V3 使用新版 SwapRouter02
315
- return false;
316
- }
317
- // ============================================================================
318
- // 工具函数
319
- // ============================================================================
320
- // ✅ 使用公共模块的 getDeadline
321
- function getDeadline(minutes = DEADLINE_MINUTES) {
322
- return _getDeadline(minutes);
323
- }
324
- function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
325
- if (config.gasLimit !== undefined) {
326
- return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
327
- }
328
- const multiplier = config.gasLimitMultiplier ?? 1.2;
329
- return BigInt(Math.ceil(defaultGas * multiplier));
330
- }
331
- async function getGasPrice(provider, config) {
332
- if (config.gasPrice)
333
- return config.gasPrice;
334
- // 转换 Gwei 配置为 Wei
335
- const gasPriceConfig = {};
336
- if (config.minGasPriceGwei !== undefined) {
337
- gasPriceConfig.minGasPrice = ethers.parseUnits(String(config.minGasPriceGwei), 'gwei');
338
- }
339
- if (config.maxGasPriceGwei !== undefined) {
340
- gasPriceConfig.maxGasPrice = ethers.parseUnits(String(config.maxGasPriceGwei), 'gwei');
341
- }
342
- return getOptimizedGasPrice(provider, gasPriceConfig);
343
- }
344
- function isNativeToken(quoteToken) {
345
- return !quoteToken || quoteToken === ZERO_ADDRESS;
346
- }
347
- // ============================================================================
348
- // XLAYER V3 报价(slot0 现货价,纯 V3,不串 V2)
349
- // - 只用于“利润换算”为原生币,避免 XLAYER 没有 v3Quoter 导致 profit=0
350
- // - 不改 BSC 的任何配置/逻辑
351
- // ============================================================================
352
- const XLAYER_V3_FACTORY_ABI = [
353
- 'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)',
354
- ];
355
- const XLAYER_V3_POOL_ABI = [
356
- 'function slot0() view returns (uint160 sqrtPriceX96,int24 tick,uint16 observationIndex,uint16 observationCardinality,uint16 observationCardinalityNext,uint8 feeProtocol,bool unlocked)',
357
- 'function token0() view returns (address)',
358
- 'function token1() view returns (address)',
359
- ];
360
- const V3_FEE_DENOMINATOR = 1000000n; // UniswapV3 fee: X / 1e6
361
- async function quoteXLayerV3TokenToNativeBySlot0(params) {
362
- try {
363
- const { provider, tokenIn, amountIn, fee } = params;
364
- if (!tokenIn || amountIn <= 0n)
365
- return 0n;
366
- const chainFactory = DIRECT_ROUTERS.XLAYER.V3_FACTORY;
367
- const wrappedNative = DIRECT_ROUTERS.XLAYER.WOKB;
368
- if (!chainFactory || !wrappedNative)
369
- return 0n;
370
- const tokenInLower = tokenIn.toLowerCase();
371
- const wrappedLower = wrappedNative.toLowerCase();
372
- if (tokenInLower === wrappedLower)
373
- return amountIn;
374
- const factory = new Contract(chainFactory, XLAYER_V3_FACTORY_ABI, provider);
375
- const poolAddr = await factory.getPool?.(tokenIn, wrappedNative, fee);
376
- if (!poolAddr || poolAddr.toLowerCase() === ZERO_ADDRESS.toLowerCase())
377
- return 0n;
378
- const pool = new Contract(poolAddr, XLAYER_V3_POOL_ABI, provider);
379
- const [t0, t1, slot0] = await Promise.all([pool.token0?.(), pool.token1?.(), pool.slot0?.()]);
380
- if (!t0 || !t1 || !slot0)
381
- return 0n;
382
- const sqrtPriceX96 = BigInt(slot0[0]);
383
- if (sqrtPriceX96 <= 0n)
384
- return 0n;
385
- const amountInLessFee = (amountIn * (V3_FEE_DENOMINATOR - BigInt(fee))) / V3_FEE_DENOMINATOR;
386
- if (amountInLessFee <= 0n)
387
- return 0n;
388
- const Q192 = 2n ** 192n;
389
- const num = sqrtPriceX96 * sqrtPriceX96;
390
- const t0Lower = String(t0).toLowerCase();
391
- const t1Lower = String(t1).toLowerCase();
392
- // sqrtPriceX96 表示 token1/token0 的现货价(raw)
393
- if (tokenInLower === t0Lower && wrappedLower === t1Lower) {
394
- return (amountInLessFee * num) / Q192;
395
- }
396
- if (tokenInLower === t1Lower && wrappedLower === t0Lower) {
397
- return (amountInLessFee * Q192) / num;
398
- }
399
- return 0n;
400
- }
401
- catch {
402
- return 0n;
403
- }
404
- }
405
- // ✅ getTokenToNativeQuote 函数已移至 ../utils/quote-helpers.ts
406
- /**
407
- * ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
408
- */
409
- function findMaxFlowIndex(amounts) {
410
- if (amounts.length === 0)
411
- return 0;
412
- let maxIndex = 0;
413
- let maxValue = amounts[0];
414
- for (let i = 1; i < amounts.length; i++) {
415
- if (amounts[i] > maxValue) {
416
- maxValue = amounts[i];
417
- maxIndex = i;
418
- }
419
- }
420
- return maxIndex;
421
- }
422
- /** 计算利润金额 */
423
- function calculateProfitAmount(totalFlowWei) {
424
- return (totalFlowWei * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
425
- }
426
- /**
427
- * 构建利润多跳转账交易(强制 2 跳中转)
428
- */
429
- async function buildProfitTransactionWithHops(provider, wallet, profitAmountWei, nonce, gasPrice, chainId, txType = 0, profitRecipient) {
430
- if (profitAmountWei <= 0n)
431
- return { signedTransactions: [], hopWallets: undefined };
432
- const profitHopResult = await buildProfitHopTransactions({
433
- provider,
434
- payerWallet: wallet,
435
- profitAmount: profitAmountWei,
436
- profitRecipient: profitRecipient || getProfitRecipient(),
437
- hopCount: PROFIT_HOP_COUNT,
438
- gasPrice,
439
- chainId,
440
- txType,
441
- startNonce: nonce
442
- });
443
- return { signedTransactions: profitHopResult.signedTransactions, hopWallets: profitHopResult.hopWallets };
444
- }
445
- /**
446
- * 获取贿赂金额(wei)
447
- * ✅ 仅 BSC 链支持贿赂
448
- */
449
- function getBribeAmount(config, chain) {
450
- // 只有 BSC 链支持贿赂
451
- if (chain.toUpperCase() !== 'BSC')
452
- return 0n;
453
- const bribeAmount = config.bribeAmount;
454
- if (typeof bribeAmount !== 'number' || bribeAmount <= 0)
455
- return 0n;
456
- // 转换为 wei
457
- return ethers.parseEther(String(bribeAmount));
458
- }
459
- /** 构建贿赂交易(向 BlockRazor Builder EOA 转账 BNB) */
460
- async function buildBribeTransaction(wallet, bribeAmountWei, nonce, gasPrice, chainId, txType = 0) {
461
- const tx = {
462
- to: BLOCKRAZOR_BUILDER_EOA,
463
- value: bribeAmountWei,
464
- nonce,
465
- gasLimit: GAS_LIMITS.BRIBE,
466
- ...buildGasFields(txType, gasPrice),
467
- chainId,
468
- };
469
- return wallet.signTransaction(tx);
470
- }
471
- // ============================================================================
472
- // V2 直接交易
473
- // ============================================================================
474
- /**
475
- * 判断是否是 SwapRouter02 (XLayer PotatoSwap V3)
476
- *
477
- * SwapRouter02 风格的 Router 使用 multicall 包装 V2 方法:
478
- * - multicall(uint256 deadline, bytes[] data)
479
- * - data 内包含 swapExactTokensForTokens 等 V2 方法
480
- *
481
- * SwapRouter02 的 V2 方法签名不同:
482
- * - swapExactTokensForTokens(amountIn, amountOutMin, path[], to) - 没有 deadline
483
- * - 需要通过 multicall 的 deadline 参数传递 deadline
484
- *
485
- * ⚠️ 注意:DYORSwap 虽然也用 multicall,但它的 ABI 完全不同,需要单独处理
486
- */
487
- function isSwapRouter02(chain, routerAddress) {
488
- const chainUpper = chain.toUpperCase();
489
- const addrLower = routerAddress.toLowerCase();
490
- if (chainUpper === 'XLAYER') {
491
- // ✅ XLayer PotatoSwap SwapRouter02 (V3 风格)
492
- if (addrLower === DIRECT_ROUTERS.XLAYER.POTATOSWAP_V3.toLowerCase()) {
493
- return true;
494
- }
495
- }
496
- // ❌ XLayer POTATOSWAP_V2 (0x881fb...) 是标准 V2 Router,不走 SwapRouter02 逻辑
497
- // ❌ XLayer DYORSWAP_ROUTER 有自己独特的 ABI,不走 SwapRouter02 逻辑
498
- // ❌ BSC V2 交易使用传统 PancakeSwap V2 Router,不走 SwapRouter02 逻辑
499
- // ❌ Monad V2 交易使用传统 V2 Router,不走 SwapRouter02 逻辑
500
- return false;
501
- }
502
- /**
503
- * 判断是否是 DYORSwap Router
504
- *
505
- * DYORSwap 使用 multicall,但它的 swapExactTokensForTokens 有特殊的 ABI:
506
- * - swapExactTokensForTokens(amountIn, amountOutMin, path[], payer, flags, factory, pools)
507
- * - unwrapWETH9(amountMinimum, recipient, pools)
508
- */
509
- function isDYORSwap(chain, routerAddress) {
510
- const chainUpper = chain.toUpperCase();
511
- const addrLower = routerAddress.toLowerCase();
512
- if (chainUpper === 'XLAYER') {
513
- if (addrLower === DIRECT_ROUTERS.XLAYER.DYORSWAP_ROUTER.toLowerCase()) {
514
- return true;
515
- }
516
- }
517
- return false;
518
- }
519
- /**
520
- * V2 批量买入(直接调用 Router)
521
- */
522
- export async function directV2BatchBuy(params) {
523
- const { chain, privateKeys, buyAmounts, tokenAddress, routerAddress, quoteToken, quoteTokenDecimals = 18, startNonces, config, } = params;
524
- if (privateKeys.length !== buyAmounts.length) {
525
- throw new Error('privateKeys 和 buyAmounts 长度必须相同');
526
- }
527
- const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
528
- // ✅ 方案 C:使用缓存的 Provider
529
- const provider = getCachedProvider(config.rpcUrl, chainId, chain);
530
- const useNative = isNativeToken(quoteToken);
531
- const wrappedNative = getWrappedNative(chain);
532
- // 创建钱包
533
- const wallets = privateKeys.map(pk => new Wallet(pk, provider));
534
- // ✅ 预先计算所有金额(同步操作,无 RPC)
535
- const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
536
- const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
537
- const baseProfitWei = calculateProfitAmount(totalFlowWei);
538
- // ✅ 方案 B:并行获取 nonces、gasPrice、ERC20 报价 以及(非原生代币时)授权额度
539
- const quoteTokenContract = (!useNative && quoteToken) ? new Contract(quoteToken, ERC20_ABI, provider) : null;
540
- const [nonces, gasPrice, nativeProfitWei, buyAllowances] = await Promise.all([
541
- startNonces && startNonces.length === wallets.length
542
- ? Promise.resolve(startNonces)
543
- : new NonceManager(provider).getNextNoncesForWallets(wallets),
544
- getGasPrice(provider, config),
545
- (!useNative && baseProfitWei > 0n && quoteToken)
546
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v2')
547
- : Promise.resolve(baseProfitWei),
548
- quoteTokenContract
549
- ? Promise.all(wallets.map(w => quoteTokenContract.allowance(w.address, routerAddress).catch(() => 0n)))
550
- : Promise.resolve(wallets.map(() => ethers.MaxUint256)),
551
- ]);
552
- // 确定最终利润金额(ENI 链额外补偿利润转账的 gas 成本)
553
- let profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
554
- if (profitWei > 0n && chain.toUpperCase() === 'ENI') {
555
- profitWei += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
556
- }
557
- const gasLimit = getGasLimit(config, 250000);
558
- const txType = config.txType ?? 0;
559
- const deadline = getDeadline();
560
- // ENI 链原生代币买入:链上余额预检,跳过余额不足的钱包
561
- let activeIndices = null;
562
- if (useNative && chain.toUpperCase() === 'ENI') {
563
- const eniBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
564
- const maxFlowIdx = findMaxFlowIndex(flowAmounts);
565
- const profitGasCost = GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
566
- const validIdx = [];
567
- for (let i = 0; i < wallets.length; i++) {
568
- let required = flowAmounts[i] + BigInt(gasLimit) * gasPrice;
569
- if (i === maxFlowIdx && profitWei > 0n) {
570
- required += profitWei + profitGasCost;
571
- }
572
- if (eniBalances[i] < required) {
573
- console.warn(`[V2 BatchBuy ENI] 钱包 ${wallets[i].address.slice(0, 10)} 余额不足: 余额=${eniBalances[i]}, 需要=${required},跳过`);
574
- continue;
575
- }
576
- validIdx.push(i);
577
- }
578
- if (validIdx.length === 0) {
579
- throw new Error('[V2 BatchBuy ENI] 所有钱包余额不足(含 swap + gas + 利润费),无法执行');
580
- }
581
- if (validIdx.length < wallets.length) {
582
- console.log(`[V2 BatchBuy ENI] 余额预检: ${wallets.length - validIdx.length} 个钱包被跳过, 剩余 ${validIdx.length} 个`);
583
- activeIndices = validIdx;
584
- }
585
- }
586
- // 如果有钱包被跳过,重建数组(仅 ENI)
587
- let activeWallets = wallets;
588
- let activeFlowAmounts = flowAmounts;
589
- let activeNonces = nonces;
590
- let activeBuyAllowances = buyAllowances;
591
- if (activeIndices) {
592
- activeWallets = activeIndices.map(i => wallets[i]);
593
- activeFlowAmounts = activeIndices.map(i => flowAmounts[i]);
594
- activeNonces = activeIndices.map(i => nonces[i]);
595
- activeBuyAllowances = activeIndices.map(i => buyAllowances[i]);
596
- }
597
- const activeTotalFlowWei = activeFlowAmounts.reduce((sum, amt) => sum + amt, 0n);
598
- const activeBaseProfitWei = calculateProfitAmount(activeTotalFlowWei);
599
- let activeProfitWei = activeBaseProfitWei > 0n ? activeBaseProfitWei : 0n;
600
- if (activeProfitWei > 0n && chain.toUpperCase() === 'ENI') {
601
- activeProfitWei += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
602
- }
603
- if (!activeIndices) {
604
- activeProfitWei = profitWei;
605
- }
606
- // ✅ 自动授权:ERC20 报价代币买入时,检查钱包对 Router 的授权额度,不足则插入 approve
607
- const buyApproveTxs = [];
608
- if (!useNative && quoteTokenContract) {
609
- for (let i = 0; i < activeWallets.length; i++) {
610
- if (activeFlowAmounts[i] <= 0n)
611
- continue;
612
- if (activeBuyAllowances[i] < activeFlowAmounts[i]) {
613
- console.log(`🔓 [V2 Buy] 钱包 ${i} 报价代币授权不足 (${activeBuyAllowances[i]} < ${activeFlowAmounts[i]}), 自动 approve`);
614
- const approveData = quoteTokenContract.interface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]);
615
- const approveTx = await activeWallets[i].signTransaction({
616
- to: quoteToken,
617
- data: approveData,
618
- value: 0n,
619
- nonce: activeNonces[i],
620
- gasLimit: 60000n,
621
- ...buildGasFields(txType, gasPrice),
622
- chainId,
623
- });
624
- buyApproveTxs.push({ walletIndex: i, tx: approveTx });
625
- activeNonces[i]++;
626
- }
627
- }
628
- }
629
- // 构建路径
630
- const inputToken = useNative ? wrappedNative : quoteToken;
631
- const path = [inputToken, tokenAddress];
632
- // 判断 Router 类型
633
- const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
634
- const useDYORSwap = isDYORSwap(chain, routerAddress);
635
- const routerIface = useSwapRouter02
636
- ? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
637
- : new ethers.Interface(V2_ROUTER_ABI);
638
- // 构建交易数据的辅助函数
639
- const buildTxData = (wallet, amountWei) => {
640
- if (useDYORSwap) {
641
- const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
642
- const multicallData = [];
643
- multicallData.push(encodeDYORRefundETH(30));
644
- multicallData.push(encodeDYORSwapExactTokensForTokens(useNative ? 0n : amountWei, 0n, path, [], wallet.address, 1n, DYORSWAP_FACTORY));
645
- return { txData: encodeDYORMulticall(BigInt(deadline), multicallData), txValue: useNative ? amountWei : 0n };
646
- }
647
- else if (useSwapRouter02) {
648
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [amountWei, 0n, path, wallet.address]);
649
- return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]), txValue: useNative ? amountWei : 0n };
650
- }
651
- else if (useNative) {
652
- return { txData: routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [0n, path, wallet.address, deadline]), txValue: amountWei };
653
- }
654
- else {
655
- return { txData: routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [amountWei, 0n, path, wallet.address, deadline]), txValue: 0n };
656
- }
657
- };
658
- // 选择金额最大的钱包支付贿赂和利润
659
- const maxFlowIndex = findMaxFlowIndex(activeFlowAmounts);
660
- // 计算贿赂金额(仅 BSC 链)
661
- const bribeWei = getBribeAmount(config, chain);
662
- const hasBribe = bribeWei > 0n && activeWallets.length > 0;
663
- const hasProfit = activeProfitWei > 0n;
664
- // 计算 nonce 偏移
665
- const nonceOffsets = activeWallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
666
- // ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
667
- const signPromises = [];
668
- // 贿赂交易
669
- if (hasBribe) {
670
- signPromises.push(buildBribeTransaction(activeWallets[maxFlowIndex], bribeWei, activeNonces[maxFlowIndex], gasPrice, chainId, txType)
671
- .then(tx => ({ type: 'bribe', index: 0, tx })));
672
- }
673
- // 主交易(并行签名)
674
- activeWallets.forEach((wallet, i) => {
675
- const { txData, txValue } = buildTxData(wallet, activeFlowAmounts[i]);
676
- signPromises.push(wallet.signTransaction({
677
- to: routerAddress,
678
- data: txData,
679
- value: txValue,
680
- nonce: activeNonces[i] + nonceOffsets[i],
681
- gasLimit,
682
- ...buildGasFields(txType, gasPrice),
683
- chainId,
684
- }).then(tx => ({ type: 'swap', index: i, tx })));
685
- });
686
- // ✅ 并行执行所有签名
687
- const signedResults = await Promise.all(signPromises);
688
- // 按类型分组并按顺序组装:approve → 贿赂 → 交易
689
- const approveSignedTxs = buyApproveTxs.sort((a, b) => a.walletIndex - b.walletIndex).map(a => a.tx);
690
- const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
691
- const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
692
- const signedTxs = [...approveSignedTxs, ...bribeTxs, ...swapTxs];
693
- // ✅ 检查是否使用分布式利润模式
694
- const profitMode = config.profitMode || 'single';
695
- const skipProfit = config.skipProfit === true;
696
- const profitAddr = getProfitRecipient();
697
- console.log('🔧 [SDK directV2BatchBuy] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:', activeWallets.length);
698
- // 利润多跳转账
699
- let profitHopWallets;
700
- if (hasProfit && !skipProfit) {
701
- if (profitMode === 'distributed') {
702
- // ✅ 分布式模式:每个钱包独立扣除自己的利润
703
- const MAX_DISTRIBUTED_WALLETS = 12;
704
- if (activeWallets.length > MAX_DISTRIBUTED_WALLETS) {
705
- throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${activeWallets.length} 个`);
706
- }
707
- profitHopWallets = [];
708
- // 计算每个钱包的利润(按比例分配)
709
- for (let i = 0; i < activeWallets.length; i++) {
710
- const walletProfit = activeTotalFlowWei > 0n
711
- ? (activeProfitWei * activeFlowAmounts[i]) / activeTotalFlowWei
712
- : 0n;
713
- if (walletProfit > 0n) {
714
- const walletProfitNonce = activeNonces[i] + nonceOffsets[i] + 1;
715
- const profitResult = await buildProfitHopTransactions({
716
- provider,
717
- payerWallet: activeWallets[i],
718
- profitAmount: walletProfit,
719
- profitRecipient: profitAddr,
720
- hopCount: PROFIT_HOP_COUNT,
721
- gasPrice,
722
- chainId,
723
- txType,
724
- startNonce: walletProfitNonce
725
- });
726
- signedTxs.push(...profitResult.signedTransactions);
727
- if (profitResult.hopWallets) {
728
- profitHopWallets.push(...profitResult.hopWallets);
729
- }
730
- }
731
- }
732
- }
733
- else {
734
- // ✅ 单一模式(默认):从金额最大的钱包统一扣除
735
- const profitNonce = activeNonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
736
- const profitResult = await buildProfitTransactionWithHops(provider, activeWallets[maxFlowIndex], activeProfitWei, profitNonce, gasPrice, chainId, txType, profitAddr);
737
- signedTxs.push(...profitResult.signedTransactions);
738
- profitHopWallets = profitResult.hopWallets;
739
- }
740
- }
741
- return {
742
- signedTransactions: signedTxs,
743
- profitHopWallets,
744
- metadata: {
745
- profitMode,
746
- profitAmount: ethers.formatEther(activeProfitWei),
747
- profitRecipient: profitAddr,
748
- totalFlow: ethers.formatUnits(activeTotalFlowWei, quoteTokenDecimals),
749
- },
750
- };
751
- }
752
- /**
753
- * V2 批量卖出(直接调用 Router)
754
- */
755
- export async function directV2BatchSell(params) {
756
- const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals: inputDecimals, routerAddress, quoteToken, startNonces, config, } = params;
757
- const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
758
- // ✅ 方案 C:使用缓存的 Provider
759
- const provider = getCachedProvider(config.rpcUrl, chainId, chain);
760
- const useNativeOutput = isNativeToken(quoteToken);
761
- const wrappedNative = getWrappedNative(chain);
762
- // 创建钱包
763
- const wallets = privateKeys.map(pk => new Wallet(pk, provider));
764
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
765
- // ✅ 性能优化:并行获取所有独立的 RPC 数据(包括授权额度)
766
- const [fetchedDecimals, balances, allowances, nonces, gasPrice] = await Promise.all([
767
- inputDecimals !== undefined
768
- ? Promise.resolve(inputDecimals)
769
- : tokenContract.decimals().then((d) => Number(d)).catch(() => 18),
770
- Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
771
- Promise.all(wallets.map(w => tokenContract.allowance(w.address, routerAddress).catch(() => 0n))),
772
- startNonces && startNonces.length === wallets.length
773
- ? Promise.resolve(startNonces)
774
- : new NonceManager(provider).getNextNoncesForWallets(wallets),
775
- getGasPrice(provider, config)
776
- ]);
777
- const tokenDecimals = fetchedDecimals;
778
- const gasLimit = getGasLimit(config, 300000);
779
- const txType = config.txType ?? 0;
780
- const deadline = getDeadline();
781
- // 计算卖出数量(同步操作,无 RPC),并安全兜底不超过链上实际余额
782
- const sellAmountsWei = [];
783
- for (let i = 0; i < wallets.length; i++) {
784
- let amount;
785
- if (sellAmounts && sellAmounts[i]) {
786
- const truncatedAmount = truncateDecimals(sellAmounts[i], tokenDecimals);
787
- amount = ethers.parseUnits(truncatedAmount, tokenDecimals);
788
- }
789
- else if (sellPercentages && sellPercentages[i]) {
790
- const pct = Math.min(100, Math.max(0, sellPercentages[i]));
791
- amount = (balances[i] * BigInt(pct)) / 100n;
792
- }
793
- else {
794
- amount = balances[i];
795
- }
796
- if (amount > balances[i]) {
797
- console.log(`⚠️ [V2 Sell] 钱包 ${i} 卖出量(${amount}) > 链上余额(${balances[i]}),已自动调整为实际余额`);
798
- amount = balances[i];
799
- }
800
- sellAmountsWei.push(amount);
801
- }
802
- const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
803
- // ENI 链卖出:检查 maxPayer 的原生余额是否足够覆盖 gas(swap gas + profit hop gas)
804
- if (chain.toUpperCase() === 'ENI') {
805
- const maxSellIdx = findMaxFlowIndex(sellAmountsWei);
806
- const nativeBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
807
- for (let i = 0; i < wallets.length; i++) {
808
- let requiredGas = BigInt(gasLimit) * gasPrice;
809
- if (i === maxSellIdx) {
810
- requiredGas += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
811
- }
812
- if (nativeBalances[i] < requiredGas) {
813
- console.warn(`[V2 BatchSell ENI] 钱包 ${wallets[i].address.slice(0, 10)} 原生余额不足 gas: 余额=${nativeBalances[i]}, 需要=${requiredGas},交易可能失败`);
814
- }
815
- }
816
- }
817
- // ✅ 自动授权:检查每个钱包对 Router 的授权额度,不足则插入 approve 交易
818
- const approveTxs = [];
819
- for (let i = 0; i < wallets.length; i++) {
820
- if (sellAmountsWei[i] <= 0n)
821
- continue;
822
- if (allowances[i] < sellAmountsWei[i]) {
823
- console.log(`🔓 [V2 Sell] 钱包 ${i} 授权不足 (${allowances[i]} < ${sellAmountsWei[i]}), 自动 approve`);
824
- const approveData = tokenContract.interface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]);
825
- const approveTx = await wallets[i].signTransaction({
826
- to: tokenAddress,
827
- data: approveData,
828
- value: 0n,
829
- nonce: nonces[i],
830
- gasLimit: 60000n,
831
- ...buildGasFields(txType, gasPrice),
832
- chainId,
833
- });
834
- approveTxs.push({ walletIndex: i, tx: approveTx });
835
- nonces[i]++;
836
- }
837
- }
838
- // ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
839
- // 使用 V2 Router 获取卖出报价,然后基于预估得到的原生代币数量计算利润
840
- const nativeProfitPromise = (async () => {
841
- try {
842
- if (useNativeOutput) {
843
- // 卖出代币 → 得到 BNB:先获取 V2 报价,再计算利润(✅ 使用当前 Router 报价,IROSwap 池需用 IROSwap Router)
844
- const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2', undefined, routerAddress);
845
- console.log(`[V2 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
846
- if (estimatedBNBOut <= 0n)
847
- return 0n;
848
- const profit = calculateProfitAmount(estimatedBNBOut);
849
- console.log(`[V2 Sell Profit] profit: ${profit}, profitBNB: ${ethers.formatEther(profit)}`);
850
- return profit;
851
- }
852
- else if (quoteToken) {
853
- // 卖出代币 → 得到 ERC20(如 USDT):两阶段报价
854
- // IROSwap 等 DEX 可能只有 token/USDT 池,无 USDT/WBNB,直接 token→WBNB 会失败
855
- // 1) 用当前 Router 报价 token→quoteToken(如 VOYA→USDT)
856
- // 2) 用默认 PancakeSwap 报价 quoteToken→WBNB(USDT/WBNB 在 Pancake 上存在)
857
- const step1 = await quoteV2(provider, tokenAddress, quoteToken, totalSellAmount, chain, routerAddress);
858
- if (!step1.amountOut || step1.amountOut <= 0n)
859
- return 0n;
860
- console.log(`[V2 Sell Profit] step1 token→quoteToken: ${step1.amountOut} wei`);
861
- const estimatedBNBOut = await getTokenToNativeQuote(provider, quoteToken, step1.amountOut, chain, 'v2');
862
- console.log(`[V2 Sell Profit] step2 quoteToken→BNB: ${estimatedBNBOut} wei`);
863
- if (estimatedBNBOut <= 0n)
864
- return 0n;
865
- const profit = calculateProfitAmount(estimatedBNBOut);
866
- console.log(`[V2 Sell Profit] profit: ${profit}, profitBNB: ${ethers.formatEther(profit)}`);
867
- return profit;
868
- }
869
- return 0n;
870
- }
871
- catch (error) {
872
- console.error(`[V2 Sell Profit] Error:`, error);
873
- return 0n;
874
- }
875
- })();
876
- // 构建路径和 Router
877
- const outputToken = useNativeOutput ? wrappedNative : quoteToken;
878
- const path = [tokenAddress, outputToken];
879
- const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
880
- const useDYORSwap = isDYORSwap(chain, routerAddress);
881
- const routerIface = useSwapRouter02 ? new ethers.Interface(SWAP_ROUTER02_V2_ABI) : new ethers.Interface(V2_ROUTER_ABI);
882
- // 构建卖出交易数据的辅助函数
883
- const buildSellTxData = (wallet, sellAmount) => {
884
- if (useDYORSwap) {
885
- const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
886
- const ADDRESS_THIS = 2n;
887
- const multicallData = [];
888
- multicallData.push(encodeDYORSwapExactTokensForTokens(sellAmount, 0n, path, [DYORSWAP_FACTORY], ADDRESS_THIS, 1n, DYORSWAP_FACTORY));
889
- if (useNativeOutput)
890
- multicallData.push(encodeDYORUnwrapWETH9(0n, wallet.address, '0x'));
891
- return encodeDYORMulticall(BigInt(deadline), multicallData);
892
- }
893
- else if (useSwapRouter02) {
894
- const ADDRESS_THIS = '0x0000000000000000000000000000000000000002';
895
- if (useNativeOutput) {
896
- const multicallData = [
897
- routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, ADDRESS_THIS]),
898
- routerIface.encodeFunctionData('unwrapWETH9(uint256,address)', [0n, wallet.address]),
899
- ];
900
- return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, multicallData]);
901
- }
902
- else {
903
- return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address])]]);
904
- }
905
- }
906
- else if (useNativeOutput) {
907
- return routerIface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
908
- }
909
- else {
910
- return routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
911
- }
912
- };
913
- const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
914
- const bribeWei = getBribeAmount(config, chain);
915
- const hasBribe = bribeWei > 0n && wallets.length > 0;
916
- const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
917
- // ✅ 方案 A:并行签名所有交易(贿赂、卖出、利润)+ 并行获取 ERC20 报价
918
- const signPromises = [];
919
- // 贿赂交易
920
- if (hasBribe) {
921
- signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
922
- .then(tx => ({ type: 'bribe', index: 0, tx })));
923
- }
924
- // 卖出交易(并行签名)
925
- wallets.forEach((wallet, i) => {
926
- if (sellAmountsWei[i] <= 0n)
927
- return;
928
- signPromises.push(wallet.signTransaction({
929
- to: routerAddress,
930
- data: buildSellTxData(wallet, sellAmountsWei[i]),
931
- value: 0n,
932
- nonce: nonces[i] + nonceOffsets[i],
933
- gasLimit,
934
- ...buildGasFields(txType, gasPrice),
935
- chainId,
936
- }).then(tx => ({ type: 'swap', index: i, tx })));
937
- });
938
- // ✅ 并行执行:签名 + ERC20 报价
939
- const [signedResults, nativeProfitWei] = await Promise.all([
940
- Promise.all(signPromises),
941
- nativeProfitPromise
942
- ]);
943
- let profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
944
- if (profitWei > 0n && chain.toUpperCase() === 'ENI') {
945
- profitWei += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
946
- }
947
- // ✅ 检查是否使用分布式利润模式
948
- const profitMode = config.profitMode || 'single';
949
- const skipProfit = config.skipProfit === true;
950
- const profitAddr = getProfitRecipient();
951
- console.log('🔧 [SDK directV2BatchSell] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:', wallets.length);
952
- // 利润多跳转账
953
- let profitTxs = [];
954
- let profitHopWallets;
955
- if (profitWei > 0n && wallets.length > 0 && !skipProfit) {
956
- if (profitMode === 'distributed') {
957
- // ✅ 分布式模式:每个钱包独立扣除自己的利润
958
- const MAX_DISTRIBUTED_WALLETS = 12;
959
- if (wallets.length > MAX_DISTRIBUTED_WALLETS) {
960
- throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${wallets.length} 个`);
961
- }
962
- profitHopWallets = [];
963
- // 计算每个钱包的利润(按卖出金额比例分配)
964
- for (let i = 0; i < wallets.length; i++) {
965
- const walletProfit = totalSellAmount > 0n
966
- ? (profitWei * sellAmountsWei[i]) / totalSellAmount
967
- : 0n;
968
- if (walletProfit > 0n) {
969
- const walletProfitNonce = nonces[i] + nonceOffsets[i] + 1;
970
- const profitResult = await buildProfitHopTransactions({
971
- provider,
972
- payerWallet: wallets[i],
973
- profitAmount: walletProfit,
974
- profitRecipient: profitAddr,
975
- hopCount: PROFIT_HOP_COUNT,
976
- gasPrice,
977
- chainId,
978
- txType,
979
- startNonce: walletProfitNonce
980
- });
981
- profitTxs.push(...profitResult.signedTransactions);
982
- if (profitResult.hopWallets) {
983
- profitHopWallets.push(...profitResult.hopWallets);
984
- }
985
- }
986
- }
987
- }
988
- else {
989
- // ✅ 单一模式(默认):从卖出金额最大的钱包统一扣除
990
- const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
991
- const profitResult = await buildProfitTransactionWithHops(provider, wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType, profitAddr);
992
- profitTxs = profitResult.signedTransactions;
993
- profitHopWallets = profitResult.hopWallets;
994
- }
995
- }
996
- // 按类型分组并按顺序组装:approve → bribe → swap → profit
997
- const validResults = signedResults.filter((r) => r !== null);
998
- const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
999
- const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
1000
- const approveSignedTxs = approveTxs.sort((a, b) => a.walletIndex - b.walletIndex).map(a => a.tx);
1001
- const signedTxs = [...approveSignedTxs, ...bribeTxs, ...swapTxs, ...profitTxs];
1002
- return {
1003
- signedTransactions: signedTxs,
1004
- profitHopWallets,
1005
- metadata: {
1006
- profitMode, // ✅ 返回使用的利润模式
1007
- profitAmount: ethers.formatEther(profitWei),
1008
- profitRecipient: profitAddr,
1009
- totalFlow: ethers.formatEther(totalSellAmount),
1010
- },
1011
- };
1012
- }
1013
- // ============================================================================
1014
- // V3 直接交易
1015
- // ============================================================================
1016
- /**
1017
- * V3 批量买入(直接调用 Router)
1018
- *
1019
- * 自动识别 SwapRouter 版本:
1020
- * - SwapRouter02 (PancakeSwap V3): exactInputSingle 不含 deadline,multicall 含 deadline
1021
- * - SwapRouter (旧版 Uniswap V3): exactInputSingle 含 deadline,multicall 不含 deadline
1022
- */
1023
- export async function directV3BatchBuy(params) {
1024
- const { chain, privateKeys, buyAmounts, tokenAddress, routerAddress, fee, quoteToken, quoteTokenDecimals = 18, startNonces, config, } = params;
1025
- if (privateKeys.length !== buyAmounts.length) {
1026
- throw new Error('privateKeys 和 buyAmounts 长度必须相同');
1027
- }
1028
- const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
1029
- // ✅ 方案 C:使用缓存的 Provider
1030
- const provider = getCachedProvider(config.rpcUrl, chainId, chain);
1031
- const useNative = isNativeToken(quoteToken);
1032
- const wrappedNative = getWrappedNative(chain);
1033
- const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
1034
- const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
1035
- const wallets = privateKeys.map(pk => new Wallet(pk, provider));
1036
- // ✅ 预先计算所有金额(同步操作,无 RPC)
1037
- const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
1038
- const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
1039
- const baseProfitWei = calculateProfitAmount(totalFlowWei);
1040
- // ✅ 方案 B:并行获取 nonces、gasPrice 和 ERC20 报价
1041
- const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
1042
- startNonces && startNonces.length === wallets.length
1043
- ? Promise.resolve(startNonces)
1044
- : new NonceManager(provider).getNextNoncesForWallets(wallets),
1045
- getGasPrice(provider, config),
1046
- // ERC20 报价(V3 买入用 V3 报价)
1047
- (!useNative && baseProfitWei > 0n && quoteToken)
1048
- ? (chain.toUpperCase() === 'XLAYER'
1049
- ? quoteXLayerV3TokenToNativeBySlot0({ provider, tokenIn: quoteToken, amountIn: baseProfitWei, fee })
1050
- : getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v3', fee))
1051
- : Promise.resolve(baseProfitWei)
1052
- ]);
1053
- let profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
1054
- if (profitWei > 0n && chain.toUpperCase() === 'ENI') {
1055
- profitWei += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
1056
- }
1057
- const gasLimit = getGasLimit(config, 300000);
1058
- const txType = config.txType ?? 0;
1059
- const routerIface = new ethers.Interface(routerAbi);
1060
- const inputToken = useNative ? wrappedNative : quoteToken;
1061
- const deadline = getDeadline();
1062
- // 构建交易数据的辅助函数
1063
- const buildV3BuyTxData = (wallet, amountWei) => {
1064
- if (useLegacyRouter) {
1065
- const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, deadline, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1066
- if (useNative) {
1067
- const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1068
- const refundData = routerIface.encodeFunctionData('refundETH', []);
1069
- return { txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]), txValue: amountWei };
1070
- }
1071
- return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1072
- }
1073
- else {
1074
- const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1075
- if (useNative) {
1076
- const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1077
- const refundData = routerIface.encodeFunctionData('refundETH', []);
1078
- return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]), txValue: amountWei };
1079
- }
1080
- // ✅ 修复:ERC20 买入也需要使用 multicall 包装以传递 deadline
1081
- // SwapRouter02 的 exactInputSingle 不包含 deadline 参数,必须通过 multicall 传递
1082
- const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1083
- return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]), txValue: 0n };
1084
- }
1085
- };
1086
- const maxFlowIndex = findMaxFlowIndex(flowAmounts);
1087
- const bribeWei = getBribeAmount(config, chain);
1088
- const hasBribe = bribeWei > 0n && wallets.length > 0;
1089
- const hasProfit = profitWei > 0n;
1090
- const nonceOffsets = wallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
1091
- // ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
1092
- const signPromises = [];
1093
- if (hasBribe) {
1094
- signPromises.push(buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType)
1095
- .then(tx => ({ type: 'bribe', index: 0, tx })));
1096
- }
1097
- wallets.forEach((wallet, i) => {
1098
- const { txData, txValue } = buildV3BuyTxData(wallet, flowAmounts[i]);
1099
- signPromises.push(wallet.signTransaction({ to: routerAddress, data: txData, value: txValue, nonce: nonces[i] + nonceOffsets[i], gasLimit, ...buildGasFields(txType, gasPrice), chainId })
1100
- .then(tx => ({ type: 'swap', index: i, tx })));
1101
- });
1102
- const signedResults = await Promise.all(signPromises);
1103
- const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
1104
- const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
1105
- // ✅ 检查是否使用分布式利润模式
1106
- const profitMode = config.profitMode || 'single';
1107
- const skipProfit = config.skipProfit === true;
1108
- const profitAddr = getProfitRecipient();
1109
- console.log('🔧 [SDK directV3BatchBuy] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:', wallets.length);
1110
- // 利润多跳转账
1111
- let profitTxs = [];
1112
- let profitHopWallets;
1113
- if (hasProfit && !skipProfit) {
1114
- if (profitMode === 'distributed') {
1115
- // ✅ 分布式模式:每个钱包独立扣除自己的利润
1116
- const MAX_DISTRIBUTED_WALLETS = 12;
1117
- if (wallets.length > MAX_DISTRIBUTED_WALLETS) {
1118
- throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${wallets.length} 个`);
1119
- }
1120
- profitHopWallets = [];
1121
- // 计算每个钱包的利润(按比例分配)
1122
- for (let i = 0; i < wallets.length; i++) {
1123
- const walletProfit = totalFlowWei > 0n
1124
- ? (profitWei * flowAmounts[i]) / totalFlowWei
1125
- : 0n;
1126
- if (walletProfit > 0n) {
1127
- const walletProfitNonce = nonces[i] + nonceOffsets[i] + 1;
1128
- const profitResult = await buildProfitHopTransactions({
1129
- provider,
1130
- payerWallet: wallets[i],
1131
- profitAmount: walletProfit,
1132
- profitRecipient: profitAddr,
1133
- hopCount: PROFIT_HOP_COUNT,
1134
- gasPrice,
1135
- chainId,
1136
- txType,
1137
- startNonce: walletProfitNonce
1138
- });
1139
- profitTxs.push(...profitResult.signedTransactions);
1140
- if (profitResult.hopWallets) {
1141
- profitHopWallets.push(...profitResult.hopWallets);
1142
- }
1143
- }
1144
- }
1145
- }
1146
- else {
1147
- // ✅ 单一模式(默认):从金额最大的钱包统一扣除
1148
- const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
1149
- const profitResult = await buildProfitTransactionWithHops(provider, wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType, profitAddr);
1150
- profitTxs = profitResult.signedTransactions;
1151
- profitHopWallets = profitResult.hopWallets;
1152
- }
1153
- }
1154
- const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
1155
- return {
1156
- signedTransactions: signedTxs,
1157
- profitHopWallets,
1158
- metadata: {
1159
- profitMode, // ✅ 返回使用的利润模式
1160
- profitAmount: ethers.formatEther(profitWei),
1161
- profitRecipient: profitAddr,
1162
- totalFlow: ethers.formatUnits(totalFlowWei, quoteTokenDecimals),
1163
- },
1164
- };
1165
- }
1166
- /**
1167
- * V3 批量卖出(直接调用 Router)
1168
- *
1169
- * 自动识别 SwapRouter 版本:
1170
- * - SwapRouter02 (PancakeSwap V3): exactInputSingle 不含 deadline,multicall 含 deadline
1171
- * - SwapRouter (旧版 Uniswap V3): exactInputSingle 含 deadline,multicall 不含 deadline
1172
- */
1173
- export async function directV3BatchSell(params) {
1174
- const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals = 18, routerAddress, fee, quoteToken, startNonces, config, } = params;
1175
- const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
1176
- // ✅ 方案 C:使用缓存的 Provider
1177
- const provider = getCachedProvider(config.rpcUrl, chainId, chain);
1178
- const useNativeOutput = isNativeToken(quoteToken);
1179
- const wrappedNative = getWrappedNative(chain);
1180
- const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
1181
- const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
1182
- const wallets = privateKeys.map(pk => new Wallet(pk, provider));
1183
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
1184
- // ✅ 并行获取所有 RPC 数据
1185
- const [balances, nonces, gasPrice] = await Promise.all([
1186
- Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
1187
- startNonces && startNonces.length === wallets.length
1188
- ? Promise.resolve(startNonces)
1189
- : new NonceManager(provider).getNextNoncesForWallets(wallets),
1190
- getGasPrice(provider, config)
1191
- ]);
1192
- const gasLimit = getGasLimit(config, 350000);
1193
- const txType = config.txType ?? 0;
1194
- // 计算卖出数量(同步操作,无 RPC)
1195
- const sellAmountsWei = [];
1196
- for (let i = 0; i < wallets.length; i++) {
1197
- if (sellAmounts && sellAmounts[i]) {
1198
- sellAmountsWei.push(ethers.parseUnits(sellAmounts[i], tokenDecimals));
1199
- }
1200
- else if (sellPercentages && sellPercentages[i]) {
1201
- const pct = Math.min(100, Math.max(0, sellPercentages[i]));
1202
- sellAmountsWei.push((balances[i] * BigInt(pct)) / 100n);
1203
- }
1204
- else {
1205
- sellAmountsWei.push(balances[i]);
1206
- }
1207
- }
1208
- const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
1209
- // ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
1210
- // 使用 V2 Router 获取卖出报价,然后基于预估得到的原生代币数量计算利润
1211
- const nativeProfitPromise = (async () => {
1212
- try {
1213
- if (useNativeOutput) {
1214
- // 卖出代币 → 得到 BNB:先获取 V3 报价,再计算利润
1215
- const estimatedBNBOut = chain.toUpperCase() === 'XLAYER'
1216
- ? await quoteXLayerV3TokenToNativeBySlot0({ provider, tokenIn: tokenAddress, amountIn: totalSellAmount, fee })
1217
- : await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
1218
- console.log(`[V3 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
1219
- if (estimatedBNBOut <= 0n)
1220
- return 0n;
1221
- const profit = calculateProfitAmount(estimatedBNBOut);
1222
- console.log(`[V3 Sell Profit] profit: ${profit}, profitBNB: ${ethers.formatEther(profit)}`);
1223
- return profit;
1224
- }
1225
- else if (quoteToken) {
1226
- // 卖出代币 → 得到 ERC20:先获取 V3 报价,再计算利润
1227
- const estimatedBNBOut = chain.toUpperCase() === 'XLAYER'
1228
- ? await quoteXLayerV3TokenToNativeBySlot0({ provider, tokenIn: tokenAddress, amountIn: totalSellAmount, fee })
1229
- : await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
1230
- if (estimatedBNBOut <= 0n)
1231
- return 0n;
1232
- const profit = calculateProfitAmount(estimatedBNBOut);
1233
- return profit;
1234
- }
1235
- return 0n;
1236
- }
1237
- catch (error) {
1238
- console.error(`[V3 Sell Profit] Error:`, error);
1239
- return 0n;
1240
- }
1241
- })();
1242
- const routerIface = new ethers.Interface(routerAbi);
1243
- const outputToken = useNativeOutput ? wrappedNative : quoteToken;
1244
- const deadline = getDeadline();
1245
- // 构建卖出交易数据的辅助函数
1246
- const buildV3SellTxData = (wallet, sellAmount) => {
1247
- if (useLegacyRouter) {
1248
- const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, deadline, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1249
- if (useNativeOutput) {
1250
- return routerIface.encodeFunctionData('multicall(bytes[])', [[routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
1251
- }
1252
- // ✅ 修复:ERC20 输出也需要使用 multicall 包装以传递 deadline
1253
- return routerIface.encodeFunctionData('multicall(bytes[])', [[routerIface.encodeFunctionData('exactInputSingle', [swapParams])]]);
1254
- }
1255
- else {
1256
- const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1257
- if (useNativeOutput) {
1258
- return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
1259
- }
1260
- // ✅ 修复:ERC20 输出也需要使用 multicall 包装以传递 deadline
1261
- return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('exactInputSingle', [swapParams])]]);
1262
- }
1263
- };
1264
- const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
1265
- const bribeWei = getBribeAmount(config, chain);
1266
- const hasBribe = bribeWei > 0n && wallets.length > 0;
1267
- const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
1268
- // ✅ 方案 A:并行签名所有交易(贿赂、卖出)+ 并行获取 ERC20 报价
1269
- const signPromises = [];
1270
- if (hasBribe) {
1271
- signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
1272
- .then(tx => ({ type: 'bribe', index: 0, tx })));
1273
- }
1274
- wallets.forEach((wallet, i) => {
1275
- if (sellAmountsWei[i] <= 0n)
1276
- return;
1277
- signPromises.push(wallet.signTransaction({
1278
- to: routerAddress,
1279
- data: buildV3SellTxData(wallet, sellAmountsWei[i]),
1280
- value: 0n,
1281
- nonce: nonces[i] + nonceOffsets[i],
1282
- gasLimit,
1283
- ...buildGasFields(txType, gasPrice),
1284
- chainId,
1285
- }).then(tx => ({ type: 'swap', index: i, tx })));
1286
- });
1287
- // ✅ 并行执行:签名 + ERC20 报价
1288
- const [signedResults, nativeProfitWei] = await Promise.all([
1289
- Promise.all(signPromises),
1290
- nativeProfitPromise
1291
- ]);
1292
- let profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
1293
- if (profitWei > 0n && chain.toUpperCase() === 'ENI') {
1294
- profitWei += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
1295
- }
1296
- // ✅ 检查是否使用分布式利润模式
1297
- const profitMode = config.profitMode || 'single';
1298
- const skipProfit = config.skipProfit === true;
1299
- const profitAddr = getProfitRecipient();
1300
- console.log('🔧 [SDK directV3BatchSell] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:', wallets.length);
1301
- // 利润多跳转账
1302
- let profitTxs = [];
1303
- let profitHopWallets;
1304
- if (profitWei > 0n && wallets.length > 0 && !skipProfit) {
1305
- if (profitMode === 'distributed') {
1306
- // ✅ 分布式模式:每个钱包独立扣除自己的利润
1307
- const MAX_DISTRIBUTED_WALLETS = 12;
1308
- if (wallets.length > MAX_DISTRIBUTED_WALLETS) {
1309
- throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${wallets.length} 个`);
1310
- }
1311
- profitHopWallets = [];
1312
- // 计算每个钱包的利润(按卖出金额比例分配)
1313
- for (let i = 0; i < wallets.length; i++) {
1314
- const walletProfit = totalSellAmount > 0n
1315
- ? (profitWei * sellAmountsWei[i]) / totalSellAmount
1316
- : 0n;
1317
- if (walletProfit > 0n) {
1318
- const walletProfitNonce = nonces[i] + nonceOffsets[i] + 1;
1319
- const profitResult = await buildProfitHopTransactions({
1320
- provider,
1321
- payerWallet: wallets[i],
1322
- profitAmount: walletProfit,
1323
- profitRecipient: profitAddr,
1324
- hopCount: PROFIT_HOP_COUNT,
1325
- gasPrice,
1326
- chainId,
1327
- txType,
1328
- startNonce: walletProfitNonce
1329
- });
1330
- profitTxs.push(...profitResult.signedTransactions);
1331
- if (profitResult.hopWallets) {
1332
- profitHopWallets.push(...profitResult.hopWallets);
1333
- }
1334
- }
1335
- }
1336
- }
1337
- else {
1338
- // ✅ 单一模式(默认):从卖出金额最大的钱包统一扣除
1339
- const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
1340
- const profitResult = await buildProfitTransactionWithHops(provider, wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType, profitAddr);
1341
- profitTxs = profitResult.signedTransactions;
1342
- profitHopWallets = profitResult.hopWallets;
1343
- }
1344
- }
1345
- // 按类型分组并按顺序组装
1346
- const validResults = signedResults.filter((r) => r !== null);
1347
- const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
1348
- const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
1349
- const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
1350
- return {
1351
- signedTransactions: signedTxs,
1352
- profitHopWallets,
1353
- metadata: {
1354
- profitMode, // ✅ 返回使用的利润模式
1355
- profitAmount: ethers.formatEther(profitWei),
1356
- profitRecipient: profitAddr,
1357
- totalFlow: ethers.formatEther(totalSellAmount),
1358
- },
1359
- };
1360
- }
1361
- // ============================================================================
1362
- // 辅助函数:获取 Router 地址
1363
- // ============================================================================
1364
- /**
1365
- * 根据链和 DEX 获取 Router 地址
1366
- */
1367
- export function getRouterAddress(chain, dexKey, version) {
1368
- const chainUpper = chain.toUpperCase();
1369
- if (chainUpper === 'BSC') {
1370
- if (dexKey === 'IROSWAP') {
1371
- return DIRECT_ROUTERS.BSC.IROSWAP_V2;
1372
- }
1373
- if (version === 'v2')
1374
- return DIRECT_ROUTERS.BSC.PANCAKESWAP_V2;
1375
- return DIRECT_ROUTERS.BSC.PANCAKESWAP_V3;
1376
- }
1377
- if (chainUpper === 'MONAD') {
1378
- if (dexKey === 'UNISWAP') {
1379
- if (version === 'v2')
1380
- return DIRECT_ROUTERS.MONAD.UNISWAP_V2;
1381
- return DIRECT_ROUTERS.MONAD.UNISWAP_V3;
1382
- }
1383
- // PancakeSwap on Monad - 支持 V2 和 V3
1384
- if (version === 'v2')
1385
- return DIRECT_ROUTERS.MONAD.PANCAKESWAP_V2;
1386
- return DIRECT_ROUTERS.MONAD.PANCAKESWAP_V3;
1387
- }
1388
- // ✅ XLayer: PotatoSwap 和 DYORSwap 支持
1389
- if (chainUpper === 'XLAYER') {
1390
- if (dexKey === 'DYORSWAP') {
1391
- // DYORSwap 使用 SwapRouter02 风格(multicall)
1392
- return DIRECT_ROUTERS.XLAYER.DYORSWAP_ROUTER;
1393
- }
1394
- // ✅ 修复:根据 version 返回正确的 PotatoSwap Router
1395
- if (version === 'v3') {
1396
- return DIRECT_ROUTERS.XLAYER.POTATOSWAP_V3;
1397
- }
1398
- return DIRECT_ROUTERS.XLAYER.POTATOSWAP_V2;
1399
- }
1400
- if (chainUpper === 'ENI') {
1401
- if (dexKey === 'IROSWAP') {
1402
- return DIRECT_ROUTERS.ENI.IROSWAP_V2;
1403
- }
1404
- if (version === 'v3') {
1405
- return DIRECT_ROUTERS.ENI.DSWAP_V3;
1406
- }
1407
- return DIRECT_ROUTERS.ENI.DSWAP_V2;
1408
- }
1409
- throw new Error(`Unsupported chain/dex combination: ${chain}/${dexKey}`);
1410
- }