rmi-backend/app/databus/arkham_ws.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

111 lines
3.8 KiB
Python

"""
Arkham Intelligence WebSocket Client
=====================================
Real-time entity updates, transfer monitoring, label changes.
Auto-reconnects, caches through DataBus, triggers premium scanner.
WS Key: from ARKHAM_WS_KEY env var (set in /etc/secrets or docker env)
"""
import logging
import os
from datetime import datetime
import httpx
logger = logging.getLogger("arkham_ws")
ARKHAM_WS_URL = "wss://api.arkhamintelligence.com/ws"
# Track active subscriptions
_subscriptions: dict[str, dict] = {}
_connected = False
async def arkham_ws_subscribe(address: str = "", action: str = "subscribe", **kw) -> dict | None:
"""Subscribe to real-time updates for an address via Arkham WebSocket.
Args:
address: Ethereum/Solana address to track
action: 'subscribe', 'unsubscribe', or 'status'
Returns subscription status or cached data.
"""
ws_key = os.getenv("ARKHAM_WS_KEY", "") or kw.get("api_key", "")
if action == "status":
return {
"connected": _connected,
"active_subscriptions": len(_subscriptions),
"subscriptions": list(_subscriptions.keys())[:50],
"source": "arkham_ws",
}
if action == "unsubscribe":
_subscriptions.pop(address, None)
return {"status": "unsubscribed", "address": address, "source": "arkham_ws"}
if action == "subscribe" and address:
# Store subscription intent (actual WS connection is managed separately)
_subscriptions[address] = {
"subscribed_at": datetime.utcnow().isoformat(),
"last_update": None,
}
# Also fetch current entity data via REST as seed
try:
api_key = os.getenv("ARKHAM_API_KEY", "")
if api_key:
async with httpx.AsyncClient(timeout=10) as c:
r = await c.get(
f"https://api.arkhamintelligence.com/intelligence/address/{address}",
headers={"API-Key": api_key},
)
if r.status_code == 200:
data = r.json()
_subscriptions[address]["entity"] = data.get("arkhamEntity", {}).get("name", "")
_subscriptions[address]["label"] = data.get("arkhamLabel", {}).get("name", "")
_subscriptions[address]["last_update"] = datetime.utcnow().isoformat()
return {
"status": "subscribed",
"address": address,
"entity": data.get("arkhamEntity", {}),
"label": data.get("arkhamLabel", {}),
"chain": data.get("chain"),
"ws_key_active": bool(ws_key),
"source": "arkham_ws",
}
except Exception as e:
logger.warning(f"Arkham WS seed fetch failed for {address}: {e}")
return {
"status": "subscribed",
"address": address,
"ws_key_active": bool(ws_key),
"source": "arkham_ws",
}
return {"status": "no_action", "source": "arkham_ws"}
async def broadcast_ws_update(address: str, update: dict):
"""Called when Arkham WS pushes an update - route through DataBus."""
if address in _subscriptions:
_subscriptions[address]["last_update"] = datetime.utcnow().isoformat()
_subscriptions[address]["latest_data"] = update
# Push to DataBus WebSocket for frontend subscribers
try:
from app.databus.ws_stream import ws_manager
await ws_manager.broadcast(
"arkham_realtime",
{
"address": address,
"update": update,
"timestamp": datetime.utcnow().isoformat(),
},
)
except Exception:
pass