"""Service — business logic for wallet analysis and scans. Composes the repository (data access) and the analyzer (pure logic). This is the only thing the api/ layer should call. """ from __future__ import annotations from app.core.logging import get_logger from app.domain.wallet.analyzer import WalletAnalyzer from app.domain.wallet.models import ( Balance, ScanRequest, ScanResult, Transaction, Wallet, WalletAnalysis, ) from app.domain.wallet.repository import WalletRepository log = get_logger(__name__) class WalletService: """Orchestrates wallet data fetch and analysis.""" def __init__( self, repo: WalletRepository | None = None, analyzer: WalletAnalyzer | None = None, ) -> None: self._repo = repo or WalletRepository() self._analyzer = analyzer or WalletAnalyzer() async def analyze( self, address: str, chain: str = "solana", ) -> WalletAnalysis: """Full wallet analysis: balance + transactions + labels → risk-scored result.""" wallet = Wallet(address=address, chain=chain) log.info("wallet_analysis_started", address=address[:12], chain=chain) balance = await self._repo.get_balance(wallet) txs = await self._repo.get_transactions(wallet, limit=10) labels = await self._repo.get_labels(wallet) analysis = self._analyzer.analyze( address=address, chain=chain, tokens=balance.tokens, recent_transactions=txs, labels=labels, ) # Override total value with the balance's authoritative value analysis.total_value_usd = balance.total_value_usd log.info( "wallet_analysis_complete", address=address[:12], risk_score=analysis.risk_score, risk_level=analysis.risk_level.value, ) return analysis async def get_balance(self, address: str, chain: str = "solana") -> Balance: """Just the balance.""" return await self._repo.get_balance(Wallet(address=address, chain=chain)) async def get_transactions( self, address: str, chain: str = "solana", limit: int = 50, ) -> list[Transaction]: """Just the transactions.""" return await self._repo.get_transactions( Wallet(address=address, chain=chain), limit=limit, ) async def scan(self, req: ScanRequest) -> ScanResult: """Threat scan. Multi-module. Returns a ScanResult. The legacy /api/v1/wallet/scan does a freemium rate-limit check before calling the scanner. That check lives in the api/ layer (HTTP concern), not here. """ log.info("wallet_scan_started", address=req.address[:12], chain=req.chain, tier=req.tier) # For now: the scan reuses analyze() + adds module-level flags. analysis = await self.analyze(req.address, req.chain) result = ScanResult( address=analysis.address, chain=analysis.chain, risk_score=analysis.risk_score, risk_level=analysis.risk_level, flags=analysis.flags, modules_run=["analyzer"], ) log.info( "wallet_scan_complete", address=req.address[:12], risk_score=result.risk_score, risk_level=result.risk_level.value, flag_count=len(result.flags), ) return result