merge: chore/cleanup-remove-bloat-and-secrets into main
This commit is contained in:
commit
bde2f3a97d
1173 changed files with 437609 additions and 0 deletions
140
app/routers/tvl_verifier.py
Normal file
140
app/routers/tvl_verifier.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#!/usr/bin/env python3
|
||||
"""#16 — DeFiLlama Protocol Mismatch Detector. Compares DeFiLlama TVL against
|
||||
actual on-chain balances. Flags inflated TVL or impending rug."""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
router = APIRouter(prefix="/api/v1/tvl-verify", tags=["tvl-verifier"])
|
||||
|
||||
DEFILLAMA_API = "https://api.llama.fi"
|
||||
DATABUS = os.environ.get("DATABUS_URL", "http://localhost:8000/api/v1/databus")
|
||||
|
||||
|
||||
async def _get_defillama_tvl(protocol_slug: str) -> dict[str, Any]:
|
||||
"""Get current TVL from DeFiLlama."""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
resp = await client.get(f"{DEFILLAMA_API}/protocol/{protocol_slug}")
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
return {
|
||||
"name": data.get("name", protocol_slug),
|
||||
"slug": protocol_slug,
|
||||
"tvl_usd": data.get("tvl", 0),
|
||||
"chain_tvls": data.get("chainTvls", {}),
|
||||
"tvl_change_24h": data.get("change_1d"),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"name": protocol_slug,
|
||||
"slug": protocol_slug,
|
||||
"tvl_usd": 0,
|
||||
"chain_tvls": {},
|
||||
"error": "DeFiLlama unavailable",
|
||||
}
|
||||
|
||||
|
||||
async def _get_onchain_tvl(protocol_name: str, chains: list[str]) -> dict[str, float]:
|
||||
"""Get actual on-chain TVL from DataBus for given chains."""
|
||||
chain_tvls: dict[str, float] = {}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
tasks = []
|
||||
for c in chains[:5]:
|
||||
tasks.append(client.get(f"{DATABUS}/{c}/tvl/{protocol_name.lower()}"))
|
||||
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
for i, resp in enumerate(responses):
|
||||
if not isinstance(resp, Exception) and resp.status_code == 200:
|
||||
data = resp.json().get("data", {})
|
||||
chain_tvls[chains[i]] = data.get("tvl_usd", 0) or 0
|
||||
except Exception:
|
||||
pass
|
||||
return chain_tvls
|
||||
|
||||
|
||||
def _compute_discrepancy(defl_tvl: float, onchain_tvl: float) -> dict:
|
||||
"""Compute TVL discrepancy with risk assessment."""
|
||||
if defl_tvl <= 0 or onchain_tvl <= 0:
|
||||
return {"discrepancy_pct": 0, "risk": "unknown", "explanation": "Insufficient data"}
|
||||
|
||||
diff_pct = ((defl_tvl - onchain_tvl) / onchain_tvl) * 100
|
||||
|
||||
if diff_pct > 30:
|
||||
risk = "CRITICAL"
|
||||
explanation = f"DeFiLlama reports {diff_pct:.0f}% higher TVL than on-chain. Possible inflated TVL."
|
||||
elif diff_pct > 15:
|
||||
risk = "HIGH"
|
||||
explanation = f"Significant discrepancy ({diff_pct:.0f}%). Verify manually."
|
||||
elif diff_pct < -30:
|
||||
risk = "HIGH"
|
||||
explanation = f"On-chain TVL exceeds reported TVL by {abs(diff_pct):.0f}%. Possible unreported risk."
|
||||
elif diff_pct < -10:
|
||||
risk = "MEDIUM"
|
||||
explanation = f"On-chain TVL higher than reported ({abs(diff_pct):.0f}%)."
|
||||
else:
|
||||
risk = "LOW"
|
||||
explanation = f"TVL aligned within {abs(diff_pct):.0f}%."
|
||||
|
||||
return {"discrepancy_pct": round(diff_pct, 1), "risk": risk, "explanation": explanation}
|
||||
|
||||
|
||||
@router.get("/protocol/{slug}")
|
||||
async def verify_protocol_tvl(slug: str):
|
||||
"""Verify a DeFiLlama protocol's TVL against on-chain data."""
|
||||
defl = await _get_defillama_tvl(slug)
|
||||
|
||||
# Get chain list from DeFiLlama
|
||||
chain_tvls = defl.get("chain_tvls", {})
|
||||
chains = list(chain_tvls.keys()) if isinstance(chain_tvls, dict) else []
|
||||
|
||||
# Fetch on-chain data for top chains
|
||||
onchain = await _get_onchain_tvl(slug, chains[:5] if chains else ["ethereum"])
|
||||
total_onchain = sum(onchain.values())
|
||||
|
||||
discrepancy = _compute_discrepancy(defl.get("tvl_usd", 0) or 0, total_onchain)
|
||||
|
||||
return {
|
||||
"protocol": defl.get("name", slug),
|
||||
"slug": slug,
|
||||
"defillama_tvl": defl.get("tvl_usd", 0) or 0,
|
||||
"onchain_tvl": round(total_onchain, 2),
|
||||
"chains_verified": len(onchain),
|
||||
"discrepancy": discrepancy,
|
||||
"chain_breakdown": {
|
||||
"defillama": {c: chain_tvls.get(c, 0) for c in chains[:5]},
|
||||
"onchain": onchain,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/top-mismatches")
|
||||
async def find_top_mismatches(limit: int = Query(10, le=20)):
|
||||
"""Scan top DeFiLlama protocols for TVL mismatches."""
|
||||
# Fetch top protocols from DeFiLlama
|
||||
top_protocols: list[str] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
resp = await client.get(f"{DEFILLAMA_API}/protocols")
|
||||
if resp.status_code == 200:
|
||||
protocols = resp.json()
|
||||
top_protocols = [p["slug"] for p in protocols[: limit * 2] if p.get("slug")]
|
||||
except Exception:
|
||||
raise HTTPException(502, "DeFiLlama unavailable")
|
||||
|
||||
mismatches: list[dict] = []
|
||||
for slug in top_protocols[: limit * 2]:
|
||||
result = await verify_protocol_tvl(slug)
|
||||
disc = result.get("discrepancy", {})
|
||||
if disc.get("risk") in ("CRITICAL", "HIGH"):
|
||||
mismatches.append(result)
|
||||
if len(mismatches) >= limit:
|
||||
break
|
||||
|
||||
mismatches.sort(key=lambda m: abs(m["discrepancy"].get("discrepancy_pct", 0)), reverse=True)
|
||||
return {"mismatches": mismatches, "total_scanned": len(top_protocols[: limit * 2])}
|
||||
Loading…
Add table
Add a link
Reference in a new issue