"""DeFi data service via DefiLlama API (no API key required).""" import logging from typing import Any import httpx logger = logging.getLogger(__name__) LLAMA_BASE = "https://api.llama.fi" STABLES_BASE = "https://stablecoins.llama.fi" YIELDS_BASE = "https://yields.llama.fi" TIMEOUT = 15.0 async def get_top_protocols(limit: int = 20) -> list[dict[str, Any]]: """Fetch top DeFi protocols ranked by TVL from DefiLlama.""" try: async with httpx.AsyncClient(timeout=TIMEOUT) as client: resp = await client.get(f"{LLAMA_BASE}/protocols") resp.raise_for_status() data = resp.json() return [ { "name": p.get("name"), "symbol": p.get("symbol"), "tvl": p.get("tvl"), "chain": p.get("chain"), "chains": p.get("chains", []), "category": p.get("category"), "change_1d": p.get("change_1d"), "change_7d": p.get("change_7d"), } for p in data[:limit] ] except Exception: logger.exception("Failed to fetch top protocols from DefiLlama") return [] async def get_chain_tvls() -> list[dict[str, Any]]: """Fetch TVL rankings for all chains from DefiLlama.""" try: async with httpx.AsyncClient(timeout=TIMEOUT) as client: resp = await client.get(f"{LLAMA_BASE}/v2/chains") resp.raise_for_status() data = resp.json() return [ { "name": c.get("name"), "tvl": c.get("tvl"), "tokenSymbol": c.get("tokenSymbol"), } for c in data ] except Exception: logger.exception("Failed to fetch chain TVLs from DefiLlama") return [] async def get_protocol_tvl(protocol: str) -> float | None: """Fetch current TVL for a specific protocol slug.""" try: async with httpx.AsyncClient(timeout=TIMEOUT) as client: resp = await client.get(f"{LLAMA_BASE}/tvl/{protocol}") resp.raise_for_status() return resp.json() except Exception: logger.exception("Failed to fetch TVL for protocol %s", protocol) return None async def get_yield_pools( chain: str | None = None, project: str | None = None, ) -> list[dict[str, Any]]: """Fetch yield pools from DefiLlama, optionally filtered by chain and/or project. Returns top 20 by TVL descending. """ try: async with httpx.AsyncClient(timeout=TIMEOUT) as client: resp = await client.get(f"{YIELDS_BASE}/pools") resp.raise_for_status() payload = resp.json() pools: list[dict[str, Any]] = payload.get("data", []) if chain is not None: pools = [p for p in pools if p.get("chain") == chain] if project is not None: pools = [p for p in pools if p.get("project") == project] pools = sorted(pools, key=lambda p: p.get("tvlUsd") or 0, reverse=True)[:20] return [ { "pool": p.get("pool"), "chain": p.get("chain"), "project": p.get("project"), "symbol": p.get("symbol"), "tvlUsd": p.get("tvlUsd"), "apy": p.get("apy"), "apyBase": p.get("apyBase"), "apyReward": p.get("apyReward"), } for p in pools ] except Exception: logger.exception("Failed to fetch yield pools from DefiLlama") return [] def _extract_circulating(asset: dict[str, Any]) -> float | None: """Extract the primary circulating supply value from a stablecoin asset dict.""" raw = asset.get("circulating") if raw is None: return None if isinstance(raw, (int, float)): return float(raw) if isinstance(raw, dict): # DefiLlama returns {"peggedUSD": , ...} values = [v for v in raw.values() if isinstance(v, (int, float))] return values[0] if values else None return None async def get_stablecoins(limit: int = 20) -> list[dict[str, Any]]: """Fetch top stablecoins by circulating supply from DefiLlama.""" try: async with httpx.AsyncClient(timeout=TIMEOUT) as client: resp = await client.get(f"{STABLES_BASE}/stablecoins") resp.raise_for_status() payload = resp.json() assets: list[dict[str, Any]] = payload.get("peggedAssets", []) return [ { "name": a.get("name"), "symbol": a.get("symbol"), "pegType": a.get("pegType"), "circulating": _extract_circulating(a), "price": a.get("price"), } for a in assets[:limit] ] except Exception: logger.exception("Failed to fetch stablecoins from DefiLlama") return [] async def get_dex_volumes() -> dict[str, Any] | None: """Fetch DEX volume overview from DefiLlama.""" try: async with httpx.AsyncClient(timeout=TIMEOUT) as client: resp = await client.get(f"{LLAMA_BASE}/overview/dexs") resp.raise_for_status() payload = resp.json() protocols = [ { "name": p.get("name"), "volume24h": p.get("total24h"), } for p in payload.get("protocols", []) ] return { "totalVolume24h": payload.get("total24h"), "totalVolume7d": payload.get("total7d"), "protocols": protocols, } except Exception: logger.exception("Failed to fetch DEX volumes from DefiLlama") return None async def get_protocol_fees() -> list[dict[str, Any]]: """Fetch protocol fees and revenue overview from DefiLlama.""" try: async with httpx.AsyncClient(timeout=TIMEOUT) as client: resp = await client.get(f"{LLAMA_BASE}/overview/fees") resp.raise_for_status() payload = resp.json() return [ { "name": p.get("name"), "fees24h": p.get("total24h"), "revenue24h": p.get("revenue24h"), } for p in payload.get("protocols", []) ] except Exception: logger.exception("Failed to fetch protocol fees from DefiLlama") return []