four-flap-meme-sdk 1.4.21 → 1.4.24

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.
@@ -31,6 +31,7 @@ export interface FourBundleSwapSignParams {
31
31
  buyerPrivateKey: string;
32
32
  tokenAddress: string;
33
33
  config: FourSwapSignConfig;
34
+ startNonces?: number[];
34
35
  }
35
36
  export interface FourBundleSwapParams {
36
37
  sellerPrivateKey: string;
@@ -69,6 +70,7 @@ export interface FourBatchSwapSignParams {
69
70
  buyerRatios?: number[];
70
71
  tokenAddress: string;
71
72
  config: FourSwapSignConfig;
73
+ startNonces?: number[];
72
74
  }
73
75
  /**
74
76
  * Four 批量换手结果
@@ -103,6 +105,7 @@ export interface FourQuickBatchSwapSignParams {
103
105
  buyerAmounts?: string[];
104
106
  tokenAddress: string;
105
107
  config: FourSwapSignConfig;
108
+ startNonces?: number[];
106
109
  }
107
110
  /**
108
111
  * Four 快捷批量换手结果
@@ -15,7 +15,8 @@ const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
15
15
  * Four内盘捆绑换手
16
16
  */
17
17
  export async function fourBundleSwapMerkle(params) {
18
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config } = params;
18
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config, startNonces // ✅ 可选:前端预获取的 nonces
19
+ } = params;
19
20
  const chainIdNum = config.chainId ?? 56;
20
21
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
21
22
  chainId: chainIdNum,
@@ -83,21 +84,21 @@ export async function fourBundleSwapMerkle(params) {
83
84
  if (extractProfit)
84
85
  sellerNonceCount++; // 利润交易
85
86
  // ✅ 优化:使用批量 nonce 获取(JSON-RPC 批量请求)
86
- // 先一次性获取 seller 和 buyer 的初始 nonce,然后分配连续 nonce
87
+ // 如果前端传入了 startNonces,直接使用;否则从链上获取
87
88
  const [sellUnsigned, buyUnsigned, noncesResult] = await Promise.all([
88
89
  tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmountWei, 0n),
89
90
  tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyerFunds, 0n, { value: buyerFunds }),
90
91
  (async () => {
91
- // ✅ 一次性获取两个地址的初始 nonce(单次网络往返)
92
+ // ✅ 如果前端传入了 startNonces,直接使用(性能优化)
93
+ if (startNonces && startNonces.length >= 2) {
94
+ const sellerNonces = Array.from({ length: sellerNonceCount }, (_, i) => startNonces[0] + i);
95
+ const buyerNonce = startNonces[1];
96
+ return { sellerNonces, buyerNonce };
97
+ }
98
+ // 否则从链上获取
92
99
  const initialNonces = await nonceManager.getNextNoncesForWallets([seller, buyer]);
93
- // seller 需要 sellerNonceCount 个连续 nonce
94
100
  const sellerNonces = Array.from({ length: sellerNonceCount }, (_, i) => initialNonces[0] + i);
95
- // buyer 只需要 1 个 nonce
96
101
  const buyerNonce = initialNonces[1];
97
- // 更新 NonceManager 缓存(seller 需要额外的 nonce)
98
- for (let i = 1; i < sellerNonceCount; i++) {
99
- await nonceManager.getNextNonce(seller); // 递增缓存
100
- }
101
102
  return { sellerNonces, buyerNonce };
102
103
  })()
103
104
  ]);
@@ -213,7 +214,8 @@ export async function fourBundleSwapMerkle(params) {
213
214
  * 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
214
215
  */
215
216
  export async function fourBatchSwapMerkle(params) {
216
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config } = params;
217
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config, startNonces // ✅ 可选:前端预获取的 nonces
218
+ } = params;
217
219
  // ✅ 校验买方数量(最多 24 个)
218
220
  const MAX_BUYERS = 24;
219
221
  if (buyerPrivateKeys.length === 0) {
@@ -309,9 +311,12 @@ export async function fourBatchSwapMerkle(params) {
309
311
  if (needApproval)
310
312
  sellerNonceCount++; // 授权交易
311
313
  sellerNonceCount++; // 利润交易
312
- // 并行获取所有初始 nonce
314
+ // 如果前端传入了 startNonces,直接使用(性能优化)
315
+ // 否则从链上获取
313
316
  const allWallets = [seller, ...buyers];
314
- const initialNonces = await nonceManager.getNextNoncesForWallets(allWallets);
317
+ const initialNonces = startNonces && startNonces.length >= allWallets.length
318
+ ? startNonces
319
+ : await nonceManager.getNextNoncesForWallets(allWallets);
315
320
  // 分配 seller nonces
316
321
  const sellerNonces = Array.from({ length: sellerNonceCount }, (_, i) => initialNonces[0] + i);
317
322
  let idx = 0;
@@ -438,7 +443,8 @@ export async function fourBatchSwapMerkle(params) {
438
443
  * 限制:最多 23 个买方(2N + 3 ≤ 50)
439
444
  */
440
445
  export async function fourQuickBatchSwapMerkle(params) {
441
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config } = params;
446
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config, startNonces // ✅ 可选:前端预获取的 nonces
447
+ } = params;
442
448
  // ✅ 校验买方数量
443
449
  // 贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23
444
450
  const MAX_BUYERS = 23;
@@ -525,7 +531,10 @@ export async function fourQuickBatchSwapMerkle(params) {
525
531
  throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`);
526
532
  }
527
533
  // ==================== 规划 Nonce ====================
528
- let sellerNonce = await nonceManager.getNextNonce(seller);
534
+ // 如果前端传入了 startNonces,直接使用(性能优化)
535
+ let sellerNonce = startNonces && startNonces.length > 0
536
+ ? startNonces[0]
537
+ : await nonceManager.getNextNonce(seller);
529
538
  // ==================== 1. 贿赂交易 ====================
530
539
  let bribeTx = null;
531
540
  if (bribeAmount > 0n) {
@@ -573,7 +582,10 @@ export async function fourQuickBatchSwapMerkle(params) {
573
582
  }));
574
583
  console.log(`[fourQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
575
584
  // ==================== 4. 买入交易 ====================
576
- const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
585
+ // 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
586
+ const buyerNonces = startNonces && startNonces.length > 1
587
+ ? startNonces.slice(1)
588
+ : await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
577
589
  const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
578
590
  const buyAmount = transferAmountsWei[i];
579
591
  const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
@@ -236,6 +236,10 @@ export type DisperseMerkleParams = {
236
236
  config: FourBundleMerkleConfig;
237
237
  };
238
238
  /** ✅ 分发参数(仅签名版本 - 精简) */
239
+ /** 代币池子类型 */
240
+ export type TokenPoolType = 'flap' | 'four' | 'v2' | 'v3';
241
+ /** 报价代币类型(池子的计价代币) */
242
+ export type QuoteTokenType = 'native' | 'usdt' | 'usdc';
239
243
  export type DisperseSignParams = {
240
244
  fromPrivateKey: string;
241
245
  recipients: string[];
@@ -254,6 +258,10 @@ export type DisperseSignParams = {
254
258
  config: FourSignConfig;
255
259
  /** ✅ 新增:起始 nonce(用于并行调用时避免 nonce 冲突) */
256
260
  startNonce?: number;
261
+ /** ✅ 新增:代币池子类型(用于利润报价),默认 'v2' */
262
+ tokenPoolType?: TokenPoolType;
263
+ /** ✅ 新增:报价代币类型(池子的计价代币),默认 'native'(BNB/MON) */
264
+ quoteToken?: QuoteTokenType;
257
265
  };
258
266
  /**
259
267
  * ✅ 分发结果(简化版)
@@ -305,6 +313,10 @@ export type SweepSignParams = {
305
313
  config: FourSignConfig;
306
314
  /** ✅ 新增:起始 nonce(用于并行调用时避免 nonce 冲突,仅对第一个源钱包生效) */
307
315
  startNonce?: number;
316
+ /** ✅ 新增:代币池子类型(用于利润报价),默认 'v2' */
317
+ tokenPoolType?: TokenPoolType;
318
+ /** ✅ 新增:报价代币类型(池子的计价代币),默认 'native'(BNB/MON) */
319
+ quoteToken?: QuoteTokenType;
308
320
  };
309
321
  /**
310
322
  * ✅ 归集结果(简化版)
@@ -1,5 +1,4 @@
1
- import { ethers, Wallet, Contract } from 'ethers';
2
- // import { MerkleClient } from '../../clients/merkle.js';
1
+ import { ethers, Wallet } from 'ethers';
3
2
  import { getOptimizedGasPrice, NonceManager } from '../../utils/bundle-helpers.js';
4
3
  import { PROFIT_CONFIG } from '../../utils/constants.js';
5
4
  import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient } from './config.js';
@@ -15,46 +14,182 @@ function calculateProfit(amount) {
15
14
  return { profit, remaining };
16
15
  }
17
16
  // ==================== ERC20 → 原生代币报价 ====================
18
- // BSC 链常量
19
- const BSC_PANCAKE_V2_ROUTER = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
20
- const BSC_WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
21
- // Monad 链常量
22
- const MONAD_PANCAKE_V2_ROUTER = '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9';
23
- const MONAD_WMON = '0x3bd359c1119da7da1d913d1c4d2b7c461115433a';
24
- const ROUTER_ABI = [
25
- 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
26
- ];
17
+ import { quote, QUOTE_CONFIG } from '../../utils/quote-helpers.js';
18
+ import { Helper3 } from '../helper3.js';
19
+ import { FlapPortal } from '../../flap/portal.js';
20
+ /** 最低利润(Wei):无法获取报价时使用,0.0001 BNB */
21
+ const MIN_PROFIT_WEI = 100000000000000n; // 0.0001 BNB = 10^14 wei
22
+ /** ID → 链名称 映射 */
23
+ const CHAIN_ID_TO_NAME = {
24
+ 56: 'BSC',
25
+ 143: 'MONAD',
26
+ 196: 'XLAYER',
27
+ };
28
+ /** 各链的稳定币地址 */
29
+ const STABLE_COINS = {
30
+ BSC: {
31
+ usdt: '0x55d398326f99059fF775485246999027B3197955',
32
+ usdc: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d',
33
+ },
34
+ MONAD: {
35
+ // TODO: 添加 Monad 链的稳定币地址
36
+ },
37
+ XLAYER: {
38
+ usdt: '0x1E4a5963aBFD975d8c9021ce480b42188849D41d',
39
+ },
40
+ };
41
+ /**
42
+ * 获取 FOUR 内盘代币 → BNB 的报价
43
+ */
44
+ async function getFourInnerQuote(rpcUrl, tokenAddress, tokenAmount) {
45
+ try {
46
+ const helper = Helper3.connectByChain('BSC', rpcUrl);
47
+ const result = await helper.trySell(tokenAddress, tokenAmount);
48
+ // result = { tokenManager, quote, funds, fee }
49
+ // funds 是卖出代币能获得的 BNB 数量
50
+ const funds = result.funds;
51
+ console.log(`[getFourInnerQuote] FOUR 内盘报价成功: ${funds} wei`);
52
+ return funds;
53
+ }
54
+ catch (error) {
55
+ console.warn(`[getFourInnerQuote] FOUR 内盘报价失败:`, error);
56
+ return 0n;
57
+ }
58
+ }
59
+ /** 零地址(原生代币) */
60
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
61
+ /**
62
+ * 获取 FLAP 内盘代币 → 原生代币的报价
63
+ * ✅ 与 portal-bundle-merkle/core.ts 使用相同的 quoteExactInput 方法
64
+ */
65
+ async function getFlapInnerQuote(rpcUrl, chainId, tokenAddress, tokenAmount) {
66
+ try {
67
+ // 根据链 ID 确定链名称
68
+ const chainName = chainId === 56 ? 'BSC' : chainId === 143 ? 'MONAD' : chainId === 196 ? 'XLAYER' : null;
69
+ if (!chainName) {
70
+ console.warn(`[getFlapInnerQuote] 不支持的链 ID: ${chainId}`);
71
+ return 0n;
72
+ }
73
+ const portal = new FlapPortal({ chain: chainName, rpcUrl });
74
+ // ✅ 使用 quoteExactInput 与 core.ts 保持一致
75
+ const funds = await portal.quoteExactInput({
76
+ inputToken: tokenAddress,
77
+ outputToken: ZERO_ADDRESS, // 输出原生代币
78
+ inputAmount: tokenAmount
79
+ });
80
+ console.log(`[getFlapInnerQuote] FLAP 内盘报价成功: ${funds} wei`);
81
+ return funds;
82
+ }
83
+ catch (error) {
84
+ console.warn(`[getFlapInnerQuote] FLAP 内盘报价失败:`, error);
85
+ return 0n;
86
+ }
87
+ }
27
88
  /**
28
89
  * 获取 ERC20 代币 → 原生代币的报价
90
+ *
29
91
  * @param provider - Provider 实例
30
92
  * @param tokenAddress - ERC20 代币地址
31
93
  * @param tokenAmount - 代币数量(wei)
32
94
  * @param chainId - 链 ID(56=BSC, 143=Monad)
33
- * @returns 等值的原生代币数量(wei),失败返回 0n
95
+ * @param poolType - 池子类型:'flap'/'four' 内盘,'v2'/'v3' 外盘
96
+ * @param quoteToken - 报价代币:'native'(BNB/MON),'usdt','usdc'
97
+ * @param rpcUrl - RPC URL(内盘报价需要)
98
+ * @returns 等值的原生代币数量(wei),失败返回 MIN_PROFIT_WEI
34
99
  */
35
- async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
100
+ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId, poolType = 'v2', quoteToken = 'native', rpcUrl) {
36
101
  if (tokenAmount <= 0n)
37
- return 0n;
102
+ return MIN_PROFIT_WEI;
103
+ // ✅ FOUR 内盘:通过 Helper3.trySell 获取真实报价
104
+ if (poolType === 'four') {
105
+ const url = rpcUrl || provider._getConnection?.()?.url || '';
106
+ if (!url) {
107
+ console.warn(`[getTokenToNativeQuote] FOUR 内盘需要 rpcUrl,使用最低利润`);
108
+ return MIN_PROFIT_WEI;
109
+ }
110
+ const funds = await getFourInnerQuote(url, tokenAddress, tokenAmount);
111
+ return funds > 0n ? funds : MIN_PROFIT_WEI;
112
+ }
113
+ // ✅ FLAP 内盘:通过 FlapPortal.previewSell 获取真实报价
114
+ if (poolType === 'flap') {
115
+ const url = rpcUrl || provider._getConnection?.()?.url || '';
116
+ if (!url) {
117
+ console.warn(`[getTokenToNativeQuote] FLAP 内盘需要 rpcUrl,使用最低利润`);
118
+ return MIN_PROFIT_WEI;
119
+ }
120
+ const funds = await getFlapInnerQuote(url, chainId, tokenAddress, tokenAmount);
121
+ return funds > 0n ? funds : MIN_PROFIT_WEI;
122
+ }
123
+ const chainName = CHAIN_ID_TO_NAME[chainId];
124
+ if (!chainName) {
125
+ console.warn(`[getTokenToNativeQuote] 不支持的链 ID: ${chainId},使用最低利润`);
126
+ return MIN_PROFIT_WEI;
127
+ }
128
+ const chainConfig = QUOTE_CONFIG[chainName];
129
+ if (!chainConfig) {
130
+ console.warn(`[getTokenToNativeQuote] 不支持的链: ${chainName},使用最低利润`);
131
+ return MIN_PROFIT_WEI;
132
+ }
38
133
  try {
39
- if (chainId === 56) {
40
- // BSC: 使用 PancakeSwap V2 Router
41
- const router = new Contract(BSC_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
42
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, BSC_WBNB]);
43
- return amounts[1];
134
+ const version = poolType === 'v3' ? 'v3' : 'v2';
135
+ let nativeAmount = 0n;
136
+ if (quoteToken === 'native') {
137
+ // 直接路径:TOKEN WBNB
138
+ console.log(`[getTokenToNativeQuote] 使用 ${version} 直接路径报价 (TOKEN → WBNB)`);
139
+ const result = await quote({
140
+ provider,
141
+ tokenIn: tokenAddress,
142
+ tokenOut: chainConfig.wrappedNative,
143
+ amountIn: tokenAmount,
144
+ chain: chainName,
145
+ version
146
+ });
147
+ nativeAmount = result.amountOut;
44
148
  }
45
- if (chainId === 143) {
46
- // ✅ Monad: 使用 PancakeSwap V2 Router
47
- const router = new Contract(MONAD_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
48
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, MONAD_WMON]);
49
- return amounts[1];
149
+ else {
150
+ // ✅ 稳定币路径:TOKEN USDT/USDC WBNB(两步报价)
151
+ const stableCoins = STABLE_COINS[chainName];
152
+ const stableCoinAddress = stableCoins?.[quoteToken];
153
+ if (!stableCoinAddress) {
154
+ console.warn(`[getTokenToNativeQuote] 链 ${chainName} 不支持 ${quoteToken},使用最低利润`);
155
+ return MIN_PROFIT_WEI;
156
+ }
157
+ console.log(`[getTokenToNativeQuote] 使用 ${version} 稳定币路径报价 (TOKEN → ${quoteToken.toUpperCase()} → WBNB)`);
158
+ // 第一步:TOKEN → USDT/USDC
159
+ const step1 = await quote({
160
+ provider,
161
+ tokenIn: tokenAddress,
162
+ tokenOut: stableCoinAddress,
163
+ amountIn: tokenAmount,
164
+ chain: chainName,
165
+ version
166
+ });
167
+ if (step1.amountOut <= 0n) {
168
+ console.warn(`[getTokenToNativeQuote] TOKEN → ${quoteToken.toUpperCase()} 报价失败`);
169
+ return MIN_PROFIT_WEI;
170
+ }
171
+ // 第二步:USDT/USDC → WBNB
172
+ const step2 = await quote({
173
+ provider,
174
+ tokenIn: stableCoinAddress,
175
+ tokenOut: chainConfig.wrappedNative,
176
+ amountIn: step1.amountOut,
177
+ chain: chainName,
178
+ version
179
+ });
180
+ nativeAmount = step2.amountOut;
181
+ }
182
+ if (nativeAmount > 0n) {
183
+ console.log(`[getTokenToNativeQuote] 报价成功: ${nativeAmount} wei`);
184
+ return nativeAmount;
50
185
  }
51
- // 其他链暂不支持报价,返回 0
52
- return 0n;
53
186
  }
54
- catch {
55
- // 报价失败返回 0(可能是流动性不足或路径不存在)
56
- return 0n;
187
+ catch (error) {
188
+ console.warn(`[getTokenToNativeQuote] 报价失败:`, error);
57
189
  }
190
+ // ✅ 报价失败,返回最低利润(0.0001 BNB)
191
+ console.log(`[getTokenToNativeQuote] 无法获取报价,使用最低利润: ${MIN_PROFIT_WEI}`);
192
+ return MIN_PROFIT_WEI;
58
193
  }
59
194
  /**
60
195
  * 分发(仅签名版本 - 不依赖 Merkle)
@@ -62,7 +197,7 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainI
62
197
  * ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突
63
198
  */
64
199
  export async function disperseWithBundleMerkle(params) {
65
- const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce } = params;
200
+ const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native' } = params;
66
201
  // 快速返回空结果
67
202
  if (!recipients || recipients.length === 0) {
68
203
  return {
@@ -184,7 +319,7 @@ export async function disperseWithBundleMerkle(params) {
184
319
  // ✅ 获取 ERC20 利润等值的原生代币(BNB)报价
185
320
  let nativeProfitAmount = 0n;
186
321
  if (extractProfit && totalTokenProfit > 0n) {
187
- nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
322
+ nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
188
323
  totalProfit = nativeProfitAmount; // 更新为原生代币利润
189
324
  }
190
325
  // ✅ 并行签名所有交易
@@ -368,7 +503,7 @@ export async function disperseWithBundleMerkle(params) {
368
503
  }
369
504
  // ✅ ERC20 多跳:获取代币利润等值的原生代币报价
370
505
  if (!isNative && extractProfit && totalTokenProfit > 0n) {
371
- const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
506
+ const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
372
507
  totalProfit = nativeProfitAmount; // 更新为原生代币利润
373
508
  }
374
509
  // 利润转账(转等值原生代币)
@@ -411,7 +546,7 @@ export async function disperseWithBundleMerkle(params) {
411
546
  * ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突
412
547
  */
413
548
  export async function sweepWithBundleMerkle(params) {
414
- const { sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce } = params;
549
+ const { sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native' } = params;
415
550
  // 快速返回空结果
416
551
  if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) {
417
552
  return {
@@ -684,7 +819,7 @@ export async function sweepWithBundleMerkle(params) {
684
819
  // ✅ ERC20 归集:获取代币利润等值的原生代币(BNB)报价
685
820
  let nativeProfitAmount = 0n;
686
821
  if (extractProfit && totalProfit > 0n) {
687
- nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum);
822
+ nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
688
823
  }
689
824
  // ✅ 如果需要提取利润,检查支付者是否有足够 BNB 支付利润转账的额外 gas
690
825
  if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && config.checkBnbForErc20NoHop) {
@@ -1036,7 +1171,7 @@ export async function sweepWithBundleMerkle(params) {
1036
1171
  // ✅ ERC20 多跳归集:获取代币利润等值的原生代币报价
1037
1172
  let nativeProfitAmount = isNative ? totalProfit : 0n;
1038
1173
  if (!isNative && totalTokenProfit > 0n) {
1039
- nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
1174
+ nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
1040
1175
  totalProfit = nativeProfitAmount; // 更新为原生代币利润
1041
1176
  }
1042
1177
  // 由归集金额最大的钱包支付利润(转等值原生代币)
@@ -38,6 +38,7 @@ export interface FlapBundleSwapSignParams {
38
38
  config: FlapSwapSignConfig;
39
39
  quoteToken?: string;
40
40
  quoteTokenDecimals?: number;
41
+ startNonces?: number[];
41
42
  }
42
43
  export interface FlapBundleSwapParams {
43
44
  chain: FlapChain;
@@ -79,6 +80,7 @@ export interface FlapBatchSwapSignParams {
79
80
  config: FlapSwapSignConfig;
80
81
  quoteToken?: string;
81
82
  quoteTokenDecimals?: number;
83
+ startNonces?: number[];
82
84
  }
83
85
  /**
84
86
  * Flap 批量换手结果
@@ -116,6 +118,7 @@ export interface FlapQuickBatchSwapSignParams {
116
118
  config: FlapSwapSignConfig;
117
119
  quoteToken?: string;
118
120
  quoteTokenDecimals?: number;
121
+ startNonces?: number[];
119
122
  }
120
123
  /**
121
124
  * Flap 快捷批量换手结果
@@ -99,7 +99,8 @@ function getNativeTokenName(chain) {
99
99
  * ✅ 支持 quoteToken:传入 USDT 等地址时,卖出得到该代币,买入使用该代币
100
100
  */
101
101
  export async function flapBundleSwapMerkle(params) {
102
- const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config, quoteToken, quoteTokenDecimals } = params;
102
+ const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config, quoteToken, quoteTokenDecimals, startNonces // ✅ 可选:前端预获取的 nonces
103
+ } = params;
103
104
  const chainContext = createChainContext(chain, config);
104
105
  const seller = new Wallet(sellerPrivateKey, chainContext.provider);
105
106
  const buyer = new Wallet(buyerPrivateKey, chainContext.provider);
@@ -179,14 +180,17 @@ export async function flapBundleSwapMerkle(params) {
179
180
  permitData: '0x'
180
181
  }, useNativeToken ? { value: buyerNeed.maxBuyerValue } : {} // ✅ ERC20 购买时 value=0
181
182
  ),
182
- planNonces({
183
- seller,
184
- buyer,
185
- approvalExists: !!approvalTx,
186
- extractProfit: nativeProfitAmount > 0n,
187
- needBribeTx, // ✅ 新增:是否需要贿赂交易
188
- nonceManager
189
- }),
183
+ // ✅ 如果前端传入了 startNonces,直接使用(性能优化)
184
+ startNonces && startNonces.length >= 2
185
+ ? buildNoncePlan(startNonces, !!approvalTx, nativeProfitAmount > 0n, needBribeTx)
186
+ : planNonces({
187
+ seller,
188
+ buyer,
189
+ approvalExists: !!approvalTx,
190
+ extractProfit: nativeProfitAmount > 0n,
191
+ needBribeTx,
192
+ nonceManager
193
+ }),
190
194
  validateBalances({
191
195
  buyerNeed,
192
196
  buyerAddress: buyer.address,
@@ -393,6 +397,24 @@ async function validateBalances({ buyerNeed, buyerAddress, portalGasCost, provid
393
397
  throw new Error(`卖方余额不足: 需要 ${ethers.formatEther(portalGasCost)} ${chainContext.nativeToken} (Gas),实际 ${ethers.formatEther(sellerBalance)} ${chainContext.nativeToken}`);
394
398
  }
395
399
  }
400
+ /**
401
+ * ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化)
402
+ * 顺序:[sellerBaseNonce, buyerNonce]
403
+ */
404
+ function buildNoncePlan(startNonces, approvalExists, extractProfit, needBribeTx) {
405
+ let sellerNonce = startNonces[0];
406
+ const buyerNonce = startNonces[1];
407
+ let bribeNonce;
408
+ let profitNonce;
409
+ if (needBribeTx)
410
+ bribeNonce = sellerNonce++;
411
+ if (approvalExists)
412
+ sellerNonce++; // 授权交易占用一个 nonce
413
+ const sellNonce = sellerNonce++;
414
+ if (extractProfit)
415
+ profitNonce = sellerNonce;
416
+ return { sellerNonce: sellNonce, buyerNonce, bribeNonce, profitNonce };
417
+ }
396
418
  /**
397
419
  * ✅ 优化:使用批量 nonce 获取(JSON-RPC 批量请求)
398
420
  * 交易顺序:贿赂 → 授权 → 卖出 → 买入 → 利润
@@ -470,7 +492,8 @@ function countTruthy(values) {
470
492
  * 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
471
493
  */
472
494
  export async function flapBatchSwapMerkle(params) {
473
- const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config, quoteToken, quoteTokenDecimals = 18 } = params;
495
+ const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
496
+ } = params;
474
497
  // ✅ 校验买方数量(最多 24 个)
475
498
  const MAX_BUYERS = 24;
476
499
  if (buyerPrivateKeys.length === 0) {
@@ -581,9 +604,18 @@ export async function flapBatchSwapMerkle(params) {
581
604
  }, useNativeToken ? { value: buyAmount } : {});
582
605
  })
583
606
  ]);
584
- // ✅ 并行获取所有 nonce
585
- const sellNonce = await nonceManager.getNextNonce(seller);
586
- const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
607
+ // ✅ 并行获取所有 nonce(如果前端传入了 startNonces,直接使用)
608
+ const allWallets = [seller, ...buyers];
609
+ let sellNonce;
610
+ let buyerNonces;
611
+ if (startNonces && startNonces.length >= allWallets.length) {
612
+ sellNonce = startNonces[0];
613
+ buyerNonces = startNonces.slice(1);
614
+ }
615
+ else {
616
+ sellNonce = await nonceManager.getNextNonce(seller);
617
+ buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
618
+ }
587
619
  // ✅ 贿赂交易和利润交易都由卖方发送(保持一致)
588
620
  const bribeAmount = getBribeAmount(config);
589
621
  let bribeTx = null;
@@ -682,7 +714,8 @@ export async function flapBatchSwapMerkle(params) {
682
714
  * 限制:最多 23 个买方(2N + 3 ≤ 50)
683
715
  */
684
716
  export async function flapQuickBatchSwapMerkle(params) {
685
- const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config, quoteToken, quoteTokenDecimals = 18 } = params;
717
+ const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
718
+ } = params;
686
719
  // ✅ 校验买方数量
687
720
  // BNB 模式:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23
688
721
  // ERC20 模式:贿赂(1) + 卖出(1) + ERC20转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23
@@ -791,7 +824,10 @@ export async function flapQuickBatchSwapMerkle(params) {
791
824
  throw new Error(`主钱包 ${chainContext.nativeToken} 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)}`);
792
825
  }
793
826
  // ==================== 规划 Nonce ====================
794
- let sellerNonce = await nonceManager.getNextNonce(seller);
827
+ // 如果前端传入了 startNonces,直接使用(性能优化)
828
+ let sellerNonce = startNonces && startNonces.length > 0
829
+ ? startNonces[0]
830
+ : await nonceManager.getNextNonce(seller);
795
831
  // ==================== 1. 贿赂交易 ====================
796
832
  let bribeTx = null;
797
833
  if (bribeAmount > 0n) {
@@ -873,7 +909,10 @@ export async function flapQuickBatchSwapMerkle(params) {
873
909
  }
874
910
  console.log(`[flapQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
875
911
  // ==================== 4. 买入交易 ====================
876
- const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
912
+ // 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
913
+ const buyerNonces = startNonces && startNonces.length > 1
914
+ ? startNonces.slice(1)
915
+ : await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
877
916
  const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
878
917
  const buyAmount = transferAmountsWei[i];
879
918
  const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
@@ -47,6 +47,7 @@ export interface PancakeBundleSwapSignParams {
47
47
  config: PancakeSwapSignConfig;
48
48
  quoteToken?: string;
49
49
  quoteTokenDecimals?: number;
50
+ startNonces?: number[];
50
51
  }
51
52
  export interface PancakeBundleSwapParams {
52
53
  sellerPrivateKey: string;
@@ -88,6 +89,7 @@ export interface PancakeBatchSwapSignParams {
88
89
  config: PancakeSwapSignConfig;
89
90
  quoteToken?: string;
90
91
  quoteTokenDecimals?: number;
92
+ startNonces?: number[];
91
93
  }
92
94
  /**
93
95
  * 批量换手结果
@@ -127,6 +129,7 @@ export interface PancakeQuickBatchSwapParams {
127
129
  config: PancakeSwapSignConfig;
128
130
  quoteToken?: string;
129
131
  quoteTokenDecimals?: number;
132
+ startNonces?: number[];
130
133
  }
131
134
  /**
132
135
  * 快捷批量换手结果
@@ -115,6 +115,30 @@ async function calculateBuyerBudget({ buyer, quotedBNBOut, reserveGasBNB, useNat
115
115
  return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB, useNativeToken };
116
116
  }
117
117
  }
118
+ /**
119
+ * ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化)
120
+ * 顺序:[sellerBaseNonce, buyerNonce]
121
+ */
122
+ function buildNoncePlanFromNonces(startNonces, sameAddress, approvalExists, profitNeeded, needBribeTx) {
123
+ if (sameAddress) {
124
+ let idx = 0;
125
+ const bribeNonce = needBribeTx ? startNonces[0] + idx++ : undefined;
126
+ if (approvalExists)
127
+ idx++;
128
+ const sellerNonce = startNonces[0] + idx++;
129
+ const buyerNonce = startNonces[0] + idx++;
130
+ const profitNonce = profitNeeded ? startNonces[0] + idx : undefined;
131
+ return { sellerNonce, buyerNonce, bribeNonce, profitNonce };
132
+ }
133
+ let sellerIdx = 0;
134
+ const bribeNonce = needBribeTx ? startNonces[0] + sellerIdx++ : undefined;
135
+ if (approvalExists)
136
+ sellerIdx++;
137
+ const sellerNonce = startNonces[0] + sellerIdx++;
138
+ const profitNonce = profitNeeded ? startNonces[0] + sellerIdx : undefined;
139
+ const buyerNonce = startNonces[1];
140
+ return { sellerNonce, buyerNonce, bribeNonce, profitNonce };
141
+ }
118
142
  /**
119
143
  * ✅ 规划 nonce
120
144
  * 交易顺序:贿赂 → 授权 → 卖出 → 买入 → 利润
@@ -294,7 +318,8 @@ const ERC20_BALANCE_OF_ABI = ['function balanceOf(address) view returns (uint256
294
318
  * ✅ 支持 quoteToken:传入 USDT 等地址时,卖出得到该代币,买入使用该代币
295
319
  */
296
320
  export async function pancakeBundleSwapMerkle(params) {
297
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18 } = params;
321
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
322
+ } = params;
298
323
  // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
299
324
  const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
300
325
  const context = createPancakeContext(config);
@@ -358,15 +383,18 @@ export async function pancakeBundleSwapMerkle(params) {
358
383
  : 0n;
359
384
  const needBribeTx = bribeAmount > 0n;
360
385
  // ✅ 使用共享的 NonceManager 规划 nonce
361
- const noncePlan = await planNonces({
362
- seller,
363
- buyer,
364
- sameAddress,
365
- approvalExists: false, // ✅ 已移除授权
366
- profitNeeded: profitAmount > 0n,
367
- needBribeTx,
368
- nonceManager
369
- });
386
+ // 如果前端传入了 startNonces,直接使用(性能优化)
387
+ const noncePlan = startNonces && startNonces.length >= (sameAddress ? 1 : 2)
388
+ ? buildNoncePlanFromNonces(startNonces, sameAddress, false, profitAmount > 0n, needBribeTx)
389
+ : await planNonces({
390
+ seller,
391
+ buyer,
392
+ sameAddress,
393
+ approvalExists: false, // ✅ 已移除授权
394
+ profitNeeded: profitAmount > 0n,
395
+ needBribeTx,
396
+ nonceManager
397
+ });
370
398
  // ✅ 并行签名所有交易
371
399
  const signPromises = [];
372
400
  // 贿赂交易
@@ -462,7 +490,8 @@ export async function pancakeBundleSwapMerkle(params) {
462
490
  * 交易顺序:[授权(可选)] → [卖出] → [买入1, 买入2, ..., 买入N] → [利润]
463
491
  */
464
492
  export async function pancakeBatchSwapMerkle(params) {
465
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18 } = params;
493
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
494
+ } = params;
466
495
  // ✅ 校验买方数量(最多 24 个)
467
496
  const MAX_BUYERS = 24;
468
497
  if (buyerPrivateKeys.length === 0) {
@@ -577,16 +606,26 @@ export async function pancakeBatchSwapMerkle(params) {
577
606
  ? ethers.parseEther(String(config.bribeAmount))
578
607
  : 0n;
579
608
  // ✅ 规划 Nonce(贿赂和利润都由卖方发送)
580
- // 卖方: [贿赂(可选)] → [授权(可选)] → [卖出] → [利润(可选)]
609
+ // 卖方: [贿赂(可选)] → [卖出] → [利润(可选)]
581
610
  let bribeNonce;
582
- let sellNonceOffset = 0;
583
- if (bribeAmount > 0n) {
584
- bribeNonce = await nonceManager.getNextNonce(seller);
585
- sellNonceOffset = 1; // 卖出交易 nonce 需要偏移
611
+ let sellNonce;
612
+ let buyerNonces;
613
+ // 如果前端传入了 startNonces,直接使用(性能优化)
614
+ if (startNonces && startNonces.length >= (1 + buyers.length)) {
615
+ let sellerIdx = 0;
616
+ if (bribeAmount > 0n) {
617
+ bribeNonce = startNonces[0] + sellerIdx++;
618
+ }
619
+ sellNonce = startNonces[0] + sellerIdx;
620
+ buyerNonces = startNonces.slice(1);
621
+ }
622
+ else {
623
+ if (bribeAmount > 0n) {
624
+ bribeNonce = await nonceManager.getNextNonce(seller);
625
+ }
626
+ sellNonce = await nonceManager.getNextNonce(seller);
627
+ buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
586
628
  }
587
- const sellNonce = await nonceManager.getNextNonce(seller);
588
- // ✅ 并行获取所有买方的 nonce
589
- const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
590
629
  // ✅ 贿赂交易放在首位(由卖方发送)
591
630
  let bribeTx = null;
592
631
  if (bribeAmount > 0n && bribeNonce !== undefined) {
@@ -616,7 +655,10 @@ export async function pancakeBatchSwapMerkle(params) {
616
655
  let profitTx = null;
617
656
  if (profitAmount > 0n) {
618
657
  // ✅ 利润由卖方发送(与贿赂交易同一钱包)
619
- const profitNonce = await nonceManager.getNextNonce(seller);
658
+ // 如果使用 startNonces,利润 nonce = 卖出 nonce + 1
659
+ const profitNonce = startNonces && startNonces.length >= 1
660
+ ? sellNonce + 1
661
+ : await nonceManager.getNextNonce(seller);
620
662
  profitTx = await seller.signTransaction({
621
663
  to: PROFIT_CONFIG.RECIPIENT,
622
664
  value: profitAmount,
@@ -689,7 +731,8 @@ export async function pancakeBatchSwapMerkle(params) {
689
731
  * 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
690
732
  */
691
733
  export async function pancakeQuickBatchSwapMerkle(params) {
692
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18 } = params;
734
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
735
+ } = params;
693
736
  // ✅ 判断是否使用原生代币
694
737
  const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
695
738
  const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'.toLowerCase();
@@ -840,7 +883,10 @@ export async function pancakeQuickBatchSwapMerkle(params) {
840
883
  throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`);
841
884
  }
842
885
  // ==================== 规划 Nonce ====================
843
- let sellerNonce = await nonceManager.getNextNonce(seller);
886
+ // 如果前端传入了 startNonces,直接使用(性能优化)
887
+ let sellerNonce = startNonces && startNonces.length > 0
888
+ ? startNonces[0]
889
+ : await nonceManager.getNextNonce(seller);
844
890
  const deadline = Math.floor(Date.now() / 1000) + 600;
845
891
  // ==================== 1. 贿赂交易 ====================
846
892
  let bribeTx = null;
@@ -926,7 +972,10 @@ export async function pancakeQuickBatchSwapMerkle(params) {
926
972
  }
927
973
  console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
928
974
  // ==================== 4. 买入交易 ====================
929
- const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
975
+ // 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
976
+ const buyerNonces = startNonces && startNonces.length > 1
977
+ ? startNonces.slice(1)
978
+ : await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
930
979
  const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
931
980
  const buyAmount = transferAmountsWei[i];
932
981
  const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.21",
3
+ "version": "1.4.24",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",