rmi-backend/app/routers/honeypot_map.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

156 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""#14 - Honeypot Network Map. Maps all known honeypot contracts, clusters by deployer,
fund flows. Interactive graph showing the honeypot factory network."""
import os
import httpx
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
router = APIRouter(prefix="/api/v1/honeypot-map", tags=["honeypot-map"])
BACKEND = os.environ.get("BACKEND_URL", "http://localhost:8000")
class HoneypotNode(BaseModel):
id: str
label: str
type: str = "contract" # contract, deployer, funder, victim
chain: str
total_victims: int = 0
total_stolen_usd: float = 0
risk_score: int = 0
class HoneypotEdge(BaseModel):
source: str
target: str
relationship: str # deployed, funded_by, stole_from
amount_usd: float = 0
@router.get("/deployer/{deployer_address}")
async def get_honeypot_deployer(deployer_address: str, chain: str = Query("ethereum")):
"""Map all honeypot contracts deployed by a single address."""
nodes: list[dict] = []
edges: list[dict] = []
# Deployer node
deployer_id = deployer_address[:16]
nodes.append(
{
"id": deployer_id,
"label": f"Deployer {deployer_address[:10]}",
"type": "deployer",
"chain": chain,
}
)
# Find all contracts deployed
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(
f"{BACKEND}/api/v1/eth-labels/search", params={"address": deployer_address, "type": "deployer"}
)
if resp.status_code == 200:
data = resp.json()
for entry in data.get("labels", []):
is_honeypot = any(w in (entry.get("label", "") or "").lower() for w in ["honeypot", "scam", "rug"])
contract_id = entry.get("contract_address", "")[:16]
if contract_id:
nodes.append(
{
"id": contract_id,
"label": entry.get("name", contract_id),
"type": "honeypot" if is_honeypot else "contract",
"chain": entry.get("chain", chain),
"risk_score": 80 if is_honeypot else 30,
}
)
edges.append(
{
"source": deployer_id,
"target": contract_id,
"relationship": "deployed",
}
)
except Exception:
pass
return {"nodes": nodes, "edges": edges, "total_contracts": len([n for n in nodes if n["type"] != "deployer"])}
@router.get("/network")
async def get_honeypot_network(
chain: str = Query("ethereum"),
limit: int = Query(50, le=100),
min_risk: int = Query(50, le=100),
):
"""Get the current honeypot network map for a chain. Clusters by deployer groups."""
nodes: list[dict] = []
edges: list[dict] = []
seen_deployers: set[str] = set()
try:
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(
f"{BACKEND}/api/v1/eth-labels/search", params={"q": "honeypot", "type": "deployer", "limit": limit}
)
if resp.status_code == 200:
entries = resp.json().get("labels", [])
for entry in entries[:limit]:
deployer = entry.get("address", "")
if deployer and deployer not in seen_deployers:
seen_deployers.add(deployer)
dep_id = deployer[:16]
nodes.append(
{
"id": dep_id,
"label": f"Deployer {deployer[:10]}",
"type": "deployer",
"chain": entry.get("chain", chain),
"risk_score": entry.get("risk_score", 70),
}
)
# Get their contracts
for contract in entry.get("contracts", [])[:5]:
c_id = contract.get("address", "")[:16]
if c_id:
nodes.append(
{
"id": c_id,
"label": contract.get("name", c_id),
"type": "contract",
"chain": entry.get("chain", chain),
"risk_score": 75,
}
)
edges.append(
{
"source": dep_id,
"target": c_id,
"relationship": "deployed",
}
)
except Exception as e:
raise HTTPException(502, f"Labels service unavailable: {e}") from e
return {
"chain": chain,
"nodes": nodes,
"edges": edges,
"total_deployers": len(seen_deployers),
"total_contracts": len([n for n in nodes if n["type"] == "contract"]),
}
@router.get("/stats")
async def get_honeypot_stats():
"""Global honeypot statistics."""
return {
"description": "Honeypot contract network intelligence from eth-labels + SENTINEL + Real-CATS",
"data_sources": ["eth-labels (115K addresses)", "Real-CATS (153K tokens)", "SENTINEL scanner"],
"chains_supported": ["ethereum", "bsc", "polygon", "arbitrum", "base", "avalanche", "optimism"],
}