four-flap-meme-sdk 1.4.6 → 1.4.7

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,109 @@ 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
+ for (const tryFee of feesToTry) {
661
+ try {
662
+ const amountOut = await quoter.quoteExactInputSingle.staticCall(tokenAddress, config.wrappedNative, tryFee, tokenAmount, 0n);
663
+ if (amountOut > 0n) {
664
+ console.log(`[getTokenToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
665
+ return amountOut;
666
+ }
667
+ }
668
+ catch {
669
+ continue;
670
+ }
671
+ }
672
+ // V3 策略 2:多跳路径 代币 → USDT → WBNB
673
+ for (const stableCoin of stableCoins) {
674
+ if (tokenLower === stableCoin.toLowerCase())
675
+ continue;
676
+ for (const fee1 of feesToTry) {
677
+ try {
678
+ // 第一跳:代币 → 稳定币
679
+ const midAmount = await quoter.quoteExactInputSingle.staticCall(tokenAddress, stableCoin, fee1, tokenAmount, 0n);
680
+ if (midAmount <= 0n)
681
+ continue;
682
+ // 第二跳:稳定币 → WBNB(使用固定费率 500 = 0.05%)
683
+ const amountOut = await quoter.quoteExactInputSingle.staticCall(stableCoin, config.wrappedNative, 500, // 稳定币/WBNB 通常使用 0.05% 费率
684
+ midAmount, 0n);
685
+ if (amountOut > 0n) {
686
+ console.log(`[getTokenToNativeQuote] V3 多跳路径成功 (via ${stableCoin.slice(0, 10)}...): ${ethers.formatEther(amountOut)} BNB`);
687
+ return amountOut;
688
+ }
689
+ }
690
+ catch {
691
+ continue;
692
+ }
693
+ }
694
+ }
695
+ console.warn(`[getTokenToNativeQuote] V3 所有报价路径均失败,跳过利润提取`);
696
+ return 0n;
581
697
  }
698
+ return 0n;
582
699
  }
583
700
  /**
584
701
  * ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
@@ -713,9 +830,9 @@ export async function directV2BatchBuy(params) {
713
830
  ? Promise.resolve(startNonces)
714
831
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
715
832
  getGasPrice(provider, config),
716
- // ERC20 报价提前并行获取
833
+ // ERC20 报价提前并行获取(V2 买入用 V2 报价)
717
834
  (!useNative && baseProfitWei > 0n && quoteToken)
718
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
835
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v2')
719
836
  : Promise.resolve(baseProfitWei)
720
837
  ]);
721
838
  // 确定最终利润金额
@@ -852,8 +969,8 @@ export async function directV2BatchSell(params) {
852
969
  const nativeProfitPromise = (async () => {
853
970
  try {
854
971
  if (useNativeOutput) {
855
- // 卖出代币 → 得到 BNB:先获取报价,再计算利润
856
- const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
972
+ // 卖出代币 → 得到 BNB:先获取 V2 报价,再计算利润
973
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
857
974
  console.log(`[V2 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
858
975
  if (estimatedBNBOut <= 0n)
859
976
  return 0n;
@@ -862,13 +979,12 @@ export async function directV2BatchSell(params) {
862
979
  return profit;
863
980
  }
864
981
  else if (quoteToken) {
865
- // 卖出代币 → 得到 ERC20:需要先获取代币→ERC20报价,再计算利润
866
- // ⚠️ 这里也需要修复!应该先获取卖出报价
867
- const estimatedQuoteOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
868
- if (estimatedQuoteOut <= 0n)
982
+ // 卖出代币 → 得到 ERC20:先获取 V2 报价,再计算利润
983
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
984
+ if (estimatedBNBOut <= 0n)
869
985
  return 0n;
870
- const baseProfitWei = calculateProfitAmount(estimatedQuoteOut);
871
- return baseProfitWei; // 已经是原生代币,不需要再转换
986
+ const profit = calculateProfitAmount(estimatedBNBOut);
987
+ return profit;
872
988
  }
873
989
  return 0n;
874
990
  }
@@ -1000,8 +1116,9 @@ export async function directV3BatchBuy(params) {
1000
1116
  ? Promise.resolve(startNonces)
1001
1117
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
1002
1118
  getGasPrice(provider, config),
1119
+ // ERC20 报价(V3 买入用 V3 报价)
1003
1120
  (!useNative && baseProfitWei > 0n && quoteToken)
1004
- ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
1121
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v3', fee)
1005
1122
  : Promise.resolve(baseProfitWei)
1006
1123
  ]);
1007
1124
  const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
@@ -1114,8 +1231,8 @@ export async function directV3BatchSell(params) {
1114
1231
  const nativeProfitPromise = (async () => {
1115
1232
  try {
1116
1233
  if (useNativeOutput) {
1117
- // 卖出代币 → 得到 BNB:先获取报价,再计算利润
1118
- const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
1234
+ // 卖出代币 → 得到 BNB:先获取 V3 报价,再计算利润
1235
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
1119
1236
  console.log(`[V3 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
1120
1237
  if (estimatedBNBOut <= 0n)
1121
1238
  return 0n;
@@ -1124,8 +1241,8 @@ export async function directV3BatchSell(params) {
1124
1241
  return profit;
1125
1242
  }
1126
1243
  else if (quoteToken) {
1127
- // 卖出代币 → 得到 ERC20:需要先获取代币→BNB报价,再计算利润
1128
- const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
1244
+ // 卖出代币 → 得到 ERC20:先获取 V3 报价,再计算利润
1245
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
1129
1246
  if (estimatedBNBOut <= 0n)
1130
1247
  return 0n;
1131
1248
  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.7",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",