| """ |
| Unified API Key Registry β Multi-Key Pools with Load Balancing |
| |
| Discovers all API keys from environment variables and groups them |
| by provider into key pools. Each pool handles: |
| - Round-robin key rotation |
| - Per-key rate limiting (token bucket) |
| - Per-key monthly quota tracking |
| - Automatic fallback on 429/401 |
| - Health scoring (success rate, latency) |
| |
| Providers managed: |
| HELIUS (3 keys) β Solana RPC |
| QUICKNODE (1 key) β Solana RPC |
| ALCHEMY (1 key) β Solana RPC |
| SOLANA_TRACKER (2) β Indexed Solana data |
| BIRDEYE (1 key) β Token analytics |
| SOLSCAN (1 key) β Block explorer API |
| MORALIS (3 keys) β EVM wallet data |
| ETHERSCAN (1 key) β EVM explorer |
| COINGECKO (1 key) β Price data |
| THEGRAPH (1 key) β Subgraph queries |
| GOPLUS (1 key) β Token security |
| NANSEN (1 key) β Wallet labels |
| DUNE (1 key) β Analytics |
| ARKHAM (1 key) β Entity intelligence |
| LUNARCRUSH (1 key) β Social signals |
| """ |
|
|
| import asyncio |
| import logging |
| import os |
| import time |
| from dataclasses import dataclass, field |
|
|
| logger = logging.getLogger("api_registry") |
|
|
| |
| |
| |
|
|
|
|
| @dataclass |
| class ProviderConfig: |
| """Provider-level configuration.""" |
|
|
| name: str |
| env_prefix: str |
| rate_rps: float = 10.0 |
| burst: int = 15 |
| monthly_quota: int = 0 |
| base_url: str = "" |
| auth_header: str = "" |
| auth_prefix: str = "" |
| auth_in_query: str = "" |
| fallback_providers: list[str] = field(default_factory=list) |
| notes: str = "" |
|
|
|
|
| |
| PROVIDER_REGISTRY: dict[str, ProviderConfig] = { |
| |
| "helius": ProviderConfig( |
| name="helius", |
| env_prefix="HELIUS_API_KEY", |
| rate_rps=25.0, |
| burst=25, |
| monthly_quota=0, |
| base_url="https://mainnet.helius-rpc.com", |
| auth_in_query="api-key", |
| notes="Free: 25 RPS per key. 2 keys configured (primary + _2). DAS API available for indexed token data.", |
| ), |
| "quicknode": ProviderConfig( |
| name="quicknode", |
| env_prefix="QUICKNODE_KEY", |
| rate_rps=25.0, |
| burst=25, |
| base_url="https://quiknode.pro", |
| notes="Free: 25 RPS. Solana mainnet RPC.", |
| ), |
| "alchemy_solana": ProviderConfig( |
| name="alchemy_solana", |
| env_prefix="ALCHEMY_SOLANA_KEY", |
| rate_rps=25.0, |
| burst=25, |
| base_url="https://solana-mainnet.g.alchemy.com/v2", |
| notes="Free: 25 RPS, 300M compute units/mo.", |
| ), |
| |
| "etherscan": ProviderConfig( |
| name="etherscan", |
| env_prefix="ETHERSCAN_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://api.etherscan.io/api", |
| auth_in_query="apikey", |
| notes="Free: 5 RPS, 100K calls/day.", |
| ), |
| "blockscout": ProviderConfig( |
| name="blockscout", |
| env_prefix="BLOCKSCOUT_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://api.blockscout.com", |
| auth_in_query="apikey", |
| notes="Free: 100K credits/day, 5 RPS, 100+ EVM chains. URL: /{chain_id}/api/v2/", |
| ), |
| "moralis": ProviderConfig( |
| name="moralis", |
| env_prefix="MORALIS_API_KEY", |
| rate_rps=25.0, |
| burst=25, |
| base_url="https://deep-index.moralis.io/api/v2.2", |
| auth_header="X-API-Key", |
| notes="Free: 25 RPS. Keys: _2, _3 configured.", |
| ), |
| |
| "solana_tracker": ProviderConfig( |
| name="solana_tracker", |
| env_prefix="SOLANATRACKER", |
| rate_rps=3.0, |
| burst=3, |
| monthly_quota=2500, |
| auth_header="x-api-key", |
| notes="Free: 3 RPS, 2,500/mo. 2 accounts (secure + generic).", |
| ), |
| "birdeye": ProviderConfig( |
| name="birdeye", |
| env_prefix="BIRDEYE_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://public-api.birdeye.so", |
| auth_header="X-API-KEY", |
| notes="Free tier: limited usage. Token prices, trades, holders.", |
| ), |
| "solscan": ProviderConfig( |
| name="solscan", |
| env_prefix="SOLSCAN_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://pro-api.solscan.io", |
| auth_header="token", |
| notes="Pro API. Token metadata, holders, transactions.", |
| ), |
| "coingecko": ProviderConfig( |
| name="coingecko", |
| env_prefix="COINGECKO_API_KEY", |
| rate_rps=30.0, |
| burst=30, |
| base_url="https://pro-api.coingecko.com/api/v3", |
| auth_header="x-cg-pro-api-key", |
| notes="Demo tier: 30 RPS. Price, market data, metadata.", |
| ), |
| |
| "goplus": ProviderConfig( |
| name="goplus", |
| env_prefix="GOPLUS_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://api.gopluslabs.io/api/v1", |
| notes="Token security scanning, honeypot detection.", |
| ), |
| "nansen": ProviderConfig( |
| name="nansen", |
| env_prefix="NANSEN_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://api.nansen.ai", |
| auth_in_query="api_key", |
| notes="Wallet labels, smart money tracking.", |
| ), |
| "thegraph": ProviderConfig( |
| name="thegraph", |
| env_prefix="THEGRAPH_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://gateway.thegraph.com/api", |
| notes="Subgraph queries for DeFi data.", |
| ), |
| "dune": ProviderConfig( |
| name="dune", |
| env_prefix="DUNE_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://api.dune.com/api/v1", |
| auth_header="X-Dune-API-Key", |
| notes="SQL analytics on blockchain data.", |
| ), |
| "arkham": ProviderConfig( |
| name="arkham", |
| env_prefix="ARKHAM_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://api.arkhamintelligence.com", |
| notes="Entity labeling, wallet intelligence.", |
| ), |
| "lunarcrush": ProviderConfig( |
| name="lunarcrush", |
| env_prefix="LUNARCRUSH_API_KEY", |
| rate_rps=5.0, |
| burst=5, |
| base_url="https://lunarcrush.com/api/v2", |
| notes="Social signals, sentiment analysis.", |
| ), |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| @dataclass |
| class ApiKey: |
| """A single API key with runtime state.""" |
|
|
| provider: str |
| key_name: str |
| value: str |
| base_url: str = "" |
| |
| calls_total: int = 0 |
| calls_this_month: int = 0 |
| errors: int = 0 |
| rate_limited: int = 0 |
| last_used: float = 0.0 |
| last_error: str = "" |
| |
| tokens: float = 0.0 |
| last_refill: float = 0.0 |
| rate_limited_until: float = 0.0 |
| consecutive_429s: int = 0 |
| disabled: bool = False |
|
|
| def __post_init__(self): |
| cfg = PROVIDER_REGISTRY.get(self.provider) |
| burst = cfg.burst if cfg else 15 |
| self.tokens = float(burst) |
| self.last_refill = time.monotonic() |
|
|
|
|
| def discover_keys() -> dict[str, list[ApiKey]]: |
| """Scan environment variables and build key pools per provider. |
| |
| For each provider in PROVIDER_REGISTRY, scans env for: |
| - {PREFIX} (primary key) |
| - {PREFIX}_2, {PREFIX}_3, ... (additional keys) |
| |
| Also handles special cases (Solana Tracker custom keys, Moralis multi-key). |
| """ |
| pools: dict[str, list[ApiKey]] = {} |
|
|
| for prov_name, cfg in PROVIDER_REGISTRY.items(): |
| keys: list[ApiKey] = [] |
|
|
| |
| if prov_name == "solana_tracker": |
| |
| secure_endpoint = os.getenv("SOLANATRACKER_SECURE_URL", "") |
| if secure_endpoint: |
| keys.append( |
| ApiKey( |
| provider=prov_name, |
| key_name="SOLANATRACKER_SECURE_URL", |
| value="", |
| base_url=secure_endpoint, |
| ) |
| ) |
| |
| st_key = os.getenv("SOLANATRACKER_API_KEY", "") |
| if st_key: |
| keys.append( |
| ApiKey( |
| provider=prov_name, |
| key_name="SOLANATRACKER_API_KEY", |
| value=st_key, |
| base_url="https://data.solanatracker.io", |
| ) |
| ) |
| st_key2 = os.getenv("SOLANATRACKER_API_KEY_2", "") |
| if st_key2: |
| keys.append( |
| ApiKey( |
| provider=prov_name, |
| key_name="SOLANATRACKER_API_KEY_2", |
| value=st_key2, |
| base_url="https://data.solanatracker.io", |
| ) |
| ) |
|
|
| |
| else: |
| for suffix in ["", "_2", "_3", "_4", "_5"]: |
| env_name = f"{cfg.env_prefix}{suffix}" |
| key_val = os.getenv(env_name, "") |
| if key_val and key_val not in ("your_key_here", "***", ""): |
| |
| if cfg.auth_in_query and key_val: |
| |
| pass |
|
|
| keys.append( |
| ApiKey( |
| provider=prov_name, |
| key_name=env_name, |
| value=key_val, |
| base_url=cfg.base_url if cfg.base_url else "", |
| ) |
| ) |
|
|
| |
| if prov_name == "helius": |
| custom_url = os.getenv("HELIUS_RPC_URL", "") |
| if custom_url: |
| for k in keys: |
| k.base_url = custom_url |
|
|
| if keys: |
| pools[prov_name] = keys |
| logger.info(f"API Registry: {prov_name} -> {len(keys)} key(s)") |
|
|
| return pools |
|
|
|
|
| |
| |
| |
|
|
|
|
| class KeyPool: |
| """Round-robin key pool with health tracking and rate limiting.""" |
|
|
| def __init__(self, provider: str, keys: list[ApiKey], config: ProviderConfig): |
| self.provider = provider |
| self.keys = keys |
| self.config = config |
| self._index = 0 |
| self._lock = asyncio.Lock() |
| self.total_calls = 0 |
|
|
| async def acquire(self) -> ApiKey | None: |
| """Get the next available key in the pool.""" |
| async with self._lock: |
| now = time.monotonic() |
| for _ in range(len(self.keys)): |
| key = self.keys[self._index] |
| self._index = (self._index + 1) % len(self.keys) |
|
|
| if key.disabled: |
| continue |
| if now < key.rate_limited_until: |
| continue |
|
|
| |
| elapsed = now - key.last_refill |
| rate = self.config.rate_rps |
| key.tokens = min(float(self.config.burst), key.tokens + elapsed * rate) |
| key.last_refill = now |
|
|
| if key.tokens < 1.0: |
| continue |
|
|
| |
| if self.config.monthly_quota > 0 and key.calls_this_month >= self.config.monthly_quota: |
| continue |
|
|
| key.tokens -= 1.0 |
| key.calls_this_month += 1 |
| key.calls_total += 1 |
| key.last_used = now |
| self.total_calls += 1 |
| return key |
|
|
| return None |
|
|
| def mark_success(self, key: ApiKey): |
| key.consecutive_429s = 0 |
| key.rate_limited_until = 0.0 |
|
|
| def mark_rate_limited(self, key: ApiKey): |
| now = time.monotonic() |
| key.consecutive_429s += 1 |
| key.rate_limited += 1 |
| backoff = min(120, 5 * (2 ** min(key.consecutive_429s, 5))) |
| key.rate_limited_until = now + backoff |
|
|
| def mark_error(self, key: ApiKey, error: str): |
| key.errors += 1 |
| key.last_error = error[:100] |
|
|
| def stats(self) -> dict: |
| now = time.monotonic() |
| key_stats = [] |
| for k in self.keys: |
| k.tokens = min(float(self.config.burst), k.tokens + (now - k.last_refill) * self.config.rate_rps) |
| key_stats.append( |
| { |
| "name": k.key_name, |
| "calls": k.calls_total, |
| "errors": k.errors, |
| "rate_limited": k.rate_limited, |
| "tokens": round(k.tokens, 1), |
| "rate_limited_now": now < k.rate_limited_until, |
| "disabled": k.disabled, |
| } |
| ) |
|
|
| return { |
| "provider": self.provider, |
| "total_keys": len(self.keys), |
| "active_keys": sum(1 for k in self.keys if not k.disabled and not (now < k.rate_limited_until)), |
| "total_calls": self.total_calls, |
| "rate_rps": self.config.rate_rps, |
| "burst": self.config.burst, |
| "monthly_quota": self.config.monthly_quota, |
| "keys": key_stats, |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| class UnifiedApiManager: |
| """Singleton manager for all API key pools. |
| |
| Usage: |
| mgr = get_api_manager() |
| key = await mgr.acquire("helius") |
| # ... make API call with key.value ... |
| if success: |
| mgr.mark_success("helius", key) |
| elif rate_limited: |
| mgr.mark_rate_limited("helius", key) |
| """ |
|
|
| def __init__(self): |
| self.pools: dict[str, KeyPool] = {} |
| self._load() |
|
|
| def _load(self): |
| pools = discover_keys() |
| for provider, keys in pools.items(): |
| config = PROVIDER_REGISTRY.get(provider) |
| if config: |
| self.pools[provider] = KeyPool(provider, keys, config) |
|
|
| def reload(self): |
| """Rescan environment for new keys.""" |
| self._load() |
|
|
| async def acquire(self, provider: str) -> ApiKey | None: |
| pool = self.pools.get(provider) |
| if pool: |
| return await pool.acquire() |
| return None |
|
|
| def mark_success(self, provider: str, key: ApiKey): |
| pool = self.pools.get(provider) |
| if pool: |
| pool.mark_success(key) |
|
|
| def mark_rate_limited(self, provider: str, key: ApiKey): |
| pool = self.pools.get(provider) |
| if pool: |
| pool.mark_rate_limited(key) |
|
|
| def mark_error(self, provider: str, key: ApiKey, error: str): |
| pool = self.pools.get(provider) |
| if pool: |
| pool.mark_error(key, error) |
|
|
| def get_pool(self, provider: str) -> KeyPool | None: |
| return self.pools.get(provider) |
|
|
| def list_providers(self) -> list[str]: |
| return sorted(self.pools.keys()) |
|
|
| def capacity_report(self) -> dict: |
| """Generate capacity analysis: RPS, quotas, bottlenecks.""" |
| providers = {} |
| total_rps = 0.0 |
| bottlenecks = [] |
|
|
| for name, pool in self.pools.items(): |
| cfg = pool.config |
| total_keys = len(pool.keys) |
| combined_rps = cfg.rate_rps * total_keys |
| combined_quota = cfg.monthly_quota * total_keys if cfg.monthly_quota > 0 else None |
| total_rps += combined_rps |
|
|
| status = "OK" |
| if combined_rps < 10: |
| status = "LOW_RPS" |
| bottlenecks.append(f"{name}: only {combined_rps:.0f} RPS across {total_keys} key(s)") |
| if combined_quota and combined_quota < 10000: |
| status = "LOW_QUOTA" |
| bottlenecks.append(f"{name}: only {combined_quota}/mo across {total_keys} key(s)") |
| if total_keys == 1 and cfg.rate_rps < 10: |
| status = "SINGLE_KEY_LIMITED" |
| bottlenecks.append(f"{name}: single key at {cfg.rate_rps} RPS β get more accounts") |
|
|
| providers[name] = { |
| "keys": total_keys, |
| "rps_per_key": cfg.rate_rps, |
| "combined_rps": combined_rps, |
| "monthly_quota": combined_quota, |
| "status": status, |
| "notes": cfg.notes, |
| } |
|
|
| return { |
| "total_providers": len(providers), |
| "total_combined_rps": round(total_rps, 1), |
| "providers": providers, |
| "bottlenecks": bottlenecks, |
| "recommendations": _generate_recommendations(bottlenecks, providers), |
| } |
|
|
|
|
| def _generate_recommendations(bottlenecks: list, providers: dict) -> list: |
| """Generate actionable recommendations based on bottlenecks.""" |
| recs = [] |
|
|
| if any("helius" in b.lower() or "solana_tracker" in b.lower() for b in bottlenecks): |
| recs.append("HIGH: Get 2-3 more Helius free accounts (email signup, instant key)") |
|
|
| if any("solana_tracker" in b.lower() for b in bottlenecks): |
| recs.append("HIGH: Get 1-2 more Solana Tracker free accounts (email signup)") |
|
|
| if any("birdeye" in b.lower() for b in bottlenecks): |
| recs.append("MEDIUM: Get 1 more Birdeye free account") |
|
|
| if any("etherscan" in b.lower() for b in bottlenecks): |
| recs.append("MEDIUM: Get 1 more Etherscan free account") |
|
|
| if any("solscan" in b.lower() for b in bottlenecks): |
| recs.append("LOW: Get 1 more Solscan Pro API key if needed for holder data") |
|
|
| if any("coingecko" in b.lower() for b in bottlenecks): |
| recs.append("LOW: CoinGecko Demo tier is generous at 30 RPS β likely sufficient") |
|
|
| if not recs: |
| recs.append("All providers have adequate capacity for current load.") |
|
|
| return recs |
|
|
|
|
| |
| |
| |
|
|
| _api_manager: UnifiedApiManager | None = None |
|
|
|
|
| def get_api_manager() -> UnifiedApiManager: |
| global _api_manager |
| if _api_manager is None: |
| _api_manager = UnifiedApiManager() |
| return _api_manager |
|
|
|
|
| |
| |
| |
|
|
| BACKEND_SOURCES = { |
| |
| "dexscreener": { |
| "type": "free_api", |
| "url": "https://api.dexscreener.com", |
| "rate_rps": 5.0, |
| "burst": 5, |
| "ttl_default": 30, |
| "data": "Token pairs, liquidity, volume, price across all DEXs", |
| "status": "in_use", |
| "module": "unified_scanner.py, all_connectors.py", |
| }, |
| "jupiter": { |
| "type": "free_api", |
| "url": "https://quote-api.jup.ag/v6", |
| "rate_rps": 10.0, |
| "burst": 15, |
| "ttl_default": 10, |
| "data": "Price quotes, swap routes, token list, strict list", |
| "status": "in_use", |
| "module": "all_connectors.py, unified_scanner.py", |
| }, |
| "gecko_terminal": { |
| "type": "free_api", |
| "url": "https://api.geckoterminal.com/api/v2", |
| "rate_rps": 5.0, |
| "burst": 5, |
| "ttl_default": 30, |
| "data": "DEX pairs, OHLCV, token info across 100+ networks", |
| "status": "available", |
| "module": "not yet wired", |
| }, |
| "coingecko_free": { |
| "type": "free_api", |
| "url": "https://api.coingecko.com/api/v3", |
| "rate_rps": 10.0, |
| "burst": 15, |
| "ttl_default": 60, |
| "data": "Price, market cap, volume (free tier β no key needed for basic)", |
| "status": "in_use", |
| "module": "coingecko_connector.py", |
| }, |
| "coinpaprika": { |
| "type": "free_api", |
| "url": "https://api.coinpaprika.com/v1", |
| "rate_rps": 5.0, |
| "burst": 5, |
| "ttl_default": 60, |
| "data": "Prices, market data, exchanges β free, no auth", |
| "status": "available", |
| "module": "not yet wired", |
| }, |
| "defillama": { |
| "type": "free_api", |
| "url": "https://yields.llama.fi", |
| "rate_rps": 2.0, |
| "burst": 2, |
| "ttl_default": 300, |
| "data": "TVL, yields, protocol data β free, no key", |
| "status": "in_use", |
| "module": "all_connectors.py", |
| }, |
| "binance_public": { |
| "type": "free_api", |
| "url": "https://api.binance.com/api/v3", |
| "rate_rps": 20.0, |
| "burst": 30, |
| "ttl_default": 5, |
| "data": "CEX spot prices, tickers (public, no key)", |
| "status": "in_use", |
| "module": "web3.binance.com, adapters/", |
| }, |
| |
| "ccxt": { |
| "type": "library", |
| "url": "ccxt library (30+ exchanges)", |
| "rate_rps": 10.0, |
| "burst": 15, |
| "ttl_default": 30, |
| "data": "Unified CEX API: Binance, OKX, Bybit, Gate, MEXC, KuCoin, etc.", |
| "status": "in_use", |
| "module": "tools_integration.py (ccxt_get_prices, ccxt_arbitrage)", |
| }, |
| |
| "solana_public_rpc": { |
| "type": "public_rpc", |
| "url": "api.mainnet-beta.solana.com, solana-rpc.publicnode.com, solana.drpc.org", |
| "rate_rps": 15.0, |
| "burst": 20, |
| "ttl_default": 10, |
| "data": "Solana RPC: getBalance, getAccountInfo, getSignaturesForAddress, etc.", |
| "status": "in_use", |
| "module": "consensus_rpc.py (3 public endpoints)", |
| }, |
| "evm_public_rpc": { |
| "type": "public_rpc", |
| "url": "9 chains: ethereum, bsc, polygon, base, arbitrum, optimism, avalanche, fantom, gnosis", |
| "rate_rps": 20.0, |
| "burst": 25, |
| "ttl_default": 10, |
| "data": "EVM RPC via PublicNode, 1RPC, LlamaRPC, BlastAPI", |
| "status": "in_use", |
| "module": "consensus_rpc.py (EVM_RPC_PROVIDERS)", |
| }, |
| |
| "chainabuse": { |
| "type": "free_api", |
| "url": "https://api.chainabuse.com", |
| "rate_rps": 2.0, |
| "burst": 2, |
| "ttl_default": 3600, |
| "data": "Scam reports, blacklisted addresses β free, no key", |
| "status": "in_use", |
| "module": "all_connectors.py, security_defense.py", |
| }, |
| "cryptoscamdb": { |
| "type": "free_api", |
| "url": "https://api.cryptoscamdb.org/v1", |
| "rate_rps": 2.0, |
| "burst": 2, |
| "ttl_default": 3600, |
| "data": "Scam database, reported addresses β free, no key", |
| "status": "in_use", |
| "module": "all_connectors.py", |
| }, |
| "honeypot_is": { |
| "type": "free_api", |
| "url": "https://api.honeypot.is/v2", |
| "rate_rps": 3.0, |
| "burst": 3, |
| "ttl_default": 60, |
| "data": "Honeypot detection for EVM tokens β free, no key", |
| "status": "in_use", |
| "module": "unified_scanner.py, all_connectors.py", |
| }, |
| "rugcheck": { |
| "type": "free_api", |
| "url": "https://api.rugcheck.xyz/v1", |
| "rate_rps": 5.0, |
| "burst": 5, |
| "ttl_default": 60, |
| "data": "Solana token rug check, risk analysis β free, no key", |
| "status": "available", |
| "module": "not yet wired (could replace solsniffer)", |
| }, |
| "blowfish": { |
| "type": "free_api", |
| "url": "https://api.blowfish.xyz", |
| "rate_rps": 5.0, |
| "burst": 5, |
| "ttl_default": 120, |
| "data": "Transaction simulation, scam detection β free tier available", |
| "status": "available", |
| "module": "all_connectors.py", |
| }, |
| |
| "solscan_public": { |
| "type": "free_api", |
| "url": "https://api-v2.solscan.io", |
| "rate_rps": 3.0, |
| "burst": 3, |
| "ttl_default": 60, |
| "data": "Solana account, token, tx data β public tier (rate limited)", |
| "status": "in_use", |
| "module": "free_solscan_client.py, unified_scanner.py", |
| }, |
| "etherscan_public": { |
| "type": "free_api", |
| "url": "https://api.etherscan.io/api", |
| "rate_rps": 1.0, |
| "burst": 1, |
| "ttl_default": 300, |
| "data": "EVM contract verification, ABI β free tier 1 RPS (no key)", |
| "status": "in_use", |
| "module": "unified_scanner.py (fallback when keyed fails)", |
| }, |
| |
| "wallet_labels_imported": { |
| "type": "imported_data", |
| "url": "local files: whalegod, sigmod, etherscan, solana labels", |
| "rate_rps": None, |
| "burst": None, |
| "ttl_default": 86400, |
| "data": "CEX wallets, scam labels, dapp labels, OFAC β pre-loaded into ClickHouse", |
| "status": "in_use", |
| "module": "wallet_memory/label_importer.py, wallet_label_loader.py", |
| }, |
| "clickhouse_wallet_memory": { |
| "type": "local_db", |
| "url": "ClickHouse (langfuse-clickhouse-1:9000)", |
| "rate_rps": None, |
| "burst": None, |
| "ttl_default": 86400, |
| "data": "Wallet history, labels, risk scores β our own indexed DB", |
| "status": "in_use", |
| "module": "wallet_memory/storage.py", |
| }, |
| "redis_rag": { |
| "type": "local_db", |
| "url": "Redis (rmi-redis:6379)", |
| "rate_rps": None, |
| "burst": None, |
| "ttl_default": 86400, |
| "data": "RAG vector embeddings, token analysis cache, alert history", |
| "status": "in_use", |
| "module": "rag_service.py, crypto_embeddings.py", |
| }, |
| |
| "crypto_news_rss": { |
| "type": "rss_feed", |
| "url": "65+ RSS feeds: CoinDesk, TheBlock, Decrypt, Bankless, etc.", |
| "rate_rps": 0.1, |
| "burst": 1, |
| "ttl_default": 900, |
| "data": "Crypto news aggregation for bulletin + Telegram", |
| "status": "in_use", |
| "module": "news_service.py (15+ sources), all_connectors.py", |
| }, |
| "blogwatcher": { |
| "type": "cli_tool", |
| "url": "blogwatcher CLI β local execution", |
| "rate_rps": 0.05, |
| "burst": 1, |
| "ttl_default": 3600, |
| "data": "Blog monitoring, RSS feed aggregation", |
| "status": "in_use", |
| "module": "tools_integration.py (blogwatcher_fetch)", |
| }, |
| |
| "deepseek": { |
| "type": "paid_api", |
| "url": "https://api.deepseek.com/v1", |
| "rate_rps": 50.0, |
| "burst": 100, |
| "ttl_default": 3600, |
| "data": "LLM: DeepSeek V4 Pro + Flash (primary inference)", |
| "status": "in_use", |
| "module": "ai_router.py, hallucination_guard.py, rag_agentic.py", |
| }, |
| "openrouter": { |
| "type": "paid_api", |
| "url": "https://openrouter.ai/api/v1", |
| "rate_rps": 20.0, |
| "burst": 30, |
| "ttl_default": 3600, |
| "data": "LLM: OpenRouter fallback (multi-model routing)", |
| "status": "in_use", |
| "module": "hallucination_guard.py, ai_router.py", |
| }, |
| |
| "dify": { |
| "type": "self_hosted", |
| "url": "http://docker-api-1:5001", |
| "rate_rps": None, |
| "burst": None, |
| "ttl_default": 300, |
| "data": "Dify AI agent platform β chat, workflows, knowledge base", |
| "status": "in_use", |
| "module": "routers/admin_extensions.py (Dify chat proxy)", |
| }, |
| "x402_gateway": { |
| "type": "self_hosted", |
| "url": "CF Workers: x402-base, x402-sol", |
| "rate_rps": 100.0, |
| "burst": 200, |
| "ttl_default": 30, |
| "data": "x402 MCP gateway β 231 tools, 14 categories, 13 chains", |
| "status": "in_use", |
| "module": "Cloudflare Workers, facilitators/", |
| }, |
| "mcp_server": { |
| "type": "self_hosted", |
| "url": "FastAPI /mcp/* endpoints", |
| "rate_rps": None, |
| "burst": None, |
| "ttl_default": 30, |
| "data": "RMI MCP server β all token scanning, wallet analysis tools", |
| "status": "in_use", |
| "module": "routers/mcp_server.py", |
| }, |
| |
| "funding_tracer": { |
| "type": "self_built", |
| "url": "app/caching_shield/funding_tracer.py", |
| "rate_rps": None, |
| "burst": None, |
| "ttl_default": 3600, |
| "data": "EVM funding source forensics β traces wallet funding back to origin (CEX/DEX/bridge/mixer/contract).", |
| "status": "built", |
| "module": "funding_tracer.py", |
| }, |
| "consensus_rpc": { |
| "type": "self_built", |
| "url": "app/consensus_rpc.py", |
| "rate_rps": 75.0, |
| "burst": 100, |
| "ttl_default": 10, |
| "data": "Multi-provider Solana RPC consensus voting (5+ providers, N-of-M). EVM via PublicNode/1RPC/BlastAPI.", |
| "status": "in_use", |
| "module": "consensus_rpc.py", |
| }, |
| "unified_scanner": { |
| "type": "self_built", |
| "url": "app/unified_scanner.py", |
| "rate_rps": None, |
| "burst": None, |
| "ttl_default": 3600, |
| "data": "SENTINEL multi-chain scanner β wallet risk, token analysis, whale tracking. 15+ enrichment modules.", |
| "status": "in_use", |
| "module": "unified_scanner.py", |
| }, |
| } |
|
|
|
|
| def list_all_sources() -> dict: |
| """Return combined view of all API keys + backend data sources.""" |
| from app.caching_shield.api_registry import get_api_manager |
|
|
| mgr = get_api_manager() |
|
|
| api_providers = {} |
| for name, pool in mgr.pools.items(): |
| api_providers[name] = { |
| "type": "api_key", |
| "keys": pool.stats(), |
| "config": { |
| "rate_rps": pool.config.rate_rps, |
| "monthly_quota": pool.config.monthly_quota, |
| }, |
| } |
|
|
| return { |
| "api_key_providers": api_providers, |
| "backend_sources": BACKEND_SOURCES, |
| "total_api_keys": sum(len(pool.keys) for pool in mgr.pools.values()), |
| "total_free_sources": len(BACKEND_SOURCES), |
| "summary": _summarize_all(mgr), |
| } |
|
|
|
|
| def _summarize_all(mgr) -> dict: |
| """Generate a unified capacity summary across all sources.""" |
| free_rps = sum(s.get("rate_rps", 0) or 0 for s in BACKEND_SOURCES.values() if s.get("rate_rps")) |
| by_type = {} |
| for s in BACKEND_SOURCES.values(): |
| t = s["type"] |
| by_type[t] = by_type.get(t, 0) + 1 |
|
|
| api_providers = mgr.list_providers() |
| api_keys_total = sum(len(mgr.pools[p].keys) for p in api_providers) |
| api_rps = sum(mgr.pools[p].config.rate_rps * len(mgr.pools[p].keys) for p in api_providers) |
|
|
| return { |
| "api_key_providers": len(api_providers), |
| "api_keys_total": api_keys_total, |
| "api_rps_combined": api_rps, |
| "free_sources": len(BACKEND_SOURCES), |
| "free_sources_by_type": by_type, |
| "free_sources_rps": free_rps, |
| "local_databases": ["ClickHouse (wallet_memory)", "Redis (cache + RAG)"], |
| "self_hosted_services": ["Dify", "MCP Server", "x402 Gateway", "n8n"], |
| "data_imported_locally": [ |
| "WhaleGod labels", |
| "SigMod labels", |
| "Etherscan labels (51K)", |
| "Solana CEX/Dapp/DeFi labels (106K+)", |
| "OFAC sanctions list", |
| "Phishing scam DB (6.2K)", |
| ], |
| } |
|
|