rmi-backend/app/domain/labels/sources/solana_labels_csv.py

74 lines
2.5 KiB
Python

"""Solana wallet labels CSV source — 103K SOL labels via DuckDB."""
from __future__ import annotations
import asyncio
import logging
from datetime import UTC, datetime
from app.domain.labels.models import Label, LabelSource
log = logging.getLogger(__name__)
CSV_PATH = "/home/dev/rmi/backend/data/wallet-labels-clean/wallet_labels_solana.csv"
async def query_solana_labels_csv(address: str, chain: str = "solana") -> list[Label]:
"""Query the Solana wallet labels CSV via DuckDB.
Schema (likely): address,source,name,entity_type,threat_group,risk_level
Only matches if chain == "solana" — returns [] for EVM chains.
"""
if chain != "solana":
return []
def _query() -> list[Label]:
from app.core.duckdb_analytics import DuckDBAnalytics
try:
d = DuckDBAnalytics()
try:
rows = d.query_parquet(
CSV_PATH,
"""
SELECT *
FROM data
WHERE LOWER(address) = ?
LIMIT 20
""",
[address.lower()],
)
labels = []
for row in rows:
# Field names may differ — try common names
label_value = (
row.get("name")
or row.get("label")
or row.get("entity_type")
or row.get("source")
)
if not label_value:
continue
labels.append(
Label(
address=row.get("address", address).lower(),
chain="solana",
label_type="entity",
label_value=label_value,
source=LabelSource.SOLANA_LABELS_CSV,
confidence=0.8,
verified_at=datetime.now(UTC),
attributes={
k: v for k, v in row.items()
if k not in ("address", "name", "label") and v
},
)
)
return labels
finally:
d.close()
except Exception as e:
log.warning("solana_labels_csv_query_fail address=%s err=%s", address[:10], e)
raise
return await asyncio.to_thread(_query)