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
119
app/routers/impersonation_detector.py
Normal file
119
app/routers/impersonation_detector.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env python3
|
||||
"""#6 — Portfolio Impersonation Detector. Scans for hidden wallets, proxy contracts,
|
||||
suspicious approvals. Reveals what an address controls beyond its obvious wallet."""
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter(prefix="/api/v1/impersonation", tags=["impersonation-detector"])
|
||||
|
||||
BACKEND = os.environ.get("BACKEND_URL", "http://localhost:8000")
|
||||
DATABUS = os.environ.get("DATABUS_URL", f"{BACKEND}/api/v1/databus")
|
||||
|
||||
|
||||
class ImpersonationReport(BaseModel):
|
||||
address: str
|
||||
chain: str
|
||||
controlled_wallets: list[dict[str, Any]]
|
||||
proxy_contracts: list[dict[str, Any]]
|
||||
suspicious_approvals: list[dict[str, Any]]
|
||||
risk_level: str
|
||||
risk_score: int
|
||||
|
||||
|
||||
async def _find_delegates(address: str, chain: str) -> list[dict]:
|
||||
"""Find delegate/proxy wallets controlled by this address."""
|
||||
delegates: list[dict] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
# Check for known proxy patterns
|
||||
resp = await client.get(f"{DATABUS}/{chain}/delegates/{address}")
|
||||
if resp.status_code == 200:
|
||||
delegates = resp.json().get("data", {}).get("delegates", [])
|
||||
except Exception:
|
||||
pass
|
||||
return delegates
|
||||
|
||||
|
||||
async def _find_approvals(address: str, chain: str) -> list[dict]:
|
||||
"""Find token approvals with unlimited spend to unverified contracts."""
|
||||
approvals: list[dict] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
resp = await client.get(f"{DATABUS}/{chain}/approvals/{address}")
|
||||
if resp.status_code == 200:
|
||||
all_approvals = resp.json().get("data", {}).get("approvals", [])
|
||||
# Flag suspicious: unlimited spend to unverified contracts
|
||||
for a in all_approvals:
|
||||
if a.get("amount") in (
|
||||
None,
|
||||
"",
|
||||
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
):
|
||||
a["suspicious"] = True
|
||||
approvals.append(a)
|
||||
except Exception:
|
||||
pass
|
||||
return approvals
|
||||
|
||||
|
||||
async def _scan_related_contracts(address: str, chain: str) -> list[dict]:
|
||||
"""Find contracts deployed by or controlled by this address."""
|
||||
contracts: list[dict] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
# Check eth-labels for any contract addresses associated
|
||||
resp = await client.get(f"{BACKEND}/api/v1/eth-labels/search?address={address}")
|
||||
if resp.status_code == 200:
|
||||
for entry in resp.json().get("labels", []):
|
||||
if entry.get("type") == "contract":
|
||||
contracts.append(entry)
|
||||
except Exception:
|
||||
pass
|
||||
return contracts
|
||||
|
||||
|
||||
def _assess_risk(delegates: list, approvals: list, contracts: list) -> tuple[str, int]:
|
||||
"""Compute impersonation risk score."""
|
||||
score = 0
|
||||
if len(delegates) > 0:
|
||||
score += 15 * min(len(delegates), 5)
|
||||
if len(approvals) > 0:
|
||||
score += 20 * min(len(approvals), 3)
|
||||
if len(contracts) > 0:
|
||||
score += 10 * min(len(contracts), 3)
|
||||
|
||||
if score >= 60:
|
||||
return "critical", score
|
||||
elif score >= 30:
|
||||
return "high", score
|
||||
elif score >= 10:
|
||||
return "medium", score
|
||||
return "low", score
|
||||
|
||||
|
||||
@router.get("/audit/{address}")
|
||||
async def audit_address(address: str, chain: str = Query("ethereum")):
|
||||
"""Full impersonation audit for an address."""
|
||||
delegates = await _find_delegates(address, chain)
|
||||
approvals = await _find_approvals(address, chain)
|
||||
contracts = await _scan_related_contracts(address, chain)
|
||||
level, score = _assess_risk(delegates, approvals, contracts)
|
||||
|
||||
return {
|
||||
"address": address,
|
||||
"chain": chain,
|
||||
"controlled_wallets": delegates,
|
||||
"suspicious_approvals": approvals,
|
||||
"related_contracts": contracts,
|
||||
"risk_level": level,
|
||||
"risk_score": score,
|
||||
"summary": (
|
||||
f"Found {len(delegates)} delegate wallets, {len(approvals)} suspicious approvals, "
|
||||
f"and {len(contracts)} related contracts. Risk: {level} ({score}/100)."
|
||||
),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue