@hypurrquant/defi-cli 0.4.1 → 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({
@@ -5324,7 +5442,7 @@ var init_dist2 = __esm({
5324
5442
  async getRates(asset) {
5325
5443
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5326
5444
  if (!this.defaultVault) {
5327
- 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 };
5328
5446
  }
5329
5447
  const [queueLenRaw] = await multicallRead(this.rpcUrl, [
5330
5448
  [this.defaultVault, encodeFunctionData19({ abi: META_MORPHO_ABI, functionName: "supplyQueueLength" })]
@@ -7469,6 +7587,52 @@ server.tool(
7469
7587
  }
7470
7588
  }
7471
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
+ );
7472
7636
  var transport = new StdioServerTransport();
7473
7637
  await server.connect(transport);
7474
7638
  //# sourceMappingURL=mcp-server.js.map