RMI Platform
feat: wire historical scrapers (Rekt DB, Chainabuse, TRM, SlowMist) into DataBus
ab145db | """ | |
| DataBus Router β Unified API Endpoints | |
| ======================================== | |
| Replaces the fractured caching_shield/router, cache_manager stats, | |
| and all connector-specific endpoints with a single clean interface. | |
| POST /api/v1/databus/fetch β Fetch data (any type) | |
| GET /api/v1/databus/health β System health + cache stats | |
| GET /api/v1/databus/capacity β Credit report + recommendations | |
| GET /api/v1/databus/chains β List all provider chains | |
| POST /api/v1/databus/invalidate β Clear cache (admin) | |
| GET /api/v1/databus/vault/status β Key pool status (admin, no key values) | |
| GET /api/v1/databus/vault/reload β Reload keys from vault (admin) | |
| GET /api/v1/databus/fetch/{type} β GET convenience for simple queries | |
| """ | |
| import logging | |
| import os | |
| from fastapi import APIRouter, HTTPException, Query, Request | |
| from app.databus.core import databus | |
| # Chains that legitimately require an address/token to return data. | |
| # When called in list-mode (no address), return 200 with empty data (not 502) | |
| # so the frontend can render an empty state instead of an error. | |
| LIST_MODE_OK = { | |
| "wallet_labels", "ohlcv", "scanner", "trades", "transfers", "holders", | |
| "token_security", "cluster_map", "entity_intel", "wallet_forensics", | |
| "rugcheck", "sophistication", "behavioral", "pnl", "portfolio", | |
| "bubble_map", "early_buyers", "funding_trail", "counterparties", | |
| "ct_rundown", "kol_track", "kol_profile", "whale_alerts", "insider_detect", | |
| "mev_detect", "bundle_detect", "sniper_detect", "wash_trade_detect", | |
| "copy_trade_detect", "bot_farm_detect", "fresh_wallet_analysis", "dev_finder", | |
| # Newly added β all require address/token to return data | |
| "dev_activity", "token_price", "token_security_check", "trader_profile", | |
| "liquidity", "top_holders", "token_info", "market_data", "risk_score", | |
| "audit", "compliance", "whale_transactions", "wallet_profile", | |
| "token_transactions", "wallet_transactions", "address_labels", "entity_lookup", | |
| "historical_ingest", "historical_sources", "defi_hacks", "scam_reports", | |
| } | |
| logger = logging.getLogger("databus.router") | |
| router = APIRouter(prefix="/api/v1/databus", tags=["databus"]) | |
| def _verify_admin(request: Request) -> bool: | |
| admin_key = os.getenv("ADMIN_API_KEY", "") | |
| if not admin_key: | |
| return True | |
| provided = request.headers.get("X-Admin-Key", "") | |
| return provided == admin_key | |
| async def databus_fetch(request: Request): | |
| """ | |
| Universal data fetch endpoint. | |
| Every response is packaged based on who's asking. | |
| No raw data leaks β consumers only get what they're authorized for. | |
| Body: { | |
| "data_type": "token_price", # required | |
| "admin_key": "", # for admin data | |
| "force_fresh": false, # skip cache | |
| "rag_index": false, # index in RAG | |
| "consumer_type": "", # public_web/authenticated/premium/admin/mcp_tool/x402_paid | |
| "tool_id": "", # MCP tool ID for scoping | |
| "x402_tier": "", # x402 pricing tier: free/basic/premium/enterprise | |
| "mint": "So111...", # type-specific params | |
| "address": "0x...", # type-specific params | |
| ... | |
| } | |
| """ | |
| body = await request.json() | |
| data_type = body.pop("data_type", None) | |
| if not data_type: | |
| raise HTTPException(400, "data_type is required") | |
| admin_key = body.pop("admin_key", "") | |
| force_fresh = body.pop("force_fresh", False) | |
| rag_index = body.pop("rag_index", False) | |
| consumer_type = body.pop("consumer_type", "") | |
| tool_id = body.pop("tool_id", "") | |
| x402_tier = body.pop("x402_tier", "") | |
| result = await databus.fetch( | |
| data_type=data_type, | |
| admin_key=admin_key, | |
| force_fresh=force_fresh, | |
| rag_index=rag_index, | |
| consumer_type=consumer_type, | |
| tool_id=tool_id, | |
| x402_tier=x402_tier, | |
| request=request, | |
| **body, | |
| ) | |
| if result is None: | |
| if data_type in LIST_MODE_OK: | |
| return {"data": [], "results": [], "empty": True, "reason": "address_required", "data_type": data_type} | |
| raise HTTPException(502, f"No data available for {data_type}") | |
| return result | |
| async def databus_fetch_get( | |
| data_type: str, | |
| request: Request, | |
| admin_key: str = Query(default=""), | |
| force_fresh: bool = Query(default=False), | |
| ): | |
| """GET convenience for simple queries. Params passed as query args.""" | |
| # Collect all query params except the known ones | |
| kwargs = {} | |
| for key, value in request.query_params.items(): | |
| if key not in ("admin_key", "force_fresh"): | |
| kwargs[key] = value | |
| result = await databus.fetch(data_type=data_type, admin_key=admin_key, force_fresh=force_fresh, **kwargs) | |
| if result is None: | |
| if data_type in LIST_MODE_OK: | |
| return {"data": [], "results": [], "empty": True, "reason": "address_required", "data_type": data_type} | |
| raise HTTPException(502, f"No data available for {data_type}") | |
| return result | |
| async def databus_health(): | |
| """Full system health: cache, vault, chains, stats.""" | |
| return await databus.health() | |
| async def databus_capacity(): | |
| """Credit report and recommendations for free tier expansion.""" | |
| if not databus._initialized: | |
| await databus.initialize() | |
| return databus.capacity_report() | |
| async def databus_chains(): | |
| """List all available data types and their provider fallback chains.""" | |
| if not databus._initialized: | |
| await databus.initialize() | |
| return databus.list_chains() | |
| async def databus_access_matrix(request: Request): | |
| """View the full access control matrix. Admin key required.""" | |
| if not _verify_admin(request): | |
| from app.databus.access_control import access_controller | |
| # Non-admin sees a limited view β only their tier's access | |
| return {"note": "Full matrix requires admin key. Your access depends on your consumer type."} | |
| from app.databus.access_control import access_controller | |
| return access_controller.list_access_matrix() | |
| async def databus_mcp_scope(tool_id: str): | |
| """Get the data types an MCP tool is authorized to access.""" | |
| from app.databus.access_control import access_controller | |
| allowed = access_controller.get_mcp_allowed_types(tool_id) | |
| if not allowed: | |
| return {"tool_id": tool_id, "allowed_types": [], "note": "Unknown tool or no scope defined"} | |
| return {"tool_id": tool_id, "allowed_types": allowed} | |
| async def databus_x402_scope(tier: str): | |
| """Get the data types an x402 pricing tier can access.""" | |
| from app.databus.access_control import access_controller | |
| allowed = access_controller.get_x402_allowed_types(tier) | |
| return {"tier": tier, "allowed_types": allowed} | |
| async def databus_invalidate(request: Request): | |
| """Clear cache. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| body = await request.json() if request.headers.get("content-type") == "application/json" else {} | |
| data_type = body.get("data_type") | |
| if data_type: | |
| await databus.invalidate(data_type, **{k: v for k, v in body.items() if k != "data_type"}) | |
| return {"status": "invalidated", "data_type": data_type} | |
| else: | |
| await databus.invalidate_all() | |
| return {"status": "all_cache_cleared"} | |
| async def databus_vault_status(request: Request): | |
| """Key pool status. NEVER exposes key values. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| if not databus.vault: | |
| await databus.initialize() | |
| return databus.vault.status() | |
| async def databus_vault_capacity(request: Request): | |
| """Detailed capacity report with free tier recommendations. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| if not databus.vault: | |
| await databus.initialize() | |
| return databus.vault.capacity_report() | |
| async def databus_vault_reload(request: Request): | |
| """Reload keys from vault without restart. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| await databus.vault.reload() | |
| return {"status": "reloaded", "providers": len(databus.vault.pools)} | |
| async def databus_vault_add_key(request: Request): | |
| """Hot-add an API key to a provider pool. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| body = await request.json() | |
| provider = body.get("provider") | |
| key_name = body.get("key_name") | |
| key_value = body.get("key_value") | |
| if not all([provider, key_name, key_value]): | |
| raise HTTPException(400, "provider, key_name, key_value required") | |
| await databus.vault.add_key(provider, key_name, key_value) | |
| return {"status": "added", "provider": provider, "key_name": key_name} | |
| async def databus_vault_reset_monthly(request: Request): | |
| """Reset monthly call counters. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| databus.vault.reset_monthly_counters() | |
| return {"status": "monthly_counters_reset"} | |
| # ββ Convenience endpoints for Arkham Intelligence ββ | |
| async def arkham_entity(address: str, request: Request): | |
| """Resolve a wallet address to an Arkham entity. Admin key required.""" | |
| admin_key = request.headers.get("X-Admin-Key", "") | |
| result = await databus.fetch("entity_intel", address=address, admin_key=admin_key) | |
| if result is None: | |
| raise HTTPException(502, f"Could not resolve entity for {address}") | |
| return result | |
| async def arkham_labels(request: Request): | |
| """Look up labels for addresses. Admin key required for Arkham, public for local.""" | |
| admin_key = request.headers.get("X-Admin-Key", "") | |
| address = request.query_params.get("address", "") | |
| result = await databus.fetch("wallet_labels", address=address, admin_key=admin_key) | |
| if result is None: | |
| raise HTTPException(502, "No labels found") | |
| return result | |
| async def arkham_portfolio(address: str, request: Request): | |
| """Get portfolio for an entity. Admin key required.""" | |
| admin_key = request.headers.get("X-Admin-Key", "") | |
| result = await databus.fetch("arkham_portfolio", address=address, admin_key=admin_key) | |
| if result is None: | |
| raise HTTPException(502, f"Could not get portfolio for {address}") | |
| return result | |
| # ββ New convenience endpoints for expanded data types ββ | |
| async def token_detail(mint: str, request: Request): | |
| """Rich token detail (metadata + price + security).""" | |
| result = await databus.fetch("token_detail", mint=mint) | |
| if result is None: | |
| raise HTTPException(502, f"No data for token {mint}") | |
| return result | |
| async def wallet_tokens(address: str, request: Request): | |
| """Wallet token holdings.""" | |
| result = await databus.fetch("wallet_tokens", address=address) | |
| if result is None: | |
| raise HTTPException(502, f"No token data for wallet {address}") | |
| return result | |
| async def wallet_pnl(address: str, request: Request): | |
| """Wallet profit/loss.""" | |
| result = await databus.fetch("wallet_pnl", address=address) | |
| if result is None: | |
| raise HTTPException(502, f"No PnL data for wallet {address}") | |
| return result | |
| async def bubble_map(address: str, request: Request, chain: str = "solana", depth: int = 2): | |
| """Interactive holder bubble map.""" | |
| result = await databus.fetch("bubble_map", address=address, chain=chain, depth=depth) | |
| if result is None: | |
| raise HTTPException(502, f"No bubble map data for {address}") | |
| return result | |
| async def rugmaps_analysis(address: str, request: Request, chain: str = "solana"): | |
| """RugMaps AI analysis + similar scam search.""" | |
| result = await databus.fetch("rugmaps_analysis", address=address, chain=chain) | |
| if result is None: | |
| raise HTTPException(502, f"No RugMaps analysis for {address}") | |
| return result | |
| async def threat_check(address: str, request: Request, chain_id: str = "1"): | |
| """Threat intelligence check (CryptoScamDB + GoPlus + Januus).""" | |
| result = await databus.fetch("threat_check", address=address, chain_id=chain_id) | |
| if result is None: | |
| raise HTTPException(502, f"No threat data for {address}") | |
| return result | |
| async def contract_scan(address: str, request: Request, chain: str = "base"): | |
| """Smart contract deep scan (Slither + Mythril).""" | |
| result = await databus.fetch("contract_scan", address=address, chain=chain) | |
| if result is None: | |
| raise HTTPException(502, f"No contract scan data for {address}") | |
| return result | |
| async def sentinel_deep(address: str, request: Request, chain: str = "solana"): | |
| """Deep SENTINEL scan + threat feeds + contract analysis.""" | |
| admin_key = request.headers.get("X-Admin-Key", "") | |
| result = await databus.fetch("sentinel_deep", address=address, chain=chain, admin_key=admin_key) | |
| if result is None: | |
| raise HTTPException(502, f"No deep scan data for {address}") | |
| return result | |
| async def prediction_markets(request: Request, query: str = "", category: str = ""): | |
| """Prediction market intel (Polymarket + Kalshi).""" | |
| result = await databus.fetch("prediction_markets", query=query, category=category) | |
| if result is None: | |
| raise HTTPException(502, "No prediction market data available") | |
| return result | |
| async def prediction_signals(request: Request): | |
| """Auto-detected trading signals from prediction markets.""" | |
| result = await databus.fetch("prediction_signals") | |
| if result is None: | |
| raise HTTPException(502, "No prediction signals available") | |
| return result | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # NEW: Batch, Cache Control, Provider Health, Schema Validation (#7,#8,#9) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def databus_batch(request: Request): | |
| """#7 Batch DataBus β fetch multiple data types in one call. | |
| Body: { | |
| "requests": [ | |
| {"data_type": "token_price", "mint": "So111..."}, | |
| {"data_type": "wallet_labels", "address": "0x..."}, | |
| ... | |
| ], | |
| "admin_key": "", # optional, applies to all | |
| "consumer_type": "", # optional, applies to all | |
| } | |
| Returns: array of results in same order as requests. | |
| """ | |
| body = await request.json() | |
| requests_list = body.get("requests", []) | |
| if not requests_list: | |
| raise HTTPException(400, "requests array is required") | |
| if len(requests_list) > 20: | |
| raise HTTPException(400, "Maximum 20 requests per batch") | |
| admin_key = body.get("admin_key", "") | |
| consumer_type = body.get("consumer_type", "") | |
| # Inject shared params into each request | |
| for req in requests_list: | |
| if admin_key and "admin_key" not in req: | |
| req["admin_key"] = admin_key | |
| if consumer_type and "consumer_type" not in req: | |
| req["consumer_type"] = consumer_type | |
| req["request"] = request | |
| results = await databus.fetch_batch(requests_list) | |
| return {"results": results, "count": len(results)} | |
| async def databus_warm_start(request: Request): | |
| """Start cache warm background task. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| body = await request.json() if request.headers.get("content-type") == "application/json" else {} | |
| interval = body.get("interval_seconds", 30) | |
| await databus.start_cache_warm(interval_seconds=interval) | |
| return {"status": "started", "interval_seconds": interval} | |
| async def databus_warm_stop(request: Request): | |
| """Stop cache warm background task. Admin key required.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| await databus.stop_cache_warm() | |
| return {"status": "stopped"} | |
| async def databus_providers_health(request: Request): | |
| """#5 Provider Health Dashboard β per-provider success/failure/latency/circuit status.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| if not databus._initialized: | |
| await databus.initialize() | |
| return { | |
| "provider_health": databus._provider_health.all_health(), | |
| "deduplication": databus._dedup.stats(), | |
| } | |
| async def databus_cache_stats(request: Request): | |
| """#10 Per-type cache stats with TTL tuning suggestions.""" | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required") | |
| return { | |
| "per_type": databus.cache.type_stats(), | |
| "tuning_alerts": [ | |
| {"data_type": dt, **stats} for dt, stats in databus.cache.type_stats().items() if stats.get("suggestion") | |
| ], | |
| } | |
| async def databus_schema_validate(request: Request): | |
| """#15 Validate a DataBus response against expected schema. | |
| Body: {"data_type": "token_price", "data": {"price_usd": 1.23, ...}} | |
| Returns: {valid: bool, missing_fields: [...]} | |
| """ | |
| body = await request.json() | |
| data_type = body.get("data_type", "") | |
| data = body.get("data", {}) | |
| from app.databus.response_schema import schema_validator | |
| is_valid, missing = schema_validator.validate(data_type, data) | |
| return {"data_type": data_type, "valid": is_valid, "missing_fields": missing} | |
| # ββ Social Data Endpoints βββββββββββββββββββββββββββββββββββββββββ | |
| async def social_x_profile(username: str): | |
| """Get X/Twitter user profile β cached 24h.""" | |
| from app.databus.social import SocialDataAggregator | |
| agg = SocialDataAggregator(databus.cache) | |
| result = await agg.x.get_user(username) | |
| if not result: | |
| raise HTTPException(404, f"User @{username} not found or API unavailable") | |
| return {"source": "x_twitter", "username": username, "data": result} | |
| async def social_x_tweets(username: str, count: int = Query(20, ge=1, le=100)): | |
| """Get recent tweets from user β cached 15min.""" | |
| from app.databus.social import SocialDataAggregator | |
| agg = SocialDataAggregator(databus.cache) | |
| result = await agg.get_our_tweets(count=count) if username.lower() == "cryptorugmunch" else None | |
| if result is None: | |
| # Generic user lookup | |
| profile = await agg.x.get_user(username) | |
| if profile: | |
| result = await agg.x.get_user_tweets(profile["id"], max_results=count) | |
| if not result: | |
| raise HTTPException(404, f"Tweets for @{username} not available") | |
| return {"source": "x_twitter", "username": username, "count": len(result), "data": result} | |
| async def social_x_mentions(count: int = Query(20, ge=1, le=100)): | |
| """Get mentions of @CryptoRugMunch β cached 15min.""" | |
| from app.databus.social import SocialDataAggregator | |
| agg = SocialDataAggregator(databus.cache) | |
| result = await agg.get_our_mentions(count=count) | |
| if not result: | |
| raise HTTPException(404, "Mentions not available (API budget exhausted)") | |
| return {"source": "x_twitter", "count": len(result), "data": result} | |
| async def social_x_engagement(tweet_ids: str = Query(..., description="Comma-separated tweet IDs")): | |
| """Get engagement metrics for tweets β cached 1h.""" | |
| from app.databus.social import SocialDataAggregator | |
| agg = SocialDataAggregator(databus.cache) | |
| ids = [tid.strip() for tid in tweet_ids.split(",")[:100]] | |
| result = await agg.x.get_engagement_metrics(ids) | |
| return {"source": "x_twitter", "count": len(result), "data": result} | |
| async def social_x_search(q: str = Query(..., description="Search query"), count: int = Query(10, ge=1, le=100)): | |
| """Search X/Twitter β VERY expensive, cached 24h. Use sparingly.""" | |
| from app.databus.social import SocialDataAggregator | |
| agg = SocialDataAggregator(databus.cache) | |
| result = await agg.search_mentions(q, count=count) | |
| if not result: | |
| raise HTTPException(404, "Search results not available (API budget exhausted or no results)") | |
| return {"source": "x_twitter", "query": q, "count": len(result), "data": result} | |
| async def social_kol_reputation(username: str): | |
| """KOL reputation score β cached 24h.""" | |
| from app.databus.social import SocialDataAggregator | |
| agg = SocialDataAggregator(databus.cache) | |
| return await agg.get_kol_reputation(username) | |
| async def social_sentiment(username: str = Query("CryptoRugMunch")): | |
| """Brand sentiment analysis β cached 1h.""" | |
| from app.databus.social import SocialDataAggregator | |
| agg = SocialDataAggregator(databus.cache) | |
| return await agg.get_sentiment(username) | |
| async def social_x_budget(): | |
| """Current X API read budget status.""" | |
| from app.databus.social import XTwitterProvider | |
| provider = XTwitterProvider(databus.cache) | |
| return { | |
| "daily_reads_used": provider._daily_reads, | |
| "daily_reads_remaining": X_DAILY_READ_BUDGET - provider._daily_reads, | |
| "monthly_free_limit": X_FREE_MONTHLY_READ_LIMIT, | |
| "daily_budget": X_DAILY_READ_BUDGET, | |
| "note": "X Free tier: 10k reads/month. Cache aggressively.", | |
| } | |
| X_HANDLE = "CryptoRugMunch" | |
| async def social_x_discover(handle: str = Query(X_HANDLE), limit: int = Query(50, ge=1, le=100)): | |
| """Discover tweets via web search β no API needed, works for free.""" | |
| from app.databus.social_scraper import XWebScraper | |
| scraper = XWebScraper(databus.cache) | |
| tweets = await scraper.discover_tweets(handle=handle, limit=limit) | |
| return {"source": "web_search", "handle": handle, "count": len(tweets), "data": tweets} | |
| async def social_x_engagement_report(handle: str = Query(X_HANDLE)): | |
| """Get engagement report for a handle β avg likes, best tweets, posting frequency.""" | |
| from app.databus.social_scraper import XWebScraper | |
| scraper = XWebScraper(databus.cache) | |
| report = await scraper.get_engagement_report(handle=handle) | |
| return report | |
| async def social_x_mentions_discover(handle: str = Query(X_HANDLE), limit: int = Query(20, ge=1, le=50)): | |
| """Find tweets mentioning a handle β via web search.""" | |
| from app.databus.social_scraper import XWebScraper | |
| scraper = XWebScraper(databus.cache) | |
| mentions = await scraper.get_mentions(handle=handle, limit=limit) | |
| return {"source": "web_search", "handle": handle, "count": len(mentions), "data": mentions} | |
| async def social_x_trending(): | |
| """Get current crypto trending topics from web search.""" | |
| from app.databus.social_scraper import XWebScraper | |
| scraper = XWebScraper(databus.cache) | |
| topics = await scraper.get_trending_topics() | |
| return {"source": "web_search", "count": len(topics), "data": topics} | |
| # ββ Bitquery Blockchain Data Endpoints βββββββββββββββββββββββββββ | |
| async def bitquery_health(): | |
| """Check Bitquery provider health and billing status.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| return await provider.health() | |
| async def bitquery_token_price(network: str, token_address: str): | |
| """Get DEX token price from Bitquery.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| result = await provider.get_token_price(network, token_address) | |
| if result and "error" in result: | |
| raise HTTPException(status_code=503, detail=result) | |
| return {"source": "bitquery", "network": network, "token": token_address, "data": result} | |
| async def bitquery_holders(network: str, token_address: str, limit: int = Query(100, ge=1, le=500)): | |
| """Get token holder distribution from Bitquery.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| result = await provider.get_holder_distribution(network, token_address, limit) | |
| if result and "error" in result: | |
| raise HTTPException(status_code=503, detail=result) | |
| return {"source": "bitquery", "network": network, "token": token_address, "data": result} | |
| async def bitquery_tx_trace(network: str, tx_hash: str): | |
| """Get full transaction trace from Bitquery.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| result = await provider.get_transaction_trace(network, tx_hash) | |
| if result and "error" in result: | |
| raise HTTPException(status_code=503, detail=result) | |
| return {"source": "bitquery", "network": network, "tx": tx_hash, "data": result} | |
| async def bitquery_dex_volume(network: str, pool: str = Query(None), timeframe: str = Query("24h")): | |
| """Get DEX trading volume from Bitquery.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| result = await provider.get_dex_volume(network, pool, timeframe) | |
| if result and "error" in result: | |
| raise HTTPException(status_code=503, detail=result) | |
| return { | |
| "source": "bitquery", | |
| "network": network, | |
| "pool": pool, | |
| "timeframe": timeframe, | |
| "data": result, | |
| } | |
| async def bitquery_balance(network: str, address: str): | |
| """Get address token balances from Bitquery.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| result = await provider.get_address_balance(network, address) | |
| if result and "error" in result: | |
| raise HTTPException(status_code=503, detail=result) | |
| return {"source": "bitquery", "network": network, "address": address, "data": result} | |
| async def bitquery_cross_chain(address: str, networks: str = Query("ethereum,bsc,solana")): | |
| """Track token transfers across chains from Bitquery.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| net_list = [n.strip() for n in networks.split(",")] | |
| result = await provider.get_cross_chain_transfers(address, net_list) | |
| if result and "error" in result: | |
| raise HTTPException(status_code=503, detail=result) | |
| return {"source": "bitquery", "address": address, "networks": net_list, "data": result} | |
| async def bitquery_contract_events( | |
| network: str, contract: str, event: str = Query(None), limit: int = Query(50, ge=1, le=200) | |
| ): | |
| """Get smart contract events from Bitquery.""" | |
| from app.databus.bitquery_provider import BitqueryProvider | |
| provider = BitqueryProvider(databus.cache) | |
| result = await provider.get_smart_contract_events(network, contract, event, limit) | |
| if result and "error" in result: | |
| raise HTTPException(status_code=503, detail=result) | |
| return {"source": "bitquery", "network": network, "contract": contract, "data": result} | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # INTELLIGENT WEBHOOK SYSTEM β Arkham, Helius, Moralis, Alchemy + custom | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def receive_webhook(service: str, request: Request): | |
| """Universal webhook receiver. Routes to correct handler based on service. | |
| Supported: arkham, helius, moralis, alchemy, custom | |
| Auto-validates signatures, deduplicates, caches in DataBus, | |
| indexes in RAG, triggers premium scanner, pushes alerts. | |
| """ | |
| try: | |
| from app.databus.webhooks import handle_webhook | |
| except ImportError: | |
| raise HTTPException(501, "Webhook system not available") | |
| raw_body = await request.body() | |
| headers = dict(request.headers) | |
| try: | |
| payload = await request.json() | |
| except: | |
| payload = {} | |
| result = await handle_webhook(service, payload, headers, raw_body) | |
| if "error" in result and result.get("status") != "duplicate": | |
| raise HTTPException(400, result["error"]) | |
| return result | |
| async def list_webhooks_endpoint(): | |
| """List all recent webhook events.""" | |
| try: | |
| from app.databus.webhooks import list_webhooks | |
| return await list_webhooks() | |
| except ImportError: | |
| raise HTTPException(501, "Webhook system not available") | |
| async def setup_webhook_endpoint(service: str, request: Request): | |
| """Programmatically register a webhook with a service. | |
| Body: { | |
| "webhook_url": "https://rugmunch.io/api/v1/databus/webhooks/helius", | |
| "events": ["transaction", "token_transfer"], | |
| "addresses": ["0x..."], | |
| "api_key": "optional-override" | |
| } | |
| """ | |
| try: | |
| from app.databus.webhooks import setup_webhook | |
| except ImportError: | |
| raise HTTPException(501, "Webhook system not available") | |
| body = await request.json() | |
| result = await setup_webhook( | |
| service=service, | |
| webhook_url=body.get("webhook_url", ""), | |
| events=body.get("events"), | |
| api_key=body.get("api_key", ""), | |
| addresses=body.get("addresses", []), | |
| chains=body.get("chains", []), | |
| ) | |
| return result | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PREMIUM SCANNER ENDPOINTS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def premium_bundle_detect(address: str, chain: str = "solana", request: Request = None): | |
| """Bubblemaps-style bundle detection. Premium tier.""" | |
| return await databus.fetch("bundle_detect", address=address, chain=chain) | |
| async def premium_cluster_map(address: str, chain: str = "solana", depth: int = 3, request: Request = None): | |
| """Full wallet cluster mapping with graph-ready nodes/edges. Premium tier.""" | |
| return await databus.fetch("cluster_map", address=address, chain=chain, depth=depth) | |
| async def premium_dev_finder(token: str, chain: str = "solana", request: Request = None): | |
| """Find developer/creator wallets behind a token. Premium tier.""" | |
| return await databus.fetch("dev_finder", token_address=token, chain=chain) | |
| async def premium_sniper_detect(address: str, chain: str = "solana", request: Request = None): | |
| """Detect snipers β first-block buyers with fast dumps. Premium tier.""" | |
| return await databus.fetch("sniper_detect", address=address, chain=chain) | |
| async def premium_bot_farms(address: str, chain: str = "solana", request: Request = None): | |
| """Detect bot farms β identical behavior patterns. Premium tier.""" | |
| return await databus.fetch("bot_farm_detect", address=address, chain=chain) | |
| async def premium_copy_trading(address: str, chain: str = "solana", request: Request = None): | |
| """Detect copy trading patterns. Premium tier.""" | |
| return await databus.fetch("copy_trade_detect", address=address, chain=chain) | |
| async def premium_insider(address: str, chain: str = "solana", request: Request = None): | |
| """Detect insider trading signals. Premium tier.""" | |
| return await databus.fetch("insider_detect", address=address, chain=chain) | |
| async def premium_wash_trading(address: str, chain: str = "solana", request: Request = None): | |
| """Detect wash trading patterns. Premium tier.""" | |
| return await databus.fetch("wash_trade_detect", address=address, chain=chain) | |
| async def premium_mev(address: str, chain: str = "solana", request: Request = None): | |
| """Detect MEV sandwich attacks. Premium tier.""" | |
| return await databus.fetch("mev_detect", address=address, chain=chain) | |
| async def premium_fresh_wallets(address: str, chain: str = "solana", request: Request = None): | |
| """Analyze fresh wallet concentration β high new-wallet % = rug risk. Premium tier.""" | |
| return await databus.fetch("fresh_wallet_analysis", address=address, chain=chain) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # RUGCHARTS β Volume Authenticity, OHLCV, Token Security | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def premium_volume_auth(address: str, chain: str = "ethereum", request: Request = None): | |
| """Fake volume % with bootstrap CI. The RugCharts moat β no one else does this.""" | |
| return await databus.fetch( | |
| "volume_authenticity", | |
| address=address, | |
| chain=chain, | |
| volume_24h=request.query_params.get("volume_24h", "0"), | |
| liquidity_usd=request.query_params.get("liquidity_usd", "0"), | |
| unique_wallets=request.query_params.get("unique_wallets", "0"), | |
| buy_count=request.query_params.get("buy_count", "0"), | |
| sell_count=request.query_params.get("sell_count", "0"), | |
| tx_count=request.query_params.get("tx_count", "0"), | |
| ) | |
| async def ohlcv_candles( | |
| address: str, | |
| chain: str = "ethereum", | |
| timeframe: str = "1h", | |
| limit: int = 100, | |
| request: Request = None, | |
| ): | |
| """OHLCV candlestick data with authenticity scoring baked in.""" | |
| return await databus.fetch("ohlcv", token=address, chain=chain, timeframe=timeframe, limit=limit) | |
| async def premium_security_scan(address: str, chain: str = "ethereum", request: Request = None): | |
| """37+ security checks β GoPlus, honeypot, contract, liquidity, holders, rug pull indicators.""" | |
| return await databus.fetch("token_security", address=address, chain=chain) | |
| async def security_check_matrix(): | |
| """Full list of all 37+ security checks with weights and descriptions.""" | |
| return await databus.fetch("token_security", action="matrix") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # RUGCHARTS INTELLIGENCE β 10 Premium Endpoints | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def smart_money_endpoint(chain: str = "solana", limit: int = 20): | |
| """What profitable wallets are buying right now β with entity labels.""" | |
| return await databus.fetch("smart_money", chain=chain, limit=limit) | |
| async def whale_alerts_endpoint(address: str, chain: str = "solana", min_value: float = 100000): | |
| """Real-time large transaction detection for a token or wallet.""" | |
| return await databus.fetch("whale_alerts", address=address, chain=chain, min_value_usd=min_value) | |
| async def token_launches_endpoint(chain: str = "solana", limit: int = 50): | |
| """Newly launched tokens with instant risk scoring by age.""" | |
| return await databus.fetch("token_launches", chain=chain, limit=limit) | |
| async def insider_detection_endpoint(address: str, chain: str = "solana"): | |
| """Detect pre-pump accumulation β volume spikes before major price moves.""" | |
| return await databus.fetch("insider_detection", address=address, chain=chain) | |
| async def liquidity_risk_endpoint(address: str, chain: str = "solana"): | |
| """LP health: concentration risk, lock status, holder entities via Arkham.""" | |
| return await databus.fetch("liquidity_risk", address=address, chain=chain) | |
| async def holder_health_endpoint(address: str, chain: str = "solana"): | |
| """Holder distribution: Gini coefficient, top concentration, decentralization score.""" | |
| return await databus.fetch("holder_health", address=address, chain=chain) | |
| async def cross_chain_endpoint(address: str): | |
| """Trace entity across all chains via Arkham. Discover all related addresses.""" | |
| return await databus.fetch("cross_chain_entity", address=address) | |
| async def rug_patterns_endpoint(address: str, chain: str = "solana"): | |
| """Match token against 10 known rug pull patterns. Similarity scoring.""" | |
| return await databus.fetch("rug_patterns", address=address, chain=chain) | |
| async def dev_reputation_endpoint(address: str, chain: str = "solana"): | |
| """Deployer wallet history: token count, lifespan, entity resolution.""" | |
| return await databus.fetch("dev_reputation", address=address, chain=chain) | |
| async def token_report_endpoint(address: str, chain: str = "solana"): | |
| """ONE-CALL enhanced token report. Smart verdicts, entity enrichment, | |
| trust adjustments, tier-aware. THE endpoint powering RugCharts.""" | |
| return await databus.fetch("token_report", address=address, chain=chain) | |
| async def tier_comparison_endpoint(): | |
| """Competitive tier comparison: RugCharts vs DexScreener vs Nansen vs GMGN.""" | |
| return await databus.fetch("tier_comparison") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MARKET DATA β Free APIs: CoinGecko, Fear & Greed, Polymarket | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def market_prices(coins: str = "bitcoin,ethereum,solana"): | |
| """Live prices from CoinGecko. Free, no key needed.""" | |
| return await databus.fetch("live_prices", coins=coins) | |
| async def fear_greed(): | |
| """Crypto Fear & Greed Index. Free, no key needed.""" | |
| return await databus.fetch("fear_greed") | |
| async def market_trending(): | |
| """Trending coins from CoinGecko.""" | |
| return await databus.fetch("trending_coins") | |
| async def prediction_markets(): | |
| """Prediction markets from Polymarket. Free, no key needed.""" | |
| return await databus.fetch("prediction_markets") | |
| async def market_brief(): | |
| """One-call market overview: prices + fear/greed + trending + predictions.""" | |
| return await databus.fetch("market_brief") | |
| async def full_news(limit: int = 15): | |
| """Complete news feed: headlines + market data + fear/greed + polymarket.""" | |
| return await databus.fetch("full_news", limit=limit) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # NEWS INTELLIGENCE β Multi-source, quality-scored, social-enabled | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def news_intel(limit: int = 30): | |
| """Complete news intelligence β 10+ sources, quality-scored, deduped, sentiment-tagged.""" | |
| return await databus.fetch("news_intel", limit=limit) | |
| async def weekly_best(limit: int = 20): | |
| """Curated weekly best β highest quality crypto journalism.""" | |
| return await databus.fetch("weekly_best", limit=limit) | |
| async def academic_papers(limit: int = 10): | |
| """Academic crypto/blockchain research papers from arXiv.""" | |
| return await databus.fetch("academic_papers", limit=limit) | |
| async def social_feed(limit: int = 30): | |
| """Crypto social feed β X/Twitter + CryptoPanic sentiment.""" | |
| return await databus.fetch("social_feed", limit=limit) | |
| async def react_article(content_hash: str, request: Request): | |
| """React to article: π₯ππ»ππ§ π€‘ππ""" | |
| body = await request.json() | |
| return await databus.fetch( | |
| "article_reactions", | |
| content_hash=content_hash, | |
| reaction=body.get("reaction", "π₯"), | |
| user=body.get("user", "anon"), | |
| ) | |
| async def get_article_reactions(content_hash: str): | |
| """Get reactions and comments for an article.""" | |
| return await databus.fetch("article_reactions", content_hash=content_hash) | |
| async def comment_article(content_hash: str, request: Request): | |
| """Comment on an article.""" | |
| body = await request.json() | |
| return await databus.fetch( | |
| "article_comments", | |
| content_hash=content_hash, | |
| user=body.get("user", "anon"), | |
| text=body.get("text", ""), | |
| ) | |
| async def create_bb_from_article(content_hash: str, request: Request): | |
| """Turn article into Bulletin Board post.""" | |
| body = await request.json() if request.headers.get("content-type") == "application/json" else {} | |
| return await databus.fetch("bb_post", content_hash=content_hash, user=body.get("user", "system")) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CT RUNDOWN β Crypto Twitter Intelligence | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def ct_rundown(limit: int = 20): | |
| """CT Rundown β top 20 Crypto Twitter stories, AI-summarized, category-diverse.""" | |
| return await databus.fetch("ct_rundown", limit=limit) | |
| async def ct_accounts(): | |
| """Curated CT account list β 150+ top accounts across 6 tiers.""" | |
| return await databus.fetch("ct_accounts") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SOCIAL INTELLIGENCE β KOL tracking, shill detection, Daily Intel | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def kol_profile(handle: str): | |
| """KOL performance profile β trust score, call history, win rate.""" | |
| return await databus.fetch("kol_profile", handle=handle) | |
| async def kol_leaderboard(limit: int = 20): | |
| """KOL leaderboard β ranked by trust score and accuracy.""" | |
| return await databus.fetch("kol_leaderboard", limit=limit) | |
| async def shill_alerts(): | |
| """Active shill campaigns β coordinated promotion, pump-and-dump patterns.""" | |
| return await databus.fetch("shill_detector") | |
| async def scam_monitor(): | |
| """Scam channel monitoring β Telegram/Discord scam pattern detection.""" | |
| return await databus.fetch("scam_monitor") | |
| async def social_metrics(): | |
| """Social metrics β trending topics, sentiment, KOL activity.""" | |
| return await databus.fetch("social_metrics") | |
| async def daily_intel(): | |
| """Daily Intelligence Report β Groq AI-powered market briefing with all data sources.""" | |
| return await databus.fetch("daily_intel") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # HISTORICAL SCAM INTELLIGENCE β Rekt DB, Chainabuse, TRM, SlowMist, Immunefi | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def historical_sources_endpoint(): | |
| """List all available historical scam/hack data sources.""" | |
| return await databus.fetch("historical_sources") | |
| async def historical_ingest_endpoint(source_id: str, request: Request): | |
| """Ingest historical data from a specific source into RAG. | |
| Admin key required. Sources: rekt_db, chainabuse, slowmist_hacked, trm_crime_report | |
| """ | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required for historical ingestion") | |
| return await databus.fetch("historical_ingest", source_id=source_id, admin_key="verified") | |
| async def historical_ingest_all(request: Request): | |
| """Run full historical ingestion across all sources. | |
| Admin key required. Ingests: Rekt DB, Chainabuse, SlowMist, TRM Labs reports. | |
| """ | |
| if not _verify_admin(request): | |
| raise HTTPException(401, "Admin key required for historical ingestion") | |
| return await databus.fetch("historical_ingest", source_id="all", admin_key="verified") | |
| async def defi_hacks_endpoint(limit: int = 50): | |
| """Fetch recent DeFi hacks from Rekt DB. Cached 24h.""" | |
| return await databus.fetch("defi_hacks", limit=limit) | |
| async def scam_reports_endpoint(limit: int = 50): | |
| """Fetch scam reports from Chainabuse. Cached 24h.""" | |
| return await databus.fetch("scam_reports", limit=limit) | |