four-flap-meme-sdk 1.4.6 → 1.4.8

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.
@@ -44,25 +44,34 @@ const DEFAULT_GAS_LIMIT = 300000;
44
44
  const DEADLINE_MINUTES = 20;
45
45
  // ✅ BlockRazor Builder EOA 地址(用于贿赂,仅 BSC 链)
46
46
  const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
47
- // ✅ Router ABI(用于 ERC20 → 原生代币报价)
47
+ // ✅ V2 Router ABI(用于 ERC20 → 原生代币报价)
48
48
  const QUOTE_ROUTER_ABI = [
49
49
  'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
50
50
  ];
51
- // ✅ 各链的报价 Router 和包装代币地址
51
+ // ✅ V3 Quoter ABI(用于 V3 报价)
52
+ const V3_QUOTER_ABI = [
53
+ 'function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut)'
54
+ ];
55
+ // ✅ 各链的报价配置(V2 Router + V3 Quoter)
52
56
  const QUOTE_CONFIG = {
53
57
  BSC: {
54
- router: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // PancakeSwap V2
58
+ router: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // PancakeSwap V2 Router
59
+ quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', // PancakeSwap V3 Quoter
55
60
  wrappedNative: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
56
61
  },
57
62
  MONAD: {
58
63
  router: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9', // PancakeSwap V2
64
+ quoter: '', // TODO: 添加 Monad V3 Quoter
59
65
  wrappedNative: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a', // WMON
60
66
  },
61
67
  XLAYER: {
62
68
  router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // PotatoSwap V2
69
+ quoter: '', // TODO: 添加 XLayer V3 Quoter
63
70
  wrappedNative: '0xe538905cf8410324e03a5a23c1c177a474d59b2b', // WOKB
64
71
  },
65
72
  };
73
+ // ✅ V3 常用费率档位(用于报价尝试)
74
+ const V3_FEE_TIERS = [100, 500, 2500, 10000]; // 0.01%, 0.05%, 0.25%, 1%
66
75
  /**
67
76
  * 截断小数位数,避免超过代币精度导致 parseUnits 报错
68
77
  * 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
@@ -551,16 +560,31 @@ async function getGasPrice(provider, config) {
551
560
  function isNativeToken(quoteToken) {
552
561
  return !quoteToken || quoteToken === ZERO_ADDRESS;
553
562
  }
563
+ // ✅ 常用稳定币地址(用于多跳报价)
564
+ const STABLE_COINS = {
565
+ BSC: [
566
+ '0x55d398326f99059fF775485246999027B3197955', // USDT
567
+ '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC
568
+ '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // BUSD
569
+ ],
570
+ MONAD: [],
571
+ XLAYER: [
572
+ '0x1E4a5963aBFD975d8c9021ce480b42188849D41d', // USDT
573
+ ],
574
+ };
554
575
  /**
555
576
  * ✅ 获取 ERC20 代币 → 原生代币的报价
556
577
  * 用于将 ERC20 利润转换为原生代币(和 core.ts 逻辑一致)
578
+ *
557
579
  * @param provider - Provider 实例
558
580
  * @param tokenAddress - ERC20 代币地址
559
581
  * @param tokenAmount - 代币数量(wei)
560
582
  * @param chain - 链名称
583
+ * @param version - 'v2' | 'v3',指定使用哪个版本的报价
584
+ * @param fee - V3 费率档位(仅 V3 时使用)
561
585
  * @returns 等值的原生代币数量(wei),失败返回 0n
562
586
  */
563
- async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain) {
587
+ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain, version = 'v2', fee) {
564
588
  if (tokenAmount <= 0n)
565
589
  return 0n;
566
590
  const chainUpper = chain.toUpperCase();
@@ -569,16 +593,120 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain)
569
593
  console.warn(`[getTokenToNativeQuote] 不支持的链: ${chain}`);
570
594
  return 0n;
571
595
  }
572
- try {
596
+ const tokenLower = tokenAddress.toLowerCase();
597
+ const wrappedNativeLower = config.wrappedNative.toLowerCase();
598
+ const stableCoins = STABLE_COINS[chainUpper] || [];
599
+ // 如果代币本身就是包装原生代币,直接返回
600
+ if (tokenLower === wrappedNativeLower) {
601
+ return tokenAmount;
602
+ }
603
+ // ==================== V2 报价 ====================
604
+ if (version === 'v2') {
573
605
  const router = new Contract(config.router, QUOTE_ROUTER_ABI, provider);
574
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, config.wrappedNative]);
575
- const nativeAmount = amounts[1];
576
- return nativeAmount;
606
+ // V2 策略 1:直接路径 代币 WBNB
607
+ try {
608
+ const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, config.wrappedNative]);
609
+ const nativeAmount = amounts[1];
610
+ if (nativeAmount > 0n) {
611
+ console.log(`[getTokenToNativeQuote] V2 直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
612
+ return nativeAmount;
613
+ }
614
+ }
615
+ catch {
616
+ // V2 直接路径失败,尝试多跳
617
+ }
618
+ // V2 策略 2:多跳路径 代币 → 稳定币 → WBNB
619
+ for (const stableCoin of stableCoins) {
620
+ // 跳过:代币本身就是稳定币
621
+ if (tokenLower === stableCoin.toLowerCase()) {
622
+ try {
623
+ const amounts = await router.getAmountsOut(tokenAmount, [stableCoin, config.wrappedNative]);
624
+ const nativeAmount = amounts[1];
625
+ if (nativeAmount > 0n) {
626
+ console.log(`[getTokenToNativeQuote] V2 稳定币直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
627
+ return nativeAmount;
628
+ }
629
+ }
630
+ catch {
631
+ continue;
632
+ }
633
+ }
634
+ // 多跳:代币 → 稳定币 → WBNB
635
+ try {
636
+ const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, stableCoin, config.wrappedNative]);
637
+ const nativeAmount = amounts[2];
638
+ if (nativeAmount > 0n) {
639
+ console.log(`[getTokenToNativeQuote] V2 多跳路径成功 (via ${stableCoin.slice(0, 10)}...): ${ethers.formatEther(nativeAmount)} BNB`);
640
+ return nativeAmount;
641
+ }
642
+ }
643
+ catch {
644
+ continue;
645
+ }
646
+ }
647
+ console.warn(`[getTokenToNativeQuote] V2 所有报价路径均失败,跳过利润提取`);
648
+ return 0n;
577
649
  }
578
- catch (error) {
579
- console.warn(`[getTokenToNativeQuote] 报价失败,跳过利润提取:`, error);
580
- return 0n; // 报价失败返回 0(可能是流动性不足或路径不存在)
650
+ // ==================== V3 报价 ====================
651
+ if (version === 'v3') {
652
+ if (!config.quoter) {
653
+ console.warn(`[getTokenToNativeQuote] 该链不支持 V3 Quoter: ${chain}`);
654
+ return 0n;
655
+ }
656
+ const quoter = new Contract(config.quoter, V3_QUOTER_ABI, provider);
657
+ // V3 策略 1:直接路径 代币 → WBNB
658
+ // 如果指定了 fee,只用指定的 fee;否则尝试所有费率
659
+ const feesToTry = fee ? [fee] : V3_FEE_TIERS;
660
+ console.log(`[getTokenToNativeQuote] 开始 V3 报价: token=${tokenAddress.slice(0, 10)}..., amount=${tokenAmount}, feesToTry=${feesToTry}`);
661
+ for (const tryFee of feesToTry) {
662
+ try {
663
+ const amountOut = await quoter.quoteExactInputSingle.staticCall(tokenAddress, config.wrappedNative, tryFee, tokenAmount, 0n);
664
+ if (amountOut > 0n) {
665
+ console.log(`[getTokenToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
666
+ return amountOut;
667
+ }
668
+ }
669
+ catch (err) {
670
+ console.log(`[getTokenToNativeQuote] V3 直接路径失败 (fee=${tryFee}): ${String(err).slice(0, 100)}`);
671
+ continue;
672
+ }
673
+ }
674
+ // V3 策略 2:多跳路径 代币 → USDT → WBNB
675
+ for (const stableCoin of stableCoins) {
676
+ if (tokenLower === stableCoin.toLowerCase())
677
+ continue;
678
+ for (const fee1 of feesToTry) {
679
+ try {
680
+ // 第一跳:代币 → 稳定币
681
+ const midAmount = await quoter.quoteExactInputSingle.staticCall(tokenAddress, stableCoin, fee1, tokenAmount, 0n);
682
+ if (midAmount <= 0n)
683
+ continue;
684
+ console.log(`[getTokenToNativeQuote] V3 第一跳成功: 代币 → ${stableCoin.slice(0, 10)}... = ${midAmount}`);
685
+ // 第二跳:稳定币 → WBNB(尝试多个费率)
686
+ const stableFees = [100, 500, 2500]; // 0.01%, 0.05%, 0.25%
687
+ for (const fee2 of stableFees) {
688
+ try {
689
+ const amountOut = await quoter.quoteExactInputSingle.staticCall(stableCoin, config.wrappedNative, fee2, midAmount, 0n);
690
+ if (amountOut > 0n) {
691
+ console.log(`[getTokenToNativeQuote] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
692
+ return amountOut;
693
+ }
694
+ }
695
+ catch {
696
+ continue;
697
+ }
698
+ }
699
+ }
700
+ catch (err) {
701
+ console.log(`[getTokenToNativeQuote] V3 第一跳失败 (fee=${fee1}): ${err}`);
702
+ continue;
703
+ }
704
+ }
705
+ }
706
+ console.warn(`[getTokenToNativeQuote] V3 所有报价路径均失败,跳过利润提取`);
707
+ return 0n;
581
708
  }
709
+ return 0n;
582
710
  }
583
711
  /**
584
712
  * ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
@@ -713,9 +841,9 @@ export async function directV2BatchBuy(params) {
713
841
  ? Promise.resolve(startNonces)
714
842
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
715
843
  getGasPrice(provider, config),
716
- // ERC20 报价提前并行获取
844
+ // ERC20 报价提前并行获取(V2 买入用 V2 报价)
717
845
  (!useNative && baseProfitWei > 0n && quoteToken)
718
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
846
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v2')
719
847
  : Promise.resolve(baseProfitWei)
720
848
  ]);
721
849
  // 确定最终利润金额
@@ -852,8 +980,8 @@ export async function directV2BatchSell(params) {
852
980
  const nativeProfitPromise = (async () => {
853
981
  try {
854
982
  if (useNativeOutput) {
855
- // 卖出代币 → 得到 BNB:先获取报价,再计算利润
856
- const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
983
+ // 卖出代币 → 得到 BNB:先获取 V2 报价,再计算利润
984
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
857
985
  console.log(`[V2 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
858
986
  if (estimatedBNBOut <= 0n)
859
987
  return 0n;
@@ -862,13 +990,12 @@ export async function directV2BatchSell(params) {
862
990
  return profit;
863
991
  }
864
992
  else if (quoteToken) {
865
- // 卖出代币 → 得到 ERC20:需要先获取代币→ERC20报价,再计算利润
866
- // ⚠️ 这里也需要修复!应该先获取卖出报价
867
- const estimatedQuoteOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
868
- if (estimatedQuoteOut <= 0n)
993
+ // 卖出代币 → 得到 ERC20:先获取 V2 报价,再计算利润
994
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
995
+ if (estimatedBNBOut <= 0n)
869
996
  return 0n;
870
- const baseProfitWei = calculateProfitAmount(estimatedQuoteOut);
871
- return baseProfitWei; // 已经是原生代币,不需要再转换
997
+ const profit = calculateProfitAmount(estimatedBNBOut);
998
+ return profit;
872
999
  }
873
1000
  return 0n;
874
1001
  }
@@ -1000,8 +1127,9 @@ export async function directV3BatchBuy(params) {
1000
1127
  ? Promise.resolve(startNonces)
1001
1128
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
1002
1129
  getGasPrice(provider, config),
1130
+ // ERC20 报价(V3 买入用 V3 报价)
1003
1131
  (!useNative && baseProfitWei > 0n && quoteToken)
1004
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
1132
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v3', fee)
1005
1133
  : Promise.resolve(baseProfitWei)
1006
1134
  ]);
1007
1135
  const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
@@ -1114,8 +1242,8 @@ export async function directV3BatchSell(params) {
1114
1242
  const nativeProfitPromise = (async () => {
1115
1243
  try {
1116
1244
  if (useNativeOutput) {
1117
- // 卖出代币 → 得到 BNB:先获取报价,再计算利润
1118
- const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
1245
+ // 卖出代币 → 得到 BNB:先获取 V3 报价,再计算利润
1246
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
1119
1247
  console.log(`[V3 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
1120
1248
  if (estimatedBNBOut <= 0n)
1121
1249
  return 0n;
@@ -1124,8 +1252,8 @@ export async function directV3BatchSell(params) {
1124
1252
  return profit;
1125
1253
  }
1126
1254
  else if (quoteToken) {
1127
- // 卖出代币 → 得到 ERC20:需要先获取代币→BNB报价,再计算利润
1128
- const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
1255
+ // 卖出代币 → 得到 ERC20:先获取 V3 报价,再计算利润
1256
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
1129
1257
  if (estimatedBNBOut <= 0n)
1130
1258
  return 0n;
1131
1259
  const profit = calculateProfitAmount(estimatedBNBOut);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.6",
3
+ "version": "1.4.8",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",