| |
| """ |
| Multi-Chain Scam Address Ingestion |
| =================================== |
| Aggregate blockchain scam addresses from multiple sources into RAG known_scams. |
| |
| Sources (address-level, chain-specific): |
| 1. Etherscan phish-hack.csv β 110 phishing/hack addresses (ETH/BSC/Polygon/etc) |
| 2. Etherscan combined_labels.csv β 3,472 scam-labeled addresses across 7 chains |
| 3. Etherscan combined_labels.csv β 46K+ labeled wallets β wallet_profiles collection |
| 4. Solana token-registry JSON β 55 explicitly flagged scam tokens (Solana) |
| 5. Rekt hack database (JSON) β DeFi exploit addresses (ETH/BSC/etc) |
| 6. MetaMask phishing domains β 198K phishing domains (domain-level, batched) |
| |
| Strategy: |
| - ADDRESS-LEVEL data goes 1:1 into known_scams (high value for our scanners) |
| - DOMAIN-LEVEL data gets batched 50/domain per doc (supplementary, lower value) |
| - Wallet profile data goes to wallet_profiles collection (non-scam labels) |
| |
| This replaces the old single-source scripts with a unified approach. |
| """ |
|
|
| import asyncio |
| import csv |
| import hashlib |
| import json |
| import logging |
| import os |
| import sys |
|
|
| import httpx |
|
|
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") |
| logger = logging.getLogger("ingest_multi_chain_scams") |
|
|
| DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") |
| RAG_API = "http://localhost:8000/api/v1/rag/ingest" |
|
|
| SCAM_LABEL_KEYWORDS = [ |
| "phish", |
| "hack", |
| "exploit", |
| "scam", |
| "drain", |
| "attack", |
| "heist", |
| "malicious", |
| "fraud", |
| "rug", |
| "compromis", |
| "whale-alert", |
| ] |
|
|
|
|
| def make_doc_id(collection: str, key: str) -> str: |
| return hashlib.sha256(f"{collection}:{key.lower()}".encode()).hexdigest()[:16] |
|
|
|
|
| async def ingest_doc(collection: str, content: str, metadata: dict) -> bool: |
| try: |
| async with httpx.AsyncClient(timeout=30) as client: |
| resp = await client.post( |
| RAG_API, |
| json={ |
| "collection": collection, |
| "content": content, |
| "metadata": metadata, |
| }, |
| ) |
| return resp.status_code == 200 |
| except Exception as e: |
| logger.debug(f"Ingest error: {e}") |
| return False |
|
|
|
|
| |
|
|
|
|
| async def ingest_etherscan_phish_hack() -> dict[str, int]: |
| csv_path = os.path.join(DATA_DIR, "etherscan_phish_hack.csv") |
| if not os.path.exists(csv_path): |
| logger.warning(f"Missing: {csv_path}") |
| return {"ingested": 0, "errors": 0} |
|
|
| ingested = 0 |
| errors = 0 |
| with open(csv_path, newline="", encoding="utf-8") as f: |
| rows = list(csv.DictReader(f)) |
|
|
| logger.info(f"Etherscan phish/hack: {len(rows)} addresses") |
| for i, row in enumerate(rows): |
| address = row["address"].strip() |
| if not address: |
| continue |
| name_tag = row.get("name_tag", "").strip() |
| label_type = row.get("label_type", "phish-hack").strip() |
| chain = row.get("chain", "Ethereum").strip() |
| balance = row.get("balance", "").strip() |
| txn_count = row.get("txn_count", "").strip() |
|
|
| doc_id = make_doc_id("known_scams", f"eth_phish:{address}:{chain}") |
| content = ( |
| f"Etherscan {label_type} label: {address} on {chain}. " |
| f"Tag: {name_tag}. Balance: {balance}. Txns: {txn_count}." |
| ) |
| metadata = { |
| "address": address.lower(), |
| "label_type": label_type, |
| "name_tag": name_tag, |
| "chain": chain.lower(), |
| "balance": balance, |
| "txn_count": txn_count, |
| "source": "etherscan_phish_hack", |
| "severity": "critical" if "phish" in label_type.lower() else "high", |
| "doc_id": doc_id, |
| } |
|
|
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| else: |
| errors += 1 |
|
|
| if (i + 1) % 20 == 0: |
| logger.info(f" Phish/hack: {i + 1}/{len(rows)} | ingested: {ingested}") |
|
|
| return {"ingested": ingested, "errors": errors} |
|
|
|
|
| |
|
|
|
|
| async def ingest_etherscan_combined() -> dict[str, int]: |
| csv_path = os.path.join(DATA_DIR, "etherscan_combined_labels.csv") |
| if not os.path.exists(csv_path): |
| logger.warning(f"Missing: {csv_path}") |
| return {"known_scams": 0, "wallet_profiles": 0, "errors": 0} |
|
|
| stats = {"known_scams": 0, "wallet_profiles": 0, "errors": 0} |
| with open(csv_path, newline="", encoding="utf-8") as f: |
| rows = list(csv.DictReader(f)) |
|
|
| scam_rows = [r for r in rows if any(kw in r.get("label", "").lower() for kw in SCAM_LABEL_KEYWORDS)] |
| wallet_rows = [r for r in rows if not any(kw in r.get("label", "").lower() for kw in SCAM_LABEL_KEYWORDS)] |
|
|
| logger.info( |
| f"Etherscan combined: {len(rows)} total β {len(scam_rows)} scam β known_scams, {len(wallet_rows)} β wallet_profiles" |
| ) |
|
|
| for i, row in enumerate(rows): |
| address = row["address"].strip() |
| name = row.get("name", "").strip() |
| label = row.get("label", "").strip() |
| chain = row.get("chain", "Ethereum").strip() |
| if not address or not label: |
| continue |
|
|
| is_scam = any(kw in label.lower() for kw in SCAM_LABEL_KEYWORDS) |
|
|
| if is_scam: |
| collection = "known_scams" |
| doc_id = make_doc_id("known_scams", f"eth_combined:{address}:{chain}") |
| content = ( |
| f"Etherscan scam label: {address} ({name}) on {chain}. " |
| f"Category: {label}. Flagged as scam/phishing/exploit address." |
| ) |
| metadata = { |
| "address": address.lower(), |
| "label": label, |
| "name": name, |
| "chain": chain.lower(), |
| "source": "etherscan_combined", |
| "severity": "high", |
| "label_type": "scam", |
| "doc_id": doc_id, |
| } |
| else: |
| collection = "wallet_profiles" |
| doc_id = make_doc_id("wallet_profiles", f"eth_combined:{address}:{chain}") |
| content = f"Etherscan labeled address: {address} ({name}) on {chain}. Category: {label}." |
| metadata = { |
| "address": address.lower(), |
| "label": label, |
| "name": name, |
| "chain": chain.lower(), |
| "source": "etherscan_combined", |
| "label_type": "profile", |
| "doc_id": doc_id, |
| } |
|
|
| if await ingest_doc(collection, content, metadata): |
| stats[collection] += 1 |
| else: |
| stats["errors"] += 1 |
|
|
| if (i + 1) % 1000 == 0: |
| logger.info( |
| f" Combined: {i + 1}/{len(rows)} | scams: {stats['known_scams']}, profiles: {stats['wallet_profiles']}, errors: {stats['errors']}" |
| ) |
| if (i + 1) % 500 == 0: |
| await asyncio.sleep(0) |
|
|
| return stats |
|
|
|
|
| |
|
|
|
|
| async def ingest_rekt_hacks() -> dict[str, int]: |
| json_path = os.path.join(DATA_DIR, "rekt_hacks.json") |
| if not os.path.exists(json_path): |
| |
| json_path = os.path.join(DATA_DIR, "rekt_hacks_raw.json") |
| if not os.path.exists(json_path): |
| logger.warning("Missing rekt_hacks JSON") |
| return {"ingested": 0, "errors": 0} |
|
|
| with open(json_path) as f: |
| hacks = json.load(f) |
|
|
| if isinstance(hacks, dict): |
| hacks = hacks.get("hacks", hacks.get("data", [])) |
| if not isinstance(hacks, list): |
| logger.warning(f"Unexpected rekt format: {type(hacks)}") |
| return {"ingested": 0, "errors": 0} |
|
|
| logger.info(f"Rekt hacks: {len(hacks)} entries") |
| ingested = 0 |
| errors = 0 |
|
|
| for i, hack in enumerate(hacks): |
| if not isinstance(hack, dict): |
| continue |
| name = hack.get("project", hack.get("name", "")) |
| chain = hack.get("chain", "ethereum") |
| attacker = hack.get("attacker_address", hack.get("attacker", "")) |
| contract = hack.get("contract_address", hack.get("contract", "")) |
| amount = hack.get("amount", hack.get("funds_lost", "")) |
| attack_type = hack.get("attack_type", hack.get("type", "")) |
| description = hack.get("description", "") |
| date = hack.get("date", "") |
|
|
| |
| if attacker and len(str(attacker)) > 10: |
| doc_id = make_doc_id("known_scams", f"rekt_attacker:{attacker}") |
| content = ( |
| f"DeFi hack attacker address: {attacker} on {chain}. " |
| f"Project: {name}. Loss: ${amount}. Type: {attack_type}. " |
| f"Date: {date}. {description[:200]}" |
| ) |
| metadata = { |
| "address": str(attacker).lower(), |
| "chain": chain.lower(), |
| "source": "rekt_hacks", |
| "name": name, |
| "attack_type": attack_type, |
| "amount": str(amount), |
| "severity": "critical", |
| "doc_id": doc_id, |
| } |
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| else: |
| errors += 1 |
|
|
| |
| if contract and len(str(contract)) > 10 and str(contract) != str(attacker): |
| doc_id = make_doc_id("known_scams", f"rekt_contract:{contract}") |
| content = ( |
| f"DeFi hack exploited contract: {contract} on {chain}. " |
| f"Project: {name}. Loss: ${amount}. Type: {attack_type}. " |
| f"Date: {date}. {description[:200]}" |
| ) |
| metadata = { |
| "address": str(contract).lower(), |
| "chain": chain.lower(), |
| "source": "rekt_hacks", |
| "name": name, |
| "attack_type": attack_type, |
| "amount": str(amount), |
| "severity": "high", |
| "doc_id": doc_id, |
| } |
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| else: |
| errors += 1 |
|
|
| if (i + 1) % 50 == 0: |
| logger.info(f" Rekt: {i + 1}/{len(hacks)} | ingested: {ingested}") |
|
|
| return {"ingested": ingested, "errors": errors} |
|
|
|
|
| |
|
|
|
|
| async def ingest_solana_flagged_tokens() -> dict[str, int]: |
| """Download Solana token list and ingest explicitly flagged scam tokens.""" |
| token_list_url = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json" |
|
|
| try: |
| async with httpx.AsyncClient(timeout=60) as client: |
| resp = await client.get(token_list_url) |
| if resp.status_code != 200: |
| logger.error(f"Solana token list fetch failed: {resp.status_code}") |
| return {"ingested": 0, "errors": 0} |
| data = resp.json() |
| except Exception as e: |
| logger.error(f"Solana token list error: {e}") |
| return {"ingested": 0, "errors": 0} |
|
|
| tokens = data.get("tokens", []) |
| |
| scam_keywords = ["scam", "spam", "phishing", "fake", "fraud", "rug", "exploit"] |
| flagged = [] |
| for t in tokens: |
| name_lower = (t.get("name", "") + " " + t.get("symbol", "")).lower() |
| tags_lower = [str(tag).lower() for tag in t.get("tags", [])] |
| if any(kw in name_lower for kw in scam_keywords) or any(kw in " ".join(tags_lower) for kw in scam_keywords): |
| flagged.append(t) |
|
|
| logger.info(f"Solana token list: {len(tokens)} total, {len(flagged)} flagged as scam/spam") |
| ingested = 0 |
| errors = 0 |
|
|
| for t in flagged: |
| address = t.get("address", "") |
| name = t.get("name", "") |
| symbol = t.get("symbol", "") |
| if not address or len(address) < 20: |
| continue |
|
|
| doc_id = make_doc_id("known_scams", f"sol_flagged:{address}") |
| content = ( |
| f"Solana flagged scam token: {name} ({symbol}). " |
| f"Mint address: {address}. Flagged in Solana token registry as scam/spam/phishing." |
| ) |
| metadata = { |
| "address": address, |
| "chain": "solana", |
| "source": "solana_token_registry", |
| "name": name, |
| "symbol": symbol, |
| "severity": "high", |
| "category": "scam_token", |
| "doc_id": doc_id, |
| } |
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| else: |
| errors += 1 |
|
|
| return {"ingested": ingested, "errors": errors} |
|
|
|
|
| |
|
|
|
|
| async def ingest_metamask_domains() -> dict[str, int]: |
| """Ingest MetaMask phishing domain blacklist in batches of 50.""" |
| config_url = "https://raw.githubusercontent.com/MetaMask/eth-phishing-detect/master/src/config.json" |
|
|
| try: |
| async with httpx.AsyncClient(timeout=60) as client: |
| resp = await client.get(config_url) |
| if resp.status_code != 200: |
| logger.error(f"MetaMask list fetch failed: {resp.status_code}") |
| return {"ingested": 0, "errors": 0} |
| data = resp.json() |
| except Exception as e: |
| logger.error(f"MetaMask list error: {e}") |
| return {"ingested": 0, "errors": 0} |
|
|
| blacklist = data.get("blacklist", []) |
| logger.info(f"MetaMask blacklist: {len(blacklist)} domains (batching 50/doc)") |
|
|
| batch_size = 50 |
| total_batches = (len(blacklist) + batch_size - 1) // batch_size |
| ingested = 0 |
| errors = 0 |
|
|
| for batch_idx in range(total_batches): |
| batch = blacklist[batch_idx * batch_size : (batch_idx + 1) * batch_size] |
| domain_list = "\n".join(batch) |
| doc_id = make_doc_id("known_scams", f"mm_batch:{batch_idx}") |
| content = ( |
| f"MetaMask Confirmed Phishing Domains (Batch {batch_idx + 1}/{total_batches}):\n" |
| f"{domain_list}\n" |
| f"Source: MetaMask eth-phishing-detect. Confirmed phishing/scam websites targeting crypto users." |
| ) |
| metadata = { |
| "source": "metamask_phishing_detect", |
| "batch_index": batch_idx, |
| "total_batches": total_batches, |
| "domain_count": len(batch), |
| "category": "phishing_domain", |
| "severity": "medium", |
| "doc_id": doc_id, |
| } |
|
|
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| else: |
| errors += 1 |
|
|
| if (batch_idx + 1) % 100 == 0: |
| logger.info(f" MetaMask: {batch_idx + 1}/{total_batches} batches | ingested: {ingested}") |
| await asyncio.sleep(0.05) |
|
|
| return {"ingested": ingested, "errors": errors} |
|
|
|
|
| |
|
|
|
|
| async def ingest_scamsniffer_github() -> dict[str, int]: |
| """Ingest ScamSniffer GitHub scam-database repo. |
| |
| Contains blacklists of phishing domains and malicious wallet addresses. |
| 7-day delay vs real-time but excellent for historical pattern analysis. |
| """ |
| base_url = "https://raw.githubusercontent.com/scamsniffer/scam-database/main" |
| files_to_fetch = [ |
| "blacklist/domains.json", |
| "blacklist/addresses.json", |
| "blacklist/urls.json", |
| ] |
|
|
| ingested = 0 |
| errors = 0 |
| total_domains = 0 |
| total_addresses = 0 |
|
|
| for file_path in files_to_fetch: |
| url = f"{base_url}/{file_path}" |
| try: |
| async with httpx.AsyncClient(timeout=60) as client: |
| resp = await client.get(url) |
| if resp.status_code != 200: |
| logger.warning(f"ScamSniffer {file_path}: HTTP {resp.status_code}") |
| continue |
| data = resp.json() |
| except Exception as e: |
| logger.warning(f"ScamSniffer {file_path} error: {e}") |
| continue |
|
|
| if not isinstance(data, list): |
| logger.warning(f"ScamSniffer {file_path}: unexpected format {type(data)}") |
| continue |
|
|
| item_type = file_path.split("/")[-1].replace(".json", "") |
| logger.info(f"ScamSniffer {item_type}: {len(data)} entries") |
|
|
| |
| batch_size = 50 |
| total_batches = (len(data) + batch_size - 1) // batch_size |
|
|
| for batch_idx in range(total_batches): |
| batch = data[batch_idx * batch_size : (batch_idx + 1) * batch_size] |
|
|
| if item_type == "domains": |
| content = ( |
| f"ScamSniffer Confirmed Phishing Domains (Batch {batch_idx + 1}/{total_batches}):\n" |
| f"{chr(10).join(batch)}\n" |
| f"Source: ScamSniffer scam-database GitHub. 7-day delayed but verified." |
| ) |
| metadata = { |
| "source": "scamsniffer_github", |
| "item_type": "domain", |
| "batch_index": batch_idx, |
| "total_batches": total_batches, |
| "item_count": len(batch), |
| "category": "phishing_domain", |
| "severity": "high", |
| } |
| total_domains += len(batch) |
| elif item_type == "addresses": |
| |
| addr_list = [] |
| for item in batch: |
| if isinstance(item, dict): |
| addr = item.get("address", item.get("addr", "")) |
| chain = item.get("chain", "unknown") |
| label = item.get("label", "") |
| if addr: |
| addr_list.append(f"{addr} ({chain}) {label}") |
| elif isinstance(item, str): |
| addr_list.append(item) |
|
|
| content = ( |
| f"ScamSniffer Malicious Addresses (Batch {batch_idx + 1}/{total_batches}):\n" |
| f"{chr(10).join(addr_list)}\n" |
| f"Source: ScamSniffer scam-database GitHub. Wallet drainer and phishing addresses." |
| ) |
| metadata = { |
| "source": "scamsniffer_github", |
| "item_type": "address", |
| "batch_index": batch_idx, |
| "total_batches": total_batches, |
| "item_count": len(batch), |
| "category": "malicious_address", |
| "severity": "critical", |
| } |
| total_addresses += len(batch) |
| else: |
| content = ( |
| f"ScamSniffer Malicious URLs (Batch {batch_idx + 1}/{total_batches}):\n" |
| f"{chr(10).join(batch)}\n" |
| f"Source: ScamSniffer scam-database GitHub." |
| ) |
| metadata = { |
| "source": "scamsniffer_github", |
| "item_type": "url", |
| "batch_index": batch_idx, |
| "total_batches": total_batches, |
| "item_count": len(batch), |
| "category": "malicious_url", |
| "severity": "high", |
| } |
|
|
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| else: |
| errors += 1 |
|
|
| if (batch_idx + 1) % 50 == 0: |
| logger.info( |
| f" ScamSniffer {item_type}: {batch_idx + 1}/{total_batches} batches | ingested: {ingested}" |
| ) |
| await asyncio.sleep(0.05) |
|
|
| logger.info(f"ScamSniffer total: {total_domains} domains, {total_addresses} addresses") |
| return { |
| "ingested": ingested, |
| "errors": errors, |
| "domains": total_domains, |
| "addresses": total_addresses, |
| } |
|
|
|
|
| |
|
|
|
|
| async def ingest_phishdestroy() -> dict[str, int]: |
| """Ingest PhishDestroy destroylist β 140K curated crypto/web3 scam domains. |
| |
| MIT licensed. Available as domains.txt, domains.csv, active_domains.json. |
| We fetch the plain text list and batch it. |
| """ |
| urls = { |
| "domains_txt": "https://raw.githubusercontent.com/PhishDestroy/destroylist/main/domains.txt", |
| "active_json": "https://raw.githubusercontent.com/PhishDestroy/destroylist/main/active_domains.json", |
| } |
|
|
| ingested = 0 |
| errors = 0 |
| total_domains = 0 |
|
|
| |
| try: |
| async with httpx.AsyncClient(timeout=60) as client: |
| resp = await client.get(urls["active_json"]) |
| if resp.status_code == 200: |
| data = resp.json() |
| if isinstance(data, list): |
| domains = data |
| elif isinstance(data, dict): |
| domains = list(data.keys()) |
| else: |
| domains = [] |
|
|
| logger.info(f"PhishDestroy active_domains.json: {len(domains)} domains") |
|
|
| batch_size = 50 |
| total_batches = (len(domains) + batch_size - 1) // batch_size |
|
|
| for batch_idx in range(total_batches): |
| batch = domains[batch_idx * batch_size : (batch_idx + 1) * batch_size] |
| domain_list = "\n".join(batch) |
| doc_id = make_doc_id("known_scams", f"pd_active:{batch_idx}") |
| content = ( |
| f"PhishDestroy Active Malicious Domains (Batch {batch_idx + 1}/{total_batches}):\n" |
| f"{domain_list}\n" |
| f"Source: PhishDestroy destroylist (MIT License). DNS-validated active crypto/web3 scam domains." |
| ) |
| metadata = { |
| "source": "phishdestroy_destroylist", |
| "batch_index": batch_idx, |
| "total_batches": total_batches, |
| "domain_count": len(batch), |
| "category": "phishing_domain", |
| "severity": "high", |
| "license": "MIT", |
| "doc_id": doc_id, |
| } |
|
|
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| total_domains += len(batch) |
| else: |
| errors += 1 |
|
|
| if (batch_idx + 1) % 100 == 0: |
| logger.info(f" PhishDestroy: {batch_idx + 1}/{total_batches} | ingested: {ingested}") |
| await asyncio.sleep(0.05) |
| except Exception as e: |
| logger.warning(f"PhishDestroy active_domains.json failed: {e}") |
|
|
| |
| if total_domains == 0: |
| try: |
| async with httpx.AsyncClient(timeout=60) as client: |
| resp = await client.get(urls["domains_txt"]) |
| if resp.status_code == 200: |
| domains = [d.strip() for d in resp.text.splitlines() if d.strip()] |
| logger.info(f"PhishDestroy domains.txt: {len(domains)} domains") |
|
|
| batch_size = 50 |
| total_batches = (len(domains) + batch_size - 1) // batch_size |
|
|
| for batch_idx in range(total_batches): |
| batch = domains[batch_idx * batch_size : (batch_idx + 1) * batch_size] |
| domain_list = "\n".join(batch) |
| doc_id = make_doc_id("known_scams", f"pd_txt:{batch_idx}") |
| content = ( |
| f"PhishDestroy Malicious Domains (Batch {batch_idx + 1}/{total_batches}):\n" |
| f"{domain_list}\n" |
| f"Source: PhishDestroy destroylist (MIT License)." |
| ) |
| metadata = { |
| "source": "phishdestroy_destroylist", |
| "batch_index": batch_idx, |
| "total_batches": total_batches, |
| "domain_count": len(batch), |
| "category": "phishing_domain", |
| "severity": "high", |
| "license": "MIT", |
| "doc_id": doc_id, |
| } |
|
|
| if await ingest_doc("known_scams", content, metadata): |
| ingested += 1 |
| total_domains += len(batch) |
| else: |
| errors += 1 |
|
|
| if (batch_idx + 1) % 100 == 0: |
| logger.info(f" PhishDestroy: {batch_idx + 1}/{total_batches} | ingested: {ingested}") |
| await asyncio.sleep(0.05) |
| except Exception as e: |
| logger.warning(f"PhishDestroy domains.txt failed: {e}") |
|
|
| logger.info(f"PhishDestroy total: {total_domains} domains ingested") |
| return {"ingested": ingested, "errors": errors, "domains": total_domains} |
|
|
|
|
| |
|
|
|
|
| async def main(): |
| total_stats = { |
| "known_scams_added": 0, |
| "wallet_profiles_added": 0, |
| "errors": 0, |
| "sources": {}, |
| } |
|
|
| |
| logger.info("=" * 60) |
| logger.info("SOURCE 1: Etherscan Phish/Hack Addresses") |
| logger.info("=" * 60) |
| r = await ingest_etherscan_phish_hack() |
| total_stats["known_scams_added"] += r["ingested"] |
| total_stats["errors"] += r["errors"] |
| total_stats["sources"]["etherscan_phish_hack"] = r |
| logger.info(f" Result: {r}") |
|
|
| |
| logger.info("=" * 60) |
| logger.info("SOURCE 2: Etherscan Combined Labels") |
| logger.info("=" * 60) |
| r = await ingest_etherscan_combined() |
| total_stats["known_scams_added"] += r["known_scams"] |
| total_stats["wallet_profiles_added"] += r["wallet_profiles"] |
| total_stats["errors"] += r["errors"] |
| total_stats["sources"]["etherscan_combined"] = r |
| logger.info(f" Result: {r}") |
|
|
| |
| logger.info("=" * 60) |
| logger.info("SOURCE 3: Rekt DeFi Hack Database") |
| logger.info("=" * 60) |
| r = await ingest_rekt_hacks() |
| total_stats["known_scams_added"] += r["ingested"] |
| total_stats["errors"] += r["errors"] |
| total_stats["sources"]["rekt_hacks"] = r |
| logger.info(f" Result: {r}") |
|
|
| |
| logger.info("=" * 60) |
| logger.info("SOURCE 4: Solana Token Registry Flagged Scams") |
| logger.info("=" * 60) |
| r = await ingest_solana_flagged_tokens() |
| total_stats["known_scams_added"] += r["ingested"] |
| total_stats["errors"] += r["errors"] |
| total_stats["sources"]["solana_flagged"] = r |
| logger.info(f" Result: {r}") |
|
|
| |
| logger.info("=" * 60) |
| logger.info("SOURCE 5: MetaMask Phishing Domain Blacklist") |
| logger.info("=" * 60) |
| r = await ingest_metamask_domains() |
| total_stats["known_scams_added"] += r["ingested"] |
| total_stats["errors"] += r["errors"] |
| total_stats["sources"]["metamask_domains"] = r |
| logger.info(f" Result: {r}") |
|
|
| |
| logger.info("=" * 60) |
| logger.info("SOURCE 6: ScamSniffer GitHub Scam Database") |
| logger.info("=" * 60) |
| r = await ingest_scamsniffer_github() |
| total_stats["known_scams_added"] += r["ingested"] |
| total_stats["errors"] += r["errors"] |
| total_stats["sources"]["scamsniffer_github"] = r |
| logger.info(f" Result: {r}") |
|
|
| |
| logger.info("=" * 60) |
| logger.info("SOURCE 7: PhishDestroy Destroylist") |
| logger.info("=" * 60) |
| r = await ingest_phishdestroy() |
| total_stats["known_scams_added"] += r["ingested"] |
| total_stats["errors"] += r["errors"] |
| total_stats["sources"]["phishdestroy"] = r |
| logger.info(f" Result: {r}") |
|
|
| logger.info("=" * 60) |
| logger.info(f"FINAL SUMMARY: {total_stats}") |
| logger.info("=" * 60) |
|
|
|
|
| if __name__ == "__main__": |
| asyncio.run(main()) |
|
|