- 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>
111 lines
3.8 KiB
Python
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
|