rmi-backend/app/databus_gateway.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

117 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""#10 - DataBus Public API Gateway. Free tier + x402 paid tier. 102 chains, 119 providers."""
import hashlib
import logging
import os
import time
from dataclasses import dataclass
from typing import Any
import httpx
logger = logging.getLogger(__name__)
DATABUS_BASE = os.environ.get("DATABUS_URL", "http://localhost:8000/api/v1/databus")
X402_VERIFY_URL = os.environ.get("X402_VERIFY_URL", "http://localhost:8000/api/v1/x402/verify")
RATE_LIMIT_FREE = int(os.environ.get("DATABUS_FREE_RATE", "100")) # req/hour
RATE_LIMIT_PAID = int(os.environ.get("DATABUS_PAID_RATE", "5000"))
# Simple in-memory rate limiter (Redis-backed in prod)
_rate_store: dict[str, list[float]] = {}
def _check_rate(key: str, limit: int, window: int = 3600) -> bool:
now = time.time()
bucket = _rate_store.setdefault(key, [])
bucket[:] = [t for t in bucket if now - t < window]
if len(bucket) >= limit:
return False
bucket.append(now)
return True
@dataclass
class DatabusQueryResult:
chain: str
data: dict[str, Any]
source: str
cached: bool
latency_ms: float
async def databus_get(chain: str, endpoint: str, params: dict | None = None, tier: str = "free") -> DatabusQueryResult:
"""Single-chain DataBus query with tier-appropriate caching."""
t0 = time.monotonic()
url = f"{DATABUS_BASE}/{chain}/{endpoint}"
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(url, params=params or {})
resp.raise_for_status()
data = resp.json()
elapsed = (time.monotonic() - t0) * 1000
return DatabusQueryResult(
chain=chain, data=data, source="databus", cached=data.get("cached", False), latency_ms=elapsed
)
async def databus_cross_chain(
query: str, chains: list[str] | None = None, tier: str = "free"
) -> dict[str, DatabusQueryResult]:
"""Query multiple DataBus chains in parallel."""
if chains is None:
chains = ["solana", "ethereum", "bsc", "base", "arbitrum"]
tasks = {c: databus_get(c, query, tier=tier) for c in chains}
results = {}
for c, task in tasks.items():
try:
results[c] = await task
except Exception as e:
logger.warning(f"DataBus query failed for {c}: {e}")
results[c] = DatabusQueryResult(
chain=c, data={"error": str(e)}, source="databus", cached=False, latency_ms=0
)
return results
async def verify_x402_payment(signature: str, payload: str, chain: str = "solana") -> bool:
"""Verify x402 micropayment signature."""
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(X402_VERIFY_URL, json={"sig": signature, "payload": payload, "chain": chain})
return resp.status_code == 200 and resp.json().get("valid", False)
def generate_api_key() -> str:
return "rmi_databus_" + hashlib.sha256(os.urandom(32)).hexdigest()[:32]
# Chain availability
SUPPORTED_CHAINS = [
"solana",
"ethereum",
"bsc",
"base",
"arbitrum",
"polygon",
"avalanche",
"optimism",
"fantom",
"linea",
"zksync",
"scroll",
"mantle",
"near",
"tron",
"ton",
"bitcoin",
"cardano",
"xrp",
"sui",
"aptos",
"sei",
]
TIER_LIMITS = {
"free": {"requests_per_hour": 100, "chains": 5, "cache_ttl": 300},
"pro": {"requests_per_hour": 1000, "chains": 20, "cache_ttl": 60},
"elite": {"requests_per_hour": 5000, "chains": 102, "cache_ttl": 15},
}