rmi-backend/app/domains/databus/arkham_ws.py

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.domains.databus.ws_stream import ws_manager
await ws_manager.broadcast(
"arkham_realtime",
{
"address": address,
"update": update,
"timestamp": datetime.utcnow().isoformat(),
},
)
except Exception:
pass