four-flap-meme-sdk 1.4.28 → 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.
@@ -748,23 +748,16 @@ export async function directV3BatchBuy(params) {
748
748
  const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
749
749
  const baseProfitWei = calculateProfitAmount(totalFlowWei);
750
750
  // ✅ 方案 B:并行获取 nonces、gasPrice 和 ERC20 报价
751
- // ✅ 修复:ERC20 报价不传 fee 参数,让报价函数自动选择最佳费率
752
- // 因为 quoteToken/WBNB 池的费率可能与交易池(tokenAddress)的费率不同
753
751
  const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
754
752
  startNonces && startNonces.length === wallets.length
755
753
  ? Promise.resolve(startNonces)
756
754
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
757
755
  getGasPrice(provider, config),
758
- // ERC20 报价(不传 fee,自动选择最佳费率)
756
+ // ERC20 报价(V3 买入用 V3 报价)
759
757
  (!useNative && baseProfitWei > 0n && quoteToken)
760
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v3')
758
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v3', fee)
761
759
  : Promise.resolve(baseProfitWei)
762
760
  ]);
763
- // ✅ 添加详细日志
764
- if (!useNative) {
765
- console.log(`[V3 Buy Profit] quoteToken=${quoteToken}, baseProfitWei=${baseProfitWei} (${ethers.formatEther(baseProfitWei)} quoteToken)`);
766
- console.log(`[V3 Buy Profit] nativeProfitWei=${nativeProfitWei} (${ethers.formatEther(nativeProfitWei)} BNB)`);
767
- }
768
761
  const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
769
762
  const gasLimit = getGasLimit(config, 300000);
770
763
  const txType = config.txType ?? 0;
@@ -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;
@@ -55,6 +53,7 @@ export interface PancakeBundleBuyFirstSignParams {
55
53
  config: PancakeBuyFirstSignConfig;
56
54
  quoteToken?: string;
57
55
  quoteTokenDecimals?: number;
56
+ startNonces?: number[];
58
57
  }
59
58
  export interface PancakeBundleBuyFirstParams {
60
59
  buyerPrivateKey: string;
@@ -73,7 +72,6 @@ export type PancakeBuyFirstResult = {
73
72
  sellerAddress: string;
74
73
  buyAmount: string;
75
74
  sellAmount: string;
76
- hasApproval?: boolean;
77
75
  profitAmount?: string;
78
76
  };
79
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, ERC20_ALLOWANCE_ABI } from '../abis/common.js';
10
+ import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_BALANCE_ABI } from '../abis/common.js';
11
11
  /**
12
12
  * 获取 Gas Limit
13
13
  */
@@ -44,10 +44,9 @@ 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
- const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18 } = params;
48
+ const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
49
+ } = params;
51
50
  // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
52
51
  const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
53
52
  const context = createPancakeContext(config);
@@ -69,7 +68,6 @@ export async function pancakeBundleBuyFirstMerkle(params) {
69
68
  buyerFundsWei: buyerFundsInfo.buyerFundsWei,
70
69
  provider: context.provider
71
70
  });
72
- const decimals = await getTokenDecimals(context.provider, tokenAddress);
73
71
  const swapUnsigned = await buildRouteTransactions({
74
72
  routeParams,
75
73
  buyerFundsWei: buyerFundsInfo.buyerFundsWei,
@@ -94,17 +92,6 @@ export async function pancakeBundleBuyFirstMerkle(params) {
94
92
  const gasPrice = await getGasPrice(context.provider, config);
95
93
  const txType = config.txType ?? 0;
96
94
  const nonceManager = new NonceManager(context.provider);
97
- const approvalTx = await ensureSellerApproval({
98
- tokenAddress,
99
- seller,
100
- provider: context.provider,
101
- decimals,
102
- chainId: context.chainId,
103
- config,
104
- nonceManager,
105
- gasPrice,
106
- txType
107
- });
108
95
  // ✅ 修复:基于买入金额估算利润,而不是基于卖出预估(因为代币可能还没有流动性)
109
96
  // 在先买后卖的场景中,卖出收益 ≈ 买入金额(忽略滑点和手续费)
110
97
  const estimatedProfitFromSell = await estimateProfitAmount({
@@ -121,15 +108,17 @@ export async function pancakeBundleBuyFirstMerkle(params) {
121
108
  ? ethers.parseEther(String(config.bribeAmount))
122
109
  : 0n;
123
110
  const needBribeTx = bribeAmount > 0n;
124
- const noncePlan = await planNonces({
125
- buyer,
126
- seller,
127
- sameAddress,
128
- approvalExists: !!approvalTx,
129
- extractProfit: profitAmount > 0n,
130
- needBribeTx, // ✅ 新增
131
- nonceManager
132
- });
111
+ // 如果前端传入了 startNonces,直接使用(性能优化,避免 nonce 冲突)
112
+ const noncePlan = startNonces && startNonces.length >= (sameAddress ? 1 : 2)
113
+ ? buildNoncePlanFromStartNonces(startNonces, sameAddress, profitAmount > 0n, needBribeTx)
114
+ : await planNonces({
115
+ buyer,
116
+ seller,
117
+ sameAddress,
118
+ extractProfit: profitAmount > 0n,
119
+ needBribeTx,
120
+ nonceManager
121
+ });
133
122
  // ✅ 贿赂交易放在首位(由卖方发送,与利润交易同一钱包)
134
123
  let bribeTx = null;
135
124
  if (needBribeTx && noncePlan.bribeNonce !== undefined) {
@@ -183,12 +172,10 @@ export async function pancakeBundleBuyFirstMerkle(params) {
183
172
  provider: context.provider,
184
173
  buyerAddress: buyer.address
185
174
  });
186
- // ✅ 组装顺序:贿赂 → 授权 → 买入 → 卖出 → 利润
175
+ // ✅ 组装顺序:贿赂 → 买入 → 卖出 → 利润
187
176
  const allTransactions = [];
188
177
  if (bribeTx)
189
178
  allTransactions.push(bribeTx);
190
- if (approvalTx)
191
- allTransactions.push(approvalTx);
192
179
  allTransactions.push(signedBuy, signedSell);
193
180
  if (profitTx)
194
181
  allTransactions.push(profitTx);
@@ -201,7 +188,6 @@ export async function pancakeBundleBuyFirstMerkle(params) {
201
188
  ? ethers.formatEther(buyerFundsInfo.buyerFundsWei)
202
189
  : ethers.formatUnits(buyerFundsInfo.buyerFundsWei, quoteTokenDecimals),
203
190
  sellAmount: quoteResult.quotedTokenOut.toString(),
204
- hasApproval: !!approvalTx,
205
191
  profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
206
192
  }
207
193
  };
@@ -303,10 +289,6 @@ async function quoteTokenOutput({ routeParams, buyerFundsWei, provider }) {
303
289
  }
304
290
  return { quotedTokenOut: result.amountOut };
305
291
  }
306
- async function getTokenDecimals(provider, tokenAddress) {
307
- const erc20 = new Contract(tokenAddress, ['function decimals() view returns (uint8)'], provider);
308
- return await erc20.decimals();
309
- }
310
292
  /**
311
293
  * ✅ 使用 quote-helpers 统一报价(卖出 → 原生代币)
312
294
  */
@@ -328,30 +310,6 @@ function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas }) {
328
310
  : buyerBalance - reserveGas;
329
311
  return { buyerNeedTotal, maxBuyerValue };
330
312
  }
331
- async function ensureSellerApproval({ tokenAddress, seller, provider, decimals, chainId, config, nonceManager, gasPrice, txType }) {
332
- if (config.skipApprovalCheck) {
333
- return null;
334
- }
335
- const erc20Contract = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
336
- // ✅ 使用官方 V2 Router 作为授权目标
337
- const currentAllowance = await erc20Contract.allowance(seller.address, PANCAKE_V2_ROUTER_ADDRESS);
338
- // ✅ 阈值:MaxUint256 / 2,如果授权额度超过这个值,认为是"无限授权"
339
- const halfMaxUint = ethers.MaxUint256 / 2n;
340
- if (currentAllowance >= halfMaxUint) {
341
- return null;
342
- }
343
- const approvalNonce = await nonceManager.getNextNonce(seller);
344
- return await seller.signTransaction({
345
- to: tokenAddress,
346
- data: APPROVE_INTERFACE.encodeFunctionData('approve', [PANCAKE_V2_ROUTER_ADDRESS, ethers.MaxUint256]),
347
- value: 0n,
348
- nonce: approvalNonce,
349
- gasLimit: 80000n,
350
- gasPrice,
351
- chainId,
352
- type: txType
353
- });
354
- }
355
313
  async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountToken, buyer, seller, tokenAddress, useNativeToken = true }) {
356
314
  const deadline = getDeadline();
357
315
  // ✅ ERC20 购买时,value 只需要 FLAT_FEE
@@ -421,25 +379,23 @@ async function estimateProfitAmount({ provider, tokenAddress, sellAmountToken, r
421
379
  }
422
380
  /**
423
381
  * ✅ 规划 nonce
424
- * 交易顺序:贿赂 → 授权 → 买入 → 卖出 → 利润
382
+ * 交易顺序:贿赂 → 买入 → 卖出 → 利润
425
383
  */
426
- async function planNonces({ buyer, seller, sameAddress, approvalExists, extractProfit, needBribeTx, nonceManager }) {
384
+ async function planNonces({ buyer, seller, sameAddress, extractProfit, needBribeTx, nonceManager }) {
427
385
  if (sameAddress) {
428
- // 同一地址:贿赂(可选) + 授权(可选) + 买入 + 卖出 + 利润(可选)
429
- const txCount = countTruthy([needBribeTx, approvalExists, true, true, extractProfit]);
386
+ // 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
387
+ const txCount = countTruthy([needBribeTx, true, true, extractProfit]);
430
388
  const nonces = await nonceManager.getNextNonceBatch(buyer, txCount);
431
389
  let idx = 0;
432
390
  const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
433
- if (approvalExists)
434
- idx++;
435
391
  const buyerNonce = nonces[idx++];
436
392
  const sellerNonce = nonces[idx++];
437
393
  const profitNonce = extractProfit ? nonces[idx] : undefined;
438
394
  return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
439
395
  }
440
- if (needBribeTx || approvalExists || extractProfit) {
441
- // 卖方需要多个 nonce:贿赂(可选) + 授权(可选) + 卖出 + 利润(可选)
442
- const sellerTxCount = countTruthy([needBribeTx, approvalExists, true, extractProfit]);
396
+ if (needBribeTx || extractProfit) {
397
+ // 卖方需要多个 nonce:贿赂(可选) + 卖出 + 利润(可选)
398
+ const sellerTxCount = countTruthy([needBribeTx, true, extractProfit]);
443
399
  // ✅ 并行获取 seller 和 buyer 的 nonce
444
400
  const [sellerNonces, buyerNonce] = await Promise.all([
445
401
  nonceManager.getNextNonceBatch(seller, sellerTxCount),
@@ -447,8 +403,6 @@ async function planNonces({ buyer, seller, sameAddress, approvalExists, extractP
447
403
  ]);
448
404
  let idx = 0;
449
405
  const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined;
450
- if (approvalExists)
451
- idx++;
452
406
  const sellerNonce = sellerNonces[idx++];
453
407
  const profitNonce = extractProfit ? sellerNonces[idx] : undefined;
454
408
  return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
@@ -512,3 +466,27 @@ async function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance,
512
466
  function countTruthy(values) {
513
467
  return values.filter(Boolean).length;
514
468
  }
469
+ /**
470
+ * ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化,避免 nonce 冲突)
471
+ * 顺序:同地址时 [baseNonce],不同地址时 [sellerNonce, buyerNonce]
472
+ */
473
+ function buildNoncePlanFromStartNonces(startNonces, sameAddress, profitNeeded, needBribeTx) {
474
+ if (sameAddress) {
475
+ // 同一地址:贿赂(可选) + 买入 + 卖出 + 利润(可选)
476
+ let idx = 0;
477
+ const baseNonce = startNonces[0];
478
+ const bribeNonce = needBribeTx ? baseNonce + idx++ : undefined;
479
+ const buyerNonce = baseNonce + idx++;
480
+ const sellerNonce = baseNonce + idx++;
481
+ const profitNonce = profitNeeded ? baseNonce + idx : undefined;
482
+ return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
483
+ }
484
+ // 不同地址
485
+ let sellerIdx = 0;
486
+ const sellerBaseNonce = startNonces[0];
487
+ const bribeNonce = needBribeTx ? sellerBaseNonce + sellerIdx++ : undefined;
488
+ const sellerNonce = sellerBaseNonce + sellerIdx++;
489
+ const profitNonce = profitNeeded ? sellerBaseNonce + sellerIdx : undefined;
490
+ const buyerNonce = startNonces[1];
491
+ return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
492
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.28",
3
+ "version": "1.4.30",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",