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.
- package/dist/contracts/tm-bundle-merkle/swap.d.ts +3 -0
- package/dist/contracts/tm-bundle-merkle/swap.js +27 -15
- package/dist/contracts/tm-bundle-merkle/types.d.ts +12 -0
- package/dist/contracts/tm-bundle-merkle/utils.js +170 -35
- package/dist/flap/portal-bundle-merkle/swap.d.ts +3 -0
- package/dist/flap/portal-bundle-merkle/swap.js +55 -16
- package/dist/pancake/bundle-swap.d.ts +3 -0
- package/dist/pancake/bundle-swap.js +72 -23
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
//
|
|
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
|
-
// ✅
|
|
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
|
|
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
|
-
//
|
|
314
|
+
// ✅ 如果前端传入了 startNonces,直接使用(性能优化)
|
|
315
|
+
// 否则从链上获取
|
|
313
316
|
const allWallets = [seller, ...buyers];
|
|
314
|
-
const initialNonces =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
'
|
|
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
|
-
* @
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
// ✅
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
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
|
|
586
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
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
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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);
|