""" RMI Tools Integration - Wires all installed open-source tools into the backend. Tools wired: Ollama, Foundry, Slither, ccxt, web3, blogwatcher, LiteLLM, Vault. """ import json import logging import os import subprocess from datetime import UTC, datetime import httpx logger = logging.getLogger(__name__) # ═══════════════════════════════════════════════════════════ # OLLAMA - Local LLM (port 11434, 2 models) # ═══════════════════════════════════════════════════════════ OLLAMA_URL = os.getenv("OLLAMA_URL", "http://172.17.0.1:11434") async def ollama_chat(prompt: str, model: str = "phi3:mini", system: str = "") -> dict: """Use local Ollama for free AI inference.""" try: messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) async with httpx.AsyncClient(timeout=60) as c: r = await c.post( f"{OLLAMA_URL}/api/chat", json={ "model": model, "messages": messages, "stream": False, "options": {"temperature": 0.3, "num_predict": 1024}, }, ) if r.status_code == 200: data = r.json() return { "text": data.get("message", {}).get("content", ""), "model": model, "provider": "ollama-local", "usage": { "prompt_tokens": data.get("prompt_eval_count", 0), "completion_tokens": data.get("eval_count", 0), }, } except Exception as e: logger.debug(f"Ollama unavailable: {e}") return {"error": "Ollama unavailable"} async def ollama_list_models() -> list[str]: """Get available Ollama models.""" try: async with httpx.AsyncClient(timeout=5) as c: r = await c.get(f"{OLLAMA_URL}/api/tags") if r.status_code == 200: return [m["name"] for m in r.json().get("models", [])] except Exception: pass return [] # ═══════════════════════════════════════════════════════════ # FOUNDRY - EVM smart contract analysis (cast) # ═══════════════════════════════════════════════════════════ CAST_PATH = "/root/.foundry/bin/cast" def cast_contract_info(address: str, chain: str = "ethereum") -> dict: """Get contract bytecode, ABI, and metadata using cast.""" rpcs = { "ethereum": os.getenv("ETH_RPC_URL", "https://eth.llamarpc.com"), "base": os.getenv("BASE_RPC_URL", "https://base.llamarpc.com"), "arbitrum": os.getenv("ARB_RPC_URL", "https://arb1.arbitrum.io/rpc"), "polygon": os.getenv("POLY_RPC_URL", "https://polygon.llamarpc.com"), "bsc": os.getenv("BSC_RPC_URL", "https://bsc-dataseed.binance.org"), "avalanche": os.getenv("AVAX_RPC_URL", "https://api.avax.network/ext/bc/C/rpc"), } rpc = rpcs.get(chain, rpcs["ethereum"]) try: env = os.environ.copy() env["ETH_RPC_URL"] = rpc # Get bytecode code = subprocess.run( [CAST_PATH, "code", address, "--rpc-url", rpc], capture_output=True, text=True, timeout=15, ) bytecode = code.stdout.strip() # Get nonce nonce = subprocess.run( [CAST_PATH, "nonce", address, "--rpc-url", rpc], capture_output=True, text=True, timeout=10, ) # Get balance balance = subprocess.run( [CAST_PATH, "balance", address, "--rpc-url", rpc], capture_output=True, text=True, timeout=10, ) return { "address": address, "chain": chain, "has_bytecode": len(bytecode) > 4, "bytecode_size": len(bytecode) // 2 if bytecode.startswith("0x") else 0, "is_contract": len(bytecode) > 4, "nonce": nonce.stdout.strip(), "balance_wei": balance.stdout.strip(), "balanced_checked": datetime.now(UTC).isoformat(), } except Exception as e: return { "address": address, "chain": chain, "error": str(e), "cast_available": os.path.exists(CAST_PATH), } def cast_tx_decode(tx_hash: str, chain: str = "ethereum") -> dict: """Decode a transaction using cast.""" rpcs = { "ethereum": "https://eth.llamarpc.com", "base": "https://base.llamarpc.com", "arbitrum": "https://arb1.arbitrum.io/rpc", "bsc": "https://bsc-dataseed.binance.org", } rpc = rpcs.get(chain, rpcs["ethereum"]) try: result = subprocess.run( [CAST_PATH, "tx", tx_hash, "--rpc-url", rpc, "--json"], capture_output=True, text=True, timeout=15, ) if result.returncode == 0: return json.loads(result.stdout) except Exception: pass return {"error": "Decode failed", "tx_hash": tx_hash} # ═══════════════════════════════════════════════════════════ # SLITHER - Solidity vulnerability scanner # ═══════════════════════════════════════════════════════════ def slither_analyze(contract_address: str, chain: str = "ethereum") -> dict: """Run Slither on a verified contract.""" try: result = subprocess.run( ["slither", contract_address, "--print", "human-summary"], capture_output=True, text=True, timeout=60, ) return { "address": contract_address, "chain": chain, "analysis": result.stdout[:2000] if result.returncode == 0 else result.stderr[:500], "success": result.returncode == 0, } except Exception as e: return {"error": str(e), "slither_available": False} # ═══════════════════════════════════════════════════════════ # CCXT - 100+ exchange price feeds # ═══════════════════════════════════════════════════════════ def ccxt_get_prices(symbols: list[str] | None = None) -> dict: """Get real-time prices from multiple exchanges via ccxt.""" try: import ccxt exchanges = { "binance": ccxt.binance(), "kraken": ccxt.kraken(), "coinbase": ccxt.coinbase(), "bybit": ccxt.bybit(), } results = {} default_symbols = [ "BTC/USDT", "ETH/USDT", "SOL/USDT", "BNB/USDT", "ARB/USDT", "MATIC/USDT", "AVAX/USDT", ] for sym in symbols or default_symbols: for ex_name, ex in exchanges.items(): try: ticker = ex.fetch_ticker(sym) if ticker and ticker.get("last"): results.setdefault(sym, {})[ex_name] = { "price": ticker["last"], "change_24h": ticker.get("percentage", ticker.get("change", 0)), "volume_24h": ticker.get("quoteVolume", ticker.get("baseVolume", 0)), } except Exception: pass return { "prices": results, "exchanges": list(exchanges.keys()), "updated_at": datetime.now(UTC).isoformat(), } except ImportError: return {"error": "ccxt not installed", "prices": {}} except Exception as e: return {"error": str(e), "prices": {}} def ccxt_arbitrage(symbol: str = "BTC/USDT") -> dict: """Find arbitrage opportunities across exchanges.""" prices = ccxt_get_prices([symbol]) price_data = prices.get("prices", {}).get(symbol, {}) if len(price_data) >= 2: prices_list = [(ex, p["price"]) for ex, p in price_data.items()] lowest = min(prices_list, key=lambda x: x[1]) highest = max(prices_list, key=lambda x: x[1]) spread = ((highest[1] - lowest[1]) / lowest[1]) * 100 return { "symbol": symbol, "lowest": {"exchange": lowest[0], "price": lowest[1]}, "highest": {"exchange": highest[0], "price": highest[1]}, "spread_pct": round(spread, 4), "profitable": spread > 0.5, } return {"symbol": symbol, "opportunities": 0} # ═══════════════════════════════════════════════════════════ # WEB3.PY - Direct Ethereum interaction # ═══════════════════════════════════════════════════════════ def web3_wallet_balance(address: str, chain: str = "ethereum") -> dict: """Get wallet balance and token holdings via web3.""" try: from web3 import Web3 rpcs = { "ethereum": "https://eth.llamarpc.com", "base": "https://base.llamarpc.com", "bsc": "https://bsc-dataseed.binance.org", "polygon": "https://polygon.llamarpc.com", "arbitrum": "https://arb1.arbitrum.io/rpc", "avalanche": "https://api.avax.network/ext/bc/C/rpc", } rpc = rpcs.get(chain, rpcs["ethereum"]) w3 = Web3(Web3.HTTPProvider(rpc)) if not w3.is_connected(): return {"error": f"RPC unavailable for {chain}"} checksum = w3.to_checksum_address(address) balance = w3.eth.get_balance(checksum) tx_count = w3.eth.get_transaction_count(checksum) code = w3.eth.get_code(checksum) return { "address": address, "chain": chain, "balance_eth": round(w3.from_wei(balance, "ether"), 6), "balance_wei": balance, "transaction_count": tx_count, "is_contract": len(code) > 0, "rpc": rpc, } except ImportError: return {"error": "web3 not installed"} except Exception as e: return {"error": str(e), "address": address, "chain": chain} # ═══════════════════════════════════════════════════════════ # BLOGWATCHER - RSS/Atom feed monitoring # ═══════════════════════════════════════════════════════════ BLOGWATCHER_PATH = "/root/go/bin/blogwatcher" def blogwatcher_fetch(feed_url: str, limit: int = 10) -> dict: """Fetch RSS/Atom feed using blogwatcher.""" try: result = subprocess.run( [ BLOGWATCHER_PATH, "fetch", "--url", feed_url, "--limit", str(limit), "--output", "json", ], capture_output=True, text=True, timeout=30, ) if result.returncode == 0: posts = json.loads(result.stdout) if result.stdout.strip() else [] return {"feed": feed_url, "posts": posts, "count": len(posts)} except Exception: pass return {"feed": feed_url, "posts": [], "error": "blogwatcher unavailable"} # ═══════════════════════════════════════════════════════════ # LITELLM - Multi-provider LLM proxy routing # ═══════════════════════════════════════════════════════════ LITELLM_CONFIG = "/srv/rugmuncher-backend/litellm/config.yaml" def litellm_status() -> dict: """Check LiteLLM proxy availability.""" config_exists = os.path.exists(LITELLM_CONFIG) return { "available": config_exists, "config_path": LITELLM_CONFIG, "config_size": os.path.getsize(LITELLM_CONFIG) if config_exists else 0, } # ═══════════════════════════════════════════════════════════ # VAULT - Secrets management # ═══════════════════════════════════════════════════════════ VAULT_ADDR = os.getenv("VAULT_ADDR", "http://172.17.0.1:8200") def vault_get_secret(path: str) -> dict | None: """Retrieve a secret from HashiCorp Vault.""" try: r = httpx.get(f"{VAULT_ADDR}/v1/{path}", headers={"X-Vault-Token": "root"}, timeout=5) if r.status_code == 200: return r.json().get("data", {}).get("data", {}) except Exception: pass return None def vault_list_secrets(path: str = "secret") -> list[str]: """List secrets in Vault.""" try: r = httpx.get(f"{VAULT_ADDR}/v1/{path}?list=true", headers={"X-Vault-Token": "root"}, timeout=5) if r.status_code == 200: return r.json().get("data", {}).get("keys", []) except Exception: pass return [] # ═══════════════════════════════════════════════════════════ # TOOL STATUS - Health check for all integrated tools # ═══════════════════════════════════════════════════════════ async def tools_health() -> dict: """Health check for all integrated tools.""" return { "ollama": { "available": bool(await ollama_list_models()), "models": await ollama_list_models(), "url": OLLAMA_URL, }, "foundry": {"installed": os.path.exists(CAST_PATH), "path": CAST_PATH}, "slither": {"installed": os.path.exists("/root/.local/bin/slither")}, "ccxt": { "available": True # pip installed }, "web3": {"available": True}, "blogwatcher": {"installed": os.path.exists(BLOGWATCHER_PATH), "path": BLOGWATCHER_PATH}, "litellm": litellm_status(), "vault": {"available": bool(vault_list_secrets()), "url": VAULT_ADDR}, "file_upload": { "running": True, # port 8001 "url": "http://localhost:8001", }, "timestamp": datetime.now(UTC).isoformat(), }