four-flap-meme-sdk 1.4.29 → 1.4.31
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 +0 -2
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +7 -48
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +0 -1
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +16 -58
- package/dist/pancake/bundle-buy-first.d.ts +0 -3
- package/dist/pancake/bundle-buy-first.js +12 -68
- package/package.json +1 -1
|
@@ -15,7 +15,6 @@ export interface FourBuyFirstConfig extends CommonBundleConfig {
|
|
|
15
15
|
reserveGasBNB?: number;
|
|
16
16
|
waitForConfirmation?: boolean;
|
|
17
17
|
waitTimeoutMs?: number;
|
|
18
|
-
skipApprovalCheck?: boolean;
|
|
19
18
|
}
|
|
20
19
|
export interface FourBundleBuyFirstSignParams {
|
|
21
20
|
buyerPrivateKey: string;
|
|
@@ -41,7 +40,6 @@ export type FourBuyFirstResult = {
|
|
|
41
40
|
sellerAddress: string;
|
|
42
41
|
buyAmount: string;
|
|
43
42
|
sellAmount: string;
|
|
44
|
-
hasApproval?: boolean;
|
|
45
43
|
profitAmount?: string;
|
|
46
44
|
};
|
|
47
45
|
};
|
|
@@ -52,18 +52,16 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
52
52
|
if (buyerFundsWei <= 0n) {
|
|
53
53
|
throw new Error('buyerFunds 需要大于 0');
|
|
54
54
|
}
|
|
55
|
-
// ✅ 优化:第二批并行 - buyQuote、sellerTokenBal、decimals
|
|
55
|
+
// ✅ 优化:第二批并行 - buyQuote、sellerTokenBal、decimals
|
|
56
56
|
const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
|
|
57
57
|
const erc20 = new Contract(tokenAddress, [
|
|
58
58
|
'function balanceOf(address) view returns (uint256)',
|
|
59
|
-
'function decimals() view returns (uint8)'
|
|
60
|
-
'function allowance(address,address) view returns (uint256)'
|
|
59
|
+
'function decimals() view returns (uint8)'
|
|
61
60
|
], provider);
|
|
62
|
-
const [buyQuote, sellerTokenBal, decimals
|
|
61
|
+
const [buyQuote, sellerTokenBal, decimals] = await Promise.all([
|
|
63
62
|
helper3.tryBuy(tokenAddress, 0n, buyerFundsWei),
|
|
64
63
|
erc20.balanceOf(seller.address),
|
|
65
|
-
erc20.decimals()
|
|
66
|
-
erc20.allowance(seller.address, TM_ADDRESS)
|
|
64
|
+
erc20.decimals()
|
|
67
65
|
]);
|
|
68
66
|
const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
|
|
69
67
|
if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
|
|
@@ -74,14 +72,9 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
74
72
|
if (!sameAddress && sellerTokenBal < sellAmountWei) {
|
|
75
73
|
throw new Error(`卖方代币余额不足: 需要 ${ethers.formatUnits(sellAmountWei, decimals)},实际 ${ethers.formatUnits(sellerTokenBal, decimals)}`);
|
|
76
74
|
}
|
|
77
|
-
// ✅ 检查是否需要授权
|
|
78
|
-
const APPROVAL_THRESHOLD = ethers.parseUnits('1000000000', decimals);
|
|
79
|
-
const needApproval = currentAllowance < APPROVAL_THRESHOLD;
|
|
80
75
|
// ✅ 优化:第三批并行 - trySell、构建交易、获取 nonces
|
|
81
76
|
const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
|
|
82
77
|
const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
|
|
83
|
-
// ✅ 已移除滑点保护:minBuyAmount 固定为 0
|
|
84
|
-
const minBuyAmount = 0n;
|
|
85
78
|
// 预先规划 nonces
|
|
86
79
|
const extractProfit = true;
|
|
87
80
|
const profitRateBps = PROFIT_CONFIG.RATE_BPS_CAPITAL; // 万分之三
|
|
@@ -93,8 +86,6 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
93
86
|
let sellerNonceCount = 1; // 卖出交易
|
|
94
87
|
if (needBribeTx)
|
|
95
88
|
sellerNonceCount++; // 贿赂交易(由卖方发送)
|
|
96
|
-
if (needApproval)
|
|
97
|
-
sellerNonceCount++; // 授权交易
|
|
98
89
|
if (extractProfit)
|
|
99
90
|
sellerNonceCount++; // 利润交易
|
|
100
91
|
// ✅ 优化:使用批量 nonce 获取(单次网络往返)
|
|
@@ -110,31 +101,22 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
110
101
|
}));
|
|
111
102
|
const [sellResult, buyUnsigned, sellUnsigned, noncesResult] = await Promise.all([
|
|
112
103
|
trySell('BSC', config.rpcUrl, tokenAddress, sellAmountWei),
|
|
113
|
-
tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyerFundsWei,
|
|
114
|
-
tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmountWei, 0n),
|
|
104
|
+
tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyerFundsWei, 0n, { value: buyerFundsWei }),
|
|
105
|
+
tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmountWei, 0n),
|
|
115
106
|
getNoncesPromise
|
|
116
107
|
]);
|
|
117
108
|
const { buyerNonces, sellerNonces } = noncesResult;
|
|
118
109
|
const estimatedSellFunds = sellResult.funds;
|
|
119
|
-
// ✅ 已移除滑点保护:minSellFunds 固定为 0
|
|
120
|
-
const minSellFunds = 0n;
|
|
121
110
|
const profitAmount = extractProfit ? (estimatedSellFunds * BigInt(profitRateBps)) / 10000n : 0n;
|
|
122
|
-
// 更新卖出交易的 minSellFunds
|
|
123
|
-
sellUnsigned.data = tmSeller.interface.encodeFunctionData('sellToken', [
|
|
124
|
-
0n, tokenAddress, sellAmountWei, minSellFunds
|
|
125
|
-
]);
|
|
126
111
|
// 分配 nonces
|
|
127
112
|
let buyerNonce;
|
|
128
113
|
let sellerNonce;
|
|
129
114
|
let bribeNonce;
|
|
130
|
-
let approvalNonce;
|
|
131
115
|
let profitNonce;
|
|
132
116
|
if (sameAddress) {
|
|
133
117
|
let idx = 0;
|
|
134
118
|
if (needBribeTx)
|
|
135
119
|
bribeNonce = sellerNonces[idx++];
|
|
136
|
-
if (needApproval)
|
|
137
|
-
approvalNonce = sellerNonces[idx++];
|
|
138
120
|
buyerNonce = sellerNonces[idx++];
|
|
139
121
|
sellerNonce = sellerNonces[idx++];
|
|
140
122
|
if (extractProfit)
|
|
@@ -145,8 +127,6 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
145
127
|
let idx = 0;
|
|
146
128
|
if (needBribeTx)
|
|
147
129
|
bribeNonce = sellerNonces[idx++];
|
|
148
|
-
if (needApproval)
|
|
149
|
-
approvalNonce = sellerNonces[idx++];
|
|
150
130
|
sellerNonce = sellerNonces[idx++];
|
|
151
131
|
if (extractProfit)
|
|
152
132
|
profitNonce = sellerNonces[idx];
|
|
@@ -166,22 +146,6 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
166
146
|
type: txType
|
|
167
147
|
}));
|
|
168
148
|
}
|
|
169
|
-
// 授权交易
|
|
170
|
-
let approvalTx = null;
|
|
171
|
-
if (needApproval && approvalNonce !== undefined) {
|
|
172
|
-
const approveInterface = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
|
|
173
|
-
const approveData = approveInterface.encodeFunctionData('approve', [TM_ADDRESS, ethers.MaxUint256]);
|
|
174
|
-
signPromises.push(seller.signTransaction({
|
|
175
|
-
to: tokenAddress,
|
|
176
|
-
data: approveData,
|
|
177
|
-
value: 0n,
|
|
178
|
-
nonce: approvalNonce,
|
|
179
|
-
gasLimit: 80000n,
|
|
180
|
-
gasPrice,
|
|
181
|
-
chainId: chainIdNum,
|
|
182
|
-
type: txType
|
|
183
|
-
}));
|
|
184
|
-
}
|
|
185
149
|
// 买入和卖出交易
|
|
186
150
|
signPromises.push(buyer.signTransaction({
|
|
187
151
|
...buyUnsigned,
|
|
@@ -222,19 +186,15 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
222
186
|
let idx = 0;
|
|
223
187
|
if (needBribeTx)
|
|
224
188
|
bribeTx = signedTxs[idx++];
|
|
225
|
-
if (needApproval)
|
|
226
|
-
approvalTx = signedTxs[idx++];
|
|
227
189
|
const signedBuy = signedTxs[idx++];
|
|
228
190
|
const signedSell = signedTxs[idx++];
|
|
229
191
|
if (extractProfit && profitAmount > 0n)
|
|
230
192
|
profitTx = signedTxs[idx];
|
|
231
193
|
nonceManager.clearTemp();
|
|
232
|
-
// ✅ 组装交易列表:贿赂 →
|
|
194
|
+
// ✅ 组装交易列表:贿赂 → 买入 → 卖出 → 利润
|
|
233
195
|
const allTransactions = [];
|
|
234
196
|
if (bribeTx)
|
|
235
197
|
allTransactions.push(bribeTx);
|
|
236
|
-
if (approvalTx)
|
|
237
|
-
allTransactions.push(approvalTx);
|
|
238
198
|
allTransactions.push(signedBuy, signedSell);
|
|
239
199
|
if (profitTx)
|
|
240
200
|
allTransactions.push(profitTx);
|
|
@@ -245,7 +205,6 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
245
205
|
sellerAddress: seller.address,
|
|
246
206
|
buyAmount: ethers.formatEther(buyerFundsWei),
|
|
247
207
|
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
248
|
-
hasApproval: !!approvalTx,
|
|
249
208
|
profitAmount: extractProfit ? ethers.formatEther(profitAmount) : undefined
|
|
250
209
|
}
|
|
251
210
|
};
|
|
@@ -18,7 +18,6 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
|
|
|
18
18
|
skipQuoteOnError?: boolean;
|
|
19
19
|
waitForConfirmation?: boolean;
|
|
20
20
|
waitTimeoutMs?: number;
|
|
21
|
-
skipApprovalCheck?: boolean;
|
|
22
21
|
}
|
|
23
22
|
export interface FlapBundleBuyFirstSignParams {
|
|
24
23
|
chain: FlapChain;
|
|
@@ -7,10 +7,8 @@ import { ethers, Contract, Wallet } from 'ethers';
|
|
|
7
7
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
8
8
|
import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
|
|
9
9
|
import { PROFIT_CONFIG, ADDRESSES, ZERO_ADDRESS } from '../../utils/constants.js';
|
|
10
|
-
import { ERC20_BALANCE_ABI,
|
|
10
|
+
import { ERC20_BALANCE_ABI, V2_ROUTER_QUOTE_ABI } from '../../abis/common.js';
|
|
11
11
|
import { getGasPriceConfig, getTxType, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI } from './config.js';
|
|
12
|
-
// ✅ ABI 别名
|
|
13
|
-
const APPROVE_INTERFACE = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
|
|
14
12
|
// ==================== 链常量 ====================
|
|
15
13
|
const BSC_PANCAKE_V2_ROUTER = ADDRESSES.BSC.PancakeV2Router;
|
|
16
14
|
const BSC_WBNB = ADDRESSES.BSC.WBNB;
|
|
@@ -105,7 +103,7 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
105
103
|
]);
|
|
106
104
|
const { buyerFundsWei, buyerBalance } = buyerFundsResult;
|
|
107
105
|
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
108
|
-
// ✅
|
|
106
|
+
// ✅ 获取报价
|
|
109
107
|
const quoteResult = await quoteBuyerOutput({
|
|
110
108
|
portalAddress: chainContext.portalAddress,
|
|
111
109
|
tokenAddress,
|
|
@@ -144,29 +142,15 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
144
142
|
}),
|
|
145
143
|
estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken)
|
|
146
144
|
]);
|
|
147
|
-
// ✅ 修复:先构建授权交易(会消耗 nonce),再规划其他 nonce
|
|
148
|
-
const approvalTx = await buildApprovalTransaction({
|
|
149
|
-
tokenAddress,
|
|
150
|
-
provider: chainContext.provider,
|
|
151
|
-
seller,
|
|
152
|
-
decimals: sellerInfo.decimals,
|
|
153
|
-
portalAddress: chainContext.portalAddress,
|
|
154
|
-
chainId: chainContext.chainId,
|
|
155
|
-
config,
|
|
156
|
-
nonceManager,
|
|
157
|
-
gasPrice,
|
|
158
|
-
txType
|
|
159
|
-
});
|
|
160
145
|
// ✅ 获取贿赂金额
|
|
161
146
|
const bribeAmount = getBribeAmount(config);
|
|
162
147
|
const needBribeTx = bribeAmount > 0n;
|
|
163
|
-
// ✅
|
|
148
|
+
// ✅ 规划 nonce(已移除授权交易)
|
|
164
149
|
const noncePlan = await planNonces({
|
|
165
150
|
buyer,
|
|
166
151
|
seller,
|
|
167
|
-
approvalExists: approvalTx !== null, // ✅ 使用实际值
|
|
168
152
|
extractProfit: true,
|
|
169
|
-
needBribeTx,
|
|
153
|
+
needBribeTx,
|
|
170
154
|
sameAddress,
|
|
171
155
|
nonceManager
|
|
172
156
|
});
|
|
@@ -228,12 +212,10 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
228
212
|
txType
|
|
229
213
|
});
|
|
230
214
|
nonceManager.clearTemp();
|
|
231
|
-
// ✅ 组装顺序:贿赂 →
|
|
215
|
+
// ✅ 组装顺序:贿赂 → 买入 → 卖出 → 利润
|
|
232
216
|
const allTransactions = [];
|
|
233
217
|
if (bribeTx)
|
|
234
218
|
allTransactions.push(bribeTx);
|
|
235
|
-
if (approvalTx)
|
|
236
|
-
allTransactions.push(approvalTx);
|
|
237
219
|
allTransactions.push(signedBuy, signedSell);
|
|
238
220
|
if (profitTx)
|
|
239
221
|
allTransactions.push(profitTx);
|
|
@@ -321,12 +303,11 @@ async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, pr
|
|
|
321
303
|
throw new Error(`买入报价失败: ${error}`);
|
|
322
304
|
}
|
|
323
305
|
}
|
|
324
|
-
// ✅ 已移除滑点保护:minOutToken 固定为 0
|
|
325
306
|
const sellAmountWei = quotedToken;
|
|
326
307
|
if (sellAmountWei <= 0n) {
|
|
327
308
|
throw new Error('卖方卖出数量为 0:报价失败');
|
|
328
309
|
}
|
|
329
|
-
return { quotedToken,
|
|
310
|
+
return { quotedToken, sellAmountWei };
|
|
330
311
|
}
|
|
331
312
|
async function ensureSellerBalance({ tokenAddress, provider, seller, sellAmountWei, skipBalanceCheck }) {
|
|
332
313
|
const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, provider);
|
|
@@ -339,25 +320,6 @@ async function ensureSellerBalance({ tokenAddress, provider, seller, sellAmountW
|
|
|
339
320
|
}
|
|
340
321
|
return { decimals };
|
|
341
322
|
}
|
|
342
|
-
async function buildApprovalTransaction({ tokenAddress, provider, seller, decimals, portalAddress, chainId, config, nonceManager, gasPrice, txType }) {
|
|
343
|
-
const allowanceContract = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
|
|
344
|
-
const currentAllowance = await allowanceContract.allowance(seller.address, portalAddress);
|
|
345
|
-
const approvalThreshold = ethers.parseUnits('1000000000', decimals);
|
|
346
|
-
if (currentAllowance >= approvalThreshold) {
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
const approvalNonce = await nonceManager.getNextNonce(seller);
|
|
350
|
-
return await seller.signTransaction({
|
|
351
|
-
to: tokenAddress,
|
|
352
|
-
data: APPROVE_INTERFACE.encodeFunctionData('approve', [portalAddress, ethers.MaxUint256]),
|
|
353
|
-
value: 0n,
|
|
354
|
-
nonce: approvalNonce,
|
|
355
|
-
gasLimit: 80000n,
|
|
356
|
-
gasPrice,
|
|
357
|
-
chainId,
|
|
358
|
-
type: txType
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
323
|
async function estimateSellFunds(portal, tokenAddress, sellAmountWei, outputToken = ZERO_ADDRESS // ✅ 新增:输出代币地址
|
|
362
324
|
) {
|
|
363
325
|
try {
|
|
@@ -379,41 +341,37 @@ function calculateProfitAmount(expectedFunds) {
|
|
|
379
341
|
return (expectedFunds * BigInt(PROFIT_CONFIG.RATE_BPS_CAPITAL)) / 10000n;
|
|
380
342
|
}
|
|
381
343
|
/**
|
|
382
|
-
* ✅
|
|
383
|
-
* 交易顺序:贿赂 →
|
|
344
|
+
* ✅ 规划 nonce
|
|
345
|
+
* 交易顺序:贿赂 → 买入 → 卖出 → 利润
|
|
384
346
|
*/
|
|
385
|
-
async function planNonces({ buyer, seller,
|
|
347
|
+
async function planNonces({ buyer, seller, extractProfit, needBribeTx, sameAddress, nonceManager }) {
|
|
386
348
|
if (sameAddress) {
|
|
387
|
-
//
|
|
388
|
-
const txCount = countTruthy([needBribeTx,
|
|
349
|
+
// 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
|
|
350
|
+
const txCount = countTruthy([needBribeTx, true, true, extractProfit]);
|
|
389
351
|
const nonces = await nonceManager.getNextNonceBatch(buyer, txCount);
|
|
390
352
|
let idx = 0;
|
|
391
353
|
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
392
|
-
if (approvalExists)
|
|
393
|
-
idx++;
|
|
394
354
|
const buyerNonce = nonces[idx++];
|
|
395
355
|
const sellerNonce = nonces[idx++];
|
|
396
356
|
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
397
357
|
return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
|
|
398
358
|
}
|
|
399
|
-
if (needBribeTx ||
|
|
400
|
-
// 卖方需要多个 nonce:贿赂(可选) +
|
|
401
|
-
const sellerTxCount = countTruthy([needBribeTx,
|
|
402
|
-
// ✅
|
|
359
|
+
if (needBribeTx || extractProfit) {
|
|
360
|
+
// 卖方需要多个 nonce:贿赂(可选) + 卖出 + 利润(可选)
|
|
361
|
+
const sellerTxCount = countTruthy([needBribeTx, true, extractProfit]);
|
|
362
|
+
// ✅ 并行获取 buyer 和 seller 的 nonce
|
|
403
363
|
const [sellerNonces, buyerNonces] = await Promise.all([
|
|
404
364
|
nonceManager.getNextNonceBatch(seller, sellerTxCount),
|
|
405
365
|
nonceManager.getNextNoncesForWallets([buyer])
|
|
406
366
|
]);
|
|
407
367
|
let idx = 0;
|
|
408
368
|
const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined;
|
|
409
|
-
if (approvalExists)
|
|
410
|
-
idx++;
|
|
411
369
|
const sellerNonce = sellerNonces[idx++];
|
|
412
370
|
const profitNonce = extractProfit ? sellerNonces[idx] : undefined;
|
|
413
371
|
const buyerNonce = buyerNonces[0];
|
|
414
372
|
return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
|
|
415
373
|
}
|
|
416
|
-
// ✅
|
|
374
|
+
// ✅ 使用 getNextNoncesForWallets 批量获取
|
|
417
375
|
const nonces = await nonceManager.getNextNoncesForWallets([buyer, seller]);
|
|
418
376
|
return { buyerNonce: nonces[0], sellerNonce: nonces[1] };
|
|
419
377
|
}
|
|
@@ -14,7 +14,6 @@ export interface PancakeBuyFirstSignConfig {
|
|
|
14
14
|
chainId?: number;
|
|
15
15
|
reserveGasBNB?: number;
|
|
16
16
|
skipQuoteOnError?: boolean;
|
|
17
|
-
skipApprovalCheck?: boolean;
|
|
18
17
|
bribeAmount?: number;
|
|
19
18
|
}
|
|
20
19
|
export type SwapRouteType = 'v2' | 'v3-single' | 'v3-multi';
|
|
@@ -43,7 +42,6 @@ export interface PancakeBuyFirstConfig extends CommonBundleConfig {
|
|
|
43
42
|
reserveGasBNB?: number;
|
|
44
43
|
waitForConfirmation?: boolean;
|
|
45
44
|
waitTimeoutMs?: number;
|
|
46
|
-
skipApprovalCheck?: boolean;
|
|
47
45
|
}
|
|
48
46
|
export interface PancakeBundleBuyFirstSignParams {
|
|
49
47
|
buyerPrivateKey: string;
|
|
@@ -74,7 +72,6 @@ export type PancakeBuyFirstResult = {
|
|
|
74
72
|
sellerAddress: string;
|
|
75
73
|
buyAmount: string;
|
|
76
74
|
sellAmount: string;
|
|
77
|
-
hasApproval?: boolean;
|
|
78
75
|
profitAmount?: string;
|
|
79
76
|
};
|
|
80
77
|
};
|
|
@@ -7,7 +7,7 @@ import { ethers, Contract, Wallet } from 'ethers';
|
|
|
7
7
|
import { NonceManager, getDeadline } from '../utils/bundle-helpers.js';
|
|
8
8
|
import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA, ZERO_ADDRESS } from '../utils/constants.js';
|
|
9
9
|
import { quoteV2, quoteV3, getTokenToNativeQuote, getWrappedNativeAddress } from '../utils/quote-helpers.js';
|
|
10
|
-
import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_BALANCE_ABI
|
|
10
|
+
import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_BALANCE_ABI } from '../abis/common.js';
|
|
11
11
|
/**
|
|
12
12
|
* 获取 Gas Limit
|
|
13
13
|
*/
|
|
@@ -44,8 +44,6 @@ 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
|
-
// ✅ getDeadline 从 bundle-helpers.js 导入
|
|
48
|
-
const APPROVE_INTERFACE = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
|
|
49
47
|
export async function pancakeBundleBuyFirstMerkle(params) {
|
|
50
48
|
const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
|
|
51
49
|
} = params;
|
|
@@ -70,7 +68,6 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
70
68
|
buyerFundsWei: buyerFundsInfo.buyerFundsWei,
|
|
71
69
|
provider: context.provider
|
|
72
70
|
});
|
|
73
|
-
const decimals = await getTokenDecimals(context.provider, tokenAddress);
|
|
74
71
|
const swapUnsigned = await buildRouteTransactions({
|
|
75
72
|
routeParams,
|
|
76
73
|
buyerFundsWei: buyerFundsInfo.buyerFundsWei,
|
|
@@ -95,19 +92,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
95
92
|
const gasPrice = await getGasPrice(context.provider, config);
|
|
96
93
|
const txType = config.txType ?? 0;
|
|
97
94
|
const nonceManager = new NonceManager(context.provider);
|
|
98
|
-
const approvalTx = await ensureSellerApproval({
|
|
99
|
-
tokenAddress,
|
|
100
|
-
seller,
|
|
101
|
-
provider: context.provider,
|
|
102
|
-
decimals,
|
|
103
|
-
chainId: context.chainId,
|
|
104
|
-
config,
|
|
105
|
-
nonceManager,
|
|
106
|
-
gasPrice,
|
|
107
|
-
txType
|
|
108
|
-
});
|
|
109
95
|
// ✅ 修复:基于买入金额估算利润,而不是基于卖出预估(因为代币可能还没有流动性)
|
|
110
|
-
// 在先买后卖的场景中,卖出收益 ≈ 买入金额(忽略滑点和手续费)
|
|
111
96
|
const estimatedProfitFromSell = await estimateProfitAmount({
|
|
112
97
|
provider: context.provider,
|
|
113
98
|
tokenAddress,
|
|
@@ -124,12 +109,11 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
124
109
|
const needBribeTx = bribeAmount > 0n;
|
|
125
110
|
// ✅ 如果前端传入了 startNonces,直接使用(性能优化,避免 nonce 冲突)
|
|
126
111
|
const noncePlan = startNonces && startNonces.length >= (sameAddress ? 1 : 2)
|
|
127
|
-
? buildNoncePlanFromStartNonces(startNonces, sameAddress,
|
|
112
|
+
? buildNoncePlanFromStartNonces(startNonces, sameAddress, profitAmount > 0n, needBribeTx)
|
|
128
113
|
: await planNonces({
|
|
129
114
|
buyer,
|
|
130
115
|
seller,
|
|
131
116
|
sameAddress,
|
|
132
|
-
approvalExists: !!approvalTx,
|
|
133
117
|
extractProfit: profitAmount > 0n,
|
|
134
118
|
needBribeTx,
|
|
135
119
|
nonceManager
|
|
@@ -187,12 +171,10 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
187
171
|
provider: context.provider,
|
|
188
172
|
buyerAddress: buyer.address
|
|
189
173
|
});
|
|
190
|
-
// ✅ 组装顺序:贿赂 →
|
|
174
|
+
// ✅ 组装顺序:贿赂 → 买入 → 卖出 → 利润
|
|
191
175
|
const allTransactions = [];
|
|
192
176
|
if (bribeTx)
|
|
193
177
|
allTransactions.push(bribeTx);
|
|
194
|
-
if (approvalTx)
|
|
195
|
-
allTransactions.push(approvalTx);
|
|
196
178
|
allTransactions.push(signedBuy, signedSell);
|
|
197
179
|
if (profitTx)
|
|
198
180
|
allTransactions.push(profitTx);
|
|
@@ -205,7 +187,6 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
205
187
|
? ethers.formatEther(buyerFundsInfo.buyerFundsWei)
|
|
206
188
|
: ethers.formatUnits(buyerFundsInfo.buyerFundsWei, quoteTokenDecimals),
|
|
207
189
|
sellAmount: quoteResult.quotedTokenOut.toString(),
|
|
208
|
-
hasApproval: !!approvalTx,
|
|
209
190
|
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
|
|
210
191
|
}
|
|
211
192
|
};
|
|
@@ -307,10 +288,6 @@ async function quoteTokenOutput({ routeParams, buyerFundsWei, provider }) {
|
|
|
307
288
|
}
|
|
308
289
|
return { quotedTokenOut: result.amountOut };
|
|
309
290
|
}
|
|
310
|
-
async function getTokenDecimals(provider, tokenAddress) {
|
|
311
|
-
const erc20 = new Contract(tokenAddress, ['function decimals() view returns (uint8)'], provider);
|
|
312
|
-
return await erc20.decimals();
|
|
313
|
-
}
|
|
314
291
|
/**
|
|
315
292
|
* ✅ 使用 quote-helpers 统一报价(卖出 → 原生代币)
|
|
316
293
|
*/
|
|
@@ -321,7 +298,6 @@ async function quoteSellerNative({ provider, tokenAddress, sellAmountToken, rout
|
|
|
321
298
|
return await getTokenToNativeQuote(provider, tokenAddress, sellAmountToken, 'BSC', version, fee);
|
|
322
299
|
}
|
|
323
300
|
function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas }) {
|
|
324
|
-
// ✅ 已移除滑点保护:直接使用报价金额
|
|
325
301
|
const estimatedBuyerNeed = quotedNative;
|
|
326
302
|
const buyerNeedTotal = estimatedBuyerNeed + reserveGas;
|
|
327
303
|
if (buyerBalance < buyerNeedTotal) {
|
|
@@ -332,30 +308,6 @@ function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas }) {
|
|
|
332
308
|
: buyerBalance - reserveGas;
|
|
333
309
|
return { buyerNeedTotal, maxBuyerValue };
|
|
334
310
|
}
|
|
335
|
-
async function ensureSellerApproval({ tokenAddress, seller, provider, decimals, chainId, config, nonceManager, gasPrice, txType }) {
|
|
336
|
-
if (config.skipApprovalCheck) {
|
|
337
|
-
return null;
|
|
338
|
-
}
|
|
339
|
-
const erc20Contract = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
|
|
340
|
-
// ✅ 使用官方 V2 Router 作为授权目标
|
|
341
|
-
const currentAllowance = await erc20Contract.allowance(seller.address, PANCAKE_V2_ROUTER_ADDRESS);
|
|
342
|
-
// ✅ 阈值:MaxUint256 / 2,如果授权额度超过这个值,认为是"无限授权"
|
|
343
|
-
const halfMaxUint = ethers.MaxUint256 / 2n;
|
|
344
|
-
if (currentAllowance >= halfMaxUint) {
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
347
|
-
const approvalNonce = await nonceManager.getNextNonce(seller);
|
|
348
|
-
return await seller.signTransaction({
|
|
349
|
-
to: tokenAddress,
|
|
350
|
-
data: APPROVE_INTERFACE.encodeFunctionData('approve', [PANCAKE_V2_ROUTER_ADDRESS, ethers.MaxUint256]),
|
|
351
|
-
value: 0n,
|
|
352
|
-
nonce: approvalNonce,
|
|
353
|
-
gasLimit: 80000n,
|
|
354
|
-
gasPrice,
|
|
355
|
-
chainId,
|
|
356
|
-
type: txType
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
311
|
async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountToken, buyer, seller, tokenAddress, useNativeToken = true }) {
|
|
360
312
|
const deadline = getDeadline();
|
|
361
313
|
// ✅ ERC20 购买时,value 只需要 FLAT_FEE
|
|
@@ -425,25 +377,23 @@ async function estimateProfitAmount({ provider, tokenAddress, sellAmountToken, r
|
|
|
425
377
|
}
|
|
426
378
|
/**
|
|
427
379
|
* ✅ 规划 nonce
|
|
428
|
-
* 交易顺序:贿赂 →
|
|
380
|
+
* 交易顺序:贿赂 → 买入 → 卖出 → 利润
|
|
429
381
|
*/
|
|
430
|
-
async function planNonces({ buyer, seller, sameAddress,
|
|
382
|
+
async function planNonces({ buyer, seller, sameAddress, extractProfit, needBribeTx, nonceManager }) {
|
|
431
383
|
if (sameAddress) {
|
|
432
|
-
// 同一地址:贿赂(可选) +
|
|
433
|
-
const txCount = countTruthy([needBribeTx,
|
|
384
|
+
// 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
|
|
385
|
+
const txCount = countTruthy([needBribeTx, true, true, extractProfit]);
|
|
434
386
|
const nonces = await nonceManager.getNextNonceBatch(buyer, txCount);
|
|
435
387
|
let idx = 0;
|
|
436
388
|
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
437
|
-
if (approvalExists)
|
|
438
|
-
idx++;
|
|
439
389
|
const buyerNonce = nonces[idx++];
|
|
440
390
|
const sellerNonce = nonces[idx++];
|
|
441
391
|
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
442
392
|
return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
|
|
443
393
|
}
|
|
444
|
-
if (needBribeTx ||
|
|
445
|
-
// 卖方需要多个 nonce:贿赂(可选) +
|
|
446
|
-
const sellerTxCount = countTruthy([needBribeTx,
|
|
394
|
+
if (needBribeTx || extractProfit) {
|
|
395
|
+
// 卖方需要多个 nonce:贿赂(可选) + 卖出 + 利润(可选)
|
|
396
|
+
const sellerTxCount = countTruthy([needBribeTx, true, extractProfit]);
|
|
447
397
|
// ✅ 并行获取 seller 和 buyer 的 nonce
|
|
448
398
|
const [sellerNonces, buyerNonce] = await Promise.all([
|
|
449
399
|
nonceManager.getNextNonceBatch(seller, sellerTxCount),
|
|
@@ -451,8 +401,6 @@ async function planNonces({ buyer, seller, sameAddress, approvalExists, extractP
|
|
|
451
401
|
]);
|
|
452
402
|
let idx = 0;
|
|
453
403
|
const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined;
|
|
454
|
-
if (approvalExists)
|
|
455
|
-
idx++;
|
|
456
404
|
const sellerNonce = sellerNonces[idx++];
|
|
457
405
|
const profitNonce = extractProfit ? sellerNonces[idx] : undefined;
|
|
458
406
|
return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
|
|
@@ -520,14 +468,12 @@ function countTruthy(values) {
|
|
|
520
468
|
* ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化,避免 nonce 冲突)
|
|
521
469
|
* 顺序:同地址时 [baseNonce],不同地址时 [sellerNonce, buyerNonce]
|
|
522
470
|
*/
|
|
523
|
-
function buildNoncePlanFromStartNonces(startNonces, sameAddress,
|
|
471
|
+
function buildNoncePlanFromStartNonces(startNonces, sameAddress, profitNeeded, needBribeTx) {
|
|
524
472
|
if (sameAddress) {
|
|
525
|
-
//
|
|
473
|
+
// 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
|
|
526
474
|
let idx = 0;
|
|
527
475
|
const baseNonce = startNonces[0];
|
|
528
476
|
const bribeNonce = needBribeTx ? baseNonce + idx++ : undefined;
|
|
529
|
-
if (approvalExists)
|
|
530
|
-
idx++;
|
|
531
477
|
const buyerNonce = baseNonce + idx++;
|
|
532
478
|
const sellerNonce = baseNonce + idx++;
|
|
533
479
|
const profitNonce = profitNeeded ? baseNonce + idx : undefined;
|
|
@@ -537,8 +483,6 @@ function buildNoncePlanFromStartNonces(startNonces, sameAddress, approvalExists,
|
|
|
537
483
|
let sellerIdx = 0;
|
|
538
484
|
const sellerBaseNonce = startNonces[0];
|
|
539
485
|
const bribeNonce = needBribeTx ? sellerBaseNonce + sellerIdx++ : undefined;
|
|
540
|
-
if (approvalExists)
|
|
541
|
-
sellerIdx++;
|
|
542
486
|
const sellerNonce = sellerBaseNonce + sellerIdx++;
|
|
543
487
|
const profitNonce = profitNeeded ? sellerBaseNonce + sellerIdx : undefined;
|
|
544
488
|
const buyerNonce = startNonces[1];
|