| """ |
| Advanced Wallet + Contract Analysis Engine |
| ========================================== |
| - Wallet balance/transaction history via RPC + public APIs |
| - Advanced funding traceback (hop-by-hop) |
| - 100-factor contract rug risk analysis |
| - Multi-chain parity |
| |
| Chains: solana, ethereum, base, bsc, arbitrum, polygon, avalanche, |
| optimism, fantom, linea, zksync, scroll, mantle |
| """ |
|
|
| import logging |
| from dataclasses import dataclass |
| from datetime import UTC, datetime |
| from typing import Any |
|
|
| import httpx |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
|
|
| RPC_URLS = { |
| "ethereum": "https://ethereum-rpc.publicnode.com", |
| "base": "https://mainnet.base.org", |
| "bsc": "https://bsc-dataseed.binance.org", |
| "arbitrum": "https://arb1.arbitrum.io/rpc", |
| "polygon": "https://polygon-rpc.com", |
| "avalanche": "https://api.avax.network/ext/bc/C/rpc", |
| "optimism": "https://mainnet.optimism.io", |
| "fantom": "https://rpc.fantom.network", |
| } |
|
|
| EXPLORER_APIS = { |
| "ethereum": "https://api.etherscan.io/api", |
| "base": "https://api.basescan.org/api", |
| "bsc": "https://api.bscscan.com/api", |
| "arbitrum": "https://api.arbiscan.io/api", |
| "polygon": "https://api.polygonscan.com/api", |
| "avalanche": "https://api.snowtrace.io/api", |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| async def get_wallet_balance(address: str, chain: str) -> dict[str, Any]: |
| """Get native token balance via RPC.""" |
| result = { |
| "native_balance": 0, |
| "native_symbol": "ETH", |
| "token_balances": [], |
| "total_value_usd": 0, |
| } |
|
|
| rpc_url = RPC_URLS.get(chain) |
| if not rpc_url: |
| return result |
|
|
| async with httpx.AsyncClient(timeout=10.0) as client: |
| try: |
| |
| resp = await client.post( |
| rpc_url, |
| json={ |
| "jsonrpc": "2.0", |
| "method": "eth_getBalance", |
| "params": [address, "latest"], |
| "id": 1, |
| }, |
| ) |
| if resp.status_code == 200: |
| data = resp.json() |
| if data.get("result"): |
| result["native_balance"] = int(data["result"], 16) / 1e18 |
|
|
| except Exception as e: |
| logger.warning(f"Balance RPC failed for {chain}: {e}") |
|
|
| |
| async with httpx.AsyncClient(timeout=15.0) as client: |
| try: |
| resp = await client.get("https://api.dexscreener.com/latest/dex/search", params={"q": address}) |
| if resp.status_code == 200: |
| pairs = resp.json().get("pairs", []) |
| tokens = {} |
| for pair in pairs: |
| base = pair.get("baseToken", {}) |
| token_addr = base.get("address", "") |
| if token_addr: |
| tokens[token_addr] = { |
| "address": token_addr, |
| "symbol": base.get("symbol", ""), |
| "name": base.get("name", ""), |
| "price_usd": float(pair.get("priceUsd", 0)), |
| "liquidity_usd": float(pair.get("liquidity", {}).get("usd", 0)), |
| } |
| result["token_balances"] = list(tokens.values())[:50] |
| except Exception: |
| pass |
|
|
| return result |
|
|
|
|
| async def get_transaction_history(address: str, chain: str, limit: int = 50, offset: int = 0) -> dict[str, Any]: |
| """Get transaction history via explorer API.""" |
| explorer_api = EXPLORER_APIS.get(chain) |
| if not explorer_api: |
| return {"transactions": [], "total": 0} |
|
|
| |
| async with httpx.AsyncClient(timeout=15.0) as client: |
| try: |
| resp = await client.get( |
| "https://api.dexscreener.com/latest/dex/search", |
| params={"q": address, "limit": limit}, |
| ) |
| if resp.status_code == 200: |
| pairs = resp.json().get("pairs", []) |
| txs = [] |
| for pair in pairs: |
| tx_data = pair.get("txns", {}) |
| buys = tx_data.get("h24", {}).get("buys", 0) |
| sells = tx_data.get("h24", {}).get("sells", 0) |
|
|
| txs.append( |
| { |
| "pair": pair.get("pairAddress", ""), |
| "dex": pair.get("dexId", ""), |
| "token_symbol": pair.get("baseToken", {}).get("symbol", ""), |
| "token_name": pair.get("baseToken", {}).get("name", ""), |
| "price_usd": float(pair.get("priceUsd", 0)), |
| "volume_24h": float(pair.get("volume", {}).get("h24", 0)), |
| "buys_24h": buys, |
| "sells_24h": sells, |
| "tx_type": "swap", |
| "chain": pair.get("chainId", chain), |
| } |
| ) |
|
|
| return {"transactions": txs[:limit], "total": len(txs)} |
| except Exception as e: |
| logger.warning(f"TX history failed for {chain}: {e}") |
|
|
| return {"transactions": [], "total": 0} |
|
|
|
|
| |
| |
| |
|
|
|
|
| @dataclass |
| class FundingHop: |
| address: str |
| chain: str |
| amount_usd: float = 0 |
| tx_hash: str = "" |
| timestamp: str | None = None |
| is_cex: bool = False |
| is_mixer: bool = False |
| is_sanctioned: bool = False |
| label: str | None = None |
|
|
|
|
| MIXER_ADDRESSES = { |
| "ethereum": [ |
| "0x12d66f87a04a9e220743712ce6d9bb1b5616b8fc", |
| "0x47ce0c6ed5b0ce3d3a51fdb1c52dc66a7c3c2936", |
| "0x910cbd523d972eb0a6f4cae4618ad62622b39dbf", |
| "0xa160cdab225685da1d56aa342ad8841c3b53f291", |
| ], |
| "bsc": [ |
| "0x84443cfd09a48af6ef2dbf80e4d06d0051ef2ddc", |
| ], |
| } |
|
|
| CEX_ADDRESSES = { |
| "binance": [ |
| "0x28c6c06298d514db089934071355e5743bf21d60", |
| "0x21a31ee1afc51d94c2efccaa2092ad1028285549", |
| ], |
| "coinbase": [ |
| "0x71660c4005ba85c37ccec55d0c4493e66fe775d3", |
| "0x503828976d22510aad0201ac7ec88293211d23da", |
| ], |
| "kraken": ["0x267be1c1d684f78cb4f6a176c4911b741e4ffdc0"], |
| } |
|
|
|
|
| def _match_label(address: str, chain: str) -> str | None: |
| """Check if address matches known labels.""" |
| addr_lower = address.lower() |
|
|
| |
| for mixer_addr in MIXER_ADDRESSES.get(chain, []): |
| if mixer_addr.lower() == addr_lower: |
| return "mixer" |
|
|
| |
| for cex_name, addrs in CEX_ADDRESSES.items(): |
| for cex_addr in addrs: |
| if cex_addr.lower() == addr_lower: |
| return cex_name |
|
|
| return None |
|
|
|
|
| async def trace_funding(address: str, chain: str, max_hops: int = 5, max_depth: int = 3) -> dict[str, Any]: |
| """Trace funding source hop-by-hop.""" |
| hops: list[FundingHop] = [] |
| visited = {address.lower()} |
| current_address = address |
| current_chain = chain |
| depth = 0 |
|
|
| async with httpx.AsyncClient(timeout=30.0) as client: |
| while depth < max_depth and len(hops) < max_hops: |
| |
| explorer_api = EXPLORER_APIS.get(current_chain) |
|
|
| if not explorer_api and current_chain == "solana": |
| |
| try: |
| resp = await client.get( |
| "https://public-api.solscan.io/account/transactions", |
| params={"account": current_address, "limit": 20}, |
| ) |
| if resp.status_code == 200: |
| txs = resp.json() |
| |
| for tx in txs[:5]: |
| signer = tx.get("signer", [""])[0] |
| if signer.lower() not in visited: |
| hop = FundingHop( |
| address=signer, |
| chain=current_chain, |
| amount_usd=float(tx.get("amount", 0)), |
| tx_hash=tx.get("txHash", ""), |
| timestamp=datetime.fromtimestamp(tx.get("blockTime", 0), tz=UTC).isoformat() |
| if tx.get("blockTime") |
| else None, |
| ) |
| label = _match_label(signer, current_chain) |
| if label: |
| hop.label = label |
| hop.is_cex = label not in ("mixer",) |
| hop.is_mixer = label == "mixer" |
| hops.append(hop) |
| visited.add(signer.lower()) |
| current_address = signer |
| break |
| except Exception: |
| break |
| else: |
| |
| try: |
| resp = await client.get( |
| "https://api.dexscreener.com/latest/dex/search", |
| params={"q": current_address, "limit": 20}, |
| ) |
| if resp.status_code == 200: |
| pairs = resp.json().get("pairs", []) |
| pair_addrs = set() |
| for pair in pairs: |
| pair_addr = pair.get("pairAddress", "") |
| if pair_addr and pair_addr.lower() not in visited: |
| pair_addrs.add(pair_addr) |
|
|
| if pair_addrs: |
| |
| for pa in list(pair_addrs)[:5]: |
| label = _match_label(pa, current_chain) |
| hop = FundingHop( |
| address=pa, |
| chain=current_chain, |
| amount_usd=float(pairs[0].get("liquidity", {}).get("usd", 0)), |
| ) |
| if label: |
| hop.label = label |
| hop.is_cex = label not in ("mixer",) |
| hop.is_mixer = label == "mixer" |
| hops.append(hop) |
| visited.add(pa.lower()) |
| except Exception: |
| pass |
|
|
| depth += 1 |
|
|
| |
| funding_source = "unknown" |
| if hops: |
| first_hop = hops[-1] |
| if first_hop.is_cex: |
| funding_source = "centralized_exchange" |
| elif first_hop.is_mixer: |
| funding_source = "mixer" |
| elif first_hop.label: |
| funding_source = first_hop.label |
| else: |
| funding_source = "external_wallet" |
|
|
| return { |
| "hops": [ |
| { |
| "address": h.address[:12] + "...", |
| "chain": h.chain, |
| "amount_usd": h.amount_usd, |
| "label": h.label, |
| "is_cex": h.is_cex, |
| "is_mixer": h.is_mixer, |
| "depth": i + 1, |
| } |
| for i, h in enumerate(hops) |
| ], |
| "total_hops": len(hops), |
| "max_depth_reached": depth >= max_depth, |
| "funding_source": funding_source, |
| "risk_level": "high" if any(h.is_mixer for h in hops) else "medium" if len(hops) > 3 else "low", |
| "traced_at": datetime.now(UTC).isoformat(), |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| @dataclass |
| class RugRiskReport: |
| token_address: str |
| chain: str |
|
|
| |
| is_verified: bool = False |
| is_proxy: bool = False |
| is_upgradeable: bool = False |
| has_mint_function: bool = False |
| has_burn_function: bool = False |
| has_blacklist_function: bool = False |
| has_pause_function: bool = False |
| has_whitelist_function: bool = False |
| has_anti_whale: bool = False |
| has_max_tx_limit: bool = False |
| has_max_wallet_limit: bool = False |
| has_transfer_fee: bool = False |
| has_reflection: bool = False |
| has_automatic_lp: bool = False |
| has_buyback: bool = False |
| has_rebase: bool = False |
| has_flash_loan_protection: bool = False |
| has_renounce_ownership: bool = False |
| has_timelock: bool = False |
| has_multisig: bool = False |
| contract_size_kb: float = 0 |
| contract_complexity_score: float = 0 |
| compiler_version: str = "" |
| optimization_enabled: bool = False |
| solidity_version_outdated: bool = False |
| similar_to_known_scams: float = 0 |
| unique_functions_count: int = 0 |
| external_calls_count: int = 0 |
| delegatecall_usage: bool = False |
| selfdestruct_present: bool = False |
|
|
| |
| total_supply: float = 0 |
| circulating_supply: float = 0 |
| max_supply: float = 0 |
| holder_count: int = 0 |
| top10_holder_pct: float = 0 |
| top50_holder_pct: float = 0 |
| top100_holder_pct: float = 0 |
| dev_wallet_pct: float = 0 |
| team_wallet_pct: float = 0 |
| marketing_wallet_pct: float = 0 |
| lp_wallet_pct: float = 0 |
| dead_wallet_pct: float = 0 |
| cex_wallet_pct: float = 0 |
| unique_wallets_24h: int = 0 |
| new_wallets_24h: int = 0 |
| wallet_retention_7d: float = 0 |
| avg_hold_time_hours: float = 0 |
| buy_tax_pct: float = 0 |
| sell_tax_pct: float = 0 |
| tax_modifiable: bool = False |
| max_tax_pct: float = 0 |
| transfer_tax_enabled: bool = False |
| liquidity_lock_days: int = 0 |
| liquidity_lock_pct: float = 0 |
| liquidity_owner: str = "" |
|
|
| |
| age_hours: float = 0 |
| current_price_usd: float = 0 |
| ath_price_usd: float = 0 |
| atl_price_usd: float = 0 |
| price_change_5m: float = 0 |
| price_change_1h: float = 0 |
| price_change_6h: float = 0 |
| price_change_24h: float = 0 |
| volume_24h_usd: float = 0 |
| volume_change_24h: float = 0 |
| liquidity_usd: float = 0 |
| liquidity_change_24h: float = 0 |
| market_cap_usd: float = 0 |
| fdv_usd: float = 0 |
| mcap_to_liquidity_ratio: float = 0 |
| volume_to_liquidity_ratio: float = 0 |
| buy_sell_ratio_24h: float = 0 |
| unique_traders_24h: int = 0 |
| avg_trade_size_usd: float = 0 |
| whale_trade_count_24h: int = 0 |
| sniper_tx_count_24h: int = 0 |
| bot_tx_count_24h: int = 0 |
| organic_tx_pct: float = 0 |
| wash_trading_score: float = 0 |
| volatility_24h: float = 0 |
|
|
| |
| has_website: bool = False |
| has_twitter: bool = False |
| has_telegram: bool = False |
| has_discord: bool = False |
| has_github: bool = False |
| has_whitepaper: bool = False |
| has_audit: bool = False |
| twitter_age_days: int = 0 |
| twitter_followers: int = 0 |
| twitter_following_ratio: float = 0 |
| twitter_posts_24h: int = 0 |
| twitter_sentiment_score: float = 0 |
| telegram_members: int = 0 |
| telegram_online_ratio: float = 0 |
| telegram_message_frequency: float = 0 |
| github_commits: int = 0 |
| github_contributors: int = 0 |
| website_age_days: int = 0 |
| audit_firm_reputation: str = "" |
| social_trust_score: float = 0 |
|
|
| |
| rug_risk_score: int = 0 |
| rug_risk_category: str = "unknown" |
| confidence: float = 0 |
| factors_analyzed: int = 0 |
|
|
|
|
| async def analyze_contract_rug_risk(token_address: str, chain: str, tier: str = "free") -> dict[str, Any]: |
| """100-factor contract rug risk analysis.""" |
| report = RugRiskReport(token_address=token_address, chain=chain) |
| factors_checked = 0 |
| risk_score = 0 |
| risk_flags = [] |
|
|
| async with httpx.AsyncClient(timeout=20.0) as client: |
| |
| try: |
| resp = await client.get(f"https://api.dexscreener.com/latest/dex/tokens/{token_address}") |
| if resp.status_code == 200: |
| data = resp.json() |
| pairs = data.get("pairs", []) |
| if pairs: |
| pair = pairs[0] |
|
|
| |
| report.current_price_usd = float(pair.get("priceUsd", 0)) |
| report.price_change_5m = float(pair.get("priceChange", {}).get("m5", 0)) |
| report.price_change_1h = float(pair.get("priceChange", {}).get("h1", 0)) |
| report.price_change_6h = float(pair.get("priceChange", {}).get("h6", 0)) |
| report.price_change_24h = float(pair.get("priceChange", {}).get("h24", 0)) |
| report.volume_24h_usd = float(pair.get("volume", {}).get("h24", 0)) |
| report.liquidity_usd = float(pair.get("liquidity", {}).get("usd", 0)) |
| report.market_cap_usd = float(pair.get("marketCap", 0)) |
| report.fdv_usd = float(pair.get("fdv", 0)) |
|
|
| |
| created = pair.get("pairCreatedAt") |
| if created: |
| report.age_hours = ( |
| datetime.now(UTC) - datetime.fromtimestamp(created / 1000, tz=UTC) |
| ).total_seconds() / 3600 |
|
|
| |
| if report.liquidity_usd > 0: |
| report.mcap_to_liquidity_ratio = report.market_cap_usd / report.liquidity_usd |
| report.volume_to_liquidity_ratio = report.volume_24h_usd / report.liquidity_usd |
|
|
| factors_checked += 15 |
|
|
| |
| |
| if report.age_hours < 1: |
| risk_score += 25 |
| risk_flags.append("FRESH_LAUNCH_<1H") |
| elif report.age_hours < 6: |
| risk_score += 15 |
| risk_flags.append("NEW_LAUNCH_<6H") |
| elif report.age_hours < 24: |
| risk_score += 8 |
| risk_flags.append("RECENT_LAUNCH_<24H") |
|
|
| |
| if report.liquidity_usd < 1000: |
| risk_score += 30 |
| risk_flags.append("MICRO_LIQUIDITY_<$1K") |
| elif report.liquidity_usd < 5000: |
| risk_score += 20 |
| risk_flags.append("LOW_LIQUIDITY_<$5K") |
| elif report.liquidity_usd < 25000: |
| risk_score += 10 |
| risk_flags.append("LIMITED_LIQUIDITY_<$25K") |
|
|
| |
| if report.volume_to_liquidity_ratio > 50: |
| risk_score += 20 |
| risk_flags.append("EXTREME_VOLUME_RATIO_>50x") |
| elif report.volume_to_liquidity_ratio > 20: |
| risk_score += 12 |
| risk_flags.append("HIGH_VOLUME_RATIO_>20x") |
| elif report.volume_to_liquidity_ratio > 10: |
| risk_score += 6 |
| risk_flags.append("ELEVATED_VOLUME_RATIO") |
|
|
| |
| if report.mcap_to_liquidity_ratio > 100: |
| risk_score += 15 |
| risk_flags.append("EXTREME_MCAP_LIQ_RATIO") |
|
|
| |
| if report.price_change_5m < -15: |
| risk_score += 10 |
| risk_flags.append("CRASHING_5M") |
| if report.price_change_1h < -40: |
| risk_score += 20 |
| risk_flags.append("DUMPING_1H") |
| if report.price_change_6h < -70: |
| risk_score += 25 |
| risk_flags.append("RUG_IN_PROGRESS") |
| if report.price_change_24h < -90: |
| risk_score += 30 |
| risk_flags.append("RUGGED_24H") |
| if report.price_change_5m > 300 and report.liquidity_usd < 10000: |
| risk_score += 15 |
| risk_flags.append("PUMP_LOW_LIQ") |
|
|
| except Exception as e: |
| logger.warning(f"DexScreener analysis failed: {e}") |
|
|
| |
| chain_id_map = { |
| "solana": "solana", |
| "ethereum": "1", |
| "bsc": "56", |
| "base": "8453", |
| "arbitrum": "42161", |
| "polygon": "137", |
| "avalanche": "43114", |
| } |
| chain_id = chain_id_map.get(chain, chain) |
|
|
| try: |
| resp = await client.get( |
| f"https://api.gopluslabs.io/api/v1/token_security/{chain_id}", |
| params={"contract_addresses": token_address}, |
| ) |
| if resp.status_code == 200: |
| goplus = resp.json().get("result", {}).get(token_address.lower(), {}) |
| if goplus: |
| |
| report.is_honeypot = goplus.get("is_honeypot") == "1" |
| report.is_open_source = goplus.get("is_open_source") == "1" |
| report.is_proxy = goplus.get("is_proxy") == "1" |
| report.is_mintable = goplus.get("is_mintable") == "1" |
| report.can_takeback_ownership = goplus.get("can_take_back_ownership") == "1" |
| report.is_blacklisted = goplus.get("is_blacklisted") == "1" |
| report.is_whitelisted = goplus.get("is_whitelisted") == "1" |
| report.has_transfer_pausable = goplus.get("transfer_pausable") == "1" |
| report.is_anti_whale = goplus.get("is_anti_whale") == "1" |
| report.has_trading_cooldown = goplus.get("trading_cooldown") == "1" |
| report.can_modify_tax = goplus.get("slippage_modifiable") == "1" |
| report.transfer_pausable = goplus.get("transfer_pausable") == "1" |
|
|
| |
| report.buy_tax_pct = float(goplus.get("buy_tax", "0")) |
| report.sell_tax_pct = float(goplus.get("sell_tax", "0")) |
| report.holder_count = int(goplus.get("holder_count", "0")) |
|
|
| lp_data = goplus.get("lp_holders", []) |
| report.total_lp_holders = len(lp_data) if isinstance(lp_data, list) else 0 |
|
|
| |
| if report.total_lp_holders > 0: |
| lp_holder = lp_data[0] if isinstance(lp_data, list) else lp_data |
| report.lp_lock_pct = float(lp_holder.get("percent", 0)) |
| report.lp_locked = ( |
| float(lp_holder.get("locked", 0)) > 0 if isinstance(lp_holder, dict) else False |
| ) |
|
|
| |
| owner = goplus.get("owner_address", "") |
| if owner == "0x0000000000000000000000000000000000000000": |
| report.ownership_renounced = True |
| else: |
| report.ownership_renounced = False |
|
|
| factors_checked += 20 |
|
|
| |
| if report.is_honeypot: |
| risk_score += 50 |
| risk_flags.append("HONEYPOT") |
| if not report.is_open_source: |
| risk_score += 15 |
| risk_flags.append("UNVERIFIED_CONTRACT") |
| if report.is_proxy: |
| risk_score += 10 |
| risk_flags.append("PROXY_CONTRACT") |
| if report.is_mintable: |
| risk_score += 15 |
| risk_flags.append("MINTABLE") |
| if report.can_takeback_ownership: |
| risk_score += 25 |
| risk_flags.append("OWNERSHIP_RECLAIMABLE") |
| if report.is_blacklisted: |
| risk_score += 40 |
| risk_flags.append("BLACKLISTED") |
| if report.can_modify_tax: |
| risk_score += 20 |
| risk_flags.append("MODIFIABLE_TAX") |
| if report.transfer_pausable: |
| risk_score += 15 |
| risk_flags.append("PAUSABLE_TRANSFERS") |
|
|
| |
| if report.buy_tax_pct > 50: |
| risk_score += 30 |
| risk_flags.append(f"EXTREME_BUY_TAX_{report.buy_tax_pct}%") |
| elif report.buy_tax_pct > 10: |
| risk_score += 15 |
| risk_flags.append(f"HIGH_BUY_TAX_{report.buy_tax_pct}%") |
| if report.sell_tax_pct > 50: |
| risk_score += 35 |
| risk_flags.append(f"EXTREME_SELL_TAX_{report.sell_tax_pct}%") |
| elif report.sell_tax_pct > 10: |
| risk_score += 20 |
| risk_flags.append(f"HIGH_SELL_TAX_{report.sell_tax_pct}%") |
|
|
| |
| if abs(report.sell_tax_pct - report.buy_tax_pct) > 20: |
| risk_score += 15 |
| risk_flags.append("TAX_DISPARITY") |
|
|
| |
| if report.lp_lock_pct < 1: |
| risk_score += 10 |
| risk_flags.append("NO_LP_LOCK") |
| if not report.ownership_renounced: |
| risk_score += 10 |
| risk_flags.append("OWNER_ACTIVE") |
|
|
| except Exception as e: |
| logger.warning(f"GoPlus analysis failed: {e}") |
|
|
| |
| try: |
| resp = await client.get(f"https://api.dexscreener.com/latest/dex/tokens/{token_address}") |
| if resp.status_code == 200: |
| pairs = resp.json().get("pairs", []) |
| if pairs: |
| |
| txns = pairs[0].get("txns", {}) |
| h24 = txns.get("h24", {}) |
| buys = h24.get("buys", 0) |
| sells = h24.get("sells", 0) |
| report.buy_sell_ratio_24h = buys / max(sells, 1) |
|
|
| factors_checked += 5 |
|
|
| |
| if report.buy_sell_ratio_24h > 10: |
| risk_score += 10 |
| risk_flags.append("ONE_SIDED_BUYING") |
| elif report.buy_sell_ratio_24h < 0.1: |
| risk_score += 15 |
| risk_flags.append("ONE_SIDED_SELLING") |
| except Exception: |
| pass |
|
|
| |
| try: |
| resp = await client.get( |
| "https://public-api.birdeye.so/public/token_security", |
| params={"address": token_address}, |
| ) |
| if resp.status_code == 200: |
| birdeye = resp.json() |
| if birdeye.get("success"): |
| data = birdeye.get("data", {}) |
| if data.get("freezeAuthority"): |
| risk_score += 10 |
| risk_flags.append("FREEZE_AUTHORITY") |
| if data.get("mintAuthority"): |
| risk_score += 5 |
| risk_flags.append("MINT_AUTHORITY") |
| factors_checked += 5 |
| except Exception: |
| pass |
|
|
| |
| report.rug_risk_score = min(100, max(0, risk_score)) |
| report.factors_analyzed = factors_checked |
| report.confidence = min(95, 30 + factors_checked * 0.8) |
|
|
| if report.rug_risk_score >= 80: |
| report.rug_risk_category = "extreme_danger" |
| elif report.rug_risk_score >= 60: |
| report.rug_risk_category = "high_risk" |
| elif report.rug_risk_score >= 35: |
| report.rug_risk_category = "medium_risk" |
| elif report.rug_risk_score >= 15: |
| report.rug_risk_category = "low_risk" |
| else: |
| report.rug_risk_category = "likely_safe" |
|
|
| return { |
| "token": token_address, |
| "chain": chain, |
| "rug_risk_score": report.rug_risk_score, |
| "rug_risk_category": report.rug_risk_category, |
| "risk_flags": risk_flags[:30], |
| "total_flags": len(risk_flags), |
| "factors_analyzed": factors_checked, |
| "confidence": round(report.confidence, 1), |
| "market": { |
| "price_usd": report.current_price_usd, |
| "liquidity_usd": report.liquidity_usd, |
| "volume_24h": report.volume_24h_usd, |
| "market_cap": report.market_cap_usd, |
| "age_hours": round(report.age_hours, 1), |
| "price_change_24h": report.price_change_24h, |
| }, |
| "contract": { |
| "verified": report.is_open_source, |
| "honeypot": report.is_honeypot, |
| "proxy": report.is_proxy, |
| "mintable": report.is_mintable, |
| "buy_tax_pct": report.buy_tax_pct, |
| "sell_tax_pct": report.sell_tax_pct, |
| "can_modify_tax": report.can_modify_tax, |
| "ownership_renounced": report.ownership_renounced, |
| "lp_locked": report.lp_locked, |
| }, |
| "holders": { |
| "count": report.holder_count, |
| "buy_sell_ratio": round(report.buy_sell_ratio_24h, 2), |
| }, |
| "analyzed_at": datetime.now(UTC).isoformat(), |
| } |
|
|