#!/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)." ), }