merge: chore/cleanup-remove-bloat-and-secrets into main
This commit is contained in:
commit
bde2f3a97d
1173 changed files with 437609 additions and 0 deletions
161
app/routers/mbal_market.py
Normal file
161
app/routers/mbal_market.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#!/usr/bin/env python3
|
||||
"""#17 — MBAL 10M-Label Market Maker. Public searchable database of the MBAL dataset
|
||||
(10M labeled addresses). Free search, paid API. Kaggle: cryptorugmuncher."""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter(prefix="/api/v1/mbal", tags=["mbal-market-maker"])
|
||||
|
||||
MBAL_DB = os.environ.get("MBAL_DB", str(Path.home() / "rmi/backend/data/mbal.db"))
|
||||
|
||||
|
||||
def _get_db() -> sqlite3.Connection | None:
|
||||
"""Open MBAL database connection."""
|
||||
db_path = Path(MBAL_DB)
|
||||
if not db_path.exists():
|
||||
return None
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
|
||||
@router.get("/search/{address}")
|
||||
async def search_mbal(address: str):
|
||||
"""Search MBAL database for an address. Returns label if found."""
|
||||
conn = _get_db()
|
||||
if not conn:
|
||||
return {
|
||||
"address": address,
|
||||
"found": False,
|
||||
"database": "unavailable",
|
||||
"note": "MBAL database not found. Visit: https://www.kaggle.com/datasets/cryptorugmuncher/mbal-10m-labeled-addresses",
|
||||
}
|
||||
|
||||
try:
|
||||
cursor = conn.execute(
|
||||
"SELECT address, label, category, confidence, source FROM addresses WHERE address = ? LIMIT 1",
|
||||
(address.lower(),),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return {
|
||||
"address": address,
|
||||
"found": True,
|
||||
"label": row["label"],
|
||||
"category": row["category"],
|
||||
"confidence": row["confidence"],
|
||||
"source": row["source"],
|
||||
"dataset": "MBAL (Multi-chain Blockchain Address Labels)",
|
||||
}
|
||||
return {"address": address, "found": False, "database": "available"}
|
||||
except sqlite3.Error:
|
||||
return {"address": address, "found": False, "database": "error"}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@router.get("/stats")
|
||||
async def mbal_stats():
|
||||
"""MBAL dataset statistics."""
|
||||
conn = _get_db()
|
||||
stats = {
|
||||
"dataset": "MBAL — Multi-chain Blockchain Address Labels",
|
||||
"total_addresses": 0,
|
||||
"categories": [],
|
||||
"chains_covered": [],
|
||||
"kaggle_url": "https://www.kaggle.com/datasets/cryptorugmuncher/mbal-10m-labeled-addresses",
|
||||
"database_available": conn is not None,
|
||||
}
|
||||
if conn:
|
||||
try:
|
||||
stats["total_addresses"] = conn.execute("SELECT COUNT(*) FROM addresses").fetchone()[0]
|
||||
stats["categories"] = [
|
||||
r[0]
|
||||
for r in conn.execute(
|
||||
"SELECT DISTINCT category FROM addresses WHERE category IS NOT NULL LIMIT 20"
|
||||
).fetchall()
|
||||
]
|
||||
with contextlib.suppress(sqlite3.Error):
|
||||
stats["chains_covered"] = [
|
||||
r[0]
|
||||
for r in conn.execute(
|
||||
"SELECT DISTINCT chain FROM addresses WHERE chain IS NOT NULL LIMIT 20"
|
||||
).fetchall()
|
||||
]
|
||||
except sqlite3.Error:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
class BulkSearchRequest(BaseModel):
|
||||
addresses: list[str]
|
||||
max_results: int = 100
|
||||
|
||||
|
||||
@router.post("/bulk-search")
|
||||
async def bulk_search_mbal(request: BulkSearchRequest):
|
||||
"""Search multiple addresses in MBAL. Paid tier (x402)."""
|
||||
conn = _get_db()
|
||||
if not conn:
|
||||
raise HTTPException(503, "MBAL database unavailable")
|
||||
|
||||
results: list[dict] = []
|
||||
try:
|
||||
for addr in request.addresses[: request.max_results]:
|
||||
cursor = conn.execute(
|
||||
"SELECT address, label, category, confidence FROM addresses WHERE address = ? LIMIT 1", (addr.lower(),)
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
results.append(
|
||||
{
|
||||
"address": addr,
|
||||
"found": True,
|
||||
"label": row["label"],
|
||||
"category": row["category"],
|
||||
"confidence": row["confidence"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
results.append({"address": addr, "found": False})
|
||||
except sqlite3.Error:
|
||||
raise HTTPException(500, "Database query error")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"searched": len(request.addresses[: request.max_results]),
|
||||
"found": sum(1 for r in results if r.get("found")),
|
||||
"results": results,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/category/{category}")
|
||||
async def search_by_category(category: str, limit: int = Query(20, le=100)):
|
||||
"""Search MBAL addresses by category (e.g., 'exchange', 'scam', 'mixer')."""
|
||||
conn = _get_db()
|
||||
if not conn:
|
||||
raise HTTPException(503, "MBAL database unavailable")
|
||||
|
||||
try:
|
||||
rows = conn.execute(
|
||||
"SELECT address, label, category, confidence FROM addresses WHERE category = ? LIMIT ?", (category, limit)
|
||||
).fetchall()
|
||||
results = [
|
||||
{"address": r["address"], "label": r["label"], "category": r["category"], "confidence": r["confidence"]}
|
||||
for r in rows
|
||||
]
|
||||
return {"category": category, "count": len(results), "addresses": results}
|
||||
except sqlite3.Error:
|
||||
raise HTTPException(500, "Database query error")
|
||||
finally:
|
||||
conn.close()
|
||||
Loading…
Add table
Add a link
Reference in a new issue