""" RugMaps AI Analysis - DeepSeek Flash powered token investigation. Uses our own data: FAISS indexes, wallet labels, clustering engine, RAG cache. """ import hashlib import json import logging import os import time logger = logging.getLogger(__name__) # LLM config - DeepSeek Flash for fast, cheap analysis DEEPSEEK_KEY = os.getenv("DEEPSEEK_API_KEY", "") or os.getenv("LLM_API_KEY", "") or os.getenv("OPENROUTER_API_KEY", "") DEEPSEEK_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1/chat/completions") AI_MODEL = os.getenv("LLM_MODEL", "deepseek-chat") # Flash by default # Analysis cache (in-memory + Redis if available) _analysis_cache: dict[str, dict] = {} CACHE_TTL = 3600 # 1 hour def _cache_key(token: str, chain: str) -> str: return hashlib.sha256(f"{chain}:{token}".encode()).hexdigest()[:16] async def get_cached_analysis(token: str, chain: str) -> dict | None: """Check if we have a cached analysis for this token.""" key = _cache_key(token, chain) # In-memory cache if key in _analysis_cache: entry = _analysis_cache[key] if time.time() - entry.get("_ts", 0) < CACHE_TTL: logger.info(f"RugMaps cache HIT for {token[:12]}...") return entry return None def set_cached_analysis(token: str, chain: str, analysis: dict): """Cache analysis result.""" key = _cache_key(token, chain) analysis["_ts"] = time.time() analysis["_cached"] = True _analysis_cache[key] = analysis # Prune old entries if memory grows if len(_analysis_cache) > 500: oldest = sorted(_analysis_cache.items(), key=lambda x: x[1].get("_ts", 0))[:100] for k, _ in oldest: del _analysis_cache[k] async def get_token_holders(token_address: str, chain: str) -> list[dict]: """Get token holders from our data sources.""" try: from app.bubble_maps import get_bubble_maps_pro bm = get_bubble_maps_pro() result = await bm.generate_map(center_wallet=token_address, depth=2, min_strength=0.1) d = result.to_dict() return d.get("nodes", []) except Exception as e: logger.warning(f"Holder fetch failed: {e}") return [] async def get_wallet_labels(addresses: list[str]) -> dict[str, dict]: """Get wallet labels from our ClickHouse/FAISS database.""" labels = {} try: from app.bubble_maps import get_bubble_maps_pro bm = get_bubble_maps_pro() for addr in addresses[:50]: try: info = await bm._get_entity_info(addr) risk, level = await bm._calculate_risk(addr) labels[addr] = { "entity": info or {}, "risk_score": risk, "risk_level": level, } except Exception: pass except Exception as e: logger.warning(f"Label fetch error: {e}") return labels async def search_similar_scams(token_address: str, chain: str) -> list[dict]: """Search FAISS indexes for similar scam patterns.""" similar = [] try: from app.ann_index import get_ann_index from app.crypto_embeddings import CryptoEmbedder ann = get_ann_index() embedder = CryptoEmbedder() # Embed token info query_text = f"token {token_address} on {chain} rug pull scam honeypot" embedding = await embedder.embed_query(query_text) # Search scam patterns index if "scam_patterns" in ann.indexes: results = ann.search("scam_patterns", embedding, k=5) for r in results: similar.append( { "pattern": r.get("metadata", {}).get("pattern", ""), "description": r.get("metadata", {}).get("description", ""), "similarity": float(r.get("score", 0)), } ) # Search known scams if "known_scams" in ann.indexes: results = ann.search("known_scams", embedding, k=5) for r in results: similar.append( { "scam_type": r.get("metadata", {}).get("type", ""), "description": r.get("metadata", {}).get("description", ""), "similarity": float(r.get("score", 0)), } ) # Search wallet profiles if "wallet_profiles" in ann.indexes: results = ann.search("wallet_profiles", embedding, k=5) for r in results: similar.append( { "wallet_type": r.get("metadata", {}).get("type", ""), "description": r.get("metadata", {}).get("label", ""), "similarity": float(r.get("score", 0)), } ) except Exception as e: logger.warning(f"FAISS search failed: {e}") # Deduplicate and sort by similarity seen = set() unique = [] for s in sorted(similar, key=lambda x: x.get("similarity", 0), reverse=True): desc = s.get("description", "") if desc and desc not in seen: seen.add(desc) unique.append(s) return unique[:10] async def generate_ai_analysis( token_address: str, chain: str, graph_data: dict, holders: list[dict], labels: dict[str, dict], similar_scams: list[dict], ) -> dict: """Generate AI analysis using DeepSeek Flash with our own data.""" import httpx # Build a data-rich prompt from our own systems holder_summary = [] for h in holders[:10]: addr = h.get("address", "")[:12] pct = h.get("supply_pct", h.get("pct", "?")) label = labels.get(h.get("address", ""), {}).get("entity", {}).get("name", "") risk = labels.get(h.get("address", ""), {}).get("risk_score", "?") holder_summary.append(f" {addr}... - {pct}% supply - label: {label or 'unknown'} - risk: {risk}/100") similar_summary = ( "\n".join( [ f" - {s.get('description', '')[:100]} (similarity: {s.get('similarity', 0):.2f})" for s in similar_scams[:5] ] ) or "No similar scam patterns found in our database." ) graph_stats = graph_data.get("stats", {}) node_count = graph_data.get("node_count", len(holders)) link_count = graph_data.get("link_count", 0) risk_score = graph_data.get("risk_score", 0) prompt = f"""You are RugMunch Intelligence's AI security analyst. Analyze this token using our proprietary on-chain data. TOKEN: {token_address} on {chain} GRAPH DATA: - {node_count} connected wallets - {link_count} transaction links - Graph risk score: {risk_score}/100 - Graph stats: {json.dumps(graph_stats)} TOP HOLDERS (from our systems): {chr(10).join(holder_summary) if holder_summary else "No holder data available"} SIMILAR SCAM PATTERNS (from our FAISS vector database): {similar_summary} WALLET LABELS (from our 393K-label database): {json.dumps({a[:12]: line.get("entity", {}).get("name") or line.get("entity", {}).get("label") for a, line in list(labels.items())[:10]})} Based on our data, provide a JSON analysis with these fields: - "verdict": (SAFE / SUSPICIOUS / HIGH_RISK / SCAM) - "risk_score": (0-100) - "summary": (2-3 sentence analysis for users) - "red_flags": (list of specific concerning findings, empty if none) - "green_flags": (list of positive indicators, empty if none) - "dev_wallets": (list of addresses likely belonging to developer/team) - "bundle_wallets": (list of addresses in coordinated bundles) - "recommendation": (one sentence: what should a user do?) - "confidence": (0-100, how confident are you in this analysis?) Return ONLY valid JSON, no markdown, no code blocks.""" try: async with httpx.AsyncClient(timeout=15) as client: resp = await client.post( DEEPSEEK_URL, headers={ "Authorization": f"Bearer {DEEPSEEK_KEY}", "Content-Type": "application/json", }, json={ "model": AI_MODEL, "messages": [ { "role": "system", "content": "You are a crypto security expert. Return only valid JSON.", }, {"role": "user", "content": prompt}, ], "temperature": 0.3, "max_tokens": 800, }, ) if resp.status_code == 200: data = resp.json() content = data.get("choices", [{}])[0].get("message", {}).get("content", "{}") # Try to parse the JSON response try: # Strip any markdown code blocks if "```" in content: content = content.split("```")[1] if content.startswith("json"): content = content[4:] analysis = json.loads(content.strip()) return analysis except json.JSONDecodeError: # Fallback: return raw text as summary return { "verdict": "UNKNOWN", "risk_score": risk_score, "summary": content[:300], "red_flags": [], "green_flags": [], "recommendation": "Manual review recommended - AI response was malformed.", "confidence": 30, } logger.warning(f"DeepSeek API returned {resp.status_code}: {resp.text[:200]}") except Exception as e: logger.error(f"AI analysis failed: {e}") # Fallback analysis from our own data (no AI needed) return generate_fallback_analysis(risk_score, holders, labels, similar_scams, node_count, link_count) def generate_fallback_analysis( risk_score: int, holders: list[dict], labels: dict[str, dict], similar_scams: list[dict], node_count: int, link_count: int, ) -> dict: """Generate analysis purely from our own data when AI is unavailable.""" red_flags = [] green_flags = [] dev_wallets = [] bundle_wallets = [] # Check for high concentration top_holder_pct = sum(h.get("supply_pct", h.get("pct", 0)) for h in holders[:3]) if top_holder_pct > 50: red_flags.append(f"Top 3 holders control {top_holder_pct:.0f}% of supply - extreme concentration risk") elif top_holder_pct < 20: green_flags.append(f"Well distributed: top 3 holders only control {top_holder_pct:.0f}%") # Check labels high_risk_count = 0 for addr, label_data in labels.items(): entity = label_data.get("entity", {}) risk = label_data.get("risk_score", 0) if entity.get("type") in ("scammer", "phishing", "exploiter"): red_flags.append(f"Known malicious entity: {entity.get('name', addr[:12])}") high_risk_count += 1 if risk >= 70: red_flags.append(f"High risk wallet ({risk}/100): {addr[:12]}... {entity.get('name', '')}") high_risk_count += 1 dev_wallets.append(addr) if entity.get("type") == "exchange": green_flags.append(f"Exchange wallet detected: {entity.get('name', addr[:12])}") # Similar scam patterns if similar_scams: top_match = similar_scams[0] if top_match.get("similarity", 0) > 0.7: red_flags.append(f"Matches known pattern: {top_match.get('description', '')[:80]}") else: green_flags.append("No matches in our scam pattern database") # Risk assessment if risk_score >= 80 or high_risk_count >= 3: verdict = "SCAM" elif risk_score >= 50 or high_risk_count >= 1: verdict = "HIGH_RISK" elif risk_score >= 25 or top_holder_pct > 40: verdict = "SUSPICIOUS" else: verdict = "SAFE" confidence = min(90, 40 + len(red_flags) * 10 + len(green_flags) * 5) return { "verdict": verdict, "risk_score": risk_score, "summary": f"Analysis based on {node_count} connected wallets across {link_count} transaction links. " f"Top holders control {top_holder_pct:.0f}% of supply. " f"{len(red_flags)} red flags, {len(green_flags)} green flags detected.", "red_flags": red_flags, "green_flags": green_flags, "dev_wallets": dev_wallets[:5], "bundle_wallets": bundle_wallets[:5], "recommendation": ( "DO NOT INVEST - clear scam indicators" if verdict == "SCAM" else "Extreme caution - multiple high-risk signals" if verdict == "HIGH_RISK" else "Proceed with caution - some suspicious patterns" if verdict == "SUSPICIOUS" else "No major concerns detected from our analysis" ), "confidence": confidence, "source": "rmi_data_engine", # Indicates this came from our own data }