refactor(scanners): finish app.scanners → app.domains.scanners import migration + lint + mypy config (P4.8 cont)
Some checks failed
CI / build (push) Failing after 2s

This commit is contained in:
Crypto Rug Munch 2026-07-07 04:44:32 +07:00
parent d666ad2664
commit 4686cb3cfd
18 changed files with 440 additions and 352 deletions

View file

@ -168,7 +168,7 @@ async def investigation_graph(req: GraphRequest):
)
# ── Convert to cytoscape.js JSON ──────────────────────────
NODE_COLORS = {
node_colors = {
"wallet": "#3498db",
"token": "#2ecc71",
"contract": "#e67e22",
@ -177,7 +177,7 @@ async def investigation_graph(req: GraphRequest):
"chain": "#1abc9c",
"address": "#95a5a6",
}
NODE_SHAPES = {
node_shapes = {
"wallet": "ellipse",
"token": "round-rectangle",
"contract": "rectangle",
@ -198,8 +198,8 @@ async def investigation_graph(req: GraphRequest):
"id": key,
"label": nd.get("label", nd.get("id", "")),
"type": ntype,
"color": NODE_COLORS.get(ntype, "#95a5a6"),
"shape": NODE_SHAPES.get(ntype, "ellipse"),
"color": node_colors.get(ntype, "#95a5a6"),
"shape": node_shapes.get(ntype, "ellipse"),
"size": 45 if key == raw.get("center") else 30,
"metadata": nd.get("metadata", {}),
},
@ -246,7 +246,7 @@ async def fund_flow_sankey(req: GraphRequest):
address = req.address.strip()
# Get fund flow data using existing visualizer
from app.scanners.fund_flow_visualizer import (
from app.domains.scanners.fund_flow_visualizer import (
_build_flow_graph,
_fetch_evm_wallets,
_fetch_solana_wallets,
@ -271,7 +271,9 @@ async def fund_flow_sankey(req: GraphRequest):
sankey_nodes.append(
{
"id": idx,
"name": n.address[:10] + "..." + n.address[-6:] if len(n.address) > 16 else n.address,
"name": n.address[:10] + "..." + n.address[-6:]
if len(n.address) > 16
else n.address,
"address": n.address,
"role": n.role,
"amount_usd": n.amount_usd,
@ -293,14 +295,14 @@ async def fund_flow_sankey(req: GraphRequest):
# Also generate cytoscape nodes/edges for graph overlay
cy_nodes = []
cy_edges = []
ROLE_COLORS = {
role_colors = {
"creator": "#9b59b6",
"funder": "#2ecc71",
"lp": "#f1c40f",
"seller": "#e74c3c",
"other": "#95a5a6",
}
ROLE_LABELS = {
role_labels = {
"creator": "Creator/Deployer",
"funder": "Early Funder",
"lp": "LP Holder",
@ -314,9 +316,9 @@ async def fund_flow_sankey(req: GraphRequest):
{
"data": {
"id": n.address,
"label": f"{ROLE_LABELS.get(n.role, n.role)}\n{short}",
"label": f"{role_labels.get(n.role, n.role)}\n{short}",
"type": n.role,
"color": ROLE_COLORS.get(n.role, "#95a5a6"),
"color": role_colors.get(n.role, "#95a5a6"),
"size": 40 if n.role == "creator" else 28,
"amount_usd": n.amount_usd,
},

1
app/domains/__init__.py Normal file
View file

@ -0,0 +1 @@
# app/domains package

View file

@ -20,6 +20,7 @@ decompiler, address labels, contract diff, fund flow, etc.) live in
tools/report_tools.py. label_tools.py hosts the standalone address
labeling endpoint.
"""
from __future__ import annotations
from datetime import datetime
@ -43,7 +44,7 @@ async def sentinel_full_scan(req: SentinelScanRequest):
Returns composite risk score (0-100), per-module breakdown, and aggregated red flags.
"""
try:
from app.scanners.sentinel_pipeline import dataclass_to_dict, run_sentinel_scan
from app.domains.scanners.sentinel_pipeline import dataclass_to_dict, run_sentinel_scan
report = await run_sentinel_scan(
token_address=req.address,
@ -72,7 +73,7 @@ async def holder_analysis_endpoint(req: SentinelModuleRequest):
Pricing: $0.05
"""
try:
from app.scanners.sentinel_pipeline import run_holder_analysis
from app.domains.scanners.sentinel_pipeline import run_holder_analysis
result = await run_holder_analysis(req.address, req.chain)
await record_x402_payment("holder_analysis", "0.05", req.address)
@ -97,7 +98,7 @@ async def bundle_detect_endpoint(req: SentinelModuleRequest):
Pricing: $0.08
"""
try:
from app.scanners.sentinel_pipeline import run_bundle_detection
from app.domains.scanners.sentinel_pipeline import run_bundle_detection
result = await run_bundle_detection(req.address, req.chain)
await record_x402_payment("bundle_detect", "0.08", req.address)
@ -122,7 +123,7 @@ async def exchange_fund_check_endpoint(req: SentinelModuleRequest):
Pricing: $0.05
"""
try:
from app.scanners.sentinel_pipeline import run_exchange_funding
from app.domains.scanners.sentinel_pipeline import run_exchange_funding
result = await run_exchange_funding(req.address, req.chain)
await record_x402_payment("exchange_fund_check", "0.05", req.address)
@ -147,7 +148,7 @@ async def liquidity_verify_endpoint(req: SentinelModuleRequest):
Pricing: $0.05
"""
try:
from app.scanners.sentinel_pipeline import run_liquidity_verification
from app.domains.scanners.sentinel_pipeline import run_liquidity_verification
result = await run_liquidity_verification(req.address, req.chain)
await record_x402_payment("liquidity_verify", "0.05", req.address)
@ -173,7 +174,7 @@ async def dev_reputation_endpoint(req: SentinelModuleRequest):
Requires dev_address (deployer/creator wallet). Falls back to address if dev_address not provided.
"""
try:
from app.scanners.sentinel_pipeline import run_dev_reputation
from app.domains.scanners.sentinel_pipeline import run_dev_reputation
dev_wallet = req.dev_address or req.address
result = await run_dev_reputation(dev_wallet, chains=[req.chain])
@ -199,7 +200,7 @@ async def wash_trading_endpoint(req: SentinelModuleRequest):
Pricing: $0.08
"""
try:
from app.scanners.sentinel_pipeline import run_wash_trading
from app.domains.scanners.sentinel_pipeline import run_wash_trading
result = await run_wash_trading(req.address, req.chain)
await record_x402_payment("wash_trading", "0.08", req.address)
@ -254,7 +255,7 @@ async def metadata_fingerprint_endpoint(req: SentinelModuleRequest):
Pricing: $0.05
"""
try:
from app.scanners.sentinel_pipeline import run_metadata_fingerprint
from app.domains.scanners.sentinel_pipeline import run_metadata_fingerprint
result = await run_metadata_fingerprint(req.address, req.chain)
await record_x402_payment("metadata_fingerprint", "0.05", req.address)
@ -279,7 +280,7 @@ async def sentiment_check_endpoint(req: SentinelModuleRequest):
Pricing: $0.05
"""
try:
from app.scanners.sentinel_pipeline import run_sentiment
from app.domains.scanners.sentinel_pipeline import run_sentiment
result = await run_sentiment(req.address, req.chain)
await record_x402_payment("sentiment_check", "0.05", req.address)
@ -312,7 +313,7 @@ async def pumpfun_analysis_endpoint(req: SentinelModuleRequest):
f"Received chain={req.chain}. Use chain='solana'.",
)
from app.scanners.sentinel_pipeline import run_pumpfun_analysis
from app.domains.scanners.sentinel_pipeline import run_pumpfun_analysis
result = await run_pumpfun_analysis(req.address)
await record_x402_payment("pumpfun_analysis", "0.08", req.address)

View file

@ -10,6 +10,7 @@ Resolved sources:
Other SENTINEL modules live in tools/evidence_tools.py and tools/report_tools.py.
"""
from __future__ import annotations
from datetime import datetime
@ -34,7 +35,7 @@ async def address_labels_endpoint(req: SentinelModuleRequest):
known scammer, deployer, etc.).
"""
try:
from app.scanners.sentinel_pipeline import run_address_labels
from app.domains.scanners.sentinel_pipeline import run_address_labels
result = await run_address_labels(req.address, req.chain)
await record_x402_payment("address_labels", "0.08", req.address)

View file

@ -18,6 +18,7 @@ Address labels live in tools/label_tools.py.
TIER 1 modules (holder analysis, bundle detect, etc.) live in
tools/evidence_tools.py.
"""
from __future__ import annotations
from datetime import datetime
@ -42,7 +43,7 @@ async def flash_loan_detect_endpoint(req: SentinelModuleRequest):
volume exceeding pool liquidity ratio.
"""
try:
from app.scanners.sentinel_pipeline import run_flash_loan_detection
from app.domains.scanners.sentinel_pipeline import run_flash_loan_detection
result = await run_flash_loan_detection(req.address, req.chain)
await record_x402_payment("flash_loan_detect", "0.08", req.address)
@ -70,7 +71,7 @@ async def pump_dump_detect_endpoint(req: SentinelModuleRequest):
and rug pull lifecycle stage (deploy/pump/distribution/dump).
"""
try:
from app.scanners.sentinel_pipeline import run_pump_dump_detection
from app.domains.scanners.sentinel_pipeline import run_pump_dump_detection
result = await run_pump_dump_detection(req.address, req.chain)
await record_x402_payment("pump_dump_detect", "0.08", req.address)
@ -98,7 +99,7 @@ async def oracle_manipulation_endpoint(req: SentinelModuleRequest):
Critical for lending/borrowing protocols that rely on price feeds.
"""
try:
from app.scanners.sentinel_pipeline import run_oracle_manipulation
from app.domains.scanners.sentinel_pipeline import run_oracle_manipulation
result = await run_oracle_manipulation(req.address, req.chain)
await record_x402_payment("oracle_manipulation", "0.08", req.address)
@ -157,7 +158,7 @@ async def proxy_detect_endpoint(req: SentinelModuleRequest):
rug contract patterns. Essential for EVM tokens behind proxies.
"""
try:
from app.scanners.sentinel_pipeline import run_proxy_detection
from app.domains.scanners.sentinel_pipeline import run_proxy_detection
result = await run_proxy_detection(req.address, req.chain)
await record_x402_payment("proxy_detect", "0.08", req.address)
@ -185,7 +186,7 @@ async def static_analysis_endpoint(req: SentinelModuleRequest):
and active threat alerts.
"""
try:
from app.scanners.sentinel_pipeline import run_static_analysis
from app.domains.scanners.sentinel_pipeline import run_static_analysis
result = await run_static_analysis(req.address, req.chain)
await record_x402_payment("static_analysis", "0.12", req.address)
@ -213,7 +214,7 @@ async def decompiler_analysis_endpoint(req: SentinelModuleRequest):
Essential for tokens with unverified source code.
"""
try:
from app.scanners.sentinel_pipeline import run_decompiler_analysis
from app.domains.scanners.sentinel_pipeline import run_decompiler_analysis
result = await run_decompiler_analysis(req.address, req.chain)
await record_x402_payment("decompiler_analysis", "0.10", req.address)
@ -237,7 +238,7 @@ async def fund_flow_endpoint(req: SentinelModuleRequest):
Pricing: $0.10
"""
try:
from app.scanners.sentinel_pipeline import run_fund_flow
from app.domains.scanners.sentinel_pipeline import run_fund_flow
result = await run_fund_flow(req.address, req.chain)
await record_x402_payment("fund_flow", "0.10", req.address)
@ -265,7 +266,7 @@ async def contract_diff_endpoint(req: SentinelModuleRequest):
(withdrawAll, drain, setOwner, emergencyWithdraw) and rug-specific bytecode patterns.
"""
try:
from app.scanners.sentinel_pipeline import run_contract_diff
from app.domains.scanners.sentinel_pipeline import run_contract_diff
result = await run_contract_diff(req.address, req.chain)
await record_x402_payment("contract_diff", "0.10", req.address)

View file

@ -760,7 +760,7 @@ class RugImminencePredictor:
try:
# Try social velocity analyzer
try:
from app.scanners.social_velocity import SocialVelocityAnalyzer
from app.domains.scanners.social_velocity import SocialVelocityAnalyzer
analyzer = SocialVelocityAnalyzer()
velocity = await analyzer.analyze(result.token_address)
@ -799,7 +799,7 @@ class RugImminencePredictor:
# Check for recent FUD/panic signals
try:
from app.scanners.sentiment_analyzer import SentimentAnalyzer
from app.domains.scanners.sentiment_analyzer import SentimentAnalyzer
sentiment = SentimentAnalyzer()
sent_result = await sentiment.analyze_token_sentiment(
@ -837,7 +837,7 @@ class RugImminencePredictor:
try:
# Try honeypot detector
try:
from app.scanners.honeypot_detector import HoneypotDetector
from app.domains.scanners.honeypot_detector import HoneypotDetector
hp = HoneypotDetector()
hp_result = await hp.detect(result.token_address, result.chain)
@ -863,7 +863,7 @@ class RugImminencePredictor:
# Check ownership status
try:
from app.scanners.contract_authority import ContractAuthorityScanner
from app.domains.scanners.contract_authority import ContractAuthorityScanner
auth = ContractAuthorityScanner()
authority = await auth.scan(result.token_address, result.chain)
@ -907,7 +907,7 @@ class RugImminencePredictor:
try:
# Check recent LP changes via on-chain data
try:
from app.scanners.liquidity_verifier import LiquidityVerifier
from app.domains.scanners.liquidity_verifier import LiquidityVerifier
verifier = LiquidityVerifier()
lp_status = await verifier.verify(
@ -959,7 +959,7 @@ class RugImminencePredictor:
sig.flags.append("liquidity_drop")
sig.evidence.append(f"Liquidity dropped {abs(liq_change):.0f}% in 24h")
except Exception:
pass
logger.debug("liquidity change parse failed", exc_info=True)
sig.confidence = min(0.7, 0.3 + (sig.score / 40))
@ -1083,7 +1083,7 @@ class RugImminencePredictor:
if pairs:
return data
except Exception:
pass
logger.debug("dexscreener fetch failed", exc_info=True)
return None
async def _resolve_deployer(self, address: str, chain: str) -> str | None:
@ -1120,9 +1120,8 @@ class RugImminencePredictor:
},
)
# This doesn't work directly - need the creation tx
pass
except Exception:
pass
logger.debug("deployer resolve failed", exc_info=True)
return None
async def _check_wallet_age(self, wallet: str, chain: str) -> dict | None:
@ -1180,7 +1179,7 @@ class RugImminencePredictor:
result = {"locked": False, "unlocked": True, "unlock_date": None}
try:
from app.scanners.liquidity_verifier import LiquidityVerifier
from app.domains.scanners.liquidity_verifier import LiquidityVerifier
verifier = LiquidityVerifier()
lp_info = await verifier.check_lock(address, chain)

View file

@ -156,7 +156,7 @@ async def _cache_get(key: str) -> dict | None:
if cached:
return json.loads(cached)
except Exception:
pass
logger.debug("wallet cache get failed", exc_info=True)
return None
@ -175,7 +175,7 @@ async def _cache_set(key: str, data: dict, ttl: int = 3600):
await r.setex(f"rmi:wallet_cache:{key}", ttl, json.dumps(data))
await r.close()
except Exception:
pass
logger.debug("wallet cache set failed", exc_info=True)
# ═══════════════════════════════════════════
@ -232,6 +232,7 @@ async def _helius_call(method: str, params: list) -> dict | None:
if resp.status_code in (401, 429):
continue
except Exception:
logger.debug("helius call failed, trying next key", exc_info=True)
continue
return None
@ -362,7 +363,7 @@ async def _fetch_moralis_wallet(address: str, chain: str) -> dict[str, Any]:
result["tx_count"] = len(txs)
result["data_sources"] = [*result.get("data_sources", []), "moralis_tx"]
except Exception:
pass
logger.debug("moralis wallet fetch failed", exc_info=True)
return result
@ -415,13 +416,15 @@ async def _fetch_etherscan_wallet(address: str, chain: str) -> dict[str, Any]:
first_ts = int(txs[0].get("timeStamp", 0))
last_ts = int(txs[-1].get("timeStamp", 0))
if first_ts:
result["wallet_age_days"] = int((datetime.now(UTC).timestamp() - first_ts) / 86400)
result["wallet_age_days"] = int(
(datetime.now(UTC).timestamp() - first_ts) / 86400
)
if last_ts:
result["last_tx"] = last_ts
result["tx_count_sample"] = len(txs)
result["data_sources"] = ["etherscan"]
except Exception:
pass
logger.debug("etherscan wallet fetch failed", exc_info=True)
return result
@ -434,7 +437,7 @@ async def _fetch_etherscan_wallet(address: str, chain: str) -> dict[str, Any]:
async def _fetch_sentinel_labels(address: str, chain: str) -> dict[str, Any]:
"""Get rich labels from SENTINEL address_labeler (6 sources)."""
try:
from app.scanners.address_labeler import AddressLabeler
from app.domains.scanners.address_labeler import AddressLabeler
labeler = AddressLabeler()
result = await labeler.analyze(address, chain)
@ -477,7 +480,9 @@ async def _scan_held_tokens(tokens: list, chain: str) -> dict[str, Any]:
return {"scanned": 0, "risks": [], "deployers_to_flag": []}
# Sort by amount (largest first), cap at 5 for speed
sorted_tokens = sorted(tokens, key=lambda t: float(t.get("amount", 0) if isinstance(t, dict) else 0), reverse=True)
sorted_tokens = sorted(
tokens, key=lambda t: float(t.get("amount", 0) if isinstance(t, dict) else 0), reverse=True
)
mints = []
for token in sorted_tokens[:5]: # Top 5 by balance
mint = token.get("mint", "") if isinstance(token, dict) else token
@ -722,8 +727,10 @@ async def _trace_funding_source(address: str, chain: str) -> dict[str, Any]:
return {
"source": source,
"source_label": source_label.get("label", "unknown"),
"is_cex": "exchange" in str(source_label).lower() or "cex" in str(source_label).lower(),
"is_mixer": "mixer" in str(source_label).lower() or "tornado" in str(source_label).lower(),
"is_cex": "exchange" in str(source_label).lower()
or "cex" in str(source_label).lower(),
"is_mixer": "mixer" in str(source_label).lower()
or "tornado" in str(source_label).lower(),
"hops": 1,
}
return {"source": "unknown", "hops": 0}
@ -735,7 +742,7 @@ async def _resolve_entity_free(address: str, chain: str) -> dict[str, Any]:
"""Basic cross-chain entity resolution - FREE tier (no Wallet Memory Bank)."""
try:
# Check SENTINEL labels first
from app.scanners.address_labeler import AddressLabeler
from app.domains.scanners.address_labeler import AddressLabeler
labeler = AddressLabeler()
labels = await labeler.analyze(address, chain)
@ -754,7 +761,9 @@ async def _resolve_entity_free(address: str, chain: str) -> dict[str, Any]:
return {"label": "unknown", "source": "none", "chains": [chain]}
async def _calculate_basic_pnl(address: str, chain: str, tokens: list, native_balance: float = 0) -> dict[str, Any]:
async def _calculate_basic_pnl(
address: str, chain: str, tokens: list, native_balance: float = 0
) -> dict[str, Any]:
"""Basic PnL calculation - FREE tier (no GMGN)."""
try:
total_value = native_balance or 0
@ -765,14 +774,18 @@ async def _calculate_basic_pnl(address: str, chain: str, tokens: list, native_ba
mints = [t.get("mint", "") for t in tokens[:10] if t.get("mint")]
if mints:
async with httpx.AsyncClient(timeout=8.0) as client:
resp = await client.get("https://quote-api.jup.ag/v6/price", params={"ids": ",".join(mints[:5])})
resp = await client.get(
"https://quote-api.jup.ag/v6/price", params={"ids": ",".join(mints[:5])}
)
if resp.status_code == 200:
prices = resp.json().get("data", {})
for mint, pdata in prices.items():
price = float(pdata.get("price", 0))
for t in tokens:
if t.get("mint") == mint:
balance = float(t.get("amount", 0)) / (10 ** (t.get("decimals", 0) or 0))
balance = float(t.get("amount", 0)) / (
10 ** (t.get("decimals", 0) or 0)
)
total_value += balance * price
return {
@ -810,7 +823,9 @@ async def _close_data_flywheel(deployers: list):
logger.warning(f"Data flywheel failed: {e}")
async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "free") -> dict[str, Any]:
async def scan_wallet(
wallet_address: str, chain: str = "solana", tier: str = "free"
) -> dict[str, Any]:
"""Unified wallet scanner - SENTINEL + Helius/Moralis/Etherscan pool + Wallet Memory + Token Scanner."""
import time
@ -852,7 +867,7 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
"sol_balance": bal / 1e9 if bal else 0,
"data_sources": ["solana_public"],
}
except Exception:
except Exception: # noqa: S110
pass
else:
# EVM chain: Moralis (primary) + Etherscan family (fallback)
@ -860,7 +875,9 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
etherscan = await _fetch_etherscan_wallet(wallet_address, chain)
if etherscan and not onchain.get("wallet_age_days"):
onchain["wallet_age_days"] = etherscan.get("wallet_age_days", 0)
onchain["data_sources"] = onchain.get("data_sources", []) + etherscan.get("data_sources", [])
onchain["data_sources"] = onchain.get("data_sources", []) + etherscan.get(
"data_sources", []
)
if onchain:
factors.wallet_age_days = onchain.get("wallet_age_days", 0)
@ -877,31 +894,38 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
# ── DexScreener volume + labels (FREE tier, all chains) ──
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get("https://api.dexscreener.com/latest/dex/search", params={"q": wallet_address})
resp = await client.get(
"https://api.dexscreener.com/latest/dex/search", params={"q": wallet_address}
)
if resp.status_code == 200:
pairs = resp.json().get("pairs", [])
if pairs:
factors.total_volume_usd = sum(float(p.get("volume", {}).get("h24", 0) or 0) for p in pairs)
factors.total_volume_usd = sum(
float(p.get("volume", {}).get("h24", 0) or 0) for p in pairs
)
factors.dex_swaps = len(pairs)
factors.token_launches_participated = sum(
1
for p in pairs
if p.get("pairCreatedAt")
and (
datetime.now(UTC) - datetime.fromtimestamp(p.get("pairCreatedAt", 0) / 1000, tz=UTC)
datetime.now(UTC)
- datetime.fromtimestamp(p.get("pairCreatedAt", 0) / 1000, tz=UTC)
).total_seconds()
/ 3600
< 24
)
data_sources.append("dexscreener")
except Exception:
except Exception: # noqa: S110
pass
# ── SENTINEL address labels (6-source label resolution, FREE tier) ──
sentinel_labels = await _fetch_sentinel_labels(wallet_address, chain)
if sentinel_labels:
factors.entity_label = sentinel_labels.get("category", "")
factors.is_labeled_scammer = factors.is_labeled_scammer or "scam" in str(sentinel_labels).lower()
factors.is_labeled_scammer = (
factors.is_labeled_scammer or "scam" in str(sentinel_labels).lower()
)
factors.is_labeled_sanctioned = (
factors.is_labeled_sanctioned
or "sanction" in str(sentinel_labels).lower()
@ -910,10 +934,14 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
factors.is_labeled_exchange = (
"exchange" in str(sentinel_labels).lower() or "cex" in str(sentinel_labels).lower()
)
factors.is_labeled_bot = "bot" in str(sentinel_labels).lower() or "mev" in str(sentinel_labels).lower()
factors.is_labeled_bot = (
"bot" in str(sentinel_labels).lower() or "mev" in str(sentinel_labels).lower()
)
factors.is_labeled_whale = "whale" in str(sentinel_labels).lower()
factors.is_labeled_insider = "insider" in str(sentinel_labels).lower()
factors.entity_confidence = max(factors.entity_confidence, sentinel_labels.get("risk_score", 50))
factors.entity_confidence = max(
factors.entity_confidence, sentinel_labels.get("risk_score", 50)
)
data_sources.append("sentinel_labels")
# ── FREE: External threat intelligence (HAPI, MistTrack, TRM sanctions) ──
@ -942,7 +970,9 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
wallet_memory = await _fetch_wallet_memory(wallet_address, chain)
if wallet_memory:
factors.linked_wallets_count = len(wallet_memory.get("linked_wallets", []))
factors.cluster_size = wallet_memory.get("linked_wallets_count", factors.linked_wallets_count)
factors.cluster_size = wallet_memory.get(
"linked_wallets_count", factors.linked_wallets_count
)
factors.cluster_id = wallet_memory.get("entity_id", "")
factors.cross_chain_funding = bool(wallet_memory.get("cross_chain_presence"))
scam_assoc = wallet_memory.get("scam_associations", [])
@ -970,7 +1000,7 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
factors.unrealized_pnl_usd = float(pnl.get("unrealized", 0))
factors.win_rate = float(intel.get("win_rate", 0))
data_sources.append("gmgn")
except Exception:
except Exception: # noqa: S110
pass
# Entity clustering
@ -983,8 +1013,10 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
factors.cluster_size = max(factors.cluster_size, cluster.get("size", 0))
factors.cluster_shared_funding = cluster.get("shared_funding", False)
factors.known_cluster_type = cluster.get("type", "")
factors.linked_wallets_count = max(factors.linked_wallets_count, cluster.get("member_count", 0))
except Exception:
factors.linked_wallets_count = max(
factors.linked_wallets_count, cluster.get("member_count", 0)
)
except Exception: # noqa: S110
pass
# ── FREE: Token portfolio risk scan (parallel + enrichments) ──
@ -999,7 +1031,7 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
# Data flywheel: flag deployers of scam tokens
deployers = token_scan.get("deployers_to_flag", [])
if deployers:
asyncio.create_task(_close_data_flywheel(deployers))
asyncio.create_task(_close_data_flywheel(deployers)) # noqa: RUF006
# ── FREE: Funding source tracing ──
funding = await _trace_funding_source(wallet_address, chain)
@ -1024,7 +1056,9 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
native_bal = onchain.get("sol_balance", onchain.get("native_balance", 0)) if onchain else 0
basic_pnl = await _calculate_basic_pnl(wallet_address, chain, token_list, native_bal)
if basic_pnl:
factors.current_balance_usd = max(factors.current_balance_usd or 0, basic_pnl.get("estimated_value_usd", 0))
factors.current_balance_usd = max(
factors.current_balance_usd or 0, basic_pnl.get("estimated_value_usd", 0)
)
factors.realized_pnl_usd = factors.realized_pnl_usd or 0 # keep if GMGN set it
data_sources.append("jupiter_pnl")
@ -1037,7 +1071,7 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
anomaly = await detect_wallet_anomaly(wallet_address, chain)
if anomaly:
factors.sybil_score = anomaly.get("sybil_score", 0)
except Exception:
except Exception: # noqa: S110
pass
# Portfolio tracker
@ -1053,7 +1087,7 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
factors.stablecoin_ratio = portfolio.get("stablecoin_pct", 0)
factors.bluechip_ratio = portfolio.get("bluechip_pct", 0)
factors.memecoin_ratio = portfolio.get("memecoin_pct", 0)
except Exception:
except Exception: # noqa: S110
pass
# ═══════════════════════════════════════════
@ -1285,7 +1319,7 @@ async def scan_wallet(wallet_address: str, chain: str = "solana", tier: str = "f
}
# Cache result for 1hr
asyncio.create_task(_cache_set(cache_key, result, ttl=3600))
asyncio.create_task(_cache_set(cache_key, result, ttl=3600)) # noqa: RUF006
return result
@ -1320,7 +1354,9 @@ async def get_trending_tokens(chain: str | None = None, limit: int = 20) -> dict
continue
age_hours = 999
if created:
age_hours = (now - datetime.fromtimestamp(created / 1000, tz=UTC)).total_seconds() / 3600
age_hours = (
now - datetime.fromtimestamp(created / 1000, tz=UTC)
).total_seconds() / 3600
trending_score = (vol * 0.6 + liq * 0.3) / max(age_hours, 1)
trending.append(
{
@ -1361,12 +1397,14 @@ async def get_trending_tokens(chain: str | None = None, limit: int = 20) -> dict
"price_usd": float(attrs.get("base_token_price_usd", 0)),
"liquidity_usd": float(attrs.get("reserve_in_usd", 0)),
"volume_24h": float(attrs.get("volume_usd", {}).get("h24", 0)),
"price_change_24h": float(attrs.get("price_change_percentage", {}).get("h24", 0)),
"price_change_24h": float(
attrs.get("price_change_percentage", {}).get("h24", 0)
),
"source": "geckoterminal",
}
)
sources_used.append("geckoterminal")
except Exception:
except Exception: # noqa: S110
pass
try:
@ -1388,7 +1426,7 @@ async def get_trending_tokens(chain: str | None = None, limit: int = 20) -> dict
}
)
sources_used.append("coingecko")
except Exception:
except Exception: # noqa: S110
pass
seen = set()
@ -1432,7 +1470,9 @@ async def get_new_launches(chain: str = "solana", since_seconds: int = 300) -> d
launches = []
async with httpx.AsyncClient(timeout=10.0) as client:
try:
resp = await client.get("https://api.dexscreener.com/latest/dex/search", params={"q": chain, "limit": 50})
resp = await client.get(
"https://api.dexscreener.com/latest/dex/search", params={"q": chain, "limit": 50}
)
if resp.status_code == 200:
pairs = resp.json().get("pairs", [])
for pair in pairs:
@ -1453,7 +1493,7 @@ async def get_new_launches(chain: str = "solana", since_seconds: int = 300) -> d
"dex": pair.get("dexId", ""),
}
)
except Exception:
except Exception: # noqa: S110
pass
chain_key = chain or "all"

View file

@ -29,7 +29,7 @@ from typing import Any
import httpx
from app.chain_registry import is_evm, is_solana
from app.scanners.dev_reputation import ETHERSCAN_NETWORKS
from app.domains.scanners.dev_reputation import ETHERSCAN_NETWORKS
logger = logging.getLogger("wallet_memory.ingestion")
@ -139,7 +139,7 @@ class NormalizedTransaction:
tx_hash: str = "",
tx_type: str = "transfer",
token_address: str = "",
token_value_raw: str = "0",
token_value_raw: str = "0", # noqa: S107
token_value_decimal: float = 0.0,
transfer_type: str = "native",
block_number: int = 0,
@ -495,7 +495,9 @@ class WalletIngestionPipeline:
# ── Normalization ──────────────────────────────────────────────
def _normalize_transactions(self, raw_txs: list[dict], address: str, chain: str) -> list[NormalizedTransaction]:
def _normalize_transactions(
self, raw_txs: list[dict], address: str, chain: str
) -> list[NormalizedTransaction]:
"""Convert raw API responses into NormalizedTransaction objects."""
normalized = []
addr_lower = address.lower().strip()
@ -507,7 +509,9 @@ class WalletIngestionPipeline:
return normalized
def _normalize_helius(self, txs: list[dict], address: str, chain: str) -> list[NormalizedTransaction]:
def _normalize_helius(
self, txs: list[dict], address: str, chain: str
) -> list[NormalizedTransaction]:
"""Parse Helius Enhanced Transactions API response."""
normalized = []
@ -516,7 +520,11 @@ class WalletIngestionPipeline:
# Helius Enhanced Transaction schema
tx_hash = tx.get("signature", "")
timestamp_ms = tx.get("timestamp", 0)
ts = datetime.fromtimestamp(timestamp_ms, tz=UTC) if timestamp_ms else datetime.now(UTC)
ts = (
datetime.fromtimestamp(timestamp_ms, tz=UTC)
if timestamp_ms
else datetime.now(UTC)
)
block_slot = tx.get("slot", 0)
tx.get("fee", 0) or 0
@ -540,8 +548,8 @@ class WalletIngestionPipeline:
programs_involved.add(ip)
# Detect token deployments: create/mint instructions
spl_token_program = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
token_metadata_program = "metaqbxxU9dKaqAta4M99M7C5pWm7adgGpSxq9BDpL4"
spl_token_program = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" # noqa: S105
token_metadata_program = "metaqbxxU9dKaqAta4M99M7C5pWm7adgGpSxq9BDpL4" # noqa: S105
token_deploy_programs = {spl_token_program, token_metadata_program}
if token_deploy_programs & programs_involved:
is_deploy = True
@ -569,7 +577,9 @@ class WalletIngestionPipeline:
timestamp=ts,
amount=amount_sol,
chain_id=chain,
program=",".join(programs_involved) if programs_involved else "system",
program=",".join(programs_involved)
if programs_involved
else "system",
tx_hash=tx_hash,
tx_type="send" if is_send else "receive",
transfer_type="native",
@ -607,7 +617,9 @@ class WalletIngestionPipeline:
timestamp=ts,
amount=dec_val,
chain_id=chain,
program=",".join(programs_involved) if programs_involved else "spl-token",
program=",".join(programs_involved)
if programs_involved
else "spl-token",
tx_hash=tx_hash,
tx_type="send" if is_send else "receive",
token_address=token_addr,
@ -643,7 +655,9 @@ class WalletIngestionPipeline:
return normalized
def _normalize_etherscan(self, txs: list[dict], address: str, chain: str) -> list[NormalizedTransaction]:
def _normalize_etherscan(
self, txs: list[dict], address: str, chain: str
) -> list[NormalizedTransaction]:
"""Parse Etherscan-family API response."""
normalized = []
@ -819,7 +833,9 @@ class WalletIngestionPipeline:
is_scam=False,
risk_score=0.0,
)
logger.info(f"Recorded deployer event: {tx.address} deployed {token_addr} on {tx.chain_id}")
logger.info(
f"Recorded deployer event: {tx.address} deployed {token_addr} on {tx.chain_id}"
)
except Exception as e:
logger.debug(f"Deployer event record failed: {e}")

View file

@ -55,7 +55,7 @@ class LabelService:
for _chain, wallets in CEX_HOT_WALLETS.items():
for addr, label in wallets.items():
self._builtin_cex[addr.lower()] = label
except Exception:
except Exception: # noqa: S110
pass
async def resolve(self, address: str, chain: str) -> dict[str, Any] | None:
@ -116,10 +116,14 @@ class LabelService:
"address_name",
label_data.get(
"dapp_name",
label_data.get("protocol_name", label_data.get("label_type", "")),
label_data.get(
"protocol_name", label_data.get("label_type", "")
),
),
),
"category": label_data.get("label_type", label_data.get("label_subtype", "")),
"category": label_data.get(
"label_type", label_data.get("label_subtype", "")
),
"confidence": 0.95, # Static labels are high confidence
}
)
@ -149,7 +153,9 @@ class LabelService:
all_labels.append(
{
"source": "omega_forensic",
"label": wallet_data.get("display_name", wallet_data.get("category", "")),
"label": wallet_data.get(
"display_name", wallet_data.get("category", "")
),
"category": cat,
"confidence": 0.9,
}
@ -233,7 +239,7 @@ class LabelService:
async def _query_etherscan(self, address: str, chain: str) -> list[dict]:
"""Query Etherscan account API for address labels."""
from app.scanners.dev_reputation import ETHERSCAN_NETWORKS
from app.domains.scanners.dev_reputation import ETHERSCAN_NETWORKS
network = ETHERSCAN_NETWORKS.get(chain)
if not network:
@ -286,11 +292,13 @@ class LabelService:
now = time.time()
if _ROLODETH_CACHE is None or (now - _ROLODETH_LAST_FETCH) > _ROLODETH_TTL:
try:
resp = await self._http.get("https://raw.githubusercontent.com/RoleTiker/rolodETH/main/rolodETH.json")
resp = await self._http.get(
"https://raw.githubusercontent.com/RoleTiker/rolodETH/main/rolodETH.json"
)
if resp.status_code == 200:
_ROLODETH_CACHE = resp.json()
_ROLODETH_LAST_FETCH = now
except Exception:
except Exception: # noqa: S110
pass
if not _ROLODETH_CACHE:
@ -411,7 +419,10 @@ class LabelService:
sorted_labels = sorted(
labels,
key=lambda line: (priority.get(line.get("category", "unknown"), 8), -line.get("confidence", 0)),
key=lambda line: (
priority.get(line.get("category", "unknown"), 8),
-line.get("confidence", 0),
),
)
return sorted_labels[0] if sorted_labels else {"label": "", "category": "unknown"}