four-flap-meme-sdk 1.3.92 → 1.3.93

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 (127) hide show
  1. package/dist/clients/blockrazor.js +0 -1
  2. package/dist/contracts/tm-bundle-merkle/config.d.ts +5 -0
  3. package/dist/contracts/tm-bundle-merkle/config.js +10 -0
  4. package/dist/contracts/tm-bundle-merkle/core.js +92 -24
  5. package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +103 -54
  6. package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +0 -1
  7. package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +36 -6
  8. package/dist/contracts/tm-bundle-merkle/swap.d.ts +0 -3
  9. package/dist/contracts/tm-bundle-merkle/swap.js +59 -6
  10. package/dist/flap/portal-bundle-merkle/config.d.ts +8 -0
  11. package/dist/flap/portal-bundle-merkle/config.js +17 -0
  12. package/dist/flap/portal-bundle-merkle/core.js +120 -68
  13. package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
  14. package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
  15. package/dist/flap/portal-bundle-merkle/pancake-proxy.js +136 -78
  16. package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +0 -2
  17. package/dist/flap/portal-bundle-merkle/swap-buy-first.js +49 -30
  18. package/dist/flap/portal-bundle-merkle/swap.d.ts +0 -2
  19. package/dist/flap/portal-bundle-merkle/swap.js +75 -47
  20. package/dist/flap/portal-bundle-merkle/types.d.ts +1 -0
  21. package/dist/index.d.ts +1 -2
  22. package/dist/index.js +0 -1
  23. package/dist/pancake/bundle-buy-first.d.ts +1 -1
  24. package/dist/pancake/bundle-buy-first.js +49 -17
  25. package/dist/pancake/bundle-swap.d.ts +1 -4
  26. package/dist/pancake/bundle-swap.js +98 -33
  27. package/dist/utils/erc20.d.ts +108 -2
  28. package/dist/utils/erc20.js +65 -17
  29. package/package.json +4 -39
  30. package/dist/sol/constants.d.ts +0 -126
  31. package/dist/sol/constants.js +0 -145
  32. package/dist/sol/dex/index.d.ts +0 -8
  33. package/dist/sol/dex/index.js +0 -12
  34. package/dist/sol/dex/meteora/client.d.ts +0 -76
  35. package/dist/sol/dex/meteora/client.js +0 -219
  36. package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +0 -61
  37. package/dist/sol/dex/meteora/damm-v1-bundle.js +0 -112
  38. package/dist/sol/dex/meteora/damm-v1.d.ts +0 -118
  39. package/dist/sol/dex/meteora/damm-v1.js +0 -315
  40. package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +0 -82
  41. package/dist/sol/dex/meteora/damm-v2-bundle.js +0 -242
  42. package/dist/sol/dex/meteora/damm-v2.d.ts +0 -172
  43. package/dist/sol/dex/meteora/damm-v2.js +0 -632
  44. package/dist/sol/dex/meteora/dbc-bundle.d.ts +0 -123
  45. package/dist/sol/dex/meteora/dbc-bundle.js +0 -304
  46. package/dist/sol/dex/meteora/dbc.d.ts +0 -192
  47. package/dist/sol/dex/meteora/dbc.js +0 -619
  48. package/dist/sol/dex/meteora/dlmm-bundle.d.ts +0 -39
  49. package/dist/sol/dex/meteora/dlmm-bundle.js +0 -189
  50. package/dist/sol/dex/meteora/dlmm.d.ts +0 -146
  51. package/dist/sol/dex/meteora/dlmm.js +0 -593
  52. package/dist/sol/dex/meteora/index.d.ts +0 -25
  53. package/dist/sol/dex/meteora/index.js +0 -65
  54. package/dist/sol/dex/meteora/types.d.ts +0 -787
  55. package/dist/sol/dex/meteora/types.js +0 -110
  56. package/dist/sol/dex/orca/index.d.ts +0 -10
  57. package/dist/sol/dex/orca/index.js +0 -16
  58. package/dist/sol/dex/orca/orca-bundle.d.ts +0 -41
  59. package/dist/sol/dex/orca/orca-bundle.js +0 -173
  60. package/dist/sol/dex/orca/orca.d.ts +0 -65
  61. package/dist/sol/dex/orca/orca.js +0 -474
  62. package/dist/sol/dex/orca/types.d.ts +0 -263
  63. package/dist/sol/dex/orca/types.js +0 -38
  64. package/dist/sol/dex/orca/wavebreak-bundle.d.ts +0 -34
  65. package/dist/sol/dex/orca/wavebreak-bundle.js +0 -198
  66. package/dist/sol/dex/orca/wavebreak-types.d.ts +0 -227
  67. package/dist/sol/dex/orca/wavebreak-types.js +0 -23
  68. package/dist/sol/dex/orca/wavebreak.d.ts +0 -78
  69. package/dist/sol/dex/orca/wavebreak.js +0 -497
  70. package/dist/sol/dex/pump/index.d.ts +0 -9
  71. package/dist/sol/dex/pump/index.js +0 -14
  72. package/dist/sol/dex/pump/pump-bundle.d.ts +0 -92
  73. package/dist/sol/dex/pump/pump-bundle.js +0 -383
  74. package/dist/sol/dex/pump/pump-swap-bundle.d.ts +0 -103
  75. package/dist/sol/dex/pump/pump-swap-bundle.js +0 -380
  76. package/dist/sol/dex/pump/pump-swap.d.ts +0 -46
  77. package/dist/sol/dex/pump/pump-swap.js +0 -199
  78. package/dist/sol/dex/pump/pump.d.ts +0 -35
  79. package/dist/sol/dex/pump/pump.js +0 -352
  80. package/dist/sol/dex/pump/types.d.ts +0 -215
  81. package/dist/sol/dex/pump/types.js +0 -5
  82. package/dist/sol/dex/raydium/index.d.ts +0 -8
  83. package/dist/sol/dex/raydium/index.js +0 -12
  84. package/dist/sol/dex/raydium/launchlab.d.ts +0 -68
  85. package/dist/sol/dex/raydium/launchlab.js +0 -210
  86. package/dist/sol/dex/raydium/raydium-bundle.d.ts +0 -64
  87. package/dist/sol/dex/raydium/raydium-bundle.js +0 -324
  88. package/dist/sol/dex/raydium/raydium.d.ts +0 -40
  89. package/dist/sol/dex/raydium/raydium.js +0 -366
  90. package/dist/sol/dex/raydium/types.d.ts +0 -240
  91. package/dist/sol/dex/raydium/types.js +0 -5
  92. package/dist/sol/index.d.ts +0 -10
  93. package/dist/sol/index.js +0 -16
  94. package/dist/sol/jito/bundle.d.ts +0 -90
  95. package/dist/sol/jito/bundle.js +0 -263
  96. package/dist/sol/jito/index.d.ts +0 -7
  97. package/dist/sol/jito/index.js +0 -7
  98. package/dist/sol/jito/tip.d.ts +0 -51
  99. package/dist/sol/jito/tip.js +0 -83
  100. package/dist/sol/jito/types.d.ts +0 -100
  101. package/dist/sol/jito/types.js +0 -5
  102. package/dist/sol/token/create-complete.d.ts +0 -115
  103. package/dist/sol/token/create-complete.js +0 -235
  104. package/dist/sol/token/create-token.d.ts +0 -57
  105. package/dist/sol/token/create-token.js +0 -230
  106. package/dist/sol/token/index.d.ts +0 -9
  107. package/dist/sol/token/index.js +0 -14
  108. package/dist/sol/token/metadata-upload.d.ts +0 -86
  109. package/dist/sol/token/metadata-upload.js +0 -173
  110. package/dist/sol/token/metadata.d.ts +0 -92
  111. package/dist/sol/token/metadata.js +0 -274
  112. package/dist/sol/token/types.d.ts +0 -153
  113. package/dist/sol/token/types.js +0 -5
  114. package/dist/sol/types.d.ts +0 -176
  115. package/dist/sol/types.js +0 -7
  116. package/dist/sol/utils/balance.d.ts +0 -160
  117. package/dist/sol/utils/balance.js +0 -638
  118. package/dist/sol/utils/connection.d.ts +0 -78
  119. package/dist/sol/utils/connection.js +0 -168
  120. package/dist/sol/utils/index.d.ts +0 -9
  121. package/dist/sol/utils/index.js +0 -9
  122. package/dist/sol/utils/lp-inspect.d.ts +0 -129
  123. package/dist/sol/utils/lp-inspect.js +0 -796
  124. package/dist/sol/utils/transfer.d.ts +0 -125
  125. package/dist/sol/utils/transfer.js +0 -220
  126. package/dist/sol/utils/wallet.d.ts +0 -107
  127. package/dist/sol/utils/wallet.js +0 -210
@@ -0,0 +1,146 @@
1
+ /**
2
+ * ECDH + AES-GCM 加密工具(浏览器兼容)
3
+ * 用于将签名交易用服务器公钥加密
4
+ */
5
+ /**
6
+ * 获取全局 crypto 对象(最简单直接的方式)
7
+ */
8
+ function getCryptoAPI() {
9
+ // 尝试所有可能的全局对象,优先浏览器环境
10
+ const cryptoObj = (typeof window !== 'undefined' && window.crypto) ||
11
+ (typeof self !== 'undefined' && self.crypto) ||
12
+ (typeof global !== 'undefined' && global.crypto) ||
13
+ (typeof globalThis !== 'undefined' && globalThis.crypto);
14
+ if (!cryptoObj) {
15
+ const env = typeof window !== 'undefined' ? 'Browser' : 'Node.js';
16
+ const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
17
+ throw new Error(`❌ Crypto API 不可用。环境: ${env}, 协议: ${protocol}. ` +
18
+ '请确保在 HTTPS 或 localhost 下运行');
19
+ }
20
+ return cryptoObj;
21
+ }
22
+ /**
23
+ * 获取 SubtleCrypto(用于加密操作)
24
+ */
25
+ function getSubtleCrypto() {
26
+ const crypto = getCryptoAPI();
27
+ if (!crypto.subtle) {
28
+ const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
29
+ const hostname = typeof location !== 'undefined' ? location.hostname : 'unknown';
30
+ throw new Error(`❌ SubtleCrypto API 不可用。协议: ${protocol}, 主机: ${hostname}. ` +
31
+ '请确保:1) 使用 HTTPS (或 localhost);2) 浏览器支持 Web Crypto API;' +
32
+ '3) 不在无痕/隐私浏览模式下');
33
+ }
34
+ return crypto.subtle;
35
+ }
36
+ /**
37
+ * Base64 转 ArrayBuffer(优先使用浏览器 API)
38
+ */
39
+ function base64ToArrayBuffer(base64) {
40
+ // 浏览器环境(优先)
41
+ if (typeof atob !== 'undefined') {
42
+ const binaryString = atob(base64);
43
+ const bytes = new Uint8Array(binaryString.length);
44
+ for (let i = 0; i < binaryString.length; i++) {
45
+ bytes[i] = binaryString.charCodeAt(i);
46
+ }
47
+ return bytes.buffer;
48
+ }
49
+ // Node.js 环境(fallback)
50
+ if (typeof Buffer !== 'undefined') {
51
+ return Buffer.from(base64, 'base64').buffer;
52
+ }
53
+ throw new Error('❌ Base64 解码不可用');
54
+ }
55
+ /**
56
+ * ArrayBuffer 转 Base64(优先使用浏览器 API)
57
+ */
58
+ function arrayBufferToBase64(buffer) {
59
+ // 浏览器环境(优先)
60
+ if (typeof btoa !== 'undefined') {
61
+ const bytes = new Uint8Array(buffer);
62
+ let binary = '';
63
+ for (let i = 0; i < bytes.length; i++) {
64
+ binary += String.fromCharCode(bytes[i]);
65
+ }
66
+ return btoa(binary);
67
+ }
68
+ // Node.js 环境(fallback)
69
+ if (typeof Buffer !== 'undefined') {
70
+ return Buffer.from(buffer).toString('base64');
71
+ }
72
+ throw new Error('❌ Base64 编码不可用');
73
+ }
74
+ /**
75
+ * 生成随机 Hex 字符串
76
+ */
77
+ function randomHex(length) {
78
+ const crypto = getCryptoAPI();
79
+ const array = new Uint8Array(length);
80
+ crypto.getRandomValues(array);
81
+ return Array.from(array)
82
+ .map(b => b.toString(16).padStart(2, '0'))
83
+ .join('');
84
+ }
85
+ /**
86
+ * 用服务器公钥加密签名交易(ECDH + AES-GCM)
87
+ *
88
+ * @param signedTransactions 签名后的交易数组
89
+ * @param publicKeyBase64 服务器提供的公钥(Base64 格式)
90
+ * @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
91
+ */
92
+ export async function encryptWithPublicKey(signedTransactions, publicKeyBase64) {
93
+ try {
94
+ // 0. 获取 SubtleCrypto 和 Crypto API
95
+ const subtle = getSubtleCrypto();
96
+ const crypto = getCryptoAPI();
97
+ // 1. 准备数据
98
+ const payload = {
99
+ signedTransactions,
100
+ timestamp: Date.now(),
101
+ nonce: randomHex(8)
102
+ };
103
+ const plaintext = JSON.stringify(payload);
104
+ // 2. 生成临时 ECDH 密钥对
105
+ const ephemeralKeyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
106
+ // 3. 导入服务器公钥
107
+ const publicKeyBuffer = base64ToArrayBuffer(publicKeyBase64);
108
+ const publicKey = await subtle.importKey('raw', publicKeyBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false, []);
109
+ // 4. 派生共享密钥(AES-256)
110
+ const sharedKey = await subtle.deriveKey({ name: 'ECDH', public: publicKey }, ephemeralKeyPair.privateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
111
+ // 5. AES-GCM 加密
112
+ const iv = crypto.getRandomValues(new Uint8Array(12));
113
+ const encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, new TextEncoder().encode(plaintext));
114
+ // 6. 导出临时公钥
115
+ const ephemeralPublicKeyRaw = await subtle.exportKey('raw', ephemeralKeyPair.publicKey);
116
+ // 7. 返回加密包(JSON 格式)
117
+ return JSON.stringify({
118
+ e: arrayBufferToBase64(ephemeralPublicKeyRaw), // 临时公钥
119
+ i: arrayBufferToBase64(iv.buffer), // IV
120
+ d: arrayBufferToBase64(encrypted) // 密文
121
+ });
122
+ }
123
+ catch (error) {
124
+ throw new Error(`加密失败: ${error?.message || String(error)}`);
125
+ }
126
+ }
127
+ /**
128
+ * 验证公钥格式(Base64)
129
+ */
130
+ export function validatePublicKey(publicKeyBase64) {
131
+ try {
132
+ if (!publicKeyBase64)
133
+ return false;
134
+ // Base64 字符集验证
135
+ if (!/^[A-Za-z0-9+/=]+$/.test(publicKeyBase64))
136
+ return false;
137
+ // ECDH P-256 公钥固定长度 65 字节(未压缩)
138
+ // Base64 编码后约 88 字符
139
+ if (publicKeyBase64.length < 80 || publicKeyBase64.length > 100)
140
+ return false;
141
+ return true;
142
+ }
143
+ catch {
144
+ return false;
145
+ }
146
+ }
@@ -1,7 +1,7 @@
1
1
  import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
2
2
  import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
3
  import { ADDRESSES } from '../../utils/constants.js';
4
- import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
4
+ import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from './config.js';
5
5
  import { batchCheckAllowances } from '../../utils/erc20.js';
6
6
  const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
7
7
  const MULTICALL3_ABI = [
@@ -56,16 +56,27 @@ function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
56
56
  return BigInt(calculatedGas);
57
57
  }
58
58
  /**
59
- * 查询代币 decimals
59
+ * 查询代币 decimals(带缓存)
60
+ * ✅ 代币精度不会变化,缓存后永久有效
60
61
  */
61
62
  async function getTokenDecimals(tokenAddress, provider) {
63
+ const cacheKey = tokenAddress.toLowerCase();
64
+ // ✅ 检查缓存
65
+ const cached = tokenDecimalsCache.get(cacheKey);
66
+ if (cached !== undefined) {
67
+ return cached;
68
+ }
62
69
  try {
63
70
  const token = new Contract(tokenAddress, ERC20_ABI, provider);
64
71
  const decimals = await token.decimals();
65
- return Number(decimals);
72
+ const result = Number(decimals);
73
+ // ✅ 缓存结果
74
+ tokenDecimalsCache.set(cacheKey, result);
75
+ return result;
66
76
  }
67
77
  catch {
68
- // 默认返回 18,兼容大部分 ERC20
78
+ // 默认返回 18,兼容大部分 ERC20(也缓存)
79
+ tokenDecimalsCache.set(cacheKey, 18);
69
80
  return 18;
70
81
  }
71
82
  }
@@ -281,11 +292,21 @@ export async function pancakeProxyBatchBuyMerkle(params) {
281
292
  const divisor = BigInt(10 ** decimalsDiff);
282
293
  actualAmountsWei = remainingAmounts.map(amount => amount / divisor);
283
294
  }
284
- // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、nonces(JSON-RPC 批量请求)
295
+ // ✅ 优化:如果前端传入了 gasPrice nonces,跳过 RPC 调用
296
+ const presetGasPrice = config.gasPrice;
297
+ const presetNonces = config.nonces;
298
+ // ✅ 只获取必需的数据(跳过已有的)
285
299
  const [gasPrice, tokenDecimals, nonces] = await Promise.all([
286
- getOptimizedGasPrice(provider, getGasPriceConfig(config)),
300
+ // gasPrice:优先使用前端传入的
301
+ presetGasPrice !== undefined
302
+ ? Promise.resolve(presetGasPrice)
303
+ : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
304
+ // tokenDecimals:有缓存
287
305
  getTokenDecimals(tokenAddress, provider),
288
- allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
306
+ // nonces:优先使用前端传入的
307
+ presetNonces && presetNonces.length === buyers.length
308
+ ? Promise.resolve(presetNonces)
309
+ : allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
289
310
  ]);
290
311
  const minOuts = resolveBuyMinOutputs(params, buyers.length, tokenDecimals);
291
312
  const needBNB = needSendBNB(routeType, params, useNativeToken);
@@ -301,8 +322,26 @@ export async function pancakeProxyBatchBuyMerkle(params) {
301
322
  minOuts,
302
323
  needBNB
303
324
  });
304
- // ✅ 并行签名所有交易
305
- const signedTxs = await Promise.all(unsignedBuys.map((unsigned, i) => {
325
+ // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
326
+ const bribeAmount = getBribeAmount(config);
327
+ const bribeTxs = [];
328
+ if (bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0) {
329
+ const bribeNonce = nonces[maxFundsIndex];
330
+ const bribeTx = await buyers[maxFundsIndex].signTransaction({
331
+ to: BLOCKRAZOR_BUILDER_EOA,
332
+ value: bribeAmount,
333
+ nonce: bribeNonce,
334
+ gasPrice,
335
+ gasLimit: 21000n,
336
+ chainId,
337
+ type: getTxType(config)
338
+ });
339
+ bribeTxs.push(bribeTx);
340
+ // 调整 maxFundsIndex 钱包的 nonce(买入交易 +1)
341
+ nonces[maxFundsIndex] = bribeNonce + 1;
342
+ }
343
+ // ✅ 并行签名所有买入交易
344
+ const signedBuys = await Promise.all(unsignedBuys.map((unsigned, i) => {
306
345
  // ✅ ERC20 购买时 value 只需要 FLAT_FEE,不需要发送代币金额
307
346
  const txValue = useNativeToken ? unsigned.value : FLAT_FEE;
308
347
  return buyers[i].signTransaction({
@@ -316,20 +355,25 @@ export async function pancakeProxyBatchBuyMerkle(params) {
316
355
  value: txValue
317
356
  });
318
357
  }));
319
- await appendProfitTransfer({
320
- extractProfit: shouldExtractProfitForBuy, // ✅ ERC20 购买时不提取利润
321
- totalProfit: nativeProfitAmount,
322
- wallets: buyers,
323
- maxIndex: maxFundsIndex,
324
- nonces,
325
- gasPrice,
326
- chainId,
327
- config,
328
- signedTxs
329
- });
358
+ // ✅ 利润交易放在末尾
359
+ const profitTxs = [];
360
+ if (shouldExtractProfitForBuy && nativeProfitAmount > 0n && maxFundsIndex >= 0) {
361
+ const profitNonce = nonces[maxFundsIndex] + 1;
362
+ const profitTx = await buyers[maxFundsIndex].signTransaction({
363
+ to: getProfitRecipient(),
364
+ value: nativeProfitAmount,
365
+ nonce: profitNonce,
366
+ gasPrice,
367
+ gasLimit: 21000n,
368
+ chainId,
369
+ type: getTxType(config)
370
+ });
371
+ profitTxs.push(profitTx);
372
+ }
330
373
  nonceManager.clearTemp();
374
+ // ✅ 组装顺序:贿赂 → 买入 → 利润
331
375
  return {
332
- signedTransactions: signedTxs
376
+ signedTransactions: [...bribeTxs, ...signedBuys, ...profitTxs]
333
377
  };
334
378
  }
335
379
  /**
@@ -350,10 +394,18 @@ export async function pancakeProxyBatchSellMerkle(params) {
350
394
  const finalGasLimit = getGasLimit(config);
351
395
  const extractProfit = shouldExtractProfit(config);
352
396
  const nonceManager = new NonceManager(provider);
397
+ // ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
398
+ const presetGasPrice = config.gasPrice;
399
+ const presetNonces = config.nonces;
353
400
  // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
354
401
  const [gasPrice, tokenDecimals] = await Promise.all([
355
- getOptimizedGasPrice(provider, getGasPriceConfig(config)),
402
+ // gasPrice:优先使用前端传入的
403
+ presetGasPrice !== undefined
404
+ ? Promise.resolve(presetGasPrice)
405
+ : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
406
+ // tokenDecimals:有缓存
356
407
  getTokenDecimals(tokenAddress, provider),
408
+ // allowances:必须检查
357
409
  ensureAllowances({
358
410
  provider,
359
411
  tokenAddress,
@@ -362,8 +414,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
362
414
  })
363
415
  ]);
364
416
  const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
365
- // ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
366
417
  const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
418
+ // 获取报价(用于计算利润)
367
419
  const { minOuts, quotedOutputs } = await resolveSellOutputs({
368
420
  params,
369
421
  provider,
@@ -388,13 +440,27 @@ export async function pancakeProxyBatchSellMerkle(params) {
388
440
  }
389
441
  }
390
442
  }
391
- // ✅ 修复:根据是否需要利润交易,统一分配 nonces
443
+ // ✅ 修复:根据是否需要贿赂/利润交易,统一分配 nonces
444
+ const bribeAmount = getBribeAmount(config);
445
+ const needBribeTx = bribeAmount > 0n && maxRevenueIndex >= 0;
392
446
  const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
447
+ // 计算 maxRevenueIndex 钱包需要的 nonce 数量:贿赂(可选) + 卖出 + 利润(可选)
448
+ const maxRevenueNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
393
449
  let nonces;
450
+ let bribeNonce;
394
451
  let profitNonce;
395
- if (needProfitTx) {
396
- // maxRevenueIndex 钱包需要 2 个连续 nonce(卖出 + 利润)
397
- const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
452
+ // 优化:如果前端传入了 nonces,直接使用
453
+ if (presetNonces && presetNonces.length === sellers.length) {
454
+ nonces = [...presetNonces];
455
+ if (needBribeTx) {
456
+ bribeNonce = nonces[maxRevenueIndex];
457
+ nonces[maxRevenueIndex] = bribeNonce + 1; // 卖出交易 nonce +1
458
+ }
459
+ profitNonce = needProfitTx ? nonces[maxRevenueIndex] + 1 : undefined;
460
+ }
461
+ else if (maxRevenueNonceCount > 1 && maxRevenueIndex >= 0) {
462
+ // maxRevenueIndex 钱包需要多个连续 nonce
463
+ const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], maxRevenueNonceCount);
398
464
  // 其他钱包各需要 1 个 nonce
399
465
  const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
400
466
  const otherNonces = otherSellers.length > 0
@@ -403,20 +469,40 @@ export async function pancakeProxyBatchSellMerkle(params) {
403
469
  // 组装最终的 nonces 数组(保持原顺序)
404
470
  nonces = [];
405
471
  let otherIdx = 0;
472
+ let nonceIdx = 0;
473
+ if (needBribeTx) {
474
+ bribeNonce = maxRevenueNonces[nonceIdx++]; // 贿赂交易用第一个 nonce
475
+ }
406
476
  for (let i = 0; i < sellers.length; i++) {
407
477
  if (i === maxRevenueIndex) {
408
- nonces.push(maxRevenueNonces[0]); // 卖出交易用第一个 nonce
478
+ nonces.push(maxRevenueNonces[nonceIdx++]); // 卖出交易
409
479
  }
410
480
  else {
411
481
  nonces.push(otherNonces[otherIdx++]);
412
482
  }
413
483
  }
414
- profitNonce = maxRevenueNonces[1]; // 利润交易用第二个 nonce
484
+ if (needProfitTx) {
485
+ profitNonce = maxRevenueNonces[nonceIdx]; // 利润交易用最后一个 nonce
486
+ }
415
487
  }
416
488
  else {
417
- // 不需要利润交易,所有钱包各 1 个 nonce
489
+ // 不需要额外交易,所有钱包各 1 个 nonce
418
490
  nonces = await nonceManager.getNextNoncesForWallets(sellers);
419
491
  }
492
+ // ✅ 贿赂交易放在首位
493
+ const bribeTxs = [];
494
+ if (needBribeTx && bribeNonce !== undefined) {
495
+ const bribeTx = await sellers[maxRevenueIndex].signTransaction({
496
+ to: BLOCKRAZOR_BUILDER_EOA,
497
+ value: bribeAmount,
498
+ nonce: bribeNonce,
499
+ gasPrice,
500
+ gasLimit: 21000n,
501
+ chainId,
502
+ type: getTxType(config)
503
+ });
504
+ bribeTxs.push(bribeTx);
505
+ }
420
506
  const unsignedSells = await buildSellTransactions({
421
507
  routeType,
422
508
  params,
@@ -426,8 +512,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
426
512
  amountsWei,
427
513
  minOuts
428
514
  });
429
- // ✅ 并行签名所有交易
430
- const signedTxs = await Promise.all(unsignedSells.map((unsigned, i) => {
515
+ // ✅ 并行签名所有卖出交易
516
+ const signedSells = await Promise.all(unsignedSells.map((unsigned, i) => {
431
517
  const txValue = unsigned.value;
432
518
  return sellers[i].signTransaction({
433
519
  ...unsigned,
@@ -440,7 +526,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
440
526
  value: txValue
441
527
  });
442
528
  }));
443
- // ✅ 添加利润交易(使用预先分配的 profitNonce)
529
+ // ✅ 利润交易放在末尾
530
+ const profitTxs = [];
444
531
  if (needProfitTx && profitNonce !== undefined) {
445
532
  const profitTx = await sellers[maxRevenueIndex].signTransaction({
446
533
  to: getProfitRecipient(),
@@ -451,16 +538,19 @@ export async function pancakeProxyBatchSellMerkle(params) {
451
538
  chainId,
452
539
  type: getTxType(config)
453
540
  });
454
- signedTxs.push(profitTx);
541
+ profitTxs.push(profitTx);
455
542
  }
456
543
  nonceManager.clearTemp();
544
+ // ✅ 组装顺序:贿赂 → 卖出 → 利润
457
545
  return {
458
- signedTransactions: signedTxs
546
+ signedTransactions: [...bribeTxs, ...signedSells, ...profitTxs]
459
547
  };
460
548
  }
461
549
  // ✅ Provider 缓存(复用连接,减少初始化开销)
462
550
  const providerCache = new Map();
463
551
  const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
552
+ // ✅ Token Decimals 缓存(代币精度不会变化)
553
+ const tokenDecimalsCache = new Map();
464
554
  function createChainContext(chain, rpcUrl) {
465
555
  const chainId = CHAIN_ID_MAP[chain];
466
556
  const cacheKey = `${chain}-${rpcUrl}`;
@@ -492,10 +582,8 @@ function findMaxAmountIndex(amounts) {
492
582
  }
493
583
  return maxIndex;
494
584
  }
495
- function resolveBuyMinOutputs(params, walletCount, tokenDecimals) {
496
- if (params.minOutputAmounts && params.minOutputAmounts.length === walletCount) {
497
- return params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseUnits(m, tokenDecimals) : m);
498
- }
585
+ function resolveBuyMinOutputs(_params, walletCount, _tokenDecimals) {
586
+ // 已移除滑点保护:minOutput 固定为 0
499
587
  return new Array(walletCount).fill(0n);
500
588
  }
501
589
  function createPancakeProxies(wallets, proxyAddress) {
@@ -551,22 +639,6 @@ async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, total
551
639
  }
552
640
  return nonces;
553
641
  }
554
- async function appendProfitTransfer({ extractProfit, totalProfit, wallets, maxIndex, nonces, gasPrice, chainId, config, signedTxs }) {
555
- if (!extractProfit || totalProfit === 0n || wallets.length === 0 || maxIndex < 0) {
556
- return;
557
- }
558
- const profitNonce = (nonces[maxIndex] ?? 0) + 1;
559
- const profitTx = await wallets[maxIndex].signTransaction({
560
- to: getProfitRecipient(),
561
- value: totalProfit,
562
- nonce: profitNonce,
563
- gasPrice,
564
- gasLimit: 21000n,
565
- chainId,
566
- type: getTxType(config)
567
- });
568
- signedTxs.push(profitTx);
569
- }
570
642
  async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
571
643
  const allowances = await batchCheckAllowances(provider, tokenAddress, owners, spender);
572
644
  const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
@@ -576,26 +648,18 @@ async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
576
648
  }
577
649
  }
578
650
  /**
579
- * ✅ 使用 Multicall3 批量获取卖出报价(单次 RPC)
651
+ * ✅ 获取卖出报价(用于计算利润,不用于滑点保护)
652
+ * ✅ 已移除滑点保护:minOutput 固定为 0
580
653
  */
581
654
  async function resolveSellOutputs({ params, provider, tokenAddress, routeType, amountsWei }) {
582
- // 如果已提供 minOutputAmounts,直接使用,跳过报价查询
583
- if (params.minOutputAmounts && params.minOutputAmounts.length === amountsWei.length) {
584
- const minOuts = params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
585
- return {
586
- minOuts,
587
- quotedOutputs: minOuts.map(m => m * 100n / 95n)
588
- };
589
- }
655
+ // 已移除滑点保护:minOutput 固定为 0
656
+ const minOuts = new Array(amountsWei.length).fill(0n);
590
657
  // 如果只有 1 个,直接调用(避免 multicall 开销)
591
658
  if (amountsWei.length === 1) {
592
659
  const quotedOutput = await getSingleQuote(params, provider, tokenAddress, routeType, amountsWei[0]);
593
- return {
594
- quotedOutputs: [quotedOutput],
595
- minOuts: [0n] // ✅ minOutput = 0,不设置滑点限制
596
- };
660
+ return { quotedOutputs: [quotedOutput], minOuts };
597
661
  }
598
- // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
662
+ // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持,用于计算利润)
599
663
  if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
600
664
  try {
601
665
  const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
@@ -619,10 +683,7 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
619
683
  }
620
684
  return 0n;
621
685
  });
622
- return {
623
- quotedOutputs,
624
- minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
625
- };
686
+ return { quotedOutputs, minOuts };
626
687
  }
627
688
  catch {
628
689
  // Multicall 失败,回退到并行调用
@@ -630,10 +691,7 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
630
691
  }
631
692
  // 回退:并行调用(V3 路由或 Multicall 失败时)
632
693
  const quotedOutputs = await Promise.all(amountsWei.map(amount => getSingleQuote(params, provider, tokenAddress, routeType, amount)));
633
- return {
634
- quotedOutputs,
635
- minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
636
- };
694
+ return { quotedOutputs, minOuts };
637
695
  }
638
696
  /**
639
697
  * 获取单个报价
@@ -701,4 +759,4 @@ async function buildSellTransactions({ routeType, params, proxies, wallets, toke
701
759
  }
702
760
  throw new Error(`Unsupported routeType: ${routeType}`);
703
761
  }
704
- // ✅ appendSellProfitTransfer 已内联到 pancakeProxyBatchSellMerkle 中,避免 nonce 竞争问题
762
+ // ✅ 贿赂交易和利润交易已内联到各函数中,确保正确的顺序:贿赂 主交易 利润
@@ -8,7 +8,6 @@ import { FlapSignConfig } from './config.js';
8
8
  export type FlapChain = 'bsc' | 'xlayer' | 'base';
9
9
  export interface FlapBuyFirstSignConfig extends FlapSignConfig {
10
10
  reserveGasETH?: number;
11
- slippageBps?: number;
12
11
  skipQuoteOnError?: boolean;
13
12
  }
14
13
  export interface FlapBuyFirstConfig extends CommonBundleConfig {
@@ -16,7 +15,6 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
16
15
  customRpcUrl?: string;
17
16
  bundleBlockOffset?: number;
18
17
  reserveGasETH?: number;
19
- slippageBps?: number;
20
18
  skipQuoteOnError?: boolean;
21
19
  waitForConfirmation?: boolean;
22
20
  waitTimeoutMs?: number;