rmi-backend/app/routers/honeypot_map.py

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}")
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"],
}