Night shift: tweet analyzer, data connectors, feed monitor, market watch portal
This commit is contained in:
167
tools/data_sources/arkham.py
Executable file
167
tools/data_sources/arkham.py
Executable file
@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Arkham Intelligence connector — whale tracking, token flows, address intelligence.
|
||||
|
||||
Requires API key for most endpoints. Set ARKHAM_API_KEY env var.
|
||||
Sign up at https://platform.arkhamintelligence.com
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
BASE = "https://api.arkhamintelligence.com"
|
||||
TIMEOUT = 30
|
||||
|
||||
NOTABLE_ADDRESSES = {
|
||||
"vitalik": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
|
||||
"justin-sun": "0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296",
|
||||
"binance-hot": "0x28C6c06298d514Db089934071355E5743bf21d60",
|
||||
"coinbase-prime": "0xA9D1e08C7793af67e9d92fe308d5697FB81d3E43",
|
||||
"aave-treasury": "0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c",
|
||||
"uniswap-deployer": "0x41653c7d61609D856f29355E404F310Ec4142Cfb",
|
||||
}
|
||||
|
||||
|
||||
def _get(path: str, params: dict | None = None) -> Any:
|
||||
key = os.environ.get("ARKHAM_API_KEY")
|
||||
headers = {"User-Agent": "Mozilla/5.0"}
|
||||
if key:
|
||||
headers["API-Key"] = key
|
||||
r = requests.get(f"{BASE}/{path}", params=params, headers=headers, timeout=TIMEOUT)
|
||||
if r.status_code in (401, 403) or "api key" in r.text.lower():
|
||||
raise EnvironmentError(
|
||||
"Arkham API key required. Set ARKHAM_API_KEY env var.\n"
|
||||
"Sign up at https://platform.arkhamintelligence.com"
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
|
||||
def resolve_address(name_or_addr: str) -> str:
|
||||
return NOTABLE_ADDRESSES.get(name_or_addr.lower(), name_or_addr)
|
||||
|
||||
|
||||
# ── Data fetchers ───────────────────────────────────────────────────────────
|
||||
|
||||
def get_address_info(address: str) -> dict:
|
||||
return _get(f"intelligence/address/{resolve_address(address)}")
|
||||
|
||||
|
||||
def get_transfers(address: str, limit: int = 20) -> dict:
|
||||
return _get("token/transfers", {"address": resolve_address(address), "limit": limit})
|
||||
|
||||
|
||||
def search_entity(query: str) -> dict:
|
||||
return _get("intelligence/search", {"query": query})
|
||||
|
||||
|
||||
# ── Summary helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
def summary_address(data: dict) -> str:
|
||||
lines = ["═══ Address Intelligence ═══", ""]
|
||||
if isinstance(data, dict):
|
||||
entity = data.get("entity", {}) or {}
|
||||
if entity:
|
||||
lines.append(f" Entity: {entity.get('name', 'Unknown')}")
|
||||
lines.append(f" Type: {entity.get('type', 'Unknown')}")
|
||||
lines.append(f" Address: {data.get('address', '?')}")
|
||||
labels = data.get("labels", [])
|
||||
if labels:
|
||||
lines.append(f" Labels: {', '.join(str(l) for l in labels)}")
|
||||
else:
|
||||
lines.append(f" {data}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def summary_transfers(data) -> str:
|
||||
lines = ["═══ Recent Transfers ═══", ""]
|
||||
transfers = data if isinstance(data, list) else (data.get("transfers", data.get("data", [])) if isinstance(data, dict) else [])
|
||||
if not transfers:
|
||||
lines.append(" No transfers found.")
|
||||
return "\n".join(lines)
|
||||
for t in transfers[:15]:
|
||||
token = t.get("token", {}).get("symbol", "?") if isinstance(t.get("token"), dict) else "?"
|
||||
amount = t.get("amount", t.get("value", "?"))
|
||||
fr = t.get("from", {})
|
||||
to = t.get("to", {})
|
||||
fl = (fr.get("label") or fr.get("address", "?")[:12]) if isinstance(fr, dict) else str(fr)[:12]
|
||||
tl = (to.get("label") or to.get("address", "?")[:12]) if isinstance(to, dict) else str(to)[:12]
|
||||
lines.append(f" {token:<8} {str(amount):>15} {fl} → {tl}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def summary_notable() -> str:
|
||||
lines = ["═══ Notable/Whale Addresses ═══", ""]
|
||||
for name, addr in NOTABLE_ADDRESSES.items():
|
||||
lines.append(f" {name:<20} {addr}")
|
||||
lines.append("")
|
||||
lines.append(" Use these as shortcuts: arkham.py address vitalik")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ── CLI ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
common = argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument("--pretty", action="store_true", help="Pretty-print JSON output")
|
||||
common.add_argument("--summary", action="store_true", help="Human-readable summary")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Arkham Intelligence connector", parents=[common])
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
p_addr = sub.add_parser("address", help="Address intelligence", parents=[common])
|
||||
p_addr.add_argument("address", help="Ethereum address or notable name")
|
||||
|
||||
p_tx = sub.add_parser("transfers", help="Recent token transfers", parents=[common])
|
||||
p_tx.add_argument("address")
|
||||
p_tx.add_argument("--limit", type=int, default=20)
|
||||
|
||||
p_search = sub.add_parser("search", help="Search entities", parents=[common])
|
||||
p_search.add_argument("query")
|
||||
|
||||
sub.add_parser("notable", help="List notable/whale addresses", parents=[common])
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.command == "notable":
|
||||
if args.summary:
|
||||
print(summary_notable())
|
||||
else:
|
||||
json.dump(NOTABLE_ADDRESSES, sys.stdout, indent=2 if args.pretty else None)
|
||||
print()
|
||||
return
|
||||
|
||||
if args.command == "address":
|
||||
data = get_address_info(args.address)
|
||||
if args.summary:
|
||||
print(summary_address(data)); return
|
||||
result = data
|
||||
elif args.command == "transfers":
|
||||
data = get_transfers(args.address, args.limit)
|
||||
if args.summary:
|
||||
print(summary_transfers(data)); return
|
||||
result = data
|
||||
elif args.command == "search":
|
||||
result = search_entity(args.query)
|
||||
else:
|
||||
parser.print_help(); return
|
||||
|
||||
json.dump(result, sys.stdout, indent=2 if args.pretty else None)
|
||||
print()
|
||||
|
||||
except EnvironmentError as e:
|
||||
print(str(e), file=sys.stderr); sys.exit(1)
|
||||
except requests.HTTPError as e:
|
||||
detail = e.response.text[:200] if e.response is not None else ""
|
||||
print(json.dumps({"error": str(e), "detail": detail}), file=sys.stderr); sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": f"{type(e).__name__}: {e}"}), file=sys.stderr); sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user