""" All Connectors Router - Wires all 20+ unwired modules into API routes. One file to rule them all. """ import logging from datetime import UTC, datetime from fastapi import APIRouter, HTTPException logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1", tags=["connectors"]) # ═══════════════════════════════════════════════════════════ # BIRDEYE - Token data, trending, OHLCV # ═══════════════════════════════════════════════════════════ @router.get("/birdeye/token/{address}") async def birdeye_token(address: str, chain: str = "solana"): try: from app.birdeye_client import BirdeyeClient client = BirdeyeClient() data = client.get_token_info(address) return {"address": address, "chain": chain, "data": data, "source": "birdeye"} except Exception as e: return {"error": str(e)} @router.get("/birdeye/trending") async def birdeye_trending(chain: str = "solana", limit: int = 20): try: from app.birdeye_client import BirdeyeClient client = BirdeyeClient() tokens = client.get_trending(limit=limit) return {"tokens": tokens, "chain": chain, "source": "birdeye"} except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # ARKHAM INTELLIGENCE - Entity labeling, wallet attribution, # institutional tracking, sanctions screening # ═══════════════════════════════════════════════════════════ @router.get("/arkham/entity/{address}") async def arkham_entity(address: str): try: from app.arkham_connector import ArkhamClient client = ArkhamClient() try: data = await client.get_entity(address) return {"address": address, "entity": data, "source": "arkham"} finally: await client.close() except Exception as e: return {"error": str(e)} @router.get("/arkham/labels") async def arkham_labels(page: int = 0, limit: int = 100): try: from app.arkham_connector import ArkhamClient client = ArkhamClient() try: data = await client.get_labels(page=page, limit=limit) return {"labels": data, "page": page, "limit": limit, "source": "arkham"} finally: await client.close() except Exception as e: return {"error": str(e)} @router.get("/arkham/portfolio/{address}") async def arkham_portfolio(address: str): try: from app.arkham_connector import ArkhamClient client = ArkhamClient() try: data = await client.get_portfolio(address) return {"address": address, "portfolio": data, "source": "arkham"} finally: await client.close() except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # BLOCKCHAIR - Multi-chain block explorer # ═══════════════════════════════════════════════════════════ @router.get("/blockchair/balance/{address}") async def blockchair_balance(address: str, chain: str = "bitcoin"): try: from app.blockchair_connector import get_address_balance result = get_address_balance(address, chain) return {"address": address, "chain": chain, "data": result, "source": "blockchair"} except Exception as e: return {"error": str(e)} @router.get("/blockchair/search") async def blockchair_search(q: str): try: from app.blockchair_connector import search_blockchain result = search_blockchain(q) return {"query": q, "results": result, "source": "blockchair"} except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # DEFILLAMA - DeFi analytics # ═══════════════════════════════════════════════════════════ @router.get("/defillama/tvl") async def defillama_tvl(): try: from app.defillama_connector import get_defi_tvl result = get_defi_tvl() return {"tvl": result, "source": "defillama"} except Exception as e: return {"error": str(e)} @router.get("/defillama/protocols") async def defillama_protocols(): try: from app.defillama_connector import get_defi_protocols result = get_defi_protocols() return {"protocols": result, "source": "defillama"} except Exception as e: return {"error": str(e)} @router.get("/defillama/chains") async def defillama_chains(): try: from app.defillama_connector import get_chain_tvls result = get_chain_tvls() return {"chains": result, "source": "defillama"} except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # ENTITY CLUSTERING - Wallet cluster analysis # ═══════════════════════════════════════════════════════════ @router.get("/entity/clusters") async def entity_clusters(address: str | None = None, min_size: int = 2): try: from app.entity_clustering import get_clustering_engine engine = get_clustering_engine() if address: entity = engine.graph.get_entity(address) return {"entity": entity, "address": address} clusters = engine.graph.find_clusters(min_size=min_size) return {"clusters": clusters, "total": len(clusters)} except Exception as e: return {"error": str(e)} @router.post("/entity/link") async def entity_link(data: dict): try: from app.entity_clustering import get_clustering_engine engine = get_clustering_engine() addr1 = data.get("address1", "") addr2 = data.get("address2", "") relationship = data.get("relationship", "related") if not addr1 or not addr2: raise HTTPException(status_code=400, detail="address1 and address2 required") engine.graph.link_wallets(addr1, addr2, relationship) return { "status": "linked", "address1": addr1, "address2": addr2, "relationship": relationship, } except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # THREAT INTEL - Sanctions, reputation, blocklists # ═══════════════════════════════════════════════════════════ @router.get("/threat/reputation/{address}") async def threat_reputation(address: str, chain: str = "ethereum"): try: from app.threat_intel import check_wallet_reputation result = check_wallet_reputation(address, chain) return {"address": address, "chain": chain, "reputation": result} except Exception as e: return {"error": str(e)} @router.get("/threat/sanctions/{address}") async def threat_sanctions(address: str): try: from app.threat_intel import check_sanctions result = check_sanctions(address) return {"address": address, "sanctions": result, "sanctioned": len(result) > 0} except Exception as e: return {"error": str(e)} @router.post("/threat/blocklist") async def threat_blocklist_add(data: dict): try: from app.threat_intel import add_to_blocklist address = data.get("address", "") reason = data.get("reason", "") if not address: raise HTTPException(status_code=400, detail="address required") success = add_to_blocklist(address, reason) return {"address": address, "added": success, "reason": reason} except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # EXCHANGE FLOW - CEX inflows/outflows # ═══════════════════════════════════════════════════════════ @router.get("/exchange/flow/{address}") async def exchange_flow(address: str, chain: str = "ethereum"): try: from app.exchange_flow_analyzer import analyze_entity_flows result = analyze_entity_flows(address, chain) return {"address": address, "chain": chain, "flows": result} except Exception as e: return {"error": str(e)} @router.get("/exchange/whales") async def exchange_whales(chain: str = "ethereum"): try: from app.exchange_flow_analyzer import ExchangeFlowAnalyzer analyzer = ExchangeFlowAnalyzer() whale_movements = analyzer.detect_large_transfers(chain=chain, min_value_usd=1000000) return {"whale_movements": whale_movements, "chain": chain} except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # CROSS-CHAIN CORRELATOR # ═══════════════════════════════════════════════════════════ @router.get("/crosschain/fingerprint/{address}") async def crosschain_fingerprint(address: str, chains: str = "ethereum,base,bsc,polygon"): try: from app.cross_chain_correlator import CrossChainCorrelator correlator = CrossChainCorrelator() chain_list = [c.strip() for c in chains.split(",")] results = {} for chain in chain_list: try: fp = correlator.get_fingerprint(address, chain) results[chain] = fp except Exception: results[chain] = {"error": f"Chain {chain} unavailable"} return {"address": address, "fingerprints": results, "chains_checked": len(results)} except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # AGENT MESH - 8 AI agents # ═══════════════════════════════════════════════════════════ AGENTS = { "nexus": { "name": "NEXUS", "role": "Strategic Coordinator", "tier": "T0", "triggers": ["strategize", "plan", "coordinate"], }, "scout": { "name": "SCOUT", "role": "Alpha Hunter", "tier": "T3", "triggers": ["find", "scan", "hunt", "alpha"], }, "tracer": { "name": "TRACER", "role": "Forensic Investigator", "tier": "T1", "triggers": ["trace", "investigate", "wallet"], }, "cipher": { "name": "CIPHER", "role": "Contract Auditor", "tier": "T1", "triggers": ["audit", "security", "contract"], }, "sentinel": { "name": "SENTINEL", "role": "Rug Detector", "tier": "T2", "triggers": ["monitor", "watch", "alert", "rug"], }, "chronicler": { "name": "CHRONICLER", "role": "Investigative Reporter", "tier": "T2", "triggers": ["write", "document", "report"], }, "forge": { "name": "FORGE", "role": "Implementation Architect", "tier": "T1", "triggers": ["code", "implement", "build"], }, "relay": { "name": "RELAY", "role": "Communications Coordinator", "tier": "T3", "triggers": ["format", "relay", "dispatch"], }, } @router.get("/agents") async def list_agents(): return {"agents": AGENTS, "total": len(AGENTS)} @router.get("/agents/{agent_id}") async def get_agent(agent_id: str): agent = AGENTS.get(agent_id) if not agent: raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found") return agent @router.post("/agents/{agent_id}/command") async def agent_command(agent_id: str, data: dict): agent = AGENTS.get(agent_id) if not agent: raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found") command = data.get("command", "") return { "agent": agent["name"], "role": agent["role"], "command": command, "status": "queued", "timestamp": datetime.now(UTC).isoformat(), } # ═══════════════════════════════════════════════════════════ # MCP SERVERS - Multi-chain data gateways # ═══════════════════════════════════════════════════════════ @router.get("/mcp/servers") async def mcp_servers_list(): return { "servers": { "dexpaprika": "Real-time DEX data for 5M+ tokens across 20+ chains", "solana": "Solana RPC - wallet balances, token prices, DeFi yields", "dexscreener": "DEX pair data, token info, market stats", "defillama": "DeFi TVL, protocols, yields, fees", "coingecko": "13K+ tokens, global stats, historical data", "helius": "Enhanced Solana RPC - parsed txs, webhooks", "goplus": "Multi-chain token security - 700K+ tokens scanned", "rugcheck": "Solana token safety audit", }, "status": "operational", } # ═══════════════════════════════════════════════════════════ # SENTIMENT - Crypto market sentiment analysis # ═══════════════════════════════════════════════════════════ @router.get("/sentiment/market") async def sentiment_market(): try: from app.ml_anomaly import AnomalyDetector detector = AnomalyDetector() anomalies = detector.detect_market_anomalies() return {"anomalies": anomalies, "timestamp": datetime.now(UTC).isoformat()} except Exception: # Fallback to fear & greed import httpx async with httpx.AsyncClient(timeout=8) as c: r = await c.get("https://api.alternative.me/fng/?limit=1") if r.status_code == 200: data = r.json().get("data", [{}])[0] return { "sentiment": { "fear_greed_index": int(data.get("value", 50)), "classification": data.get("value_classification", "Neutral"), }, "source": "alternative.me", "timestamp": datetime.now(UTC).isoformat(), } return {"error": "Sentiment data unavailable"} @router.get("/sentiment/token/{address}") async def sentiment_token(address: str): # On-chain sentiment from buy/sell ratio try: import httpx async with httpx.AsyncClient(timeout=10) as c: r = await c.get(f"https://api.dexscreener.com/latest/dex/tokens/{address}") if r.status_code == 200: pairs = r.json().get("pairs", []) if pairs: p = pairs[0] buys = p.get("txns", {}).get("h24", {}).get("buys", 0) sells = p.get("txns", {}).get("h24", {}).get("sells", 0) total = buys + sells buy_ratio = buys / total if total > 0 else 0.5 sentiment = "bullish" if buy_ratio > 0.6 else ("bearish" if buy_ratio < 0.4 else "neutral") return { "token": address, "sentiment": sentiment, "buy_ratio": round(buy_ratio, 3), "buys_24h": buys, "sells_24h": sells, "source": "dexscreener", } except Exception: pass return {"token": address, "sentiment": "unknown"} # ═══════════════════════════════════════════════════════════ # NANSEN - Wallet labels, smart money, token flow # ═══════════════════════════════════════════════════════════ @router.get("/nansen/labels/{address}") async def nansen_labels(address: str): try: from app.nansen_connector import get_wallet_labels result = get_wallet_labels(address) return {"address": address, "labels": result, "source": "nansen"} except Exception as e: return {"error": str(e)} @router.get("/nansen/smart-money") async def nansen_smart_money(): try: from app.nansen_connector import get_smart_money result = get_smart_money() return {"smart_money": result, "source": "nansen"} except Exception as e: return {"error": str(e)} @router.get("/nansen/activity/{address}") async def nansen_activity(address: str): try: from app.nansen_connector import get_wallet_activity result = get_wallet_activity(address) return {"address": address, "activity": result, "source": "nansen"} except Exception as e: return {"error": str(e)} # ═══════════════════════════════════════════════════════════ # MEMPOOL - Bitcoin mempool monitoring # ═══════════════════════════════════════════════════════════ @router.get("/mempool/status") async def mempool_status(): try: import httpx async with httpx.AsyncClient(timeout=8) as c: r = await c.get("https://mempool.space/api/v1/fees/recommended") if r.status_code == 200: fees = r.json() r2 = await c.get("https://mempool.space/api/mempool") mempool = r2.json() if r2.status_code == 200 else {} return { "fees": fees, "mempool_tx_count": mempool.get("count", 0), "mempool_size_mb": round(mempool.get("vsize", 0) / 1_000_000, 2), "source": "mempool.space", "timestamp": datetime.now(UTC).isoformat(), } except Exception: pass return {"error": "Mempool data unavailable"} # ═══════════════════════════════════════════════════════════ # COINGECKO - Price data, trending, global metrics # ═══════════════════════════════════════════════════════════ @router.get("/coingecko/ping") async def coingecko_ping(): try: from app.coingecko_connector import get_coingecko_connector cg = get_coingecko_connector() result = await cg.ping() return {"ping": result, "source": "coingecko"} except Exception as e: return {"error": str(e)} @router.get("/coingecko/trending") async def coingecko_trending(): try: from app.coingecko_connector import get_coingecko_connector cg = get_coingecko_connector() result = await cg.get_trending() return {"trending": result, "source": "coingecko"} except Exception as e: return {"error": str(e)} @router.get("/coingecko/markets") async def coingecko_markets(vs_currency: str = "usd", per_page: int = 50): try: from app.coingecko_connector import get_coingecko_connector cg = get_coingecko_connector() result = await cg.get_market_overview(vs_currency=vs_currency, per_page=per_page) return {"markets": result, "vs_currency": vs_currency, "source": "coingecko"} except Exception as e: return {"error": str(e)} @router.get("/coingecko/price/{coin_id}") async def coingecko_price(coin_id: str): try: from app.coingecko_connector import get_coingecko_connector cg = get_coingecko_connector() result = await cg.get_token_price(coin_id) return {"coin_id": coin_id, "price": result, "source": "coingecko"} except Exception as e: return {"error": str(e)} @router.get("/coingecko/global") async def coingecko_global(): try: from app.coingecko_connector import get_coingecko_connector cg = get_coingecko_connector() result = await cg.get_global_metrics() return {"global": result, "source": "coingecko"} except Exception as e: return {"error": str(e)} @router.get("/coingecko/coin/{coin_id}") async def coingecko_coin(coin_id: str): try: from app.coingecko_connector import get_coingecko_connector cg = get_coingecko_connector() result = await cg.get_token_detail(coin_id) return {"coin_id": coin_id, "coin": result, "source": "coingecko"} except Exception as e: return {"error": str(e)}