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,938 +0,0 @@
1
- import { ethers, Wallet, Contract, Interface } from 'ethers';
2
- import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../../utils/bundle-helpers.js';
3
- import { ADDRESSES, ZERO_ADDRESS } from '../../../../utils/constants.js';
4
- import { GAS_LIMITS, CHAINS } from '../../../constants/index.js';
5
- import { MULTICALL3_ABI, V2_ROUTER_QUOTE_ABI } from '../../../abis/common.js';
6
- import { FLAP_PORTAL_ADDRESSES, FLAP_ORIGINAL_PORTAL_ADDRESSES } from '../../constants.js';
7
- import { buildVaultParams, VAULT_PORTAL_ADDRESSES, VAULT_PORTAL_ABI } from '../../vault.js';
8
- import { CHAIN_ID_MAP, PORTAL_ABI, getErrorMessage, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from '../config.js';
9
- // ✅ 常量
10
- const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
11
- // ==================== ERC20 → 原生代币报价 ====================
12
- // BSC 链常量
13
- const BSC_PANCAKE_V2_ROUTER = ADDRESSES.BSC.PancakeV2Router;
14
- const BSC_WBNB = ADDRESSES.BSC.WBNB;
15
- // ✅ Monad 链常量
16
- const MONAD_PANCAKE_V2_ROUTER = '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9';
17
- const MONAD_WMON = ADDRESSES.MONAD.WMON;
18
- // ✅ XLAYER 链常量(PotatoSwap V2)
19
- const XLAYER_POTATOSWAP_V2_ROUTER = ADDRESSES.XLAYER.PotatoSwapV2Router;
20
- const XLAYER_WOKB = ADDRESSES.XLAYER.WOKB;
21
- // ✅ Base 链常量(Uniswap V3 SwapRouter02)
22
- const BASE_WETH = ADDRESSES.BASE.WETH;
23
- const ROUTER_ABI = V2_ROUTER_QUOTE_ABI;
24
- /**
25
- * 获取 ERC20 代币 → 原生代币的报价
26
- * @param provider - Provider 实例
27
- * @param tokenAddress - ERC20 代币地址
28
- * @param tokenAmount - 代币数量(wei)
29
- * @param chainId - 链 ID(56=BSC, 143=Monad)
30
- * @returns 等值的原生代币数量(wei),失败返回 0n
31
- */
32
- async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
33
- if (tokenAmount <= 0n)
34
- return 0n;
35
- try {
36
- if (chainId === CHAINS.BSC.chainId) {
37
- // BSC: 使用 PancakeSwap V2 Router
38
- const router = new Contract(BSC_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
39
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, BSC_WBNB]);
40
- return amounts[1];
41
- }
42
- if (chainId === CHAINS.MONAD.chainId) {
43
- // ✅ Monad: 使用 PancakeSwap V2 Router
44
- const router = new Contract(MONAD_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
45
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, MONAD_WMON]);
46
- return amounts[1];
47
- }
48
- if (chainId === CHAINS.XLAYER.chainId) {
49
- // ✅ XLAYER: 使用 PotatoSwap V2 Router(USDT0 → WOKB)
50
- const router = new Contract(XLAYER_POTATOSWAP_V2_ROUTER, ROUTER_ABI, provider);
51
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, XLAYER_WOKB]);
52
- return amounts[1];
53
- }
54
- if (chainId === CHAINS.BASE.chainId) {
55
- // ✅ Base: 目前大部分 Flap 交易使用 ETH(原生代币),ERC20 报价暂不支持
56
- // Base 主力 DEX 是 Uniswap V3,V2 Router 流动性有限,暂返回 0
57
- // TODO: 后续可接入 Uniswap V3 QuoterV2 进行精确报价
58
- return 0n;
59
- }
60
- // 其他链暂不支持报价,返回 0
61
- return 0n;
62
- }
63
- catch {
64
- // 报价失败返回 0(可能是流动性不足或路径不存在)
65
- return 0n;
66
- }
67
- }
68
- /**
69
- * 获取 Gas Limit
70
- * 优先使用 config.gasLimit,否则使用默认值 * multiplier
71
- *
72
- * ✅ ethers v6 最佳实践:直接使用 bigint 类型
73
- */
74
- function getGasLimit(config, defaultGas = 800000) {
75
- // 优先使用前端传入的 gasLimit
76
- if (config.gasLimit !== undefined) {
77
- // 如果已经是 bigint,直接返回;否则转换
78
- return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
79
- }
80
- // 使用默认值 * multiplier
81
- const multiplier = config.gasLimitMultiplier ?? 1.0;
82
- const calculatedGas = Math.ceil(defaultGas * multiplier);
83
- // JavaScript 原生 BigInt 转换(ethers v6 兼容)
84
- return BigInt(calculatedGas);
85
- }
86
- /**
87
- * Flap Protocol: 创建代币 + 捆绑购买(仅签名版本 - 不依赖 Merkle)
88
- * ✅ 精简版:只负责签名交易,不提交到 Merkle
89
- */
90
- export async function createTokenWithBundleBuyMerkle(params) {
91
- const { chain, privateKeys, buyAmounts, tokenInfo, tokenAddress, config } = params;
92
- if (privateKeys.length === 0) {
93
- throw new Error(getErrorMessage('NO_PRIVATE_KEY'));
94
- }
95
- if (buyAmounts.length !== privateKeys.length - 1) {
96
- throw new Error(getErrorMessage('AMOUNT_MISMATCH', buyAmounts.length, privateKeys.length - 1));
97
- }
98
- const { provider, chainId } = createChainContext(chain, config.rpcUrl);
99
- const nonceManager = new NonceManager(provider);
100
- const signedTxs = [];
101
- const devWallet = new Wallet(privateKeys[0], provider);
102
- const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
103
- const originalPortalAddr = FLAP_ORIGINAL_PORTAL_ADDRESSES[chain];
104
- const portal = new ethers.Contract(originalPortalAddr, PORTAL_ABI, devWallet);
105
- // ✅ 判断使用哪个版本的 newToken
106
- // V5: 提供了 taxV2Config 且 taxRate > 0(Tax Token V2 高级税收分配)
107
- // V4: 提供了 dexId 或 lpFeeProfile(支持 DEX 选择和 LP 费率配置)
108
- // V3: 提供了 extensionID(支持扩展数据)
109
- // V2: 默认
110
- const useV5 = params.taxV2Config !== undefined && (params.taxRate ?? 0) > 0;
111
- const useV4 = !useV5 && (params.dexId !== undefined || params.lpFeeProfile !== undefined);
112
- const useV3 = !useV5 && !useV4 && !!params.extensionID;
113
- // ✅ 优化:并行获取 gasPrice、devWallet nonce 和 createTx
114
- let createTxPromise;
115
- if (useV5) {
116
- // V5: Tax Token V2 高级税收分配
117
- const tv2 = params.taxV2Config;
118
- const dist = tv2.distribution;
119
- const total = dist.mktBps + dist.deflationBps + dist.dividendBps + dist.lpBps;
120
- if (total !== 10000) {
121
- throw new Error(`Tax distribution must sum to 10000 (100%), got ${total}`);
122
- }
123
- // ✅ 判断是否使用金库模式(VaultPortal.newTaxTokenWithVault)
124
- const hasVault = tv2.vaultConfig && tv2.vaultConfig.vaultType !== 'none';
125
- if (hasVault) {
126
- // ✅ 金库模式:通过 VaultPortal.newTaxTokenWithVault 创建代币
127
- const vaultPortalAddr = VAULT_PORTAL_ADDRESSES[chain];
128
- if (!vaultPortalAddr) {
129
- throw new Error(`链 "${chain}" 不支持 Tax Vault 金库功能`);
130
- }
131
- const vaultPortal = new ethers.Contract(vaultPortalAddr, VAULT_PORTAL_ABI, devWallet);
132
- const { vaultFactory, vaultData } = buildVaultParams(tv2.vaultConfig, chain);
133
- createTxPromise = vaultPortal.newTaxTokenWithVault.populateTransaction({
134
- name: tokenInfo.name,
135
- symbol: tokenInfo.symbol,
136
- meta: tokenInfo.meta,
137
- dexThresh: (params.dexThresh ?? 1) & 0xff,
138
- salt: params.salt ?? '0x' + '00'.repeat(32),
139
- taxRate: (params.taxRate ?? 0) & 0xffff,
140
- migratorType: (params.migratorType ?? 0) & 0xff,
141
- quoteToken: params.quoteToken || ZERO_ADDRESS,
142
- quoteAmt: 0n,
143
- permitData: '0x',
144
- extensionID: '0x' + '00'.repeat(32),
145
- extensionData: '0x',
146
- dexId: (params.dexId ?? 0) & 0xff,
147
- lpFeeProfile: (params.lpFeeProfile ?? 0) & 0xff,
148
- taxDuration: BigInt(tv2.taxDuration),
149
- antiFarmerDuration: BigInt(tv2.antiFarmerDuration),
150
- mktBps: dist.mktBps,
151
- deflationBps: dist.deflationBps,
152
- dividendBps: dist.dividendBps,
153
- lpBps: dist.lpBps,
154
- minimumShareBalance: BigInt(dist.minimumShareBalance ?? 10000) * (10n ** 18n),
155
- vaultFactory,
156
- vaultData,
157
- });
158
- }
159
- else {
160
- // 标准 V5: Portal.newTokenV5(无金库)
161
- createTxPromise = portal.newTokenV5.populateTransaction({
162
- name: tokenInfo.name,
163
- symbol: tokenInfo.symbol,
164
- meta: tokenInfo.meta,
165
- dexThresh: (params.dexThresh ?? 1) & 0xff,
166
- salt: params.salt ?? '0x' + '00'.repeat(32),
167
- taxRate: (params.taxRate ?? 0) & 0xffff,
168
- migratorType: (params.migratorType ?? 0) & 0xff,
169
- quoteToken: params.quoteToken || ZERO_ADDRESS,
170
- quoteAmt: 0n,
171
- beneficiary: devWallet.address,
172
- permitData: '0x',
173
- extensionID: params.extensionID ?? '0x' + '00'.repeat(32),
174
- extensionData: params.extensionData ?? '0x',
175
- dexId: (params.dexId ?? 0) & 0xff,
176
- lpFeeProfile: (params.lpFeeProfile ?? 0) & 0xff,
177
- taxDuration: BigInt(tv2.taxDuration),
178
- antiFarmerDuration: BigInt(tv2.antiFarmerDuration),
179
- mktBps: dist.mktBps,
180
- deflationBps: dist.deflationBps,
181
- dividendBps: dist.dividendBps,
182
- lpBps: dist.lpBps,
183
- minimumShareBalance: BigInt(dist.minimumShareBalance ?? 10000) * (10n ** 18n),
184
- });
185
- }
186
- }
187
- else if (useV4) {
188
- // V4: 支持 DEX ID 和 LP 费率配置
189
- createTxPromise = portal.newTokenV4.populateTransaction({
190
- name: tokenInfo.name,
191
- symbol: tokenInfo.symbol,
192
- meta: tokenInfo.meta,
193
- dexThresh: (params.dexThresh ?? 1) & 0xff,
194
- salt: params.salt ?? '0x' + '00'.repeat(32),
195
- taxRate: (params.taxRate ?? 0) & 0xffff,
196
- migratorType: (params.migratorType ?? 0) & 0xff,
197
- quoteToken: params.quoteToken || ZERO_ADDRESS,
198
- quoteAmt: 0n,
199
- beneficiary: devWallet.address,
200
- permitData: '0x',
201
- extensionID: params.extensionID ?? '0x' + '00'.repeat(32),
202
- extensionData: params.extensionData ?? '0x',
203
- dexId: (params.dexId ?? 0) & 0xff,
204
- lpFeeProfile: (params.lpFeeProfile ?? 0) & 0xff
205
- });
206
- }
207
- else if (useV3) {
208
- // V3: 支持扩展数据
209
- createTxPromise = portal.newTokenV3.populateTransaction({
210
- name: tokenInfo.name,
211
- symbol: tokenInfo.symbol,
212
- meta: tokenInfo.meta,
213
- dexThresh: (params.dexThresh ?? 1) & 0xff,
214
- salt: params.salt ?? '0x' + '00'.repeat(32),
215
- taxRate: (params.taxRate ?? 0) & 0xffff,
216
- migratorType: (params.migratorType ?? 0) & 0xff,
217
- quoteToken: params.quoteToken || ZERO_ADDRESS,
218
- quoteAmt: 0n,
219
- beneficiary: devWallet.address,
220
- permitData: '0x',
221
- extensionID: params.extensionID,
222
- extensionData: params.extensionData ?? '0x'
223
- });
224
- }
225
- else {
226
- // V2: 基础版本
227
- createTxPromise = portal.newTokenV2.populateTransaction({
228
- name: tokenInfo.name,
229
- symbol: tokenInfo.symbol,
230
- meta: tokenInfo.meta,
231
- dexThresh: (params.dexThresh ?? 1) & 0xff,
232
- salt: params.salt ?? '0x' + '00'.repeat(32),
233
- taxRate: (params.taxRate ?? 0) & 0xffff,
234
- migratorType: (params.migratorType ?? 0) & 0xff,
235
- quoteToken: params.quoteToken || ZERO_ADDRESS,
236
- quoteAmt: 0n,
237
- beneficiary: devWallet.address,
238
- permitData: '0x'
239
- });
240
- }
241
- const [gasPrice, createTxUnsigned, devNonce] = await Promise.all([
242
- resolveGasPrice(provider, config),
243
- createTxPromise,
244
- nonceManager.getNextNonce(devWallet)
245
- ]);
246
- const createTxRequest = {
247
- ...createTxUnsigned,
248
- from: devWallet.address,
249
- nonce: devNonce,
250
- gasLimit: getGasLimit(config),
251
- gasPrice,
252
- chainId,
253
- type: getTxType(config)
254
- };
255
- const signedCreateTx = await devWallet.signTransaction(createTxRequest);
256
- signedTxs.push(signedCreateTx);
257
- const buyers = createWallets(privateKeys.slice(1), provider);
258
- const extractProfit = shouldExtractProfit(config);
259
- const { fundsList, originalAmounts, totalBuyAmount, totalProfit } = analyzeBuyFunds(buyAmounts, config, extractProfit);
260
- const maxFundsIndex = findMaxIndex(originalAmounts);
261
- const gasLimits = buildGasLimitList(buyers.length, config);
262
- // ✅ 支持 quoteToken:如果传入了 quoteToken 且非零地址,则使用它
263
- const inputToken = params.quoteToken && params.quoteToken !== ZERO_ADDRESS ? params.quoteToken : ZERO_ADDRESS;
264
- const useNativeToken = inputToken === ZERO_ADDRESS;
265
- // ✅ 如果使用非原生代币(如 USDC),需要根据精度转换金额
266
- let adjustedFundsList = fundsList;
267
- let profitTokenAmount = totalProfit;
268
- if (!useNativeToken && params.quoteTokenDecimals !== undefined && params.quoteTokenDecimals !== 18) {
269
- const decimalsDiff = 18 - params.quoteTokenDecimals;
270
- const divisor = BigInt(10 ** decimalsDiff);
271
- adjustedFundsList = fundsList.map(amount => amount / divisor);
272
- profitTokenAmount = totalProfit / divisor;
273
- }
274
- // ✅ 优化:并行获取 unsignedBuys 和 buyerNonces
275
- const [unsignedBuys, buyerNonces] = await Promise.all([
276
- populateBuyTransactionsWithQuote(buyers, portalAddr, tokenAddress, adjustedFundsList, inputToken, useNativeToken),
277
- allocateBuyerNonces(buyers, extractProfit, maxFundsIndex, useNativeToken ? totalProfit : profitTokenAmount, nonceManager)
278
- ]);
279
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
280
- const bribeAmount = getBribeAmount(config);
281
- const bribeTxs = [];
282
- if (bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0) {
283
- // 贿赂交易使用 maxFundsIndex 钱包的第一个 nonce
284
- const bribeNonce = buyerNonces[maxFundsIndex];
285
- const bribeTx = await buyers[maxFundsIndex].signTransaction({
286
- to: BLOCKRAZOR_BUILDER_EOA,
287
- value: bribeAmount,
288
- nonce: bribeNonce,
289
- gasPrice,
290
- gasLimit: GAS_LIMITS.BRIBE,
291
- chainId,
292
- type: getTxType(config)
293
- });
294
- bribeTxs.push(bribeTx);
295
- // 调整 maxFundsIndex 钱包的 nonce(买入交易 +1)
296
- buyerNonces[maxFundsIndex] = bribeNonce + 1;
297
- }
298
- const signedBuys = await signBuyTransactions({
299
- unsignedBuys,
300
- buyers,
301
- nonces: buyerNonces,
302
- gasLimits,
303
- gasPrice,
304
- chainId,
305
- config,
306
- fundsList: adjustedFundsList, // ✅ 使用调整后的金额
307
- useNativeToken // ✅ 传递是否使用原生代币
308
- });
309
- // ✅ 利润多跳转账(强制 2 跳中转)
310
- const profitTxs = [];
311
- let profitHopWallets;
312
- if (extractProfit && maxFundsIndex >= 0) {
313
- const profitNonce = buyerNonces[maxFundsIndex] + 1;
314
- // ✅ 原生币:直接刮原生币;ERC20:先换算成原生币再刮(xLayer: USDT0→OKB)
315
- const nativeProfitAmount = useNativeToken
316
- ? totalProfit
317
- : await getTokenToNativeQuote(provider, inputToken, profitTokenAmount, chainId);
318
- if (nativeProfitAmount > 0n) {
319
- const profitHopResult = await buildProfitHopTransactions({
320
- provider,
321
- payerWallet: buyers[maxFundsIndex],
322
- profitAmount: nativeProfitAmount,
323
- profitRecipient: getProfitRecipient(),
324
- hopCount: PROFIT_HOP_COUNT,
325
- gasPrice,
326
- chainId,
327
- txType: getTxType(config),
328
- startNonce: profitNonce
329
- });
330
- profitTxs.push(...profitHopResult.signedTransactions);
331
- profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
332
- }
333
- }
334
- nonceManager.clearTemp();
335
- // ✅ 组装顺序:贿赂 → 创建代币 → 买入 → 利润多跳
336
- return {
337
- signedTransactions: [...bribeTxs, ...signedTxs, ...signedBuys, ...profitTxs],
338
- tokenAddress,
339
- profitHopWallets, // ✅ 返回利润多跳钱包
340
- metadata: buildProfitMetadata(extractProfit, totalBuyAmount, totalProfit, buyers.length)
341
- };
342
- }
343
- /**
344
- * Flap Protocol: 批量购买(仅签名版本 - 不依赖 Merkle)
345
- * ✅ 精简版:只负责签名交易,不提交到 Merkle
346
- * ✅ 支持 quoteToken:传入 USDC 等地址时使用该代币购买,否则使用原生代币
347
- */
348
- export async function batchBuyWithBundleMerkle(params) {
349
- const { chain, privateKeys, buyAmounts, tokenAddress, quoteToken, quoteTokenDecimals, config } = params;
350
- if (privateKeys.length === 0 || buyAmounts.length !== privateKeys.length) {
351
- throw new Error(getErrorMessage('KEY_AMOUNT_MISMATCH'));
352
- }
353
- const { provider, chainId } = createChainContext(chain, config.rpcUrl);
354
- const nonceManager = new NonceManager(provider);
355
- const signedTxs = [];
356
- const buyers = createWallets(privateKeys, provider);
357
- const extractProfit = shouldExtractProfit(config);
358
- const { fundsList, originalAmounts, totalBuyAmount, totalProfit } = analyzeBuyFunds(buyAmounts, config, extractProfit);
359
- const maxFundsIndex = findMaxIndex(originalAmounts);
360
- const gasLimits = buildGasLimitList(buyers.length, config);
361
- // ✅ 确定 inputToken:如果传入了 quoteToken 且非零地址,则使用它;否则使用零地址(原生代币)
362
- const inputToken = quoteToken && quoteToken !== ZERO_ADDRESS ? quoteToken : ZERO_ADDRESS;
363
- // ✅ 如果使用非原生代币,则 value 应为 0(不发送原生代币)
364
- const useNativeToken = inputToken === ZERO_ADDRESS;
365
- // ✅ 如果使用非原生代币(如 USDC),需要根据精度转换金额
366
- // buyAmounts 是按 18 位精度传入的,需要转换为目标代币精度
367
- let adjustedFundsList = fundsList;
368
- let profitTokenAmount = totalProfit;
369
- if (!useNativeToken && quoteTokenDecimals !== undefined && quoteTokenDecimals !== 18) {
370
- // 从 18 位精度转换为目标精度
371
- // 例如:0.1 ETH (18位) = 100000000000000000 wei
372
- // 转为 USDC (6位) = 100000 (0.1 USDC)
373
- const decimalsDiff = 18 - quoteTokenDecimals;
374
- const divisor = BigInt(10 ** decimalsDiff);
375
- adjustedFundsList = fundsList.map(amount => amount / divisor);
376
- profitTokenAmount = totalProfit / divisor;
377
- }
378
- // ✅ ERC20 购买:获取代币利润等值的原生代币(BNB)报价
379
- let nativeProfitAmount = totalProfit; // 原生代币购买时直接使用
380
- if (!useNativeToken && extractProfit && totalProfit > 0n) {
381
- // 将代币利润转换为等值原生代币(BNB/OKB/MON)
382
- nativeProfitAmount = await getTokenToNativeQuote(provider, inputToken, profitTokenAmount, chainId);
383
- }
384
- // ✅ 优化:如果前端传入了 nonces,需要调整以避免利润交易 nonce 冲突
385
- const presetNonces = config.nonces;
386
- // ✅ 并行执行需要的 RPC 操作
387
- const [gasPrice, unsignedBuys, buyerNonces] = await Promise.all([
388
- resolveGasPrice(provider, config),
389
- populateBuyTransactionsWithQuote(buyers, FLAP_PORTAL_ADDRESSES[chain], tokenAddress, adjustedFundsList, inputToken, useNativeToken),
390
- presetNonces && presetNonces.length === buyers.length
391
- ? adjustNoncesForProfit(presetNonces) // ✅ 调整 nonces 避免冲突
392
- : allocateBuyerNonces(buyers, extractProfit, maxFundsIndex, nativeProfitAmount, nonceManager)
393
- ]);
394
- if (presetNonces) {
395
- }
396
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
397
- const bribeAmount = getBribeAmount(config);
398
- const bribeTxs = [];
399
- if (bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0) {
400
- const bribeNonce = buyerNonces[maxFundsIndex];
401
- const bribeTx = await buyers[maxFundsIndex].signTransaction({
402
- to: BLOCKRAZOR_BUILDER_EOA,
403
- value: bribeAmount,
404
- nonce: bribeNonce,
405
- gasPrice,
406
- gasLimit: GAS_LIMITS.BRIBE,
407
- chainId,
408
- type: getTxType(config)
409
- });
410
- bribeTxs.push(bribeTx);
411
- // 调整 maxFundsIndex 钱包的 nonce(买入交易 +1)
412
- buyerNonces[maxFundsIndex] = bribeNonce + 1;
413
- }
414
- const signedBuys = await signBuyTransactions({
415
- unsignedBuys,
416
- buyers,
417
- nonces: buyerNonces,
418
- gasLimits,
419
- gasPrice,
420
- chainId,
421
- config,
422
- fundsList: adjustedFundsList, // ✅ 使用调整后的金额
423
- useNativeToken // ✅ USDT 购买时 value=0,BNB 购买时 value=金额
424
- });
425
- // ✅ 利润多跳转账
426
- const profitTxs = [];
427
- let profitHopWallets;
428
- // ✅ 检查是否使用分布式利润模式
429
- const profitMode = config.profitMode || 'single';
430
- console.log('🔧 [SDK batchBuyWithBundleMerkle] config.profitMode:', config.profitMode, '-> resolved:', profitMode, 'wallets:', buyers.length);
431
- if (extractProfit && nativeProfitAmount > 0n) {
432
- if (profitMode === 'distributed') {
433
- // ✅ 分布式模式:每个钱包独立扣除自己的利润,每个都有独立的多跳
434
- // 最多支持 12 个钱包(12 交易 + 36 多跳 + 1 贿赂 = 49 笔)
435
- const MAX_DISTRIBUTED_WALLETS = 12;
436
- console.log('🔧 [SDK] 使用分布式利润模式,钱包数:', buyers.length);
437
- if (buyers.length > MAX_DISTRIBUTED_WALLETS) {
438
- throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${buyers.length} 个`);
439
- }
440
- profitHopWallets = [];
441
- // 计算每个钱包的利润(按比例分配)
442
- const perWalletProfits = [];
443
- for (let i = 0; i < buyers.length; i++) {
444
- const walletOriginalAmount = originalAmounts[i];
445
- // 按比例计算该钱包应承担的利润
446
- const walletProfit = totalBuyAmount > 0n
447
- ? (nativeProfitAmount * walletOriginalAmount) / totalBuyAmount
448
- : 0n;
449
- perWalletProfits.push(walletProfit);
450
- }
451
- // 为每个钱包生成独立的利润多跳交易
452
- for (let i = 0; i < buyers.length; i++) {
453
- const walletProfit = perWalletProfits[i];
454
- if (walletProfit > 0n) {
455
- const profitNonce = buyerNonces[i] + 1; // 每个钱包买入后 +1
456
- const profitHopResult = await buildProfitHopTransactions({
457
- provider,
458
- payerWallet: buyers[i],
459
- profitAmount: walletProfit,
460
- profitRecipient: getProfitRecipient(),
461
- hopCount: PROFIT_HOP_COUNT,
462
- gasPrice,
463
- chainId,
464
- txType: getTxType(config),
465
- startNonce: profitNonce
466
- });
467
- profitTxs.push(...profitHopResult.signedTransactions);
468
- if (profitHopResult.hopWallets) {
469
- profitHopWallets.push(...profitHopResult.hopWallets);
470
- }
471
- }
472
- }
473
- }
474
- else {
475
- // ✅ 单一模式(默认):从金额最大的钱包统一扣除所有利润
476
- if (maxFundsIndex >= 0) {
477
- const profitNonce = buyerNonces[maxFundsIndex] + 1;
478
- const profitHopResult = await buildProfitHopTransactions({
479
- provider,
480
- payerWallet: buyers[maxFundsIndex],
481
- profitAmount: nativeProfitAmount,
482
- profitRecipient: getProfitRecipient(),
483
- hopCount: PROFIT_HOP_COUNT,
484
- gasPrice,
485
- chainId,
486
- txType: getTxType(config),
487
- startNonce: profitNonce
488
- });
489
- profitTxs.push(...profitHopResult.signedTransactions);
490
- profitHopWallets = profitHopResult.hopWallets;
491
- }
492
- }
493
- }
494
- nonceManager.clearTemp();
495
- // ✅ 组装顺序:贿赂 → 买入 → 利润多跳
496
- return {
497
- signedTransactions: [...bribeTxs, ...signedBuys, ...profitTxs],
498
- profitHopWallets, // ✅ 返回利润多跳钱包
499
- metadata: {
500
- ...buildProfitMetadata(extractProfit, totalBuyAmount, nativeProfitAmount, buyers.length),
501
- profitMode, // ✅ 返回使用的利润模式
502
- }
503
- };
504
- }
505
- /**
506
- * Flap Protocol: 批量卖出(仅签名版本 - 不依赖 Merkle)
507
- * ✅ 精简版:只负责签名交易,不提交到 Merkle
508
- * ✅ 支持 quoteToken:传入 USDC 等地址时卖出为该代币,否则卖出为原生代币
509
- */
510
- export async function batchSellWithBundleMerkle(params) {
511
- const { chain, privateKeys, sellAmounts, tokenAddress, quoteToken, minOutputAmounts, config } = params;
512
- if (privateKeys.length === 0 || sellAmounts.length !== privateKeys.length) {
513
- throw new Error(getErrorMessage('SELL_KEY_AMOUNT_MISMATCH'));
514
- }
515
- const { provider, chainId } = createChainContext(chain, config.rpcUrl);
516
- const nonceManager = new NonceManager(provider);
517
- const signedTxs = [];
518
- const wallets = createWallets(privateKeys, provider);
519
- const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, 18));
520
- const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
521
- const readOnlyPortal = new ethers.Contract(portalAddr, PORTAL_ABI, provider);
522
- const gasLimits = buildGasLimitList(wallets.length, config);
523
- const portals = wallets.map(w => new ethers.Contract(portalAddr, PORTAL_ABI, w));
524
- // ✅ 确定 outputToken:如果传入了 quoteToken 且非零地址,则使用它;否则使用零地址(原生代币)
525
- const outputToken = quoteToken && quoteToken !== ZERO_ADDRESS ? quoteToken : ZERO_ADDRESS;
526
- const useNativeOutput = outputToken === ZERO_ADDRESS;
527
- // ✅ 优化:如果前端传入了 nonces,直接使用(跳过 RPC 调用)
528
- const presetNonces = config.nonces;
529
- const extractProfit = shouldExtractProfit(config);
530
- // ✅ 并行执行 gasPrice 和 quoteSellOutputs(Multicall3)
531
- const [gasPrice, quotedOutputs] = await Promise.all([
532
- resolveGasPrice(provider, config),
533
- quoteSellOutputsWithQuote(readOnlyPortal, tokenAddress, amountsWei, outputToken)
534
- ]);
535
- // ✅ 计算利润和 maxRevenueIndex(用于 nonce 分配)
536
- let totalTokenProfit = 0n;
537
- let maxRevenueIndex = -1;
538
- let maxRevenue = 0n;
539
- if (extractProfit && quotedOutputs.length > 0) {
540
- for (let i = 0; i < wallets.length; i++) {
541
- const quoted = quotedOutputs[i];
542
- if (quoted > 0n) {
543
- const { profit } = calculateProfit(quoted, config);
544
- totalTokenProfit += profit;
545
- if (quoted > maxRevenue) {
546
- maxRevenue = quoted;
547
- maxRevenueIndex = i;
548
- }
549
- }
550
- }
551
- }
552
- // ✅ 修复:根据是否需要贿赂/利润交易,统一分配 nonces
553
- const bribeAmount = getBribeAmount(config);
554
- const needBribeTx = bribeAmount > 0n && maxRevenueIndex >= 0;
555
- const needProfitTx = extractProfit && totalTokenProfit > 0n && maxRevenueIndex >= 0;
556
- // 计算 maxRevenueIndex 钱包需要的 nonce 数量:贿赂(可选) + 卖出 + 利润(可选)
557
- const maxRevenueNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
558
- let nonces;
559
- let bribeNonce;
560
- let profitNonce;
561
- if (presetNonces && presetNonces.length === wallets.length) {
562
- // ✅ 使用前端传入的 nonces
563
- nonces = [...presetNonces];
564
- if (needBribeTx) {
565
- bribeNonce = nonces[maxRevenueIndex];
566
- nonces[maxRevenueIndex] = bribeNonce + 1; // 卖出交易 nonce +1
567
- }
568
- profitNonce = needProfitTx ? nonces[maxRevenueIndex] + 1 : undefined;
569
- }
570
- else if (maxRevenueNonceCount > 1 && maxRevenueIndex >= 0) {
571
- // maxRevenueIndex 钱包需要多个连续 nonce
572
- const maxRevenueNonces = await nonceManager.getNextNonceBatch(wallets[maxRevenueIndex], maxRevenueNonceCount);
573
- // 其他钱包各需要 1 个 nonce
574
- const otherWallets = wallets.filter((_, i) => i !== maxRevenueIndex);
575
- const otherNonces = otherWallets.length > 0
576
- ? await nonceManager.getNextNoncesForWallets(otherWallets)
577
- : [];
578
- // 组装最终的 nonces 数组(保持原顺序)
579
- nonces = [];
580
- let otherIdx = 0;
581
- let nonceIdx = 0;
582
- if (needBribeTx) {
583
- bribeNonce = maxRevenueNonces[nonceIdx++]; // 贿赂交易用第一个 nonce
584
- }
585
- for (let i = 0; i < wallets.length; i++) {
586
- if (i === maxRevenueIndex) {
587
- nonces.push(maxRevenueNonces[nonceIdx++]); // 卖出交易
588
- }
589
- else {
590
- nonces.push(otherNonces[otherIdx++]);
591
- }
592
- }
593
- if (needProfitTx) {
594
- profitNonce = maxRevenueNonces[nonceIdx]; // 利润交易用最后一个 nonce
595
- }
596
- }
597
- else {
598
- // 不需要额外交易,所有钱包各 1 个 nonce
599
- nonces = await nonceManager.getNextNoncesForWallets(wallets);
600
- }
601
- const minOuts = resolveMinOutputs(minOutputAmounts, wallets.length, quotedOutputs);
602
- // ✅ 优化:构建未签名交易(这里是本地操作,但仍然并行执行以提高效率)
603
- const unsignedList = await Promise.all(portals.map((portal, i) => portal.swapExactInput.populateTransaction({
604
- inputToken: tokenAddress,
605
- outputToken, // ✅ 使用动态 outputToken
606
- inputAmount: amountsWei[i],
607
- minOutputAmount: minOuts[i],
608
- permitData: '0x'
609
- })));
610
- // ✅ 贿赂交易放在首位
611
- const bribeTxs = [];
612
- if (needBribeTx && bribeNonce !== undefined) {
613
- const bribeTx = await wallets[maxRevenueIndex].signTransaction({
614
- to: BLOCKRAZOR_BUILDER_EOA,
615
- value: bribeAmount,
616
- nonce: bribeNonce,
617
- gasPrice,
618
- gasLimit: GAS_LIMITS.BRIBE,
619
- chainId,
620
- type: getTxType(config)
621
- });
622
- bribeTxs.push(bribeTx);
623
- }
624
- // ✅ 签名所有卖出交易(并行)
625
- // ✅ 卖出交易 value 必须为 0,不能发送原生代币
626
- const signedList = await Promise.all(unsignedList.map((unsigned, i) => wallets[i].signTransaction({
627
- ...unsigned,
628
- from: wallets[i].address,
629
- nonce: nonces[i],
630
- gasLimit: gasLimits[i],
631
- gasPrice,
632
- chainId,
633
- type: getTxType(config),
634
- value: 0n // ✅ 卖出交易不发送原生代币
635
- })));
636
- // ✅ 利润多跳转账
637
- const profitTxs = [];
638
- let profitHopWallets;
639
- // ✅ 检查是否使用分布式利润模式
640
- const profitMode = config.profitMode || 'single';
641
- console.log('🔧 [SDK batchSellWithBundleMerkle] config.profitMode:', config.profitMode, '-> resolved:', profitMode, 'wallets:', wallets.length);
642
- if (needProfitTx) {
643
- // ERC20 输出时:获取代币利润等值的原生代币(BNB)报价
644
- let totalNativeProfitAmount = totalTokenProfit;
645
- if (!useNativeOutput && outputToken) {
646
- totalNativeProfitAmount = await getTokenToNativeQuote(provider, outputToken, totalTokenProfit, chainId);
647
- }
648
- if (totalNativeProfitAmount > 0n) {
649
- if (profitMode === 'distributed') {
650
- // ✅ 分布式模式:每个钱包独立扣除自己的利润,每个都有独立的多跳
651
- // 最多支持 12 个钱包(12 交易 + 36 多跳 + 1 贿赂 = 49 笔)
652
- const MAX_DISTRIBUTED_WALLETS = 12;
653
- if (wallets.length > MAX_DISTRIBUTED_WALLETS) {
654
- throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${wallets.length} 个`);
655
- }
656
- profitHopWallets = [];
657
- // 计算每个钱包的利润(按卖出收益比例分配)
658
- const totalQuoted = quotedOutputs.reduce((sum, q) => sum + q, 0n);
659
- for (let i = 0; i < wallets.length; i++) {
660
- const quoted = quotedOutputs[i];
661
- if (quoted > 0n && totalQuoted > 0n) {
662
- // 按比例计算该钱包应承担的利润
663
- const walletProfit = (totalNativeProfitAmount * quoted) / totalQuoted;
664
- if (walletProfit > 0n) {
665
- const walletProfitNonce = nonces[i] + 1; // 每个钱包卖出后 +1
666
- const profitHopResult = await buildProfitHopTransactions({
667
- provider,
668
- payerWallet: wallets[i],
669
- profitAmount: walletProfit,
670
- profitRecipient: getProfitRecipient(),
671
- hopCount: PROFIT_HOP_COUNT,
672
- gasPrice,
673
- chainId,
674
- txType: getTxType(config),
675
- startNonce: walletProfitNonce
676
- });
677
- profitTxs.push(...profitHopResult.signedTransactions);
678
- if (profitHopResult.hopWallets) {
679
- profitHopWallets.push(...profitHopResult.hopWallets);
680
- }
681
- }
682
- }
683
- }
684
- }
685
- else {
686
- // ✅ 单一模式(默认):从收益最大的钱包统一扣除所有利润
687
- if (profitNonce !== undefined) {
688
- const profitHopResult = await buildProfitHopTransactions({
689
- provider,
690
- payerWallet: wallets[maxRevenueIndex],
691
- profitAmount: totalNativeProfitAmount,
692
- profitRecipient: getProfitRecipient(),
693
- hopCount: PROFIT_HOP_COUNT,
694
- gasPrice,
695
- chainId,
696
- txType: getTxType(config),
697
- startNonce: profitNonce
698
- });
699
- profitTxs.push(...profitHopResult.signedTransactions);
700
- profitHopWallets = profitHopResult.hopWallets;
701
- }
702
- }
703
- }
704
- }
705
- nonceManager.clearTemp();
706
- // ✅ 组装顺序:贿赂 → 卖出 → 利润多跳
707
- return {
708
- signedTransactions: [...bribeTxs, ...signedList, ...profitTxs],
709
- profitHopWallets, // ✅ 返回利润多跳钱包
710
- metadata: {
711
- profitMode, // ✅ 返回使用的利润模式
712
- totalProfit: totalTokenProfit.toString(),
713
- }
714
- };
715
- }
716
- // ✅ Provider 缓存(复用连接,减少初始化开销)
717
- const providerCache = new Map();
718
- const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
719
- function createChainContext(chain, rpcUrl) {
720
- const chainId = CHAIN_ID_MAP[chain];
721
- const cacheKey = `${chain}-${rpcUrl}`;
722
- const now = Date.now();
723
- // ✅ 检查缓存
724
- const cached = providerCache.get(cacheKey);
725
- if (cached && cached.expireAt > now) {
726
- return { chainId, provider: cached.provider };
727
- }
728
- // ✅ 创建新 Provider 并缓存
729
- const provider = new ethers.JsonRpcProvider(rpcUrl, { chainId, name: chain });
730
- providerCache.set(cacheKey, { provider, expireAt: now + PROVIDER_CACHE_TTL_MS });
731
- return { chainId, provider };
732
- }
733
- async function resolveGasPrice(provider, config) {
734
- // ✅ 优化:如果前端传入了 gasPrice,直接使用(跳过 RPC 调用)
735
- if (config.gasPrice !== undefined) {
736
- const gasPrice = config.gasPrice;
737
- return gasPrice;
738
- }
739
- return await getOptimizedGasPrice(provider, getGasPriceConfig(config));
740
- }
741
- function createWallets(privateKeys, provider) {
742
- return privateKeys.map(key => new Wallet(key, provider));
743
- }
744
- function analyzeBuyFunds(buyAmounts, config, extractProfit) {
745
- const result = {
746
- fundsList: [],
747
- originalAmounts: [],
748
- totalBuyAmount: 0n,
749
- totalProfit: 0n
750
- };
751
- buyAmounts.forEach(amount => {
752
- const originalAmount = ethers.parseEther(amount);
753
- result.originalAmounts.push(originalAmount);
754
- result.totalBuyAmount += originalAmount;
755
- if (extractProfit) {
756
- const { remaining, profit } = calculateProfit(originalAmount, config);
757
- result.totalProfit += profit;
758
- result.fundsList.push(remaining);
759
- }
760
- else {
761
- result.fundsList.push(originalAmount);
762
- }
763
- });
764
- return result;
765
- }
766
- function findMaxIndex(values) {
767
- if (values.length === 0) {
768
- return -1;
769
- }
770
- let maxIndex = 0;
771
- let maxValue = values[0];
772
- for (let i = 1; i < values.length; i++) {
773
- if (values[i] > maxValue) {
774
- maxValue = values[i];
775
- maxIndex = i;
776
- }
777
- }
778
- return maxIndex;
779
- }
780
- /**
781
- * ✅ 支持 quoteToken 的买入交易构建
782
- * 当 inputToken 为非零地址(如 USDC)时,value 为 0
783
- */
784
- async function populateBuyTransactionsWithQuote(buyers, portalAddr, tokenAddress, fundsList, inputToken, useNativeToken) {
785
- const portals = buyers.map(wallet => new ethers.Contract(portalAddr, PORTAL_ABI, wallet));
786
- return await Promise.all(portals.map((portal, i) => portal.swapExactInput.populateTransaction({
787
- inputToken,
788
- outputToken: tokenAddress,
789
- inputAmount: fundsList[i],
790
- minOutputAmount: 0n,
791
- permitData: '0x'
792
- },
793
- // ✅ 如果使用原生代币,value 为购买金额;否则为 0
794
- useNativeToken ? { value: fundsList[i] } : {})));
795
- }
796
- function buildGasLimitList(length, config) {
797
- const gasLimit = getGasLimit(config);
798
- return new Array(length).fill(gasLimit);
799
- }
800
- /**
801
- * ✅ 修复:不再调整其他钱包的 nonce
802
- *
803
- * 之前的错误逻辑:如果钱包 B 的 nonce 与利润交易的 nonce 数值相同,就 +1
804
- * 这是错误的!因为不同钱包的 nonce 是独立的,即使数值相同也不会冲突!
805
- *
806
- * 正确逻辑:直接返回原始 nonces,不做任何调整
807
- * - 每个钱包使用自己的 nonce 发送买入交易
808
- * - 利润交易使用 maxIndex 钱包的 nonce + 1(在构建利润交易时处理)
809
- */
810
- function adjustNoncesForProfit(presetNonces) {
811
- // ✅ 直接返回原始 nonces,不做任何调整
812
- // 不同钱包的 nonce 是独立的,不会冲突
813
- return presetNonces;
814
- }
815
- /**
816
- * ✅ 修复:明确分配 nonces,避免隐式状态依赖
817
- */
818
- async function allocateBuyerNonces(buyers, extractProfit, maxIndex, totalProfit, nonceManager) {
819
- const needProfitTx = extractProfit && totalProfit > 0n && maxIndex >= 0 && maxIndex < buyers.length;
820
- if (!needProfitTx) {
821
- // 不需要利润交易,所有钱包各 1 个 nonce
822
- return await nonceManager.getNextNoncesForWallets(buyers);
823
- }
824
- // 需要利润交易:maxIndex 钱包需要 2 个连续 nonce(买入 + 利润)
825
- const maxIndexNonces = await nonceManager.getNextNonceBatch(buyers[maxIndex], 2);
826
- // 其他钱包各需要 1 个 nonce
827
- const otherBuyers = buyers.filter((_, i) => i !== maxIndex);
828
- const otherNonces = otherBuyers.length > 0
829
- ? await nonceManager.getNextNoncesForWallets(otherBuyers)
830
- : [];
831
- // 组装最终的 nonces 数组(保持原顺序)
832
- const nonces = [];
833
- let otherIdx = 0;
834
- for (let i = 0; i < buyers.length; i++) {
835
- if (i === maxIndex) {
836
- nonces.push(maxIndexNonces[0]); // 买入交易用第一个 nonce
837
- }
838
- else {
839
- nonces.push(otherNonces[otherIdx++]);
840
- }
841
- }
842
- return nonces;
843
- }
844
- async function signBuyTransactions({ unsignedBuys, buyers, nonces, gasLimits, gasPrice, chainId, config, fundsList, useNativeToken = true // ✅ 默认使用原生代币
845
- }) {
846
- return await Promise.all(unsignedBuys.map((unsigned, i) => buyers[i].signTransaction({
847
- ...unsigned,
848
- from: buyers[i].address,
849
- nonce: nonces[i],
850
- gasLimit: gasLimits[i],
851
- gasPrice,
852
- chainId,
853
- type: getTxType(config),
854
- value: useNativeToken ? fundsList[i] : 0n // ✅ 非原生代币时 value 为 0
855
- })));
856
- }
857
- function buildProfitMetadata(extractProfit, totalBuyAmount, totalProfit, buyerCount) {
858
- if (!extractProfit) {
859
- return undefined;
860
- }
861
- return {
862
- totalBuyAmount: ethers.formatEther(totalBuyAmount),
863
- profitAmount: ethers.formatEther(totalProfit),
864
- profitRecipient: getProfitRecipient(),
865
- buyerCount
866
- };
867
- }
868
- /**
869
- * ✅ 使用 Multicall3 批量获取卖出报价(单次 RPC)
870
- * 比 Promise.all 更高效,N 个报价只需 1 次网络请求
871
- */
872
- async function quoteSellOutputsWithQuote(portal, tokenAddress, amountsWei, outputToken) {
873
- if (amountsWei.length === 0)
874
- return [];
875
- // 如果只有 1 个,直接调用(避免 multicall 开销)
876
- if (amountsWei.length === 1) {
877
- try {
878
- const result = await portal.quoteExactInput.staticCall({
879
- inputToken: tokenAddress,
880
- outputToken,
881
- inputAmount: amountsWei[0]
882
- });
883
- return [result];
884
- }
885
- catch {
886
- return [0n];
887
- }
888
- }
889
- const provider = portal.runner;
890
- const portalAddress = await portal.getAddress();
891
- const portalIface = new Interface(PORTAL_ABI);
892
- const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
893
- // 构建批量调用
894
- const calls = amountsWei.map(amount => ({
895
- target: portalAddress,
896
- allowFailure: true, // 允许单个失败
897
- callData: portalIface.encodeFunctionData('quoteExactInput', [{
898
- inputToken: tokenAddress,
899
- outputToken,
900
- inputAmount: amount
901
- }])
902
- }));
903
- try {
904
- const results = await multicall.aggregate3.staticCall(calls);
905
- return results.map((r) => {
906
- if (r.success && r.returnData && r.returnData !== '0x') {
907
- try {
908
- const decoded = portalIface.decodeFunctionResult('quoteExactInput', r.returnData);
909
- return BigInt(decoded[0]);
910
- }
911
- catch {
912
- return 0n;
913
- }
914
- }
915
- return 0n;
916
- });
917
- }
918
- catch {
919
- // Multicall 失败,回退到并行调用
920
- return await Promise.all(amountsWei.map(async (amount) => {
921
- try {
922
- return await portal.quoteExactInput.staticCall({
923
- inputToken: tokenAddress,
924
- outputToken,
925
- inputAmount: amount
926
- });
927
- }
928
- catch {
929
- return 0n;
930
- }
931
- }));
932
- }
933
- }
934
- function resolveMinOutputs(_provided, walletCount, _quotedOutputs) {
935
- // ✅ 已移除滑点保护:minOutput 固定为 0
936
- return Array(walletCount).fill(0n);
937
- }
938
- // ✅ 贿赂交易和利润交易已内联到各函数中,确保正确的顺序:贿赂 → 主交易 → 利润