rmi-backend/app/_archive/legacy_2026_07/vision_analysis.py
cryptorugmunch 628c1d2a10
Some checks failed
CI / build (push) Failing after 3s
refactor(rmi-backend,audit): mount Wave 3 + archive 136 dead-code files (P2.3)
PHASE 2.3 (AUDIT-2026-Q3.md):

Task 1 — Wire-in Wave 3 (1 router mounted, 2 deferred):
  - app.routers.unified_scanner_router mounted at /api/v2/scanner/* (2 routes:
    POST /api/v2/scanner/token/scan, POST /api/v2/scanner/wallet/scan).
    Refactored prefix from /api/v2 -> /api/v2/scanner to avoid future conflicts
    with the v1 /api/v1/scanner/ stub.
  - app.routers.unified_wallet_scanner DEFERRED (no router APIRouter attribute;
    library module consumed by unified_scanner_router via get_wallet_scanner()).
  - app.routers.admin_extensions DEFERRED (DORMANT per audit; 25 routes at
    /api/v1/admin/* would shadow /api/v1/admin/alerts_webhook).

Task 2 — Archive 136 dead-code files to app/_archive/legacy_2026_07/:
  - 73 routers in app/routers/ (reach graph showed zero reach into mount.py).
  - 63 flat app/*.py (domain modules never imported by live code).
  - 1 file RESTORED post-archive: app/routers/x402_bridge_health.py (caught by
    tests/unit/test_bridge_health.py which directly imports it; reach graph
    considered tests/ only as transitive reach — to be patched in next cycle).

Forced-LIVE (NOT archived per user directive):
  - app/ai_pipeline_v3.py  (3 importers in audit window, importers themselves DEAD)
  - app/splade_bm25.py       (LIVE via app.rag_service)
  - app/wallet_manager_v2.py (LIVE via x402_enforcement, x402_tools, sweep_all, sweep_now)
  - app/crypto_embeddings.py (NOT in audit ARCHIVE list; heavy import graph)

Verification (forward-import closure from mount.py + main.py + factory.py + lifespan.py):
  - imports = 348 app.* modules
  - reached = 194 files reachable from roots
  - archive set = audit_dead (186) - reached - forced_live (4) - test_live (1) = 136
  - Net delta: 136 files moved, 44,932 LOC reduction, 293->295 active routes (+2 from Wave 3)

pyproject.toml updates:
  - setuptools.packages.find: added exclude for app._archive*
  - ruff.extend-exclude: added "app/_archive/"
  - mypy.exclude: added "app/_archive/"

Smoke test: pytest tests/ — 817 passed, 3 pre-existing failures unchanged
(0 new failures; 0 routes lost; all 4 forced-LIVE files still importable).

Restoration: git mv app/_archive/legacy_2026_07/<name>.py <original-path>
and add the import to app/mount.py ROUTER_MODULES.

Refs: AUDIT-2026-Q3.md /home/dev/pry/rmi-final-deadcode-2026-07-06.md
2026-07-06 20:52:31 +02:00

135 lines
5.4 KiB
Python

"""Multi-Modal Token Analysis - Gemini 2.5 Flash vision for logo/website scanning.
Detects: stolen artwork, template websites, fake team photos, suspicious branding."""
import base64
import os
import httpx
from fastapi import APIRouter, Query
router = APIRouter(prefix="/api/v1/vision", tags=["vision-analysis"])
GEMINI_KEY = os.getenv("GEMINI_API_KEY", "")
GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
GEMINI_VISION_MODEL = "gemini-2.5-flash" # 1,500 req/day free
GEMINI_PRO_MODEL = "gemini-2.5-pro" # 50 req/day free - use sparingly
async def _analyze_image(image_url: str, question: str) -> dict:
"""Send image to Gemini 2.5 Flash for vision analysis."""
if not GEMINI_KEY:
return {"error": "GEMINI_API_KEY not configured"}
try:
# Fetch image and convert to base64
async with httpx.AsyncClient(timeout=15) as c:
r = await c.get(image_url)
if r.status_code != 200:
return {"error": f"Image fetch failed: {r.status_code}"}
img_b64 = base64.b64encode(r.content).decode()
# Send to Gemini
payload = {
"contents": [{"parts": [{"text": question}, {"inline_data": {"mime_type": "image/png", "data": img_b64}}]}]
}
async with httpx.AsyncClient(timeout=30) as c:
r = await c.post(f"{GEMINI_URL}?key={GEMINI_KEY}", json=payload)
if r.status_code == 200:
data = r.json()
text = data.get("candidates", [{}])[0].get("content", {}).get("parts", [{}])[0].get("text", "")
return {"analysis": text, "model": "gemini-2.5-flash"}
return {"error": f"Gemini API: {r.status_code}"}
except Exception as e:
return {"error": str(e)}
@router.get("/token-logo")
async def analyze_token_logo(token_address: str, chain: str = Query("solana")):
"""Analyze token logo for scam indicators - stolen art, AI-generated, generic."""
# Build logo URL based on chain
logo_urls = {
"solana": f"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/{token_address}/logo.png",
"ethereum": f"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/{token_address}/logo.png",
"bsc": f"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/smartchain/assets/{token_address}/logo.png",
}
logo_url = logo_urls.get(chain, logo_urls["solana"])
question = """Analyze this token logo for scam indicators:
1. Is it AI-generated? (look for artifacts, unnatural symmetry)
2. Is it stolen from another project? (known brands, copied designs)
3. Is it generic/template? (no original design elements)
4. Does it look professional or rushed?
5. SCAM_SCORE: give a 0-100 score where 100 = definitely a scam logo
Respond in JSON: {"ai_generated": bool, "stolen": bool, "generic": bool, "professional": bool, "scam_score": int, "explanation": "one sentence"}"""
result = await _analyze_image(logo_url, question)
result["token"] = token_address
result["chain"] = chain
return result
@router.get("/website-scan")
async def scan_token_website(url: str):
"""Scan a token's website screenshot for red flags."""
# Use a screenshot service or just analyze the URL patterns
website_red_flags = []
# Quick pattern checks
import re
if re.search(r"(airdrop|claim|giveaway|free).*\.(io|com|xyz)", url.lower()):
website_red_flags.append("Scam giveaway pattern in URL")
if url.endswith((".xyz", ".click", ".win", ".loan")):
website_red_flags.append("Suspicious TLD - common for scams")
if len(url) > 50:
website_red_flags.append("Unusually long URL - possible phishing")
risk = min(100, len(website_red_flags) * 30)
return {
"url": url,
"red_flags": website_red_flags,
"risk_score": risk,
"risk_level": "HIGH" if risk > 50 else "MEDIUM" if risk > 20 else "LOW",
"note": "Full screenshot analysis requires Gemini Pro vision. Upgrade for visual scan."
if not GEMINI_KEY
else "Gemini vision analysis ready.",
}
@router.get("/token-visual-audit")
async def full_visual_audit(token_address: str, chain: str = "solana", website_url: str = ""):
"""Complete visual audit: logo + website + social presence."""
logo_check = await analyze_token_logo(token_address, chain)
website_check = await scan_token_website(website_url) if website_url else {}
logo_score = logo_check.get("analysis", {})
website_score = website_check.get("risk_score", 0)
# Combined score
logo_scam = logo_score.get("scam_score", 0) if isinstance(logo_score, dict) else 0
combined_score = (logo_scam * 0.6) + (website_score * 0.4)
return {
"token": token_address,
"chain": chain,
"overall_visual_risk": round(combined_score, 1),
"risk_level": "CRITICAL"
if combined_score > 70
else "HIGH"
if combined_score > 40
else "MEDIUM"
if combined_score > 20
else "LOW",
"logo_analysis": logo_check,
"website_analysis": website_check,
"verdict": "Visual audit shows significant scam indicators. Avoid."
if combined_score > 50
else "Visual audit passed. No obvious red flags in branding."
if combined_score < 25
else "Some concerns. Proceed with caution.",
}