four-flap-meme-sdk 1.4.61 → 1.4.63
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-buy-first.d.ts +4 -0
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +133 -78
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +4 -0
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +160 -41
- package/dist/pancake/bundle-buy-first.d.ts +4 -0
- package/dist/pancake/bundle-buy-first.js +185 -35
- package/package.json +1 -1
|
@@ -24,6 +24,8 @@ export interface FourBundleBuyFirstSignParams {
|
|
|
24
24
|
sellerPrivateKey: string;
|
|
25
25
|
tokenAddress: string;
|
|
26
26
|
config: FourBuyFirstSignConfig;
|
|
27
|
+
buyCount?: number;
|
|
28
|
+
sellCount?: number;
|
|
27
29
|
}
|
|
28
30
|
export interface FourBundleBuyFirstParams {
|
|
29
31
|
buyerPrivateKey: string;
|
|
@@ -43,6 +45,8 @@ export type FourBuyFirstResult = {
|
|
|
43
45
|
buyAmount: string;
|
|
44
46
|
sellAmount: string;
|
|
45
47
|
profitAmount?: string;
|
|
48
|
+
buyCount?: number;
|
|
49
|
+
sellCount?: number;
|
|
46
50
|
};
|
|
47
51
|
};
|
|
48
52
|
export declare function fourBundleBuyFirstMerkle(params: FourBundleBuyFirstSignParams): Promise<FourBuyFirstResult>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { ethers, Contract, Wallet } from 'ethers';
|
|
7
7
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../utils/bundle-helpers.js';
|
|
8
|
-
import { ADDRESSES,
|
|
8
|
+
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
|
|
9
9
|
import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
|
|
10
10
|
import { getTxType, getGasPriceConfig, getProfitRecipient, getBribeAmount } from './config.js';
|
|
11
11
|
import { trySell } from '../tm.js';
|
|
@@ -17,8 +17,61 @@ function getGasLimit(config, defaultGas = 800000) {
|
|
|
17
17
|
const multiplier = config.gasLimitMultiplier ?? 1.0;
|
|
18
18
|
return BigInt(Math.ceil(defaultGas * multiplier));
|
|
19
19
|
}
|
|
20
|
+
// ==================== 多笔买卖常量 ====================
|
|
21
|
+
/** 最大 Bundle 签名数 */
|
|
22
|
+
const MAX_BUNDLE_SIGNATURES = 50;
|
|
23
|
+
/** 贿赂交易数 */
|
|
24
|
+
const BRIBE_TX_COUNT = 1;
|
|
25
|
+
/** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
|
|
26
|
+
const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
|
|
27
|
+
/** 最大买卖交易数 */
|
|
28
|
+
const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
|
|
29
|
+
/** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
|
|
30
|
+
const PROFIT_RATE_PER_TX_BPS = 3;
|
|
31
|
+
/**
|
|
32
|
+
* 验证买卖笔数
|
|
33
|
+
*/
|
|
34
|
+
function validateSwapCounts(buyCount, sellCount) {
|
|
35
|
+
if (buyCount < 1) {
|
|
36
|
+
throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
|
|
37
|
+
}
|
|
38
|
+
if (sellCount < 1) {
|
|
39
|
+
throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
|
|
40
|
+
}
|
|
41
|
+
const totalSwaps = buyCount + sellCount;
|
|
42
|
+
if (totalSwaps > MAX_SWAP_TX_COUNT) {
|
|
43
|
+
throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
|
|
44
|
+
`最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 将金额均匀拆分成多份
|
|
49
|
+
*/
|
|
50
|
+
function splitAmount(totalAmount, count) {
|
|
51
|
+
if (count <= 0) {
|
|
52
|
+
throw new Error('拆分份数必须大于 0');
|
|
53
|
+
}
|
|
54
|
+
if (count === 1) {
|
|
55
|
+
return [totalAmount];
|
|
56
|
+
}
|
|
57
|
+
const baseAmount = totalAmount / BigInt(count);
|
|
58
|
+
const remainder = totalAmount % BigInt(count);
|
|
59
|
+
const amounts = [];
|
|
60
|
+
for (let i = 0; i < count - 1; i++) {
|
|
61
|
+
amounts.push(baseAmount);
|
|
62
|
+
}
|
|
63
|
+
amounts.push(baseAmount + remainder);
|
|
64
|
+
return amounts;
|
|
65
|
+
}
|
|
20
66
|
export async function fourBundleBuyFirstMerkle(params) {
|
|
21
|
-
const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config } = params;
|
|
67
|
+
const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
68
|
+
// ✅ 解析并验证买卖笔数
|
|
69
|
+
const buyCount = _buyCount ?? 1;
|
|
70
|
+
const sellCount = _sellCount ?? 1;
|
|
71
|
+
validateSwapCounts(buyCount, sellCount);
|
|
72
|
+
// ✅ 计算利润比例:每笔万分之3
|
|
73
|
+
const totalTxCount = buyCount + sellCount;
|
|
74
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
22
75
|
const chainIdNum = config.chainId ?? 56;
|
|
23
76
|
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
24
77
|
chainId: chainIdNum,
|
|
@@ -75,115 +128,82 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
75
128
|
// ✅ 优化:第三批并行 - trySell、构建交易、获取 nonces
|
|
76
129
|
const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
|
|
77
130
|
const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
|
|
131
|
+
// ✅ 拆分买入和卖出金额
|
|
132
|
+
const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
|
|
133
|
+
const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
|
|
78
134
|
// 预先规划 nonces
|
|
79
135
|
const extractProfit = true;
|
|
80
|
-
const profitRateBps = PROFIT_CONFIG.RATE_BPS_SWAP; // 万分之六
|
|
81
136
|
// ✅ 获取贿赂金额
|
|
82
137
|
const bribeAmount = getBribeAmount(config);
|
|
83
138
|
const needBribeTx = bribeAmount > 0n;
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// seller 需要 sellerNonceCount 个连续 nonce,从初始 nonce 开始
|
|
100
|
-
sellerNonces: Array.from({ length: sellerNonceCount }, (_, i) => initialNonces[1] + i)
|
|
101
|
-
}));
|
|
102
|
-
const [sellResult, buyUnsigned, sellUnsigned, noncesResult] = await Promise.all([
|
|
139
|
+
// ✅ 构建多笔买入和卖出交易
|
|
140
|
+
const buyUnsignedPromises = buyAmountsWei.map(amount => tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, amount, 0n, { value: amount }));
|
|
141
|
+
const sellUnsignedPromises = sellAmountsWei.map(amount => tmSeller.sellToken.populateTransaction(0n, tokenAddress, amount, 0n));
|
|
142
|
+
// ✅ 规划多笔交易 nonce
|
|
143
|
+
const multiNoncePlan = await planMultiNonces({
|
|
144
|
+
buyer,
|
|
145
|
+
seller,
|
|
146
|
+
buyCount,
|
|
147
|
+
sellCount,
|
|
148
|
+
extractProfit,
|
|
149
|
+
needBribeTx,
|
|
150
|
+
sameAddress,
|
|
151
|
+
nonceManager
|
|
152
|
+
});
|
|
153
|
+
const [sellResult, buyUnsignedArray, sellUnsignedArray] = await Promise.all([
|
|
103
154
|
trySell('BSC', config.rpcUrl, tokenAddress, sellAmountWei),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
getNoncesPromise
|
|
155
|
+
Promise.all(buyUnsignedPromises),
|
|
156
|
+
Promise.all(sellUnsignedPromises)
|
|
107
157
|
]);
|
|
108
|
-
const { buyerNonces, sellerNonces } = noncesResult;
|
|
109
158
|
const estimatedSellFunds = sellResult.funds;
|
|
110
159
|
const profitAmount = extractProfit ? (estimatedSellFunds * BigInt(profitRateBps)) / 10000n : 0n;
|
|
111
|
-
// 分配 nonces
|
|
112
|
-
let buyerNonce;
|
|
113
|
-
let sellerNonce;
|
|
114
|
-
let bribeNonce;
|
|
115
|
-
let profitNonce;
|
|
116
|
-
if (sameAddress) {
|
|
117
|
-
let idx = 0;
|
|
118
|
-
if (needBribeTx)
|
|
119
|
-
bribeNonce = sellerNonces[idx++];
|
|
120
|
-
buyerNonce = sellerNonces[idx++];
|
|
121
|
-
sellerNonce = sellerNonces[idx++];
|
|
122
|
-
if (extractProfit)
|
|
123
|
-
profitNonce = sellerNonces[idx];
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
buyerNonce = buyerNonces[0];
|
|
127
|
-
let idx = 0;
|
|
128
|
-
if (needBribeTx)
|
|
129
|
-
bribeNonce = sellerNonces[idx++];
|
|
130
|
-
sellerNonce = sellerNonces[idx++];
|
|
131
|
-
if (extractProfit)
|
|
132
|
-
profitNonce = sellerNonces[idx];
|
|
133
|
-
}
|
|
134
|
-
// ✅ 并行签名所有交易
|
|
135
|
-
const signPromises = [];
|
|
136
160
|
// ✅ 贿赂交易放在首位
|
|
137
161
|
let bribeTx = null;
|
|
138
|
-
if (needBribeTx && bribeNonce !== undefined) {
|
|
139
|
-
|
|
162
|
+
if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
|
|
163
|
+
bribeTx = await seller.signTransaction({
|
|
140
164
|
to: BLOCKRAZOR_BUILDER_EOA,
|
|
141
165
|
value: bribeAmount,
|
|
142
|
-
nonce: bribeNonce,
|
|
166
|
+
nonce: multiNoncePlan.bribeNonce,
|
|
143
167
|
gasPrice,
|
|
144
168
|
gasLimit: 21000n,
|
|
145
169
|
chainId: chainIdNum,
|
|
146
170
|
type: txType
|
|
147
|
-
})
|
|
171
|
+
});
|
|
148
172
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
...
|
|
173
|
+
// ✅ 并行签名所有买入和卖出交易
|
|
174
|
+
const buySignPromises = buyUnsignedArray.map((unsigned, i) => buyer.signTransaction({
|
|
175
|
+
...unsigned,
|
|
152
176
|
from: buyer.address,
|
|
153
|
-
nonce:
|
|
177
|
+
nonce: multiNoncePlan.buyerNonces[i],
|
|
154
178
|
gasLimit: finalGasLimit,
|
|
155
179
|
gasPrice,
|
|
156
180
|
chainId: chainIdNum,
|
|
157
181
|
type: txType,
|
|
158
|
-
value:
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
...sellUnsigned,
|
|
182
|
+
value: buyAmountsWei[i]
|
|
183
|
+
}));
|
|
184
|
+
const sellSignPromises = sellUnsignedArray.map((unsigned, i) => seller.signTransaction({
|
|
185
|
+
...unsigned,
|
|
163
186
|
from: seller.address,
|
|
164
|
-
nonce:
|
|
187
|
+
nonce: multiNoncePlan.sellerNonces[i],
|
|
165
188
|
gasLimit: finalGasLimit,
|
|
166
189
|
gasPrice,
|
|
167
190
|
chainId: chainIdNum,
|
|
168
191
|
type: txType,
|
|
169
|
-
value: 0n
|
|
192
|
+
value: 0n
|
|
170
193
|
}));
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
bribeTx = signedTxs[idx++];
|
|
176
|
-
const signedBuy = signedTxs[idx++];
|
|
177
|
-
const signedSell = signedTxs[idx++];
|
|
194
|
+
const [signedBuys, signedSells] = await Promise.all([
|
|
195
|
+
Promise.all(buySignPromises),
|
|
196
|
+
Promise.all(sellSignPromises)
|
|
197
|
+
]);
|
|
178
198
|
nonceManager.clearTemp();
|
|
179
|
-
// ✅ 组装交易列表:贿赂 → 买入 → 卖出
|
|
199
|
+
// ✅ 组装交易列表:贿赂 → 买入(多笔) → 卖出(多笔)
|
|
180
200
|
const allTransactions = [];
|
|
181
201
|
if (bribeTx)
|
|
182
202
|
allTransactions.push(bribeTx);
|
|
183
|
-
allTransactions.push(
|
|
203
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
184
204
|
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
185
205
|
let profitHopWallets;
|
|
186
|
-
if (extractProfit && profitAmount > 0n && profitNonce !== undefined) {
|
|
206
|
+
if (extractProfit && profitAmount > 0n && multiNoncePlan.profitNonce !== undefined) {
|
|
187
207
|
const profitHopResult = await buildProfitHopTransactions({
|
|
188
208
|
provider,
|
|
189
209
|
payerWallet: seller,
|
|
@@ -193,7 +213,7 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
193
213
|
gasPrice,
|
|
194
214
|
chainId: chainIdNum,
|
|
195
215
|
txType,
|
|
196
|
-
startNonce: profitNonce
|
|
216
|
+
startNonce: multiNoncePlan.profitNonce
|
|
197
217
|
});
|
|
198
218
|
allTransactions.push(...profitHopResult.signedTransactions);
|
|
199
219
|
profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
@@ -206,7 +226,42 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
206
226
|
sellerAddress: seller.address,
|
|
207
227
|
buyAmount: ethers.formatEther(buyerFundsWei),
|
|
208
228
|
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
209
|
-
profitAmount: extractProfit ? ethers.formatEther(profitAmount) : undefined
|
|
229
|
+
profitAmount: extractProfit ? ethers.formatEther(profitAmount) : undefined,
|
|
230
|
+
// ✅ 返回多笔买卖信息
|
|
231
|
+
buyCount,
|
|
232
|
+
sellCount
|
|
210
233
|
}
|
|
211
234
|
};
|
|
212
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* ✅ 规划多笔交易 nonce
|
|
238
|
+
* 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
|
|
239
|
+
*/
|
|
240
|
+
async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, sameAddress, nonceManager }) {
|
|
241
|
+
const profitNonceCount = extractProfit ? 1 : 0;
|
|
242
|
+
if (sameAddress) {
|
|
243
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
244
|
+
const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
|
|
245
|
+
const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
|
|
246
|
+
let idx = 0;
|
|
247
|
+
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
248
|
+
const buyerNonces = nonces.slice(idx, idx + buyCount);
|
|
249
|
+
idx += buyCount;
|
|
250
|
+
const sellerNonces = nonces.slice(idx, idx + sellCount);
|
|
251
|
+
idx += sellCount;
|
|
252
|
+
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
253
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
254
|
+
}
|
|
255
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
256
|
+
const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
|
|
257
|
+
const [buyerNonces, sellerNoncesAll] = await Promise.all([
|
|
258
|
+
nonceManager.getNextNonceBatch(buyer, buyCount),
|
|
259
|
+
nonceManager.getNextNonceBatch(seller, sellerTxCount)
|
|
260
|
+
]);
|
|
261
|
+
let idx = 0;
|
|
262
|
+
const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
|
|
263
|
+
const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
|
|
264
|
+
idx += sellCount;
|
|
265
|
+
const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
|
|
266
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
267
|
+
}
|
|
@@ -30,6 +30,8 @@ export interface FlapBundleBuyFirstSignParams {
|
|
|
30
30
|
config: FlapBuyFirstSignConfig;
|
|
31
31
|
quoteToken?: string;
|
|
32
32
|
quoteTokenDecimals?: number;
|
|
33
|
+
buyCount?: number;
|
|
34
|
+
sellCount?: number;
|
|
33
35
|
}
|
|
34
36
|
export interface FlapBundleBuyFirstParams {
|
|
35
37
|
chain: FlapChain;
|
|
@@ -50,6 +52,8 @@ export type FlapBuyFirstResult = {
|
|
|
50
52
|
buyAmount: string;
|
|
51
53
|
sellAmount: string;
|
|
52
54
|
profitAmount?: string;
|
|
55
|
+
buyCount?: number;
|
|
56
|
+
sellCount?: number;
|
|
53
57
|
};
|
|
54
58
|
};
|
|
55
59
|
export declare function flapBundleBuyFirstMerkle(params: FlapBundleBuyFirstSignParams): Promise<FlapBuyFirstResult>;
|
|
@@ -57,6 +57,33 @@ function getGasLimit(config, defaultGas = 800000) {
|
|
|
57
57
|
const multiplier = config.gasLimitMultiplier ?? 1.0;
|
|
58
58
|
return BigInt(Math.ceil(defaultGas * multiplier));
|
|
59
59
|
}
|
|
60
|
+
// ==================== 多笔买卖常量 ====================
|
|
61
|
+
/** 最大 Bundle 签名数 */
|
|
62
|
+
const MAX_BUNDLE_SIGNATURES = 50;
|
|
63
|
+
/** 贿赂交易数 */
|
|
64
|
+
const BRIBE_TX_COUNT = 1;
|
|
65
|
+
/** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
|
|
66
|
+
const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
|
|
67
|
+
/** 最大买卖交易数 */
|
|
68
|
+
const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
|
|
69
|
+
/** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
|
|
70
|
+
const PROFIT_RATE_PER_TX_BPS = 3;
|
|
71
|
+
/**
|
|
72
|
+
* 验证买卖笔数
|
|
73
|
+
*/
|
|
74
|
+
function validateSwapCounts(buyCount, sellCount) {
|
|
75
|
+
if (buyCount < 1) {
|
|
76
|
+
throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
|
|
77
|
+
}
|
|
78
|
+
if (sellCount < 1) {
|
|
79
|
+
throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
|
|
80
|
+
}
|
|
81
|
+
const totalSwaps = buyCount + sellCount;
|
|
82
|
+
if (totalSwaps > MAX_SWAP_TX_COUNT) {
|
|
83
|
+
throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
|
|
84
|
+
`最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
60
87
|
function getChainId(chain) {
|
|
61
88
|
switch (chain) {
|
|
62
89
|
case 'bsc': return 56;
|
|
@@ -73,7 +100,14 @@ function getNativeTokenName(chain) {
|
|
|
73
100
|
}
|
|
74
101
|
// 使用公共工具的 getGasLimit,移除本地重复实现
|
|
75
102
|
export async function flapBundleBuyFirstMerkle(params) {
|
|
76
|
-
const { chain, buyerPrivateKey, sellerPrivateKey, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals } = params;
|
|
103
|
+
const { chain, buyerPrivateKey, sellerPrivateKey, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
104
|
+
// ✅ 解析并验证买卖笔数
|
|
105
|
+
const buyCount = _buyCount ?? 1;
|
|
106
|
+
const sellCount = _sellCount ?? 1;
|
|
107
|
+
validateSwapCounts(buyCount, sellCount);
|
|
108
|
+
// ✅ 计算利润比例:每笔万分之3
|
|
109
|
+
const totalTxCount = buyCount + sellCount;
|
|
110
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
77
111
|
const chainContext = createChainContext(chain, config.rpcUrl);
|
|
78
112
|
const buyer = new Wallet(buyerPrivateKey, chainContext.provider);
|
|
79
113
|
const seller = new Wallet(sellerPrivateKey, chainContext.provider);
|
|
@@ -123,97 +157,106 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
123
157
|
});
|
|
124
158
|
const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
|
|
125
159
|
const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
|
|
126
|
-
// ✅
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
// ✅ 拆分买入和卖出金额
|
|
161
|
+
const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
|
|
162
|
+
const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
|
|
163
|
+
// ✅ 计算每笔买入预期获得的代币数量(用于 minOutputAmount)
|
|
164
|
+
const minOutputPerBuy = sellAmountWei / BigInt(buyCount);
|
|
165
|
+
// ✅ 第三批并行 - 构建多笔买入和卖出交易、estimatedSellFunds
|
|
166
|
+
const buyUnsignedPromises = buyAmountsWei.map(amount => portalBuyer.swapExactInput.populateTransaction({
|
|
167
|
+
inputToken,
|
|
168
|
+
outputToken: tokenAddress,
|
|
169
|
+
inputAmount: amount,
|
|
170
|
+
minOutputAmount: minOutputPerBuy,
|
|
171
|
+
permitData: '0x'
|
|
172
|
+
}, useNativeToken ? { value: amount } : {}));
|
|
173
|
+
const sellUnsignedPromises = sellAmountsWei.map(amount => portalSeller.swapExactInput.populateTransaction({
|
|
174
|
+
inputToken: tokenAddress,
|
|
175
|
+
outputToken,
|
|
176
|
+
inputAmount: amount,
|
|
177
|
+
minOutputAmount: 0,
|
|
178
|
+
permitData: '0x'
|
|
179
|
+
}));
|
|
180
|
+
const [buyUnsignedArray, sellUnsignedArray, estimatedSellFunds] = await Promise.all([
|
|
181
|
+
Promise.all(buyUnsignedPromises),
|
|
182
|
+
Promise.all(sellUnsignedPromises),
|
|
143
183
|
estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken)
|
|
144
184
|
]);
|
|
145
185
|
// ✅ 获取贿赂金额
|
|
146
186
|
const bribeAmount = getBribeAmount(config);
|
|
147
187
|
const needBribeTx = bribeAmount > 0n;
|
|
148
|
-
// ✅
|
|
149
|
-
const
|
|
188
|
+
// ✅ 规划多笔交易 nonce
|
|
189
|
+
const multiNoncePlan = await planMultiNonces({
|
|
150
190
|
buyer,
|
|
151
191
|
seller,
|
|
192
|
+
buyCount,
|
|
193
|
+
sellCount,
|
|
152
194
|
extractProfit: true,
|
|
153
195
|
needBribeTx,
|
|
154
196
|
sameAddress,
|
|
155
197
|
nonceManager
|
|
156
198
|
});
|
|
157
|
-
// ✅
|
|
199
|
+
// ✅ 修复:基于卖出收益估算利润(使用动态利润比例)
|
|
158
200
|
const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : buyerFundsWei;
|
|
159
|
-
const tokenProfitAmount =
|
|
201
|
+
const tokenProfitAmount = calculateMultiProfitAmount(profitBase, profitRateBps);
|
|
160
202
|
// ✅ ERC20 购买:获取代币利润等值的原生代币(BNB)报价
|
|
161
203
|
let nativeProfitAmount = tokenProfitAmount; // 原生代币购买时直接使用
|
|
162
204
|
if (!useNativeToken && tokenProfitAmount > 0n) {
|
|
163
205
|
// 将代币利润转换为等值 BNB
|
|
164
206
|
nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, inputToken, tokenProfitAmount, chainContext.chainId);
|
|
165
207
|
}
|
|
166
|
-
|
|
208
|
+
// ✅ 构建多笔买入交易
|
|
209
|
+
const buyTxs = buyUnsignedArray.map((unsigned, i) => buildTransactionRequest(unsigned, {
|
|
167
210
|
from: buyer.address,
|
|
168
|
-
nonce:
|
|
211
|
+
nonce: multiNoncePlan.buyerNonces[i],
|
|
169
212
|
gasLimit: finalGasLimit,
|
|
170
213
|
gasPrice,
|
|
171
214
|
priorityFee,
|
|
172
215
|
chainId: chainContext.chainId,
|
|
173
216
|
txType,
|
|
174
|
-
value: useNativeToken ?
|
|
175
|
-
});
|
|
176
|
-
// ✅
|
|
177
|
-
const
|
|
217
|
+
value: useNativeToken ? buyAmountsWei[i] : 0n
|
|
218
|
+
}));
|
|
219
|
+
// ✅ 构建多笔卖出交易
|
|
220
|
+
const sellTxs = sellUnsignedArray.map((unsigned, i) => buildTransactionRequest(unsigned, {
|
|
178
221
|
from: seller.address,
|
|
179
|
-
nonce:
|
|
222
|
+
nonce: multiNoncePlan.sellerNonces[i],
|
|
180
223
|
gasLimit: finalGasLimit,
|
|
181
224
|
gasPrice,
|
|
182
225
|
priorityFee,
|
|
183
226
|
chainId: chainContext.chainId,
|
|
184
227
|
txType,
|
|
185
|
-
value: 0n
|
|
186
|
-
});
|
|
228
|
+
value: 0n
|
|
229
|
+
}));
|
|
187
230
|
// ✅ 贿赂交易放在首位
|
|
188
231
|
let bribeTx = null;
|
|
189
|
-
if (needBribeTx &&
|
|
232
|
+
if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
|
|
190
233
|
bribeTx = await seller.signTransaction({
|
|
191
234
|
to: BLOCKRAZOR_BUILDER_EOA,
|
|
192
235
|
value: bribeAmount,
|
|
193
|
-
nonce:
|
|
236
|
+
nonce: multiNoncePlan.bribeNonce,
|
|
194
237
|
gasPrice,
|
|
195
238
|
gasLimit: 21000n,
|
|
196
239
|
chainId: chainContext.chainId,
|
|
197
240
|
type: txType
|
|
198
241
|
});
|
|
199
242
|
}
|
|
200
|
-
// ✅
|
|
201
|
-
const [
|
|
202
|
-
buyer.signTransaction(
|
|
203
|
-
seller.signTransaction(
|
|
243
|
+
// ✅ 并行签名所有买入和卖出交易
|
|
244
|
+
const [signedBuys, signedSells] = await Promise.all([
|
|
245
|
+
Promise.all(buyTxs.map(tx => buyer.signTransaction(tx))),
|
|
246
|
+
Promise.all(sellTxs.map(tx => seller.signTransaction(tx)))
|
|
204
247
|
]);
|
|
205
248
|
nonceManager.clearTemp();
|
|
206
|
-
// ✅ 组装顺序:贿赂 → 买入 → 卖出
|
|
249
|
+
// ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)
|
|
207
250
|
const allTransactions = [];
|
|
208
251
|
if (bribeTx)
|
|
209
252
|
allTransactions.push(bribeTx);
|
|
210
|
-
allTransactions.push(
|
|
253
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
211
254
|
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
212
255
|
const profitResult = await buildProfitTransaction({
|
|
213
256
|
provider: chainContext.provider,
|
|
214
257
|
seller,
|
|
215
258
|
profitAmount: nativeProfitAmount,
|
|
216
|
-
profitNonce:
|
|
259
|
+
profitNonce: multiNoncePlan.profitNonce,
|
|
217
260
|
gasPrice,
|
|
218
261
|
chainId: chainContext.chainId,
|
|
219
262
|
txType
|
|
@@ -231,7 +274,10 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
231
274
|
sellerAddress: seller.address,
|
|
232
275
|
buyAmount: ethers.formatEther(buyerFundsWei),
|
|
233
276
|
sellAmount: ethers.formatUnits(sellAmountWei, sellerInfo.decimals),
|
|
234
|
-
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined
|
|
277
|
+
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
|
|
278
|
+
// ✅ 返回多笔买卖信息
|
|
279
|
+
buyCount,
|
|
280
|
+
sellCount
|
|
235
281
|
}
|
|
236
282
|
};
|
|
237
283
|
}
|
|
@@ -345,6 +391,40 @@ function calculateProfitAmount(expectedFunds) {
|
|
|
345
391
|
// 万分之六
|
|
346
392
|
return (expectedFunds * BigInt(PROFIT_CONFIG.RATE_BPS_SWAP)) / 10000n;
|
|
347
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* 计算多笔交易的利润金额
|
|
396
|
+
* @param expectedFunds - 预期收益
|
|
397
|
+
* @param rateBps - 利润比例(基点)
|
|
398
|
+
*/
|
|
399
|
+
function calculateMultiProfitAmount(expectedFunds, rateBps) {
|
|
400
|
+
if (expectedFunds <= 0n || rateBps <= 0) {
|
|
401
|
+
return 0n;
|
|
402
|
+
}
|
|
403
|
+
return (expectedFunds * BigInt(rateBps)) / 10000n;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* 将金额均匀拆分成多份
|
|
407
|
+
* @param totalAmount - 总金额
|
|
408
|
+
* @param count - 拆分份数
|
|
409
|
+
* @returns 拆分后的金额数组(最后一份包含余数)
|
|
410
|
+
*/
|
|
411
|
+
function splitAmount(totalAmount, count) {
|
|
412
|
+
if (count <= 0) {
|
|
413
|
+
throw new Error('拆分份数必须大于 0');
|
|
414
|
+
}
|
|
415
|
+
if (count === 1) {
|
|
416
|
+
return [totalAmount];
|
|
417
|
+
}
|
|
418
|
+
const baseAmount = totalAmount / BigInt(count);
|
|
419
|
+
const remainder = totalAmount % BigInt(count);
|
|
420
|
+
// 前 count-1 份使用基础金额,最后一份包含余数
|
|
421
|
+
const amounts = [];
|
|
422
|
+
for (let i = 0; i < count - 1; i++) {
|
|
423
|
+
amounts.push(baseAmount);
|
|
424
|
+
}
|
|
425
|
+
amounts.push(baseAmount + remainder);
|
|
426
|
+
return amounts;
|
|
427
|
+
}
|
|
348
428
|
/**
|
|
349
429
|
* ✅ 规划 nonce
|
|
350
430
|
* 交易顺序:贿赂 → 买入 → 卖出 → 利润
|
|
@@ -380,6 +460,45 @@ async function planNonces({ buyer, seller, extractProfit, needBribeTx, sameAddre
|
|
|
380
460
|
const nonces = await nonceManager.getNextNoncesForWallets([buyer, seller]);
|
|
381
461
|
return { buyerNonce: nonces[0], sellerNonce: nonces[1] };
|
|
382
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* ✅ 规划多笔交易 nonce
|
|
465
|
+
* 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
|
|
466
|
+
*
|
|
467
|
+
* 利润中转需要 PROFIT_HOP_COUNT + 1 个 nonce(支付者→跳1→跳2→利润地址)
|
|
468
|
+
*/
|
|
469
|
+
async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, sameAddress, nonceManager }) {
|
|
470
|
+
// 利润中转交易数(支付者只需要 1 个 nonce,中转钱包的 nonce 由 buildProfitHopTransactions 内部处理)
|
|
471
|
+
const profitNonceCount = extractProfit ? 1 : 0;
|
|
472
|
+
if (sameAddress) {
|
|
473
|
+
// 同一地址:贿赂(可选) + 买入(多笔) + 卖出(多笔) + 利润(可选)
|
|
474
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
475
|
+
const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
|
|
476
|
+
const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
|
|
477
|
+
let idx = 0;
|
|
478
|
+
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
479
|
+
const buyerNonces = nonces.slice(idx, idx + buyCount);
|
|
480
|
+
idx += buyCount;
|
|
481
|
+
const sellerNonces = nonces.slice(idx, idx + sellCount);
|
|
482
|
+
idx += sellCount;
|
|
483
|
+
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
484
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
485
|
+
}
|
|
486
|
+
// 不同地址
|
|
487
|
+
// 买方:buyCount 个 nonce
|
|
488
|
+
// 卖方:贿赂(可选) + sellCount + 利润(可选) 个 nonce
|
|
489
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
490
|
+
const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
|
|
491
|
+
const [buyerNonces, sellerNoncesAll] = await Promise.all([
|
|
492
|
+
nonceManager.getNextNonceBatch(buyer, buyCount),
|
|
493
|
+
nonceManager.getNextNonceBatch(seller, sellerTxCount)
|
|
494
|
+
]);
|
|
495
|
+
let idx = 0;
|
|
496
|
+
const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
|
|
497
|
+
const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
|
|
498
|
+
idx += sellCount;
|
|
499
|
+
const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
|
|
500
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
501
|
+
}
|
|
383
502
|
function buildTransactionRequest(unsigned, { from, nonce, gasLimit, gasPrice, priorityFee, chainId, txType, value }) {
|
|
384
503
|
const tx = {
|
|
385
504
|
...unsigned,
|
|
@@ -55,6 +55,8 @@ export interface PancakeBundleBuyFirstSignParams {
|
|
|
55
55
|
quoteToken?: string;
|
|
56
56
|
quoteTokenDecimals?: number;
|
|
57
57
|
startNonces?: number[];
|
|
58
|
+
buyCount?: number;
|
|
59
|
+
sellCount?: number;
|
|
58
60
|
}
|
|
59
61
|
export interface PancakeBundleBuyFirstParams {
|
|
60
62
|
buyerPrivateKey: string;
|
|
@@ -75,6 +77,8 @@ export type PancakeBuyFirstResult = {
|
|
|
75
77
|
buyAmount: string;
|
|
76
78
|
sellAmount: string;
|
|
77
79
|
profitAmount?: string;
|
|
80
|
+
buyCount?: number;
|
|
81
|
+
sellCount?: number;
|
|
78
82
|
};
|
|
79
83
|
};
|
|
80
84
|
export declare function pancakeBundleBuyFirstMerkle(params: PancakeBundleBuyFirstSignParams): Promise<PancakeBuyFirstResult>;
|
|
@@ -44,9 +44,62 @@ const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router;
|
|
|
44
44
|
// 常量
|
|
45
45
|
const FLAT_FEE = 0n;
|
|
46
46
|
const WBNB_ADDRESS = ADDRESSES.BSC.WBNB;
|
|
47
|
+
// ==================== 多笔买卖常量 ====================
|
|
48
|
+
/** 最大 Bundle 签名数 */
|
|
49
|
+
const MAX_BUNDLE_SIGNATURES = 50;
|
|
50
|
+
/** 贿赂交易数 */
|
|
51
|
+
const BRIBE_TX_COUNT = 1;
|
|
52
|
+
/** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
|
|
53
|
+
const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
|
|
54
|
+
/** 最大买卖交易数 */
|
|
55
|
+
const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
|
|
56
|
+
/** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
|
|
57
|
+
const PROFIT_RATE_PER_TX_BPS = 3;
|
|
58
|
+
/**
|
|
59
|
+
* 验证买卖笔数
|
|
60
|
+
*/
|
|
61
|
+
function validateSwapCounts(buyCount, sellCount) {
|
|
62
|
+
if (buyCount < 1) {
|
|
63
|
+
throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
|
|
64
|
+
}
|
|
65
|
+
if (sellCount < 1) {
|
|
66
|
+
throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
|
|
67
|
+
}
|
|
68
|
+
const totalSwaps = buyCount + sellCount;
|
|
69
|
+
if (totalSwaps > MAX_SWAP_TX_COUNT) {
|
|
70
|
+
throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
|
|
71
|
+
`最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 将金额均匀拆分成多份
|
|
76
|
+
*/
|
|
77
|
+
function splitAmount(totalAmount, count) {
|
|
78
|
+
if (count <= 0) {
|
|
79
|
+
throw new Error('拆分份数必须大于 0');
|
|
80
|
+
}
|
|
81
|
+
if (count === 1) {
|
|
82
|
+
return [totalAmount];
|
|
83
|
+
}
|
|
84
|
+
const baseAmount = totalAmount / BigInt(count);
|
|
85
|
+
const remainder = totalAmount % BigInt(count);
|
|
86
|
+
const amounts = [];
|
|
87
|
+
for (let i = 0; i < count - 1; i++) {
|
|
88
|
+
amounts.push(baseAmount);
|
|
89
|
+
}
|
|
90
|
+
amounts.push(baseAmount + remainder);
|
|
91
|
+
return amounts;
|
|
92
|
+
}
|
|
47
93
|
export async function pancakeBundleBuyFirstMerkle(params) {
|
|
48
|
-
const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
|
|
49
|
-
|
|
94
|
+
const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
|
|
95
|
+
buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
96
|
+
// ✅ 解析并验证买卖笔数
|
|
97
|
+
const buyCount = _buyCount ?? 1;
|
|
98
|
+
const sellCount = _sellCount ?? 1;
|
|
99
|
+
validateSwapCounts(buyCount, sellCount);
|
|
100
|
+
// ✅ 计算利润比例:每笔万分之3
|
|
101
|
+
const totalTxCount = buyCount + sellCount;
|
|
102
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
50
103
|
// ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
|
|
51
104
|
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
52
105
|
const context = createPancakeContext(config);
|
|
@@ -68,10 +121,14 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
68
121
|
buyerFundsWei: buyerFundsInfo.buyerFundsWei,
|
|
69
122
|
provider: context.provider
|
|
70
123
|
});
|
|
71
|
-
|
|
124
|
+
// ✅ 拆分买入和卖出金额
|
|
125
|
+
const buyAmountsWei = splitAmount(buyerFundsInfo.buyerFundsWei, buyCount);
|
|
126
|
+
const sellAmountsWei = splitAmount(quoteResult.quotedTokenOut, sellCount);
|
|
127
|
+
// ✅ 构建多笔买入和卖出交易
|
|
128
|
+
const swapUnsignedArray = await buildMultiRouteTransactions({
|
|
72
129
|
routeParams,
|
|
73
|
-
|
|
74
|
-
|
|
130
|
+
buyAmountsWei,
|
|
131
|
+
sellAmountsWei,
|
|
75
132
|
buyer,
|
|
76
133
|
seller,
|
|
77
134
|
tokenAddress,
|
|
@@ -92,63 +149,65 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
92
149
|
const gasPrice = await getGasPrice(context.provider, config);
|
|
93
150
|
const txType = config.txType ?? 0;
|
|
94
151
|
const nonceManager = new NonceManager(context.provider);
|
|
95
|
-
// ✅
|
|
152
|
+
// ✅ 修复:基于买入金额估算利润(使用动态利润比例)
|
|
96
153
|
const estimatedProfitFromSell = await estimateProfitAmount({
|
|
97
154
|
provider: context.provider,
|
|
98
155
|
tokenAddress,
|
|
99
156
|
sellAmountToken: quoteResult.quotedTokenOut,
|
|
100
|
-
routeParams
|
|
157
|
+
routeParams
|
|
101
158
|
});
|
|
102
|
-
//
|
|
103
|
-
const profitBase = estimatedProfitFromSell > 0n ? estimatedProfitFromSell :
|
|
104
|
-
const profitAmount = profitBase;
|
|
159
|
+
// 使用动态利润比例
|
|
160
|
+
const profitBase = estimatedProfitFromSell > 0n ? estimatedProfitFromSell : buyerFundsInfo.buyerFundsWei;
|
|
161
|
+
const profitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
|
|
105
162
|
// ✅ 获取贿赂金额
|
|
106
163
|
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
107
164
|
? ethers.parseEther(String(config.bribeAmount))
|
|
108
165
|
: 0n;
|
|
109
166
|
const needBribeTx = bribeAmount > 0n;
|
|
110
|
-
// ✅
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
167
|
+
// ✅ 规划多笔交易 nonce(忽略 startNonces,使用新的多笔规划)
|
|
168
|
+
const multiNoncePlan = await planMultiNonces({
|
|
169
|
+
buyer,
|
|
170
|
+
seller,
|
|
171
|
+
buyCount,
|
|
172
|
+
sellCount,
|
|
173
|
+
sameAddress,
|
|
174
|
+
extractProfit: profitAmount > 0n,
|
|
175
|
+
needBribeTx,
|
|
176
|
+
nonceManager
|
|
177
|
+
});
|
|
121
178
|
// ✅ 贿赂交易放在首位(由卖方发送,与利润交易同一钱包)
|
|
122
179
|
let bribeTx = null;
|
|
123
|
-
if (needBribeTx &&
|
|
180
|
+
if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
|
|
124
181
|
bribeTx = await seller.signTransaction({
|
|
125
182
|
to: BLOCKRAZOR_BUILDER_EOA,
|
|
126
183
|
value: bribeAmount,
|
|
127
|
-
nonce:
|
|
184
|
+
nonce: multiNoncePlan.bribeNonce,
|
|
128
185
|
gasPrice,
|
|
129
186
|
gasLimit: 21000n,
|
|
130
187
|
chainId: context.chainId,
|
|
131
188
|
type: txType
|
|
132
189
|
});
|
|
133
190
|
}
|
|
134
|
-
|
|
135
|
-
|
|
191
|
+
// ✅ 并行签名所有买入交易
|
|
192
|
+
const signedBuys = await Promise.all(swapUnsignedArray.buyUnsignedArray.map((unsigned, i) => buyer.signTransaction({
|
|
193
|
+
...unsigned,
|
|
136
194
|
from: buyer.address,
|
|
137
|
-
nonce:
|
|
195
|
+
nonce: multiNoncePlan.buyerNonces[i],
|
|
138
196
|
gasLimit: finalGasLimit,
|
|
139
197
|
gasPrice,
|
|
140
198
|
chainId: context.chainId,
|
|
141
199
|
type: txType
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
|
|
200
|
+
})));
|
|
201
|
+
// ✅ 并行签名所有卖出交易
|
|
202
|
+
const signedSells = await Promise.all(swapUnsignedArray.sellUnsignedArray.map((unsigned, i) => seller.signTransaction({
|
|
203
|
+
...unsigned,
|
|
145
204
|
from: seller.address,
|
|
146
|
-
nonce:
|
|
205
|
+
nonce: multiNoncePlan.sellerNonces[i],
|
|
147
206
|
gasLimit: finalGasLimit,
|
|
148
207
|
gasPrice,
|
|
149
208
|
chainId: context.chainId,
|
|
150
209
|
type: txType
|
|
151
|
-
});
|
|
210
|
+
})));
|
|
152
211
|
nonceManager.clearTemp();
|
|
153
212
|
validateFinalBalances({
|
|
154
213
|
sameAddress,
|
|
@@ -162,17 +221,17 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
162
221
|
provider: context.provider,
|
|
163
222
|
buyerAddress: buyer.address
|
|
164
223
|
});
|
|
165
|
-
// ✅ 组装顺序:贿赂 → 买入 → 卖出
|
|
224
|
+
// ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)
|
|
166
225
|
const allTransactions = [];
|
|
167
226
|
if (bribeTx)
|
|
168
227
|
allTransactions.push(bribeTx);
|
|
169
|
-
allTransactions.push(
|
|
228
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
170
229
|
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
171
230
|
const profitResult = await buildProfitTransaction({
|
|
172
231
|
provider: context.provider,
|
|
173
232
|
seller,
|
|
174
233
|
profitAmount,
|
|
175
|
-
profitNonce:
|
|
234
|
+
profitNonce: multiNoncePlan.profitNonce,
|
|
176
235
|
gasPrice,
|
|
177
236
|
chainId: context.chainId,
|
|
178
237
|
txType
|
|
@@ -192,7 +251,10 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
192
251
|
? ethers.formatEther(buyerFundsInfo.buyerFundsWei)
|
|
193
252
|
: ethers.formatUnits(buyerFundsInfo.buyerFundsWei, quoteTokenDecimals),
|
|
194
253
|
sellAmount: quoteResult.quotedTokenOut.toString(),
|
|
195
|
-
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
|
|
254
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
255
|
+
// ✅ 返回多笔买卖信息
|
|
256
|
+
buyCount,
|
|
257
|
+
sellCount
|
|
196
258
|
}
|
|
197
259
|
};
|
|
198
260
|
}
|
|
@@ -362,6 +424,62 @@ async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountTo
|
|
|
362
424
|
// V3 多跳暂不支持官方合约
|
|
363
425
|
throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
|
|
364
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* ✅ 构建多笔买入和卖出交易
|
|
429
|
+
*/
|
|
430
|
+
async function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmountsWei, buyer, seller, tokenAddress, useNativeToken = true }) {
|
|
431
|
+
const deadline = getDeadline();
|
|
432
|
+
if (routeParams.routeType === 'v2') {
|
|
433
|
+
const { v2Path } = routeParams;
|
|
434
|
+
const reversePath = [...v2Path].reverse();
|
|
435
|
+
const v2RouterBuyer = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
|
|
436
|
+
const v2RouterSeller = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
|
|
437
|
+
// 构建多笔买入交易
|
|
438
|
+
const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => {
|
|
439
|
+
const buyValue = useNativeToken ? amount + FLAT_FEE : FLAT_FEE;
|
|
440
|
+
return v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyValue });
|
|
441
|
+
}));
|
|
442
|
+
// 构建多笔卖出交易
|
|
443
|
+
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
|
|
444
|
+
return { buyUnsignedArray, sellUnsignedArray };
|
|
445
|
+
}
|
|
446
|
+
if (routeParams.routeType === 'v3-single') {
|
|
447
|
+
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
448
|
+
const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
449
|
+
const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
|
|
450
|
+
const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
|
|
451
|
+
// 构建多笔买入交易
|
|
452
|
+
const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => {
|
|
453
|
+
const buyValue = useNativeToken ? amount + FLAT_FEE : FLAT_FEE;
|
|
454
|
+
const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
455
|
+
tokenIn: v3TokenIn,
|
|
456
|
+
tokenOut: v3TokenOut,
|
|
457
|
+
fee: v3Fee,
|
|
458
|
+
recipient: buyer.address,
|
|
459
|
+
amountIn: amount,
|
|
460
|
+
amountOutMinimum: 0n,
|
|
461
|
+
sqrtPriceLimitX96: 0n
|
|
462
|
+
}]);
|
|
463
|
+
return v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
|
|
464
|
+
}));
|
|
465
|
+
// 构建多笔卖出交易
|
|
466
|
+
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => {
|
|
467
|
+
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
468
|
+
tokenIn: v3TokenOut,
|
|
469
|
+
tokenOut: v3TokenIn,
|
|
470
|
+
fee: v3Fee,
|
|
471
|
+
recipient: PANCAKE_V3_ROUTER_ADDRESS,
|
|
472
|
+
amountIn: amount,
|
|
473
|
+
amountOutMinimum: 0n,
|
|
474
|
+
sqrtPriceLimitX96: 0n
|
|
475
|
+
}]);
|
|
476
|
+
const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
|
|
477
|
+
return v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
|
|
478
|
+
}));
|
|
479
|
+
return { buyUnsignedArray, sellUnsignedArray };
|
|
480
|
+
}
|
|
481
|
+
throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
|
|
482
|
+
}
|
|
365
483
|
/**
|
|
366
484
|
* ✅ 使用 quote-helpers 统一报价(估算利润)
|
|
367
485
|
*/
|
|
@@ -416,6 +534,38 @@ async function planNonces({ buyer, seller, sameAddress, extractProfit, needBribe
|
|
|
416
534
|
]);
|
|
417
535
|
return { buyerNonce, sellerNonce };
|
|
418
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* ✅ 规划多笔交易 nonce
|
|
539
|
+
* 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
|
|
540
|
+
*/
|
|
541
|
+
async function planMultiNonces({ buyer, seller, buyCount, sellCount, sameAddress, extractProfit, needBribeTx, nonceManager }) {
|
|
542
|
+
const profitNonceCount = extractProfit ? 1 : 0;
|
|
543
|
+
if (sameAddress) {
|
|
544
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
545
|
+
const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
|
|
546
|
+
const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
|
|
547
|
+
let idx = 0;
|
|
548
|
+
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
549
|
+
const buyerNonces = nonces.slice(idx, idx + buyCount);
|
|
550
|
+
idx += buyCount;
|
|
551
|
+
const sellerNonces = nonces.slice(idx, idx + sellCount);
|
|
552
|
+
idx += sellCount;
|
|
553
|
+
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
554
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
555
|
+
}
|
|
556
|
+
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
557
|
+
const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
|
|
558
|
+
const [buyerNonces, sellerNoncesAll] = await Promise.all([
|
|
559
|
+
nonceManager.getNextNonceBatch(buyer, buyCount),
|
|
560
|
+
nonceManager.getNextNonceBatch(seller, sellerTxCount)
|
|
561
|
+
]);
|
|
562
|
+
let idx = 0;
|
|
563
|
+
const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
|
|
564
|
+
const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
|
|
565
|
+
idx += sellCount;
|
|
566
|
+
const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
|
|
567
|
+
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
568
|
+
}
|
|
419
569
|
/**
|
|
420
570
|
* 构建利润多跳转账交易(强制 2 跳中转)
|
|
421
571
|
*/
|