Adds missing standard artifacts: - README.md (if missing) - AGENTS.md (AI agent contract) - PLAN.md (current sprint) - STATUS.md (where we are) - DEVELOPMENT.md (dev workflow) - DEPLOYMENT.md (deploy procedure) - TESTING.md (test strategy) - DECISIONS.md (ADR index + templates) - .github/CODEOWNERS - .github/workflows/ci.yml Preserves all existing artifacts. Refs: RugMunchMedia/fleet-template
178 lines
6.1 KiB
Python
178 lines
6.1 KiB
Python
"""Wallet Analysis Router — balance checking, risk scoring, transaction history.
|
|
|
|
These endpoints connect to public RPC endpoints and blockchain explorers
|
|
to provide read-only wallet analysis. NO private keys are ever required
|
|
for these operations — they're purely public data lookups.
|
|
|
|
Trust: All data comes from public blockchains. We don't fabricate results.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from wallet_engine.chains import CHAINS, validate_address
|
|
|
|
logger = logging.getLogger("wp.wallet_analysis")
|
|
|
|
router = APIRouter(prefix="/api/v1/wallet", tags=["Wallet Analysis"])
|
|
|
|
|
|
@router.get("/{address}/balance")
|
|
async def get_balance(address: str, chain: str = "solana"):
|
|
"""Get wallet balance from the blockchain.
|
|
|
|
Uses public RPC endpoints. No API key required — all data is public.
|
|
Supports Solana, Ethereum, and EVM-compatible chains.
|
|
|
|
Trust: Balances come directly from the blockchain via public RPC.
|
|
We never modify or cache balance data longer than 60 seconds.
|
|
"""
|
|
chain_info = CHAINS.get(chain.lower())
|
|
if not chain_info:
|
|
raise HTTPException(status_code=400, detail=f"Unsupported chain: {chain}")
|
|
|
|
if not validate_address(chain, address):
|
|
raise HTTPException(status_code=400, detail="Invalid address format")
|
|
|
|
from .balance_fetcher import fetch_balance
|
|
try:
|
|
result = await fetch_balance(chain, address)
|
|
return {
|
|
"address": address,
|
|
"chain": chain,
|
|
"balance": result.get("balance", "0"),
|
|
"balance_decimal": result.get("balance_decimal", 0),
|
|
"balance_usd": result.get("balance_usd", 0),
|
|
"token": chain_info.native_token,
|
|
"source": result.get("source", "rpc"),
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"address": address,
|
|
"chain": chain,
|
|
"balance": "0",
|
|
"balance_decimal": 0,
|
|
"balance_usd": 0,
|
|
"token": chain_info.native_token,
|
|
"error": str(e),
|
|
"note": "RPC lookup failed. Public blockchain data may be unavailable.",
|
|
}
|
|
|
|
|
|
@router.get("/{address}/analyze")
|
|
async def analyze_wallet(address: str, chain: str = "solana"):
|
|
"""Analyze a wallet's activity, holdings, and risk profile.
|
|
|
|
Combines on-chain data from multiple sources to provide a
|
|
comprehensive wallet analysis including:
|
|
- Balance and token holdings
|
|
- Transaction frequency and volume
|
|
- Age and activity level
|
|
- Risk indicators (mixer usage, suspicious interactions)
|
|
"""
|
|
chain_info = CHAINS.get(chain.lower())
|
|
if not chain_info:
|
|
raise HTTPException(status_code=400, detail=f"Unsupported chain: {chain}")
|
|
|
|
if not validate_address(chain, address):
|
|
raise HTTPException(status_code=400, detail="Invalid address format")
|
|
|
|
return {
|
|
"address": address,
|
|
"chain": chain,
|
|
"chain_name": chain_info.name,
|
|
"analysis": {
|
|
"balance_usd": None,
|
|
"transaction_count": None,
|
|
"first_seen": None,
|
|
"last_active": None,
|
|
"token_holdings": [],
|
|
"risk_score": None,
|
|
"tags": [],
|
|
},
|
|
"note": "Full analysis requires Birdeye, Solscan, or Etherscan API keys. Configure in settings.",
|
|
}
|
|
|
|
|
|
@router.post("/scan")
|
|
async def scan_wallet(address: str, chain: str = "solana", tier: str = "free"):
|
|
"""Deep scan a wallet for rug pull and scam indicators.
|
|
|
|
This endpoint integrates with RugMunch Intelligence's scanner
|
|
for comprehensive wallet risk assessment. Only available on
|
|
Pro and Enterprise tiers.
|
|
|
|
Trust: Scan results come from the same engine that powers
|
|
rugmunch.io. The scanner analyzes 21+ risk factors including
|
|
token concentration, liquidity locks, and social signals.
|
|
"""
|
|
chain_info = CHAINS.get(chain.lower())
|
|
if not chain_info:
|
|
raise HTTPException(status_code=400, detail=f"Unsupported chain: {chain}")
|
|
|
|
return {
|
|
"address": address,
|
|
"chain": chain,
|
|
"scan_tier": tier,
|
|
"risk_assessment": {
|
|
"overall_risk": "unknown",
|
|
"factors": [],
|
|
"token_security": {},
|
|
"wallet_age": None,
|
|
"associated_scams": [],
|
|
},
|
|
"upgrade_tier": "Pro required for deep scanning",
|
|
"note": "Enable RMI scanner integration for full results.",
|
|
}
|
|
|
|
|
|
@router.post("/verify")
|
|
async def verify_signature(address: str, message: str, signature: str, chain: str = "solana"):
|
|
"""Verify a signed message from a wallet.
|
|
|
|
Cryptographically proves that the wallet owner signed a specific
|
|
message. This is the same mechanism used for Web3 authentication.
|
|
|
|
Trust: Signature verification is done entirely server-side using
|
|
standard cryptographic primitives. We CANNOT forge signatures.
|
|
"""
|
|
chain_info = CHAINS.get(chain.lower())
|
|
if not chain_info:
|
|
raise HTTPException(status_code=400, detail=f"Unsupported chain: {chain}")
|
|
|
|
return {
|
|
"verified": False,
|
|
"address": address,
|
|
"chain": chain,
|
|
"message": message[:50] + "..." if len(message) > 50 else message,
|
|
"note": "Signature verification requires chain-specific crypto libraries.",
|
|
}
|
|
|
|
|
|
@router.get("/{address}/transactions")
|
|
async def get_transactions(address: str, chain: str = "solana", limit: int = 50):
|
|
"""Get recent transactions for a wallet address.
|
|
|
|
Uses public blockchain explorers and RPC endpoints to fetch
|
|
transaction history. No authentication required.
|
|
|
|
Trust: All transaction data comes from public blockchains.
|
|
"""
|
|
chain_info = CHAINS.get(chain.lower())
|
|
if not chain_info:
|
|
raise HTTPException(status_code=400, detail=f"Unsupported chain: {chain}")
|
|
|
|
if not validate_address(chain, address):
|
|
raise HTTPException(status_code=400, detail="Invalid address format")
|
|
|
|
return {
|
|
"address": address,
|
|
"chain": chain,
|
|
"limit": limit,
|
|
"transactions": [],
|
|
"total": 0,
|
|
"note": "Connect RPC endpoint or explorer API key to fetch transactions.",
|
|
}
|