four-flap-meme-sdk 1.4.10 → 1.4.11

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.
@@ -294,6 +294,132 @@ function calculateProfitAmount(estimatedBNBOut) {
294
294
  }
295
295
  return (estimatedBNBOut * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
296
296
  }
297
+ /**
298
+ * ✅ 获取 ERC20 代币 → 原生代币(BNB)的报价
299
+ * 用于将 ERC20 利润转换为 BNB
300
+ *
301
+ * @param provider - Provider 实例
302
+ * @param tokenAddress - ERC20 代币地址(如 USDT)
303
+ * @param tokenAmount - 代币数量(wei)
304
+ * @param version - 'v2' | 'v3',指定使用哪个版本的报价
305
+ * @param fee - V3 费率档位(仅 V3 时使用)
306
+ * @returns 等值的 BNB 数量(wei),失败返回 0n
307
+ */
308
+ async function getERC20ToNativeQuote(provider, tokenAddress, tokenAmount, version = 'v2', fee) {
309
+ if (tokenAmount <= 0n)
310
+ return 0n;
311
+ const tokenLower = tokenAddress.toLowerCase();
312
+ const wbnbLower = WBNB_ADDRESS.toLowerCase();
313
+ // 如果代币本身就是 WBNB,直接返回
314
+ if (tokenLower === wbnbLower) {
315
+ return tokenAmount;
316
+ }
317
+ // ==================== V2 报价 ====================
318
+ if (version === 'v2') {
319
+ const router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
320
+ // V2 策略 1:直接路径 代币 → WBNB
321
+ try {
322
+ const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, WBNB_ADDRESS]);
323
+ const nativeAmount = amounts[1];
324
+ if (nativeAmount > 0n) {
325
+ console.log(`[getERC20ToNativeQuote] V2 直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
326
+ return nativeAmount;
327
+ }
328
+ }
329
+ catch {
330
+ // V2 直接路径失败,尝试多跳
331
+ }
332
+ // V2 策略 2:多跳路径 代币 → 稳定币 → WBNB
333
+ for (const stableCoin of STABLE_COINS) {
334
+ if (tokenLower === stableCoin.toLowerCase()) {
335
+ // 代币本身就是稳定币
336
+ try {
337
+ const amounts = await router.getAmountsOut(tokenAmount, [stableCoin, WBNB_ADDRESS]);
338
+ const nativeAmount = amounts[1];
339
+ if (nativeAmount > 0n) {
340
+ console.log(`[getERC20ToNativeQuote] V2 稳定币直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
341
+ return nativeAmount;
342
+ }
343
+ }
344
+ catch {
345
+ continue;
346
+ }
347
+ }
348
+ }
349
+ console.warn(`[getERC20ToNativeQuote] V2 所有报价路径均失败`);
350
+ return 0n;
351
+ }
352
+ // ==================== V3 报价 ====================
353
+ if (version === 'v3') {
354
+ const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
355
+ const feesToTry = fee ? [fee] : V3_FEE_TIERS;
356
+ // V3 策略 1:直接路径 代币 → WBNB
357
+ for (const tryFee of feesToTry) {
358
+ try {
359
+ const result = await quoter.quoteExactInputSingle.staticCall({
360
+ tokenIn: tokenAddress,
361
+ tokenOut: WBNB_ADDRESS,
362
+ amountIn: tokenAmount,
363
+ fee: tryFee,
364
+ sqrtPriceLimitX96: 0n
365
+ });
366
+ const amountOut = Array.isArray(result) ? result[0] : result;
367
+ if (amountOut && amountOut > 0n) {
368
+ console.log(`[getERC20ToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
369
+ return amountOut;
370
+ }
371
+ }
372
+ catch {
373
+ continue;
374
+ }
375
+ }
376
+ // V3 策略 2:多跳路径 代币 → 稳定币 → WBNB
377
+ for (const stableCoin of STABLE_COINS) {
378
+ if (tokenLower === stableCoin.toLowerCase())
379
+ continue;
380
+ for (const fee1 of feesToTry) {
381
+ try {
382
+ const midResult = await quoter.quoteExactInputSingle.staticCall({
383
+ tokenIn: tokenAddress,
384
+ tokenOut: stableCoin,
385
+ amountIn: tokenAmount,
386
+ fee: fee1,
387
+ sqrtPriceLimitX96: 0n
388
+ });
389
+ const midAmount = Array.isArray(midResult) ? midResult[0] : midResult;
390
+ if (!midAmount || midAmount <= 0n)
391
+ continue;
392
+ const stableFees = [100, 500, 2500];
393
+ for (const fee2 of stableFees) {
394
+ try {
395
+ const finalResult = await quoter.quoteExactInputSingle.staticCall({
396
+ tokenIn: stableCoin,
397
+ tokenOut: WBNB_ADDRESS,
398
+ amountIn: midAmount,
399
+ fee: fee2,
400
+ sqrtPriceLimitX96: 0n
401
+ });
402
+ const amountOut = Array.isArray(finalResult) ? finalResult[0] : finalResult;
403
+ if (amountOut && amountOut > 0n) {
404
+ console.log(`[getERC20ToNativeQuote] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
405
+ return amountOut;
406
+ }
407
+ }
408
+ catch {
409
+ continue;
410
+ }
411
+ }
412
+ }
413
+ catch {
414
+ continue;
415
+ }
416
+ }
417
+ }
418
+ console.warn(`[getERC20ToNativeQuote] V3 所有报价路径均失败`);
419
+ return 0n;
420
+ }
421
+ return 0n;
422
+ }
297
423
  async function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress }) {
298
424
  const gasCost = gasLimit * gasPrice;
299
425
  if (sameAddress) {
@@ -463,7 +589,24 @@ export async function pancakeBundleSwapMerkle(params) {
463
589
  tokenAddress,
464
590
  useNativeToken
465
591
  });
466
- const profitAmount = calculateProfitAmount(quoteResult.estimatedBNBOut);
592
+ // 修复:利润计算应基于 BNB 数量,不是 ERC20 数量
593
+ // 如果输出是原生代币(BNB),直接使用报价结果
594
+ // 如果输出是 ERC20(如 USDT),需要先转换为 BNB 等值
595
+ let profitAmount;
596
+ if (useNativeToken) {
597
+ // 输出是 BNB,直接计算利润
598
+ profitAmount = calculateProfitAmount(quoteResult.estimatedBNBOut);
599
+ console.log(`[pancakeBundleSwapMerkle] 原生代币利润: ${ethers.formatEther(profitAmount)} BNB`);
600
+ }
601
+ else {
602
+ // 输出是 ERC20,需要先获取 ERC20 → BNB 的报价
603
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
604
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
605
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, quoteResult.estimatedBNBOut, // 这实际上是 ERC20 数量
606
+ version, fee);
607
+ profitAmount = calculateProfitAmount(estimatedBNBValue);
608
+ console.log(`[pancakeBundleSwapMerkle] ERC20→BNB 报价: ${ethers.formatUnits(quoteResult.estimatedBNBOut, quoteTokenDecimals)} ${quoteToken?.slice(0, 10)}... → ${ethers.formatEther(estimatedBNBValue)} BNB, 利润: ${ethers.formatEther(profitAmount)} BNB`);
609
+ }
467
610
  // ✅ 获取贿赂金额
468
611
  const bribeAmount = config.bribeAmount && config.bribeAmount > 0
469
612
  ? ethers.parseEther(String(config.bribeAmount))
@@ -711,8 +854,19 @@ export async function pancakeBatchSwapMerkle(params) {
711
854
  type: txType
712
855
  });
713
856
  }
714
- // 利润交易放在末尾(由卖方发送,与贿赂交易一致)
715
- const profitAmount = calculateProfitAmount(estimatedBNBOut);
857
+ // ✅ 修复:利润计算应基于 BNB 数量,不是 ERC20 数量
858
+ let profitAmount;
859
+ if (useNativeToken) {
860
+ profitAmount = calculateProfitAmount(estimatedBNBOut);
861
+ console.log(`[pancakeBatchSwapMerkle] 原生代币利润: ${ethers.formatEther(profitAmount)} BNB`);
862
+ }
863
+ else {
864
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
865
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
866
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedBNBOut, version, fee);
867
+ profitAmount = calculateProfitAmount(estimatedBNBValue);
868
+ console.log(`[pancakeBatchSwapMerkle] ERC20→BNB 报价: ${ethers.formatUnits(estimatedBNBOut, quoteTokenDecimals)} → ${ethers.formatEther(estimatedBNBValue)} BNB, 利润: ${ethers.formatEther(profitAmount)} BNB`);
869
+ }
716
870
  let profitTx = null;
717
871
  if (profitAmount > 0n) {
718
872
  // ✅ 利润由卖方发送(与贿赂交易同一钱包)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.10",
3
+ "version": "1.4.11",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",