rmi-backend/app/_archive/legacy_2026_07/x402_launch_fairness.py
cryptorugmunch 628c1d2a10
Some checks failed
CI / build (push) Failing after 3s
refactor(rmi-backend,audit): mount Wave 3 + archive 136 dead-code files (P2.3)
PHASE 2.3 (AUDIT-2026-Q3.md):

Task 1 — Wire-in Wave 3 (1 router mounted, 2 deferred):
  - app.routers.unified_scanner_router mounted at /api/v2/scanner/* (2 routes:
    POST /api/v2/scanner/token/scan, POST /api/v2/scanner/wallet/scan).
    Refactored prefix from /api/v2 -> /api/v2/scanner to avoid future conflicts
    with the v1 /api/v1/scanner/ stub.
  - app.routers.unified_wallet_scanner DEFERRED (no router APIRouter attribute;
    library module consumed by unified_scanner_router via get_wallet_scanner()).
  - app.routers.admin_extensions DEFERRED (DORMANT per audit; 25 routes at
    /api/v1/admin/* would shadow /api/v1/admin/alerts_webhook).

Task 2 — Archive 136 dead-code files to app/_archive/legacy_2026_07/:
  - 73 routers in app/routers/ (reach graph showed zero reach into mount.py).
  - 63 flat app/*.py (domain modules never imported by live code).
  - 1 file RESTORED post-archive: app/routers/x402_bridge_health.py (caught by
    tests/unit/test_bridge_health.py which directly imports it; reach graph
    considered tests/ only as transitive reach — to be patched in next cycle).

Forced-LIVE (NOT archived per user directive):
  - app/ai_pipeline_v3.py  (3 importers in audit window, importers themselves DEAD)
  - app/splade_bm25.py       (LIVE via app.rag_service)
  - app/wallet_manager_v2.py (LIVE via x402_enforcement, x402_tools, sweep_all, sweep_now)
  - app/crypto_embeddings.py (NOT in audit ARCHIVE list; heavy import graph)

Verification (forward-import closure from mount.py + main.py + factory.py + lifespan.py):
  - imports = 348 app.* modules
  - reached = 194 files reachable from roots
  - archive set = audit_dead (186) - reached - forced_live (4) - test_live (1) = 136
  - Net delta: 136 files moved, 44,932 LOC reduction, 293->295 active routes (+2 from Wave 3)

pyproject.toml updates:
  - setuptools.packages.find: added exclude for app._archive*
  - ruff.extend-exclude: added "app/_archive/"
  - mypy.exclude: added "app/_archive/"

Smoke test: pytest tests/ — 817 passed, 3 pre-existing failures unchanged
(0 new failures; 0 routes lost; all 4 forced-LIVE files still importable).

Restoration: git mv app/_archive/legacy_2026_07/<name>.py <original-path>
and add the import to app/mount.py ROUTER_MODULES.

Refs: AUDIT-2026-Q3.md /home/dev/pry/rmi-final-deadcode-2026-07-06.md
2026-07-06 20:52:31 +02:00

225 lines
6.9 KiB
Python

"""
x402 Router: launch_fairness
==============================
Wraps Launch Fairness Analyzer with:
- Address validation
- x402 payment middleware integration
- Caching (Redis if available)
- Trial quota tracking
- Rate limiting support
TOOL : launch_fairness
TIER : premium
PRICE : $0.10 (100000 atoms)
TRIAL : 2 free checks
ROUTER: /api/v1/x402-tools/launch_fairness
"""
import json
import logging
import os
import time
from contextlib import suppress
from datetime import UTC, datetime
from typing import Any
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field, field_validator
from app.launch_fairness_analyzer import analyze_launch_fairness
logger = logging.getLogger("x402_launch_fairness")
router = APIRouter(prefix="/api/v1/x402-tools", tags=["x402-tools"])
# ── Redis cache (best-effort) ───────────────────────────────────
_redis = None
try:
import redis.asyncio as aioredis
_redis = aioredis.from_url(
os.getenv("REDIS_URL", "redis://localhost:6379/0"),
decode_responses=True,
socket_connect_timeout=2,
)
except Exception:
logger.debug("Redis not available for launch_fairness cache")
async def _get_cache(key: str) -> dict[str, Any] | None:
"""Get cached result if Redis is available."""
if _redis is None:
return None
with suppress(Exception):
data = await _redis.get(f"x402:cache:launch_fairness:{key}")
if data:
result: dict[str, Any] = json.loads(data)
return result
return None
async def _set_cache(key: str, result: dict[str, Any], ttl: int = 300) -> None:
"""Cache result in Redis with TTL."""
if _redis is None:
return
with suppress(Exception):
await _redis.setex(
f"x402:cache:launch_fairness:{key}", ttl, json.dumps(result, default=str)
)
# ── Trial tracking ──────────────────────────────────────────────
def _trial_key(wallet: str) -> str:
return f"x402:launch_fairness_trials:{wallet}"
async def _check_trials(wallet: str) -> int:
"""Check how many trials this wallet has used."""
if _redis is None:
return 0
try:
used = await _redis.get(_trial_key(wallet))
return int(used) if used else 0
except Exception:
return 0
async def _increment_trials(wallet: str) -> None:
"""Increment trial usage for a wallet."""
if _redis is None:
return
try:
await _redis.incr(_trial_key(wallet))
await _redis.expire(_trial_key(wallet), 86400 * 30) # Reset monthly
except Exception:
pass
# ── Request / Response models ───────────────────────────────────
class LaunchFairnessRequest(BaseModel):
"""Request model for launch fairness analysis."""
address: str = Field(..., description="Token contract address to analyze")
chain: str = Field(
"auto",
description="Blockchain (ethereum, solana, bsc, base, polygon, or 'auto' for detection)",
)
simulate_data: bool = Field(True, description="Use simulated data for demonstration / testing")
@field_validator("address")
@classmethod
def validate_address(cls, v: str) -> str:
v = v.strip()
is_evm = v.startswith("0x") and len(v) == 42
is_solana = not v.startswith("0x") and 32 <= len(v) <= 44 and v.isascii()
if not is_evm and not is_solana:
raise ValueError(
"Address must be a valid token contract address (0x... for EVM, base58 for Solana)"
)
return v.lower()
@field_validator("chain")
@classmethod
def validate_chain(cls, v: str) -> str:
valid_chains = {
"auto",
"ethereum",
"solana",
"bsc",
"base",
"polygon",
"arbitrum",
"avalanche",
}
v = v.lower().strip()
if v not in valid_chains:
raise ValueError(f"Chain must be one of: {', '.join(sorted(valid_chains))}")
return v
class LaunchFairnessResponse(BaseModel):
"""Response model for launch fairness analysis."""
success: bool = True
tool: str = "launch_fairness"
data: dict[str, Any] = Field(default_factory=dict)
cached: bool = False
scanned_at: str = ""
# ── Endpoint ────────────────────────────────────────────────────
@router.post("/launch_fairness")
async def analyze_launch_fairness_endpoint(
request: Request,
body: LaunchFairnessRequest,
) -> LaunchFairnessResponse:
"""
Analyze token launch fairness - detect sniped distributions, bundled launches,
bot activity, LP manipulation, and presale concentration.
Returns a fairness score (0-100) with per-signal breakdown and evidence.
"""
start = time.time()
wallet = request.headers.get("x-wallet-address", "anonymous")
# ── Trial check ──
max_trial = 2
trials_used = await _check_trials(wallet)
if trials_used < max_trial:
await _increment_trials(wallet)
# ── Cache check ──
cache_key = f"{body.address}:{body.chain}:{body.simulate_data}"
cached = await _get_cache(cache_key)
if cached:
elapsed = time.time() - start
logger.info(f"Launch fairness cache hit for {body.address[:12]}... ({elapsed:.2f}s)")
return LaunchFairnessResponse(
data=cached,
cached=True,
scanned_at=cached.get("scanned_at", ""),
)
# ── Run analysis ──
try:
result = await analyze_launch_fairness(
token_address=body.address,
chain=body.chain,
simulate_data=body.simulate_data,
)
# Add metadata
result["scanned_at"] = datetime.now(tz=UTC).isoformat()
result["tier"] = "premium"
result["price_usd"] = 0.10
# ── Cache result (short TTL - fairness data changes quickly) ──
await _set_cache(cache_key, result, ttl=120)
logger.info(
f"Launch fairness analyzed {body.address[:12]}... "
f"score={result.get('fairness_score', '?')}% "
f"({time.time() - start:.2f}s)"
)
return LaunchFairnessResponse(
data=result,
cached=False,
scanned_at=result["scanned_at"],
)
except ValueError as e:
logger.warning(f"Launch fairness validation error: {e}")
raise HTTPException(status_code=400, detail=str(e)) from e
except Exception as e:
logger.error(f"Launch fairness analysis failed: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail="Launch fairness analysis failed. Please try again.",
) from e