four-flap-meme-sdk 1.3.88 → 1.3.89

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 (122) hide show
  1. package/dist/clients/blockrazor.js +0 -1
  2. package/dist/contracts/tm-bundle-merkle/core.js +6 -3
  3. package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +30 -38
  4. package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +0 -1
  5. package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +4 -3
  6. package/dist/contracts/tm-bundle-merkle/swap.d.ts +0 -3
  7. package/dist/contracts/tm-bundle-merkle/swap.js +2 -2
  8. package/dist/flap/portal-bundle-merkle/core.js +2 -6
  9. package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
  10. package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
  11. package/dist/flap/portal-bundle-merkle/pancake-proxy.js +55 -35
  12. package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +0 -2
  13. package/dist/flap/portal-bundle-merkle/swap-buy-first.js +6 -11
  14. package/dist/flap/portal-bundle-merkle/swap.d.ts +0 -2
  15. package/dist/flap/portal-bundle-merkle/swap.js +10 -22
  16. package/dist/index.d.ts +1 -2
  17. package/dist/index.js +0 -1
  18. package/dist/pancake/bundle-buy-first.d.ts +0 -1
  19. package/dist/pancake/bundle-buy-first.js +4 -9
  20. package/dist/pancake/bundle-swap.d.ts +0 -4
  21. package/dist/pancake/bundle-swap.js +7 -14
  22. package/dist/utils/erc20.d.ts +108 -2
  23. package/dist/utils/erc20.js +65 -17
  24. package/package.json +4 -39
  25. package/dist/sol/constants.d.ts +0 -126
  26. package/dist/sol/constants.js +0 -145
  27. package/dist/sol/dex/index.d.ts +0 -8
  28. package/dist/sol/dex/index.js +0 -12
  29. package/dist/sol/dex/meteora/client.d.ts +0 -76
  30. package/dist/sol/dex/meteora/client.js +0 -219
  31. package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +0 -61
  32. package/dist/sol/dex/meteora/damm-v1-bundle.js +0 -112
  33. package/dist/sol/dex/meteora/damm-v1.d.ts +0 -118
  34. package/dist/sol/dex/meteora/damm-v1.js +0 -315
  35. package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +0 -82
  36. package/dist/sol/dex/meteora/damm-v2-bundle.js +0 -242
  37. package/dist/sol/dex/meteora/damm-v2.d.ts +0 -172
  38. package/dist/sol/dex/meteora/damm-v2.js +0 -632
  39. package/dist/sol/dex/meteora/dbc-bundle.d.ts +0 -123
  40. package/dist/sol/dex/meteora/dbc-bundle.js +0 -304
  41. package/dist/sol/dex/meteora/dbc.d.ts +0 -192
  42. package/dist/sol/dex/meteora/dbc.js +0 -619
  43. package/dist/sol/dex/meteora/dlmm-bundle.d.ts +0 -39
  44. package/dist/sol/dex/meteora/dlmm-bundle.js +0 -189
  45. package/dist/sol/dex/meteora/dlmm.d.ts +0 -146
  46. package/dist/sol/dex/meteora/dlmm.js +0 -593
  47. package/dist/sol/dex/meteora/index.d.ts +0 -25
  48. package/dist/sol/dex/meteora/index.js +0 -65
  49. package/dist/sol/dex/meteora/types.d.ts +0 -787
  50. package/dist/sol/dex/meteora/types.js +0 -110
  51. package/dist/sol/dex/orca/index.d.ts +0 -10
  52. package/dist/sol/dex/orca/index.js +0 -16
  53. package/dist/sol/dex/orca/orca-bundle.d.ts +0 -41
  54. package/dist/sol/dex/orca/orca-bundle.js +0 -173
  55. package/dist/sol/dex/orca/orca.d.ts +0 -65
  56. package/dist/sol/dex/orca/orca.js +0 -474
  57. package/dist/sol/dex/orca/types.d.ts +0 -263
  58. package/dist/sol/dex/orca/types.js +0 -38
  59. package/dist/sol/dex/orca/wavebreak-bundle.d.ts +0 -34
  60. package/dist/sol/dex/orca/wavebreak-bundle.js +0 -198
  61. package/dist/sol/dex/orca/wavebreak-types.d.ts +0 -227
  62. package/dist/sol/dex/orca/wavebreak-types.js +0 -23
  63. package/dist/sol/dex/orca/wavebreak.d.ts +0 -78
  64. package/dist/sol/dex/orca/wavebreak.js +0 -486
  65. package/dist/sol/dex/pump/index.d.ts +0 -9
  66. package/dist/sol/dex/pump/index.js +0 -14
  67. package/dist/sol/dex/pump/pump-bundle.d.ts +0 -92
  68. package/dist/sol/dex/pump/pump-bundle.js +0 -383
  69. package/dist/sol/dex/pump/pump-swap-bundle.d.ts +0 -103
  70. package/dist/sol/dex/pump/pump-swap-bundle.js +0 -380
  71. package/dist/sol/dex/pump/pump-swap.d.ts +0 -46
  72. package/dist/sol/dex/pump/pump-swap.js +0 -199
  73. package/dist/sol/dex/pump/pump.d.ts +0 -35
  74. package/dist/sol/dex/pump/pump.js +0 -352
  75. package/dist/sol/dex/pump/types.d.ts +0 -215
  76. package/dist/sol/dex/pump/types.js +0 -5
  77. package/dist/sol/dex/raydium/index.d.ts +0 -8
  78. package/dist/sol/dex/raydium/index.js +0 -12
  79. package/dist/sol/dex/raydium/launchlab.d.ts +0 -68
  80. package/dist/sol/dex/raydium/launchlab.js +0 -210
  81. package/dist/sol/dex/raydium/raydium-bundle.d.ts +0 -64
  82. package/dist/sol/dex/raydium/raydium-bundle.js +0 -324
  83. package/dist/sol/dex/raydium/raydium.d.ts +0 -40
  84. package/dist/sol/dex/raydium/raydium.js +0 -366
  85. package/dist/sol/dex/raydium/types.d.ts +0 -240
  86. package/dist/sol/dex/raydium/types.js +0 -5
  87. package/dist/sol/index.d.ts +0 -10
  88. package/dist/sol/index.js +0 -16
  89. package/dist/sol/jito/bundle.d.ts +0 -90
  90. package/dist/sol/jito/bundle.js +0 -263
  91. package/dist/sol/jito/index.d.ts +0 -7
  92. package/dist/sol/jito/index.js +0 -7
  93. package/dist/sol/jito/tip.d.ts +0 -51
  94. package/dist/sol/jito/tip.js +0 -83
  95. package/dist/sol/jito/types.d.ts +0 -100
  96. package/dist/sol/jito/types.js +0 -5
  97. package/dist/sol/token/create-complete.d.ts +0 -115
  98. package/dist/sol/token/create-complete.js +0 -235
  99. package/dist/sol/token/create-token.d.ts +0 -57
  100. package/dist/sol/token/create-token.js +0 -230
  101. package/dist/sol/token/index.d.ts +0 -9
  102. package/dist/sol/token/index.js +0 -14
  103. package/dist/sol/token/metadata-upload.d.ts +0 -86
  104. package/dist/sol/token/metadata-upload.js +0 -173
  105. package/dist/sol/token/metadata.d.ts +0 -92
  106. package/dist/sol/token/metadata.js +0 -274
  107. package/dist/sol/token/types.d.ts +0 -153
  108. package/dist/sol/token/types.js +0 -5
  109. package/dist/sol/types.d.ts +0 -176
  110. package/dist/sol/types.js +0 -7
  111. package/dist/sol/utils/balance.d.ts +0 -160
  112. package/dist/sol/utils/balance.js +0 -638
  113. package/dist/sol/utils/connection.d.ts +0 -78
  114. package/dist/sol/utils/connection.js +0 -168
  115. package/dist/sol/utils/index.d.ts +0 -9
  116. package/dist/sol/utils/index.js +0 -9
  117. package/dist/sol/utils/lp-inspect.d.ts +0 -129
  118. package/dist/sol/utils/lp-inspect.js +0 -521
  119. package/dist/sol/utils/transfer.d.ts +0 -125
  120. package/dist/sol/utils/transfer.js +0 -220
  121. package/dist/sol/utils/wallet.d.ts +0 -107
  122. package/dist/sol/utils/wallet.js +0 -210
@@ -151,7 +151,6 @@ export class BlockRazorClient {
151
151
  const gasPrice = tx.gasPrice || 0n;
152
152
  const minGasPrice = ethers.parseUnits(String(MIN_GAS_PRICE_GWEI), 'gwei');
153
153
  if (gasPrice < minGasPrice) {
154
- console.warn(`⚠️ 交易 Gas Price (${ethers.formatUnits(gasPrice, 'gwei')} Gwei) 低于最低要求 (${MIN_GAS_PRICE_GWEI} Gwei)`);
155
154
  }
156
155
  }
157
156
  catch {
@@ -412,7 +412,8 @@ async function resolveSellOutputs(params) {
412
412
  try {
413
413
  const result = await trySell('BSC', rpcUrl, tokenAddress, amountsWei[0]);
414
414
  const quotedOutputs = [result.funds];
415
- const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
415
+ // ✅ 已移除滑点保护:minOuts 固定为 0
416
+ const minOuts = [0n];
416
417
  return { minOuts, quotedOutputs };
417
418
  }
418
419
  catch {
@@ -445,7 +446,8 @@ async function resolveSellOutputs(params) {
445
446
  }
446
447
  return 0n;
447
448
  });
448
- const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
449
+ // ✅ 已移除滑点保护:minOuts 固定为 0
450
+ const minOuts = quotedOutputs.map(() => 0n);
449
451
  return { minOuts, quotedOutputs };
450
452
  }
451
453
  catch {
@@ -459,7 +461,8 @@ async function resolveSellOutputs(params) {
459
461
  return 0n;
460
462
  }
461
463
  }));
462
- const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
464
+ // ✅ 已移除滑点保护:minOuts 固定为 0
465
+ const minOuts = quotedOutputs.map(() => 0n);
463
466
  return { minOuts, quotedOutputs };
464
467
  }
465
468
  }
@@ -429,48 +429,40 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
429
429
  if (needApprovalIndexes.length > 0) {
430
430
  throw new Error(`需要授权: ${needApprovalIndexes.length} 个钱包尚未授权。请先完成授权后再卖出。`);
431
431
  }
432
- // ✅ 自动获取报价或使用用户提供的 minOutputAmounts
433
- let minOuts;
432
+ // ✅ 获取报价(用于计算利润),已移除滑点保护
434
433
  let quotedOutputs;
435
- if (params.minOutputAmounts && params.minOutputAmounts.length === sellers.length) {
436
- // 用户提供了 minOutputAmounts,跳过报价查询
437
- minOuts = params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
438
- quotedOutputs = minOuts.map(m => m * 100n / 95n); // 反推预期收益
434
+ // 使用 Multicall3 批量获取报价(仅 V2 路由支持)
435
+ if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
436
+ quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
437
+ }
438
+ else if (routeType === 'v3-single') {
439
+ // V3 单跳:并行调用(V3 Quoter 是 non-view 函数,不支持 Multicall3)
440
+ quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
441
+ try {
442
+ const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
443
+ const result = await quoter.quoteExactInputSingle.staticCall({
444
+ tokenIn: tokenAddress,
445
+ tokenOut: params.v3TokenOut,
446
+ amountIn: amount,
447
+ fee: params.v3Fee,
448
+ sqrtPriceLimitX96: 0
449
+ });
450
+ return result[0];
451
+ }
452
+ catch {
453
+ return 0n;
454
+ }
455
+ }));
456
+ }
457
+ else if (routeType === 'v3-multi' && params.v2Path && params.v2Path.length >= 2) {
458
+ // V3 多跳:使用 V2 备选路由
459
+ quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
439
460
  }
440
461
  else {
441
- // 使用 Multicall3 批量获取报价(仅 V2 路由支持)
442
- if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
443
- quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
444
- }
445
- else if (routeType === 'v3-single') {
446
- // V3 单跳:并行调用(V3 Quoter 是 non-view 函数,不支持 Multicall3)
447
- quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
448
- try {
449
- const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
450
- const result = await quoter.quoteExactInputSingle.staticCall({
451
- tokenIn: tokenAddress,
452
- tokenOut: params.v3TokenOut,
453
- amountIn: amount,
454
- fee: params.v3Fee,
455
- sqrtPriceLimitX96: 0
456
- });
457
- return result[0];
458
- }
459
- catch {
460
- return 0n;
461
- }
462
- }));
463
- }
464
- else if (routeType === 'v3-multi' && params.v2Path && params.v2Path.length >= 2) {
465
- // V3 多跳:使用 V2 备选路由
466
- quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
467
- }
468
- else {
469
- quotedOutputs = new Array(amountsWei.length).fill(0n);
470
- }
471
- // ✅ minOuts = 0,不设置滑点限制(大额交易更稳定)
472
- minOuts = quotedOutputs.map(() => 0n);
462
+ quotedOutputs = new Array(amountsWei.length).fill(0n);
473
463
  }
464
+ // ✅ 已移除滑点保护:minOuts 固定为 0
465
+ const minOuts = new Array(sellers.length).fill(0n);
474
466
  // ✅ 计算利润并找出收益最多的钱包
475
467
  let totalProfit = 0n;
476
468
  let maxRevenueIndex = 0;
@@ -7,7 +7,6 @@ import { CommonBundleConfig } from '../../utils/bundle-helpers.js';
7
7
  import { FourSignConfig } from './types.js';
8
8
  export interface FourBuyFirstSignConfig extends FourSignConfig {
9
9
  reserveGasBNB?: number;
10
- slippageBps?: number;
11
10
  }
12
11
  export interface FourBuyFirstConfig extends CommonBundleConfig {
13
12
  apiKey: string;
@@ -82,8 +82,8 @@ export async function fourBundleBuyFirstMerkle(params) {
82
82
  // ✅ 优化:第三批并行 - trySell、构建交易、获取 nonces
83
83
  const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
84
84
  const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
85
- const slippageBps = config.slippageBps ?? 100;
86
- const minBuyAmount = (estimatedTokenAmount * BigInt(10000 - slippageBps)) / 10000n;
85
+ // 已移除滑点保护:minBuyAmount 固定为 0
86
+ const minBuyAmount = 0n;
87
87
  // 预先规划 nonces
88
88
  const extractProfit = true;
89
89
  const profitRateBps = getProfitRateBps();
@@ -121,7 +121,8 @@ export async function fourBundleBuyFirstMerkle(params) {
121
121
  ]);
122
122
  const { buyerNonces, sellerNonces } = noncesResult;
123
123
  const estimatedSellFunds = sellResult.funds;
124
- const minSellFunds = (estimatedSellFunds * BigInt(10000 - slippageBps)) / 10000n;
124
+ // ✅ 已移除滑点保护:minSellFunds 固定为 0
125
+ const minSellFunds = 0n;
125
126
  const profitAmount = extractProfit ? (estimatedSellFunds * BigInt(profitRateBps)) / 10000n : 0n;
126
127
  // 更新卖出交易的 minSellFunds
127
128
  sellUnsigned.data = tmSeller.interface.encodeFunctionData('sellToken', [
@@ -29,7 +29,6 @@ export interface FourBundleSwapSignParams {
29
29
  sellPercentage?: number;
30
30
  buyerPrivateKey: string;
31
31
  tokenAddress: string;
32
- slippageTolerance?: number;
33
32
  config: FourSwapSignConfig;
34
33
  }
35
34
  export interface FourBundleSwapParams {
@@ -38,7 +37,6 @@ export interface FourBundleSwapParams {
38
37
  sellPercentage?: number;
39
38
  buyerPrivateKey: string;
40
39
  tokenAddress: string;
41
- slippageTolerance?: number;
42
40
  config: FourSwapConfig;
43
41
  }
44
42
  /**
@@ -69,7 +67,6 @@ export interface FourBatchSwapSignParams {
69
67
  buyerPrivateKeys: string[];
70
68
  buyerRatios?: number[];
71
69
  tokenAddress: string;
72
- slippageTolerance?: number;
73
70
  config: FourSwapSignConfig;
74
71
  }
75
72
  /**
@@ -12,7 +12,7 @@ import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
12
12
  * Four内盘捆绑换手
13
13
  */
14
14
  export async function fourBundleSwapMerkle(params) {
15
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, slippageTolerance = 0.5, config } = params;
15
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config } = params;
16
16
  const chainIdNum = config.chainId ?? 56;
17
17
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
18
18
  chainId: chainIdNum,
@@ -185,7 +185,7 @@ export async function fourBundleSwapMerkle(params) {
185
185
  * 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
186
186
  */
187
187
  export async function fourBatchSwapMerkle(params) {
188
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, slippageTolerance = 0.5, config } = params;
188
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config } = params;
189
189
  // ✅ 校验买方数量(最多 24 个)
190
190
  const MAX_BUYERS = 24;
191
191
  if (buyerPrivateKeys.length === 0) {
@@ -628,12 +628,8 @@ async function quoteSellOutputsWithQuote(portal, tokenAddress, amountsWei, outpu
628
628
  }));
629
629
  }
630
630
  }
631
- function resolveMinOutputs(provided, walletCount, _quotedOutputs) {
632
- if (provided && provided.length === walletCount) {
633
- return provided.map(m => typeof m === 'string' ? ethers.parseEther(m) : BigInt(m));
634
- }
635
- // ✅ 默认 minOutput = 0,不设置滑点限制
636
- // 原因:大额交易时 5% 滑点可能不够,导致交易失败
631
+ function resolveMinOutputs(_provided, walletCount, _quotedOutputs) {
632
+ // 已移除滑点保护:minOutput 固定为 0
637
633
  return Array(walletCount).fill(0n);
638
634
  }
639
635
  // ✅ appendSellProfitTransaction 已内联到 batchSellWithBundleMerkle 中,避免 nonce 竞争问题
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ECDH + AES-GCM 加密工具(浏览器兼容)
3
+ * 用于将签名交易用服务器公钥加密
4
+ */
5
+ /**
6
+ * 用服务器公钥加密签名交易(ECDH + AES-GCM)
7
+ *
8
+ * @param signedTransactions 签名后的交易数组
9
+ * @param publicKeyBase64 服务器提供的公钥(Base64 格式)
10
+ * @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
11
+ */
12
+ export declare function encryptWithPublicKey(signedTransactions: string[], publicKeyBase64: string): Promise<string>;
13
+ /**
14
+ * 验证公钥格式(Base64)
15
+ */
16
+ export declare function validatePublicKey(publicKeyBase64: string): boolean;
@@ -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
+ }
@@ -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);
@@ -350,10 +371,18 @@ export async function pancakeProxyBatchSellMerkle(params) {
350
371
  const finalGasLimit = getGasLimit(config);
351
372
  const extractProfit = shouldExtractProfit(config);
352
373
  const nonceManager = new NonceManager(provider);
374
+ // ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
375
+ const presetGasPrice = config.gasPrice;
376
+ const presetNonces = config.nonces;
353
377
  // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
354
378
  const [gasPrice, tokenDecimals] = await Promise.all([
355
- getOptimizedGasPrice(provider, getGasPriceConfig(config)),
379
+ // gasPrice:优先使用前端传入的
380
+ presetGasPrice !== undefined
381
+ ? Promise.resolve(presetGasPrice)
382
+ : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
383
+ // tokenDecimals:有缓存
356
384
  getTokenDecimals(tokenAddress, provider),
385
+ // allowances:必须检查
357
386
  ensureAllowances({
358
387
  provider,
359
388
  tokenAddress,
@@ -362,8 +391,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
362
391
  })
363
392
  ]);
364
393
  const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
365
- // ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
366
394
  const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
395
+ // 获取报价(用于计算利润)
367
396
  const { minOuts, quotedOutputs } = await resolveSellOutputs({
368
397
  params,
369
398
  provider,
@@ -392,7 +421,12 @@ export async function pancakeProxyBatchSellMerkle(params) {
392
421
  const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
393
422
  let nonces;
394
423
  let profitNonce;
395
- if (needProfitTx) {
424
+ // 优化:如果前端传入了 nonces,直接使用
425
+ if (presetNonces && presetNonces.length === sellers.length) {
426
+ nonces = presetNonces;
427
+ profitNonce = needProfitTx ? presetNonces[maxRevenueIndex] + 1 : undefined;
428
+ }
429
+ else if (needProfitTx) {
396
430
  // maxRevenueIndex 钱包需要 2 个连续 nonce(卖出 + 利润)
397
431
  const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
398
432
  // 其他钱包各需要 1 个 nonce
@@ -461,6 +495,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
461
495
  // ✅ Provider 缓存(复用连接,减少初始化开销)
462
496
  const providerCache = new Map();
463
497
  const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
498
+ // ✅ Token Decimals 缓存(代币精度不会变化)
499
+ const tokenDecimalsCache = new Map();
464
500
  function createChainContext(chain, rpcUrl) {
465
501
  const chainId = CHAIN_ID_MAP[chain];
466
502
  const cacheKey = `${chain}-${rpcUrl}`;
@@ -492,10 +528,8 @@ function findMaxAmountIndex(amounts) {
492
528
  }
493
529
  return maxIndex;
494
530
  }
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
- }
531
+ function resolveBuyMinOutputs(_params, walletCount, _tokenDecimals) {
532
+ // 已移除滑点保护:minOutput 固定为 0
499
533
  return new Array(walletCount).fill(0n);
500
534
  }
501
535
  function createPancakeProxies(wallets, proxyAddress) {
@@ -576,26 +610,18 @@ async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
576
610
  }
577
611
  }
578
612
  /**
579
- * ✅ 使用 Multicall3 批量获取卖出报价(单次 RPC)
613
+ * ✅ 获取卖出报价(用于计算利润,不用于滑点保护)
614
+ * ✅ 已移除滑点保护:minOutput 固定为 0
580
615
  */
581
616
  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
- }
617
+ // 已移除滑点保护:minOutput 固定为 0
618
+ const minOuts = new Array(amountsWei.length).fill(0n);
590
619
  // 如果只有 1 个,直接调用(避免 multicall 开销)
591
620
  if (amountsWei.length === 1) {
592
621
  const quotedOutput = await getSingleQuote(params, provider, tokenAddress, routeType, amountsWei[0]);
593
- return {
594
- quotedOutputs: [quotedOutput],
595
- minOuts: [0n] // ✅ minOutput = 0,不设置滑点限制
596
- };
622
+ return { quotedOutputs: [quotedOutput], minOuts };
597
623
  }
598
- // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
624
+ // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持,用于计算利润)
599
625
  if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
600
626
  try {
601
627
  const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
@@ -619,10 +645,7 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
619
645
  }
620
646
  return 0n;
621
647
  });
622
- return {
623
- quotedOutputs,
624
- minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
625
- };
648
+ return { quotedOutputs, minOuts };
626
649
  }
627
650
  catch {
628
651
  // Multicall 失败,回退到并行调用
@@ -630,10 +653,7 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
630
653
  }
631
654
  // 回退:并行调用(V3 路由或 Multicall 失败时)
632
655
  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
- };
656
+ return { quotedOutputs, minOuts };
637
657
  }
638
658
  /**
639
659
  * 获取单个报价
@@ -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;
@@ -124,12 +124,11 @@ export async function flapBundleBuyFirstMerkle(params) {
124
124
  ]);
125
125
  const { buyerFundsWei, buyerBalance } = buyerFundsResult;
126
126
  const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
127
- // ✅ 获取报价
127
+ // ✅ 获取报价(已移除滑点保护)
128
128
  const quoteResult = await quoteBuyerOutput({
129
129
  portalAddress: chainContext.portalAddress,
130
130
  tokenAddress,
131
131
  buyerFundsWei,
132
- slippageBps: config.slippageBps,
133
132
  provider: chainContext.provider,
134
133
  skipQuoteOnError: config.skipQuoteOnError,
135
134
  inputToken // ✅ 传递输入代币
@@ -300,35 +299,31 @@ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, re
300
299
  }
301
300
  return { buyerFundsWei, buyerBalance };
302
301
  }
303
- async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, slippageBps, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
302
+ async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
304
303
  }) {
305
304
  let quotedToken = 0n;
306
- let minOutToken = 0n;
307
305
  const portal = new Contract(portalAddress, PORTAL_ABI, provider);
308
- const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
309
306
  try {
310
307
  quotedToken = await portal.quoteExactInput.staticCall({
311
308
  inputToken, // ✅ 使用动态输入代币
312
309
  outputToken: tokenAddress,
313
310
  inputAmount: buyerFundsWei
314
311
  });
315
- const keep = BigInt(10000 - safeSlippage);
316
- minOutToken = (quotedToken * keep) / 10000n;
317
312
  }
318
313
  catch (error) {
319
314
  if (skipQuoteOnError ?? true) {
320
315
  quotedToken = 0n;
321
- minOutToken = 0n;
322
316
  }
323
317
  else {
324
318
  throw new Error(`买入报价失败: ${error}`);
325
319
  }
326
320
  }
327
- const sellAmountWei = minOutToken > 0n ? minOutToken : quotedToken;
321
+ // 已移除滑点保护:minOutToken 固定为 0
322
+ const sellAmountWei = quotedToken;
328
323
  if (sellAmountWei <= 0n) {
329
- throw new Error('卖方卖出数量为 0:报价失败或滑点过高');
324
+ throw new Error('卖方卖出数量为 0:报价失败');
330
325
  }
331
- return { quotedToken, minOutToken, sellAmountWei };
326
+ return { quotedToken, minOutToken: 0n, sellAmountWei };
332
327
  }
333
328
  async function ensureSellerBalance({ tokenAddress, provider, seller, sellAmountWei, skipBalanceCheck }) {
334
329
  const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, provider);
@@ -13,7 +13,6 @@ export interface FlapSwapSignConfig {
13
13
  txType?: 0 | 2;
14
14
  chainId?: number;
15
15
  reserveGasETH?: number;
16
- slippageBps?: number;
17
16
  skipQuoteOnError?: boolean;
18
17
  skipApprovalCheck?: boolean;
19
18
  }
@@ -23,7 +22,6 @@ export interface FlapSwapConfig extends CommonBundleConfig {
23
22
  customRpcUrl?: string;
24
23
  bundleBlockOffset?: number;
25
24
  reserveGasETH?: number;
26
- slippageBps?: number;
27
25
  skipQuoteOnError?: boolean;
28
26
  waitForConfirmation?: boolean;
29
27
  waitTimeoutMs?: number;