@hypurrquant/defi-cli 0.4.0 → 0.5.0

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.
@@ -436,9 +436,9 @@ var init_dist = __esm({
436
436
  getProtocolsByCategory(category) {
437
437
  return this.protocols.filter((p) => p.category === category);
438
438
  }
439
- getProtocolsForChain(chain) {
439
+ getProtocolsForChain(chain, includeUnverified = false) {
440
440
  return this.protocols.filter(
441
- (p) => p.chain.toLowerCase() === chain.toLowerCase()
441
+ (p) => p.chain.toLowerCase() === chain.toLowerCase() && (includeUnverified || p.verified !== false)
442
442
  );
443
443
  }
444
444
  resolveToken(chain, symbol) {
@@ -932,7 +932,7 @@ function createLiquidStaking(entry, rpcUrl) {
932
932
  return new GenericLstAdapter(entry, rpcUrl);
933
933
  }
934
934
  }
935
- function createGauge(entry, rpcUrl) {
935
+ function createGauge(entry, rpcUrl, tokens) {
936
936
  if (entry.interface === "hybra" || entry.contracts?.["gauge_manager"]) {
937
937
  return new HybraGaugeAdapter(entry, rpcUrl);
938
938
  }
@@ -940,7 +940,11 @@ function createGauge(entry, rpcUrl) {
940
940
  case "solidly_v2":
941
941
  case "solidly_cl":
942
942
  case "algebra_v3":
943
- return new SolidlyGaugeAdapter(entry, rpcUrl);
943
+ return new SolidlyGaugeAdapter(entry, rpcUrl, tokens);
944
+ // uniswap_v3 with voter = ve(3,3) CL (e.g., Aerodrome Slipstream, Ramses CL)
945
+ case "uniswap_v3":
946
+ if (entry.contracts?.["voter"]) return new SolidlyGaugeAdapter(entry, rpcUrl, tokens);
947
+ throw DefiError.unsupported(`Gauge interface '${entry.interface}' not supported (no voter contract)`);
944
948
  default:
945
949
  throw DefiError.unsupported(`Gauge interface '${entry.interface}' not supported`);
946
950
  }
@@ -2295,6 +2299,8 @@ var init_dist2 = __esm({
2295
2299
  gauge,
2296
2300
  token0: t0 ? symbolMap.get(t0) ?? t0.slice(0, 10) : "?",
2297
2301
  token1: t1 ? symbolMap.get(t1) ?? t1.slice(0, 10) : "?",
2302
+ token0Addr: t0 ?? void 0,
2303
+ token1Addr: t1 ?? void 0,
2298
2304
  type: "CL"
2299
2305
  });
2300
2306
  }
@@ -2500,6 +2506,7 @@ var init_dist2 = __esm({
2500
2506
  "function getReward(address account, address[] tokens) external",
2501
2507
  "function getReward(uint256 tokenId) external",
2502
2508
  "function earned(address account) external view returns (uint256)",
2509
+ "function earned(address account, uint256 tokenId) external view returns (uint256)",
2503
2510
  "function earned(address token, address account) external view returns (uint256)",
2504
2511
  "function earned(uint256 tokenId) external view returns (uint256)",
2505
2512
  "function rewardRate() external view returns (uint256)",
@@ -2550,7 +2557,8 @@ var init_dist2 = __esm({
2550
2557
  rpcUrl;
2551
2558
  clFactory;
2552
2559
  v2Factory;
2553
- constructor(entry, rpcUrl) {
2560
+ tokens;
2561
+ constructor(entry, rpcUrl, tokens) {
2554
2562
  this.protocolName = entry.name;
2555
2563
  const voter = entry.contracts?.["voter"];
2556
2564
  if (!voter) {
@@ -2563,6 +2571,7 @@ var init_dist2 = __esm({
2563
2571
  this.voter = voter;
2564
2572
  this.veToken = veToken;
2565
2573
  this.rpcUrl = rpcUrl;
2574
+ this.tokens = tokens;
2566
2575
  this.clFactory = entry.contracts?.["cl_factory"] ?? entry.contracts?.["factory"];
2567
2576
  this.v2Factory = entry.contracts?.["pair_factory"] ?? entry.contracts?.["factory"];
2568
2577
  }
@@ -2577,57 +2586,148 @@ var init_dist2 = __esm({
2577
2586
  this._discoverV2GaugedPools(results),
2578
2587
  this._discoverCLGaugedPools(results)
2579
2588
  ]);
2589
+ await this._enrichGaugeMetrics(results);
2580
2590
  return results;
2581
2591
  }
2592
+ /**
2593
+ * Batch query rewardRate, totalSupply, rewardToken for all discovered gauges.
2594
+ * Handles both single-token (rewardRate) and multi-token (rewardData) gauges.
2595
+ */
2596
+ async _enrichGaugeMetrics(pools) {
2597
+ if (!this.rpcUrl || pools.length === 0) return;
2598
+ const _u256Abi = parseAbi10(["function f() view returns (uint256)"]);
2599
+ const calls = [];
2600
+ for (const p of pools) {
2601
+ calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardRate" })]);
2602
+ calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "totalSupply" })]);
2603
+ calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardToken" })]);
2604
+ }
2605
+ const results = await multicallRead(this.rpcUrl, calls).catch(() => []);
2606
+ for (let i = 0; i < pools.length; i++) {
2607
+ const base = i * 3;
2608
+ try {
2609
+ pools[i].rewardRate = results[base] ? decodeFunctionResult3({ abi: _u256Abi, functionName: "f", data: results[base] }) : 0n;
2610
+ } catch {
2611
+ pools[i].rewardRate = 0n;
2612
+ }
2613
+ try {
2614
+ pools[i].totalStaked = results[base + 1] ? decodeFunctionResult3({ abi: _u256Abi, functionName: "f", data: results[base + 1] }) : 0n;
2615
+ } catch {
2616
+ pools[i].totalStaked = 0n;
2617
+ }
2618
+ try {
2619
+ pools[i].rewardToken = results[base + 2] ? decodeAddress2(results[base + 2]) ?? void 0 : void 0;
2620
+ } catch {
2621
+ }
2622
+ }
2623
+ const KNOWN_REWARD_TOKENS = [
2624
+ "0x555570a286F15EbDFE42B66eDE2f724Aa1AB5555",
2625
+ // xRAM
2626
+ "0x5555555555555555555555555555555555555555",
2627
+ // WHYPE
2628
+ "0x067b0C72aa4C6Bd3BFEFfF443c536DCd6a25a9C8",
2629
+ // HYBR
2630
+ "0x07c57E32a3C29D5659bda1d3EFC2E7BF004E3035"
2631
+ // NEST
2632
+ ];
2633
+ const needsFallback = pools.filter((p) => (p.rewardRate ?? 0n) === 0n && (p.totalStaked ?? 0n) > 0n);
2634
+ if (needsFallback.length === 0) return;
2635
+ const rdCalls = [];
2636
+ const rdMeta = [];
2637
+ for (const p of needsFallback) {
2638
+ const poolIdx = pools.indexOf(p);
2639
+ for (const token of KNOWN_REWARD_TOKENS) {
2640
+ rdCalls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardData", args: [token] })]);
2641
+ rdMeta.push({ poolIdx, token });
2642
+ }
2643
+ }
2644
+ const rdResults = await multicallRead(this.rpcUrl, rdCalls).catch(() => []);
2645
+ const _rdAbi = parseAbi10(["function f() view returns (uint256, uint256, uint256, uint256)"]);
2646
+ for (let i = 0; i < rdMeta.length; i++) {
2647
+ const { poolIdx, token } = rdMeta[i];
2648
+ const pool = pools[poolIdx];
2649
+ if ((pool.rewardRate ?? 0n) > 0n) continue;
2650
+ try {
2651
+ if (!rdResults[i]) continue;
2652
+ const decoded = decodeFunctionResult3({ abi: _rdAbi, functionName: "f", data: rdResults[i] });
2653
+ const [periodFinish, rewardRate] = decoded;
2654
+ const now = BigInt(Math.floor(Date.now() / 1e3));
2655
+ if (rewardRate > 0n && periodFinish > now) {
2656
+ pool.rewardRate = rewardRate;
2657
+ pool.rewardToken = token;
2658
+ }
2659
+ } catch {
2660
+ }
2661
+ }
2662
+ }
2582
2663
  async _discoverV2GaugedPools(out) {
2583
2664
  if (!this.rpcUrl || !this.v2Factory) return;
2584
- const v2FactoryAbi = parseAbi10([
2585
- "function allPairsLength() external view returns (uint256)",
2586
- "function allPairs(uint256) external view returns (address)"
2587
- ]);
2588
2665
  const pairAbi = parseAbi10([
2589
2666
  "function token0() external view returns (address)",
2590
2667
  "function token1() external view returns (address)",
2591
2668
  "function stable() external view returns (bool)"
2592
2669
  ]);
2593
2670
  const erc20SymbolAbi = parseAbi10(["function symbol() external view returns (string)"]);
2671
+ const voterLengthAbi = parseAbi10(["function length() external view returns (uint256)"]);
2672
+ const voterPoolsAbi = parseAbi10(["function pools(uint256) external view returns (address)"]);
2673
+ const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
2674
+ const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
2675
+ const gaugesAbi = parseAbi10(["function gauges(address) external view returns (address)"]);
2594
2676
  const client = createPublicClient6({ transport: http6(this.rpcUrl) });
2595
- let pairCount;
2677
+ let pairs = [];
2678
+ let voterPoolCount = 0;
2596
2679
  try {
2597
- pairCount = await client.readContract({
2598
- address: this.v2Factory,
2599
- abi: v2FactoryAbi,
2600
- functionName: "allPairsLength"
2601
- });
2680
+ const len = await client.readContract({ address: this.voter, abi: voterLengthAbi, functionName: "length" });
2681
+ voterPoolCount = Number(len);
2602
2682
  } catch {
2603
- return;
2604
2683
  }
2605
- const count = Number(pairCount);
2606
- if (count === 0) return;
2607
- const pairAddressCalls = [];
2608
- for (let i = 0; i < count; i++) {
2609
- pairAddressCalls.push([
2610
- this.v2Factory,
2611
- encodeFunctionData10({ abi: v2FactoryAbi, functionName: "allPairs", args: [BigInt(i)] })
2612
- ]);
2684
+ if (voterPoolCount > 0) {
2685
+ const MAX_SCAN = 500;
2686
+ const startIdx = Math.max(0, voterPoolCount - MAX_SCAN);
2687
+ const poolCalls = [];
2688
+ for (let i = startIdx; i < voterPoolCount; i++) {
2689
+ poolCalls.push([this.voter, encodeFunctionData10({ abi: voterPoolsAbi, functionName: "pools", args: [BigInt(i)] })]);
2690
+ }
2691
+ const poolResults = await multicallRead(this.rpcUrl, poolCalls);
2692
+ pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
2693
+ } else {
2694
+ const v2FactoryAbi = parseAbi10(["function allPairsLength() view returns (uint256)", "function allPairs(uint256) view returns (address)"]);
2695
+ const solidlyFactoryAbi = parseAbi10(["function allPoolsLength() view returns (uint256)", "function allPools(uint256) view returns (address)"]);
2696
+ let pairCount = 0n;
2697
+ let useSolidly = false;
2698
+ try {
2699
+ pairCount = await client.readContract({ address: this.v2Factory, abi: v2FactoryAbi, functionName: "allPairsLength" });
2700
+ } catch {
2701
+ try {
2702
+ pairCount = await client.readContract({ address: this.v2Factory, abi: solidlyFactoryAbi, functionName: "allPoolsLength" });
2703
+ useSolidly = true;
2704
+ } catch {
2705
+ return;
2706
+ }
2707
+ }
2708
+ const count = Number(pairCount);
2709
+ if (count === 0) return;
2710
+ const MAX_SCAN = 500;
2711
+ const startIdx = Math.max(0, count - MAX_SCAN);
2712
+ const fetchAbi = useSolidly ? solidlyFactoryAbi : v2FactoryAbi;
2713
+ const fetchFn = useSolidly ? "allPools" : "allPairs";
2714
+ const pairCalls = [];
2715
+ for (let i = startIdx; i < count; i++) pairCalls.push([this.v2Factory, encodeFunctionData10({ abi: fetchAbi, functionName: fetchFn, args: [BigInt(i)] })]);
2716
+ const pairResults = await multicallRead(this.rpcUrl, pairCalls);
2717
+ pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
2613
2718
  }
2614
- const pairAddressResults = await multicallRead(this.rpcUrl, pairAddressCalls);
2615
- const pairs = pairAddressResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
2616
2719
  if (pairs.length === 0) return;
2617
- const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
2618
- const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
2619
- const gaugeCalls = pairs.map((pair) => [
2620
- this.voter,
2621
- encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [pair] })
2622
- ]);
2720
+ const gaugeCalls = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [p] })]);
2623
2721
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
2624
- const allNullV2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
2625
- if (allNullV2) {
2626
- const fallbackCalls = pairs.map((pair) => [
2627
- this.voter,
2628
- encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [pair] })
2629
- ]);
2630
- gaugeResults = await multicallRead(this.rpcUrl, fallbackCalls);
2722
+ const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
2723
+ if (allNull) {
2724
+ const fb1 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [p] })]);
2725
+ gaugeResults = await multicallRead(this.rpcUrl, fb1);
2726
+ }
2727
+ const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
2728
+ if (allNull2) {
2729
+ const fb2 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugesAbi, functionName: "gauges", args: [p] })]);
2730
+ gaugeResults = await multicallRead(this.rpcUrl, fb2);
2631
2731
  }
2632
2732
  const gaugedPairs = [];
2633
2733
  for (let i = 0; i < pairs.length; i++) {
@@ -2672,6 +2772,8 @@ var init_dist2 = __esm({
2672
2772
  gauge,
2673
2773
  token0: t0 ? symbolMap.get(t0) ?? t0.slice(0, 10) : "?",
2674
2774
  token1: t1 ? symbolMap.get(t1) ?? t1.slice(0, 10) : "?",
2775
+ token0Addr: t0 ?? void 0,
2776
+ token1Addr: t1 ?? void 0,
2675
2777
  type: "V2",
2676
2778
  stable
2677
2779
  });
@@ -2692,8 +2794,7 @@ var init_dist2 = __esm({
2692
2794
  const erc20SymbolAbi = parseAbi10(["function symbol() external view returns (string)"]);
2693
2795
  const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
2694
2796
  const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
2695
- const tokenEntries = Object.entries(HYPEREVM_TOKENS);
2696
- const tokenAddresses = tokenEntries.map(([, addr]) => addr);
2797
+ const tokenAddresses = this.tokens ?? Object.values(HYPEREVM_TOKENS);
2697
2798
  const pairs = [];
2698
2799
  for (let i = 0; i < tokenAddresses.length; i++) {
2699
2800
  for (let j = i + 1; j < tokenAddresses.length; j++) {
@@ -2798,6 +2899,8 @@ var init_dist2 = __esm({
2798
2899
  gauge,
2799
2900
  token0: symbolMap.get(t0) ?? t0.slice(0, 10),
2800
2901
  token1: symbolMap.get(t1) ?? t1.slice(0, 10),
2902
+ token0Addr: t0,
2903
+ token1Addr: t1,
2801
2904
  type: "CL",
2802
2905
  tickSpacing
2803
2906
  });
@@ -3032,6 +3135,21 @@ var init_dist2 = __esm({
3032
3135
  args: [tokenId]
3033
3136
  });
3034
3137
  }
3138
+ /**
3139
+ * Get pending rewards for an Aerodrome Slipstream CL gauge NFT position.
3140
+ * Uses the earned(address account, uint256 tokenId) overload, which is required
3141
+ * for CL gauges — the single-param earned(address) reverts on these contracts.
3142
+ */
3143
+ async getPendingRewardsByCLTokenId(gauge, user, tokenId) {
3144
+ if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
3145
+ const client = createPublicClient6({ transport: http6(this.rpcUrl) });
3146
+ return await client.readContract({
3147
+ address: gauge,
3148
+ abi: gaugeAbi,
3149
+ functionName: "earned",
3150
+ args: [user, tokenId]
3151
+ });
3152
+ }
3035
3153
  // IVoteEscrow
3036
3154
  async buildCreateLock(amount, lockDuration) {
3037
3155
  const data = encodeFunctionData10({
@@ -3312,7 +3430,8 @@ var init_dist2 = __esm({
3312
3430
  "function findBestPathFromAmountIn(address[] calldata route, uint128 amountIn) external view returns ((address[] route, address[] pairs, uint256[] binSteps, uint256[] versions, uint128[] amounts, uint128[] virtualAmountsWithoutSlippage, uint128[] fees))"
3313
3431
  ]);
3314
3432
  erc20Abi2 = parseAbi12([
3315
- "function symbol() external view returns (string)"
3433
+ "function symbol() external view returns (string)",
3434
+ "function balanceOf(address account) external view returns (uint256)"
3316
3435
  ]);
3317
3436
  _addressAbi = parseAbi12(["function f() external view returns (address)"]);
3318
3437
  _uint256Abi = parseAbi12(["function f() external view returns (uint256)"]);
@@ -3729,6 +3848,74 @@ var init_dist2 = __esm({
3729
3848
  } catch {
3730
3849
  }
3731
3850
  }
3851
+ const stableSymbols = /* @__PURE__ */ new Set(["USDT", "USDC", "USDT0", "MUSD", "AUSD", "USDY", "FDUSD", "USDe", "sUSDe"]);
3852
+ const mntSymbols = /* @__PURE__ */ new Set(["WMNT", "MNT"]);
3853
+ const moeSymbols = /* @__PURE__ */ new Set(["MOE"]);
3854
+ const sixDecimalStables = /* @__PURE__ */ new Set(["USDT", "USDC", "USDT0", "FDUSD"]);
3855
+ const tokenPriceMap = /* @__PURE__ */ new Map();
3856
+ const tokenDecimalsMap = /* @__PURE__ */ new Map();
3857
+ for (const [addr, sym] of symbolMap) {
3858
+ const key = addr.toLowerCase();
3859
+ if (stableSymbols.has(sym)) {
3860
+ tokenPriceMap.set(key, 1);
3861
+ tokenDecimalsMap.set(key, sixDecimalStables.has(sym) ? 6 : 18);
3862
+ } else if (mntSymbols.has(sym)) {
3863
+ tokenPriceMap.set(key, wmntPriceUsd);
3864
+ tokenDecimalsMap.set(key, 18);
3865
+ } else if (moeSymbols.has(sym)) {
3866
+ tokenPriceMap.set(key, moePriceUsd);
3867
+ tokenDecimalsMap.set(key, 18);
3868
+ }
3869
+ }
3870
+ const unknownTokenAddrs = [];
3871
+ for (let i = 0; i < rewardedPairs.length; i++) {
3872
+ for (const addr of [tokenXAddresses[i], tokenYAddresses[i]]) {
3873
+ if (addr && !tokenPriceMap.has(addr.toLowerCase())) {
3874
+ if (!unknownTokenAddrs.some((a) => a.toLowerCase() === addr.toLowerCase())) {
3875
+ unknownTokenAddrs.push(addr);
3876
+ }
3877
+ }
3878
+ }
3879
+ }
3880
+ if (unknownTokenAddrs.length > 0 && this.lbQuoter && this.wmnt && wmntPriceUsd > 0) {
3881
+ const erc20DecimalsAbi = parseAbi12(["function decimals() external view returns (uint8)"]);
3882
+ const decCalls = unknownTokenAddrs.map((addr) => [
3883
+ addr,
3884
+ encodeFunctionData12({ abi: erc20DecimalsAbi, functionName: "decimals" })
3885
+ ]);
3886
+ const decResults = await multicallRead(rpcUrl, decCalls).catch(() => []);
3887
+ for (let i = 0; i < unknownTokenAddrs.length; i++) {
3888
+ const dec = decResults[i] ? Number(decodeUint256Result(decResults[i]) ?? 18n) : 18;
3889
+ tokenDecimalsMap.set(unknownTokenAddrs[i].toLowerCase(), dec);
3890
+ }
3891
+ const quotePromises = unknownTokenAddrs.map(async (tokenAddr) => {
3892
+ try {
3893
+ const dec = tokenDecimalsMap.get(tokenAddr.toLowerCase()) ?? 18;
3894
+ const quoteUnit = 10n ** BigInt(Math.max(dec - 2, 0));
3895
+ const quote = await client.readContract({
3896
+ address: this.lbQuoter,
3897
+ abi: lbQuoterAbi2,
3898
+ functionName: "findBestPathFromAmountIn",
3899
+ args: [[tokenAddr, this.wmnt], quoteUnit]
3900
+ });
3901
+ const amountOut = quote.amounts?.at(-1) ?? 0n;
3902
+ const priceInWmnt = Number(amountOut) / 1e18 * (10 ** dec / Number(quoteUnit));
3903
+ return { addr: tokenAddr, price: priceInWmnt * wmntPriceUsd };
3904
+ } catch {
3905
+ return { addr: tokenAddr, price: 0 };
3906
+ }
3907
+ });
3908
+ const priceResults = await Promise.all(quotePromises);
3909
+ for (const { addr, price } of priceResults) {
3910
+ if (price > 0) tokenPriceMap.set(addr.toLowerCase(), price);
3911
+ }
3912
+ }
3913
+ const getTokenPriceUsd = (_sym, addr) => {
3914
+ return tokenPriceMap.get(addr.toLowerCase()) ?? 0;
3915
+ };
3916
+ const getTokenDecimals = (_sym, addr) => {
3917
+ return tokenDecimalsMap.get(addr.toLowerCase()) ?? 18;
3918
+ };
3732
3919
  const binRequests = [];
3733
3920
  for (let i = 0; i < rewardedPairs.length; i++) {
3734
3921
  const range = poolData[i].range;
@@ -3759,19 +3946,23 @@ var init_dist2 = __esm({
3759
3946
  binReservesY.get(poolIdx).set(binId, decoded[1]);
3760
3947
  }
3761
3948
  }
3762
- const stableSymbols = /* @__PURE__ */ new Set(["USDT", "USDC", "MUSD", "AUSD", "USDY", "FDUSD"]);
3763
- const mntSymbols = /* @__PURE__ */ new Set(["WMNT", "MNT"]);
3764
- const moeSymbols = /* @__PURE__ */ new Set(["MOE"]);
3765
- const sixDecimalStables = /* @__PURE__ */ new Set(["USDT", "USDC", "FDUSD"]);
3766
- const getTokenPriceUsd = (sym) => {
3767
- if (stableSymbols.has(sym)) return 1;
3768
- if (mntSymbols.has(sym)) return wmntPriceUsd;
3769
- if (moeSymbols.has(sym)) return moePriceUsd;
3770
- return 0;
3771
- };
3772
- const getTokenDecimals = (sym) => {
3773
- return sixDecimalStables.has(sym) ? 6 : 18;
3774
- };
3949
+ const poolBalanceX = /* @__PURE__ */ new Map();
3950
+ const poolBalanceY = /* @__PURE__ */ new Map();
3951
+ {
3952
+ const balCalls = [];
3953
+ for (let i = 0; i < rewardedPairs.length; i++) {
3954
+ const tx = tokenXAddresses[i];
3955
+ const ty = tokenYAddresses[i];
3956
+ const pool = rewardedPairs[i].pool;
3957
+ balCalls.push([tx ?? "0x0000000000000000000000000000000000000000", encodeFunctionData12({ abi: erc20Abi2, functionName: "balanceOf", args: [pool] })]);
3958
+ balCalls.push([ty ?? "0x0000000000000000000000000000000000000000", encodeFunctionData12({ abi: erc20Abi2, functionName: "balanceOf", args: [pool] })]);
3959
+ }
3960
+ const balResults = await multicallRead(rpcUrl, balCalls).catch(() => []);
3961
+ for (let i = 0; i < rewardedPairs.length; i++) {
3962
+ poolBalanceX.set(i, decodeUint256Result(balResults[i * 2] ?? null) ?? 0n);
3963
+ poolBalanceY.set(i, decodeUint256Result(balResults[i * 2 + 1] ?? null) ?? 0n);
3964
+ }
3965
+ }
3775
3966
  const results = [];
3776
3967
  for (let i = 0; i < rewardedPairs.length; i++) {
3777
3968
  const { pool, rewarder } = rewardedPairs[i];
@@ -3796,18 +3987,25 @@ var init_dist2 = __esm({
3796
3987
  const maxBin = Number(range[1]);
3797
3988
  rewardedBins = maxBin - minBin + 1;
3798
3989
  if (rxMap && ryMap) {
3799
- const priceX = getTokenPriceUsd(symX);
3800
- const priceY = getTokenPriceUsd(symY);
3801
- const decX = getTokenDecimals(symX);
3802
- const decY = getTokenDecimals(symY);
3990
+ const priceX2 = getTokenPriceUsd(symX, tokenX);
3991
+ const priceY2 = getTokenPriceUsd(symY, tokenY);
3992
+ const decX2 = getTokenDecimals(symX, tokenX);
3993
+ const decY2 = getTokenDecimals(symY, tokenY);
3803
3994
  for (let b = minBin; b <= maxBin; b++) {
3804
3995
  const rx = rxMap.get(b) ?? 0n;
3805
3996
  const ry = ryMap.get(b) ?? 0n;
3806
- rangeTvlUsd += Number(rx) / 10 ** decX * priceX;
3807
- rangeTvlUsd += Number(ry) / 10 ** decY * priceY;
3997
+ rangeTvlUsd += Number(rx) / 10 ** decX2 * priceX2;
3998
+ rangeTvlUsd += Number(ry) / 10 ** decY2 * priceY2;
3808
3999
  }
3809
4000
  }
3810
4001
  }
4002
+ const priceX = getTokenPriceUsd(symX, tokenX);
4003
+ const priceY = getTokenPriceUsd(symY, tokenY);
4004
+ const decX = getTokenDecimals(symX, tokenX);
4005
+ const decY = getTokenDecimals(symY, tokenY);
4006
+ const fullBalX = poolBalanceX.get(i) ?? 0n;
4007
+ const fullBalY = poolBalanceY.get(i) ?? 0n;
4008
+ const poolTvlUsd = Number(fullBalX) / 10 ** decX * priceX + Number(fullBalY) / 10 ** decY * priceY;
3811
4009
  const aprPercent = rangeTvlUsd > 0 && moePriceUsd > 0 ? poolMoePerDay * moePriceUsd * 365 / rangeTvlUsd * 100 : 0;
3812
4010
  results.push({
3813
4011
  pool,
@@ -3824,8 +4022,11 @@ var init_dist2 = __esm({
3824
4022
  isTopPool,
3825
4023
  moePerDay: poolMoePerDay,
3826
4024
  rangeTvlUsd,
4025
+ poolTvlUsd,
3827
4026
  aprPercent,
3828
- rewardedBins
4027
+ rewardedBins,
4028
+ totalMoePerDay: moePerDay,
4029
+ moePriceUsd
3829
4030
  });
3830
4031
  }
3831
4032
  return results;
@@ -5241,7 +5442,7 @@ var init_dist2 = __esm({
5241
5442
  async getRates(asset) {
5242
5443
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5243
5444
  if (!this.defaultVault) {
5244
- throw DefiError.contractError(`[${this.protocolName}] No MetaMorpho vault configured for rate query`);
5445
+ return { protocol: this.protocolName, asset, supply_apy: 0, borrow_variable_apy: 0, borrow_stable_apy: 0, utilization: 0, total_supply: 0n, total_borrow: 0n };
5245
5446
  }
5246
5447
  const [queueLenRaw] = await multicallRead(this.rpcUrl, [
5247
5448
  [this.defaultVault, encodeFunctionData19({ abi: META_MORPHO_ABI, functionName: "supplyQueueLength" })]
@@ -7386,6 +7587,52 @@ server.tool(
7386
7587
  }
7387
7588
  }
7388
7589
  );
7590
+ server.tool(
7591
+ "defi_yield_scan",
7592
+ "Scan all configured chains for the best lending yield opportunities for a given asset. Compares supply APY across Aave V3, Compound, Venus, Morpho, etc. Returns ranked results with chain, protocol, supply_apy, borrow_apy, utilization.",
7593
+ {
7594
+ asset: z.string().optional().describe("Token symbol to scan (default: USDC)")
7595
+ },
7596
+ async ({ asset }) => {
7597
+ const registry = Registry.loadEmbedded();
7598
+ const symbol = asset ?? "USDC";
7599
+ const results = [];
7600
+ const chains = Array.from(registry.chains.keys());
7601
+ await Promise.allSettled(
7602
+ chains.map(async (chainName) => {
7603
+ const chain = registry.getChain(chainName);
7604
+ const rpcUrl = chain.effectiveRpcUrl();
7605
+ const lendingProtos = registry.getProtocolsForChain(chainName).filter((p) => p.category === "lending");
7606
+ for (const proto of lendingProtos) {
7607
+ try {
7608
+ const adapter = createLending(proto, rpcUrl);
7609
+ const tokens = registry.tokens.get(chainName);
7610
+ const token = tokens?.find((t) => t.symbol.toUpperCase() === symbol.toUpperCase());
7611
+ if (!token) continue;
7612
+ const rates = await adapter.getRates(token.address);
7613
+ results.push({
7614
+ chain: chainName,
7615
+ protocol: proto.name,
7616
+ slug: proto.slug,
7617
+ asset: token.symbol,
7618
+ asset_address: token.address,
7619
+ supply_apy: rates.supply_apy,
7620
+ borrow_variable_apy: rates.borrow_variable_apy,
7621
+ utilization: rates.utilization,
7622
+ total_supply: rates.total_supply?.toString(),
7623
+ total_borrow: rates.total_borrow?.toString()
7624
+ });
7625
+ } catch {
7626
+ }
7627
+ }
7628
+ })
7629
+ );
7630
+ results.sort((a, b) => b.supply_apy - a.supply_apy);
7631
+ return {
7632
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
7633
+ };
7634
+ }
7635
+ );
7389
7636
  var transport = new StdioServerTransport();
7390
7637
  await server.connect(transport);
7391
7638
  //# sourceMappingURL=mcp-server.js.map