four-flap-meme-sdk 1.4.29 → 1.4.30
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.
|
@@ -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,17 +92,6 @@ 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
96
|
// 在先买后卖的场景中,卖出收益 ≈ 买入金额(忽略滑点和手续费)
|
|
111
97
|
const estimatedProfitFromSell = await estimateProfitAmount({
|
|
@@ -124,12 +110,11 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
124
110
|
const needBribeTx = bribeAmount > 0n;
|
|
125
111
|
// ✅ 如果前端传入了 startNonces,直接使用(性能优化,避免 nonce 冲突)
|
|
126
112
|
const noncePlan = startNonces && startNonces.length >= (sameAddress ? 1 : 2)
|
|
127
|
-
? buildNoncePlanFromStartNonces(startNonces, sameAddress,
|
|
113
|
+
? buildNoncePlanFromStartNonces(startNonces, sameAddress, profitAmount > 0n, needBribeTx)
|
|
128
114
|
: await planNonces({
|
|
129
115
|
buyer,
|
|
130
116
|
seller,
|
|
131
117
|
sameAddress,
|
|
132
|
-
approvalExists: !!approvalTx,
|
|
133
118
|
extractProfit: profitAmount > 0n,
|
|
134
119
|
needBribeTx,
|
|
135
120
|
nonceManager
|
|
@@ -187,12 +172,10 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
187
172
|
provider: context.provider,
|
|
188
173
|
buyerAddress: buyer.address
|
|
189
174
|
});
|
|
190
|
-
// ✅ 组装顺序:贿赂 →
|
|
175
|
+
// ✅ 组装顺序:贿赂 → 买入 → 卖出 → 利润
|
|
191
176
|
const allTransactions = [];
|
|
192
177
|
if (bribeTx)
|
|
193
178
|
allTransactions.push(bribeTx);
|
|
194
|
-
if (approvalTx)
|
|
195
|
-
allTransactions.push(approvalTx);
|
|
196
179
|
allTransactions.push(signedBuy, signedSell);
|
|
197
180
|
if (profitTx)
|
|
198
181
|
allTransactions.push(profitTx);
|
|
@@ -205,7 +188,6 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
205
188
|
? ethers.formatEther(buyerFundsInfo.buyerFundsWei)
|
|
206
189
|
: ethers.formatUnits(buyerFundsInfo.buyerFundsWei, quoteTokenDecimals),
|
|
207
190
|
sellAmount: quoteResult.quotedTokenOut.toString(),
|
|
208
|
-
hasApproval: !!approvalTx,
|
|
209
191
|
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
|
|
210
192
|
}
|
|
211
193
|
};
|
|
@@ -307,10 +289,6 @@ async function quoteTokenOutput({ routeParams, buyerFundsWei, provider }) {
|
|
|
307
289
|
}
|
|
308
290
|
return { quotedTokenOut: result.amountOut };
|
|
309
291
|
}
|
|
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
292
|
/**
|
|
315
293
|
* ✅ 使用 quote-helpers 统一报价(卖出 → 原生代币)
|
|
316
294
|
*/
|
|
@@ -332,30 +310,6 @@ function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas }) {
|
|
|
332
310
|
: buyerBalance - reserveGas;
|
|
333
311
|
return { buyerNeedTotal, maxBuyerValue };
|
|
334
312
|
}
|
|
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
313
|
async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountToken, buyer, seller, tokenAddress, useNativeToken = true }) {
|
|
360
314
|
const deadline = getDeadline();
|
|
361
315
|
// ✅ ERC20 购买时,value 只需要 FLAT_FEE
|
|
@@ -425,25 +379,23 @@ async function estimateProfitAmount({ provider, tokenAddress, sellAmountToken, r
|
|
|
425
379
|
}
|
|
426
380
|
/**
|
|
427
381
|
* ✅ 规划 nonce
|
|
428
|
-
* 交易顺序:贿赂 →
|
|
382
|
+
* 交易顺序:贿赂 → 买入 → 卖出 → 利润
|
|
429
383
|
*/
|
|
430
|
-
async function planNonces({ buyer, seller, sameAddress,
|
|
384
|
+
async function planNonces({ buyer, seller, sameAddress, extractProfit, needBribeTx, nonceManager }) {
|
|
431
385
|
if (sameAddress) {
|
|
432
|
-
// 同一地址:贿赂(可选) +
|
|
433
|
-
const txCount = countTruthy([needBribeTx,
|
|
386
|
+
// 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
|
|
387
|
+
const txCount = countTruthy([needBribeTx, true, true, extractProfit]);
|
|
434
388
|
const nonces = await nonceManager.getNextNonceBatch(buyer, txCount);
|
|
435
389
|
let idx = 0;
|
|
436
390
|
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
437
|
-
if (approvalExists)
|
|
438
|
-
idx++;
|
|
439
391
|
const buyerNonce = nonces[idx++];
|
|
440
392
|
const sellerNonce = nonces[idx++];
|
|
441
393
|
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
442
394
|
return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
|
|
443
395
|
}
|
|
444
|
-
if (needBribeTx ||
|
|
445
|
-
// 卖方需要多个 nonce:贿赂(可选) +
|
|
446
|
-
const sellerTxCount = countTruthy([needBribeTx,
|
|
396
|
+
if (needBribeTx || extractProfit) {
|
|
397
|
+
// 卖方需要多个 nonce:贿赂(可选) + 卖出 + 利润(可选)
|
|
398
|
+
const sellerTxCount = countTruthy([needBribeTx, true, extractProfit]);
|
|
447
399
|
// ✅ 并行获取 seller 和 buyer 的 nonce
|
|
448
400
|
const [sellerNonces, buyerNonce] = await Promise.all([
|
|
449
401
|
nonceManager.getNextNonceBatch(seller, sellerTxCount),
|
|
@@ -451,8 +403,6 @@ async function planNonces({ buyer, seller, sameAddress, approvalExists, extractP
|
|
|
451
403
|
]);
|
|
452
404
|
let idx = 0;
|
|
453
405
|
const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined;
|
|
454
|
-
if (approvalExists)
|
|
455
|
-
idx++;
|
|
456
406
|
const sellerNonce = sellerNonces[idx++];
|
|
457
407
|
const profitNonce = extractProfit ? sellerNonces[idx] : undefined;
|
|
458
408
|
return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
|
|
@@ -520,14 +470,12 @@ function countTruthy(values) {
|
|
|
520
470
|
* ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化,避免 nonce 冲突)
|
|
521
471
|
* 顺序:同地址时 [baseNonce],不同地址时 [sellerNonce, buyerNonce]
|
|
522
472
|
*/
|
|
523
|
-
function buildNoncePlanFromStartNonces(startNonces, sameAddress,
|
|
473
|
+
function buildNoncePlanFromStartNonces(startNonces, sameAddress, profitNeeded, needBribeTx) {
|
|
524
474
|
if (sameAddress) {
|
|
525
|
-
//
|
|
475
|
+
// 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
|
|
526
476
|
let idx = 0;
|
|
527
477
|
const baseNonce = startNonces[0];
|
|
528
478
|
const bribeNonce = needBribeTx ? baseNonce + idx++ : undefined;
|
|
529
|
-
if (approvalExists)
|
|
530
|
-
idx++;
|
|
531
479
|
const buyerNonce = baseNonce + idx++;
|
|
532
480
|
const sellerNonce = baseNonce + idx++;
|
|
533
481
|
const profitNonce = profitNeeded ? baseNonce + idx : undefined;
|
|
@@ -537,8 +485,6 @@ function buildNoncePlanFromStartNonces(startNonces, sameAddress, approvalExists,
|
|
|
537
485
|
let sellerIdx = 0;
|
|
538
486
|
const sellerBaseNonce = startNonces[0];
|
|
539
487
|
const bribeNonce = needBribeTx ? sellerBaseNonce + sellerIdx++ : undefined;
|
|
540
|
-
if (approvalExists)
|
|
541
|
-
sellerIdx++;
|
|
542
488
|
const sellerNonce = sellerBaseNonce + sellerIdx++;
|
|
543
489
|
const profitNonce = profitNeeded ? sellerBaseNonce + sellerIdx : undefined;
|
|
544
490
|
const buyerNonce = startNonces[1];
|