feat: add known entity registry for CEX and bridge detection (#12)

- Add EntityType enum for classifying blockchain entities
- Add entity_data.py with Polygon CEX hot wallets, bridges, DEX contracts
- Add EntityRegistry class with classify/is_terminal methods
- Support custom entity additions and overrides
- Include 49 comprehensive unit tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Selamy
2026-01-04 16:43:03 -05:00
co-authored by Claude Opus 4.5
parent 4111d1ee00
commit 0c4d2bebea
4 changed files with 811 additions and 0 deletions
@@ -9,6 +9,12 @@ from polymarket_insider_tracker.profiler.chain import (
RateLimitError,
RPCError,
)
from polymarket_insider_tracker.profiler.entities import (
EntityRegistry,
)
from polymarket_insider_tracker.profiler.entity_data import (
EntityType,
)
from polymarket_insider_tracker.profiler.models import (
Transaction,
WalletInfo,
@@ -18,6 +24,9 @@ from polymarket_insider_tracker.profiler.models import (
__all__ = [
# Analyzer
"WalletAnalyzer",
# Entity Registry
"EntityRegistry",
"EntityType",
# Polygon Client
"PolygonClient",
"PolygonClientError",
@@ -0,0 +1,279 @@
"""Known entity registry for blockchain address classification.
This module provides the EntityRegistry class for classifying blockchain
addresses as known entities (CEX hot wallets, bridges, DEX contracts, etc.)
to support funding chain analysis and suspiciousness scoring.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from polymarket_insider_tracker.profiler.entity_data import (
EntityType,
get_all_known_entities,
)
if TYPE_CHECKING:
pass
logger = logging.getLogger(__name__)
class EntityRegistry:
"""Registry of known blockchain entities for address classification.
The registry contains mappings from blockchain addresses to known entity
types (CEX, bridges, DEX, etc.). This is used to:
- Terminate funding chain traces at known entities
- Classify funding sources for suspiciousness scoring
- Identify retail vs sophisticated wallet patterns
Attributes:
_entities: Internal mapping of address to entity type.
"""
# Entity types that should terminate funding chain traces
TERMINAL_ENTITY_TYPES = frozenset(
[
EntityType.CEX_BINANCE,
EntityType.CEX_COINBASE,
EntityType.CEX_KRAKEN,
EntityType.CEX_OKX,
EntityType.CEX_KUCOIN,
EntityType.CEX_BYBIT,
EntityType.CEX_CRYPTO_COM,
EntityType.CEX_OTHER,
EntityType.BRIDGE_POLYGON,
EntityType.BRIDGE_MULTICHAIN,
EntityType.BRIDGE_STARGATE,
EntityType.BRIDGE_HOP,
EntityType.BRIDGE_OTHER,
]
)
# Entity types that indicate CEX origin
CEX_ENTITY_TYPES = frozenset(
[
EntityType.CEX_BINANCE,
EntityType.CEX_COINBASE,
EntityType.CEX_KRAKEN,
EntityType.CEX_OKX,
EntityType.CEX_KUCOIN,
EntityType.CEX_BYBIT,
EntityType.CEX_CRYPTO_COM,
EntityType.CEX_OTHER,
]
)
# Entity types that indicate bridge origin
BRIDGE_ENTITY_TYPES = frozenset(
[
EntityType.BRIDGE_POLYGON,
EntityType.BRIDGE_MULTICHAIN,
EntityType.BRIDGE_STARGATE,
EntityType.BRIDGE_HOP,
EntityType.BRIDGE_OTHER,
]
)
# Entity types for DEX contracts
DEX_ENTITY_TYPES = frozenset(
[
EntityType.DEX_UNISWAP,
EntityType.DEX_SUSHISWAP,
EntityType.DEX_QUICKSWAP,
EntityType.DEX_1INCH,
EntityType.DEX_OTHER,
]
)
def __init__(
self,
custom_entities: dict[str, EntityType] | None = None,
*,
include_defaults: bool = True,
) -> None:
"""Initialize the entity registry.
Args:
custom_entities: Additional custom entity mappings to include.
include_defaults: Whether to include default known entities.
"""
self._entities: dict[str, EntityType] = {}
if include_defaults:
self._entities.update(get_all_known_entities())
if custom_entities:
# Add custom entities (normalized to lowercase)
for address, entity_type in custom_entities.items():
self._entities[address.lower()] = entity_type
logger.info(f"EntityRegistry initialized with {len(self._entities)} known entities")
def classify(self, address: str) -> EntityType:
"""Classify an address by its entity type.
Args:
address: The blockchain address to classify.
Returns:
The EntityType for the address, or UNKNOWN if not in registry.
"""
return self._entities.get(address.lower(), EntityType.UNKNOWN)
def is_known_entity(self, address: str) -> bool:
"""Check if an address is a known entity.
Args:
address: The blockchain address to check.
Returns:
True if the address is in the registry, False otherwise.
"""
return address.lower() in self._entities
def is_cex(self, address: str) -> bool:
"""Check if an address is a known CEX hot wallet.
Args:
address: The blockchain address to check.
Returns:
True if the address is a CEX hot wallet.
"""
return self.classify(address) in self.CEX_ENTITY_TYPES
def is_bridge(self, address: str) -> bool:
"""Check if an address is a known bridge contract.
Args:
address: The blockchain address to check.
Returns:
True if the address is a bridge contract.
"""
return self.classify(address) in self.BRIDGE_ENTITY_TYPES
def is_dex(self, address: str) -> bool:
"""Check if an address is a known DEX contract.
Args:
address: The blockchain address to check.
Returns:
True if the address is a DEX contract.
"""
return self.classify(address) in self.DEX_ENTITY_TYPES
def is_terminal(self, address: str) -> bool:
"""Check if an address should terminate a funding chain trace.
Terminal entities are those where tracing further back provides
diminishing returns (CEX, bridges). These indicate the practical
origin of funds from the perspective of on-chain analysis.
Args:
address: The blockchain address to check.
Returns:
True if the address should terminate a funding trace.
"""
return self.classify(address) in self.TERMINAL_ENTITY_TYPES
def is_contract(self, address: str) -> bool:
"""Check if an address is a known smart contract.
This includes DEX routers, token contracts, and DeFi protocols.
Args:
address: The blockchain address to check.
Returns:
True if the address is a known smart contract.
"""
entity_type = self.classify(address)
contract_types = (
self.DEX_ENTITY_TYPES
| {
EntityType.TOKEN_USDC,
EntityType.TOKEN_USDT,
EntityType.TOKEN_WETH,
EntityType.TOKEN_WMATIC,
EntityType.DEFI_AAVE,
EntityType.DEFI_COMPOUND,
EntityType.DEFI_OTHER,
EntityType.CONTRACT,
}
)
return entity_type in contract_types
def get_entity_category(self, address: str) -> str:
"""Get a human-readable category for an address.
Args:
address: The blockchain address to categorize.
Returns:
Category string: "cex", "bridge", "dex", "token", "defi", "contract", or "unknown".
"""
entity_type = self.classify(address)
if entity_type in self.CEX_ENTITY_TYPES:
return "cex"
if entity_type in self.BRIDGE_ENTITY_TYPES:
return "bridge"
if entity_type in self.DEX_ENTITY_TYPES:
return "dex"
if entity_type in {
EntityType.TOKEN_USDC,
EntityType.TOKEN_USDT,
EntityType.TOKEN_WETH,
EntityType.TOKEN_WMATIC,
}:
return "token"
if entity_type in {
EntityType.DEFI_AAVE,
EntityType.DEFI_COMPOUND,
EntityType.DEFI_OTHER,
}:
return "defi"
if entity_type == EntityType.CONTRACT:
return "contract"
return "unknown"
def add_entity(self, address: str, entity_type: EntityType) -> None:
"""Add or update an entity in the registry.
Args:
address: The blockchain address.
entity_type: The entity type to assign.
"""
self._entities[address.lower()] = entity_type
logger.debug(f"Added entity: {address} -> {entity_type.value}")
def remove_entity(self, address: str) -> bool:
"""Remove an entity from the registry.
Args:
address: The blockchain address to remove.
Returns:
True if the entity was removed, False if not found.
"""
normalized = address.lower()
if normalized in self._entities:
del self._entities[normalized]
return True
return False
def __len__(self) -> int:
"""Return the number of entities in the registry."""
return len(self._entities)
def __contains__(self, address: str) -> bool:
"""Check if an address is in the registry."""
return self.is_known_entity(address)
@@ -0,0 +1,154 @@
"""Known blockchain entity address mappings.
This module contains address-to-entity mappings for known blockchain
entities on Polygon including CEX hot wallets, bridges, and DEX contracts.
Sources:
- Etherscan labels
- Arkham Intelligence
- Official protocol documentation
"""
from __future__ import annotations
from enum import Enum
class EntityType(Enum):
"""Classification of known blockchain entities."""
# Centralized Exchanges
CEX_BINANCE = "cex_binance"
CEX_COINBASE = "cex_coinbase"
CEX_KRAKEN = "cex_kraken"
CEX_OKX = "cex_okx"
CEX_KUCOIN = "cex_kucoin"
CEX_BYBIT = "cex_bybit"
CEX_CRYPTO_COM = "cex_crypto_com"
CEX_OTHER = "cex_other"
# Bridges
BRIDGE_POLYGON = "bridge_polygon"
BRIDGE_MULTICHAIN = "bridge_multichain"
BRIDGE_STARGATE = "bridge_stargate"
BRIDGE_HOP = "bridge_hop"
BRIDGE_OTHER = "bridge_other"
# Decentralized Exchanges
DEX_UNISWAP = "dex_uniswap"
DEX_SUSHISWAP = "dex_sushiswap"
DEX_QUICKSWAP = "dex_quickswap"
DEX_1INCH = "dex_1inch"
DEX_OTHER = "dex_other"
# Token Contracts
TOKEN_USDC = "token_usdc"
TOKEN_USDT = "token_usdt"
TOKEN_WETH = "token_weth"
TOKEN_WMATIC = "token_wmatic"
# Lending/DeFi
DEFI_AAVE = "defi_aave"
DEFI_COMPOUND = "defi_compound"
DEFI_OTHER = "defi_other"
# Other
CONTRACT = "contract"
UNKNOWN = "unknown"
# CEX hot wallet addresses on Polygon
# Sources: Etherscan labels, Arkham Intelligence, public disclosures
CEX_ADDRESSES: dict[str, EntityType] = {
# Binance
"0x28c6c06298d514db089934071355e5743bf21d60": EntityType.CEX_BINANCE,
"0x21a31ee1afc51d94c2efccaa2092ad1028285549": EntityType.CEX_BINANCE,
"0xf89d7b9c864f589bbf53a82105107622b35eaa40": EntityType.CEX_BINANCE,
"0xdfd5293d8e347dfe59e90efd55b2956a1343963d": EntityType.CEX_BINANCE,
# Coinbase
"0x503828976d22510aad0339f595f37cc4e4645c80": EntityType.CEX_COINBASE,
"0x71660c4005ba85c37ccec55d0c4493e66fe775d3": EntityType.CEX_COINBASE,
"0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43": EntityType.CEX_COINBASE,
# Kraken
"0x2910543af39aba0cd09dbb2d50200b3e800a63d2": EntityType.CEX_KRAKEN,
"0x0a869d79a7052c7f1b55a8ebabbea3420f0d1e13": EntityType.CEX_KRAKEN,
# OKX
"0x5041ed759dd4afc3a72b8192c143f72f4724081a": EntityType.CEX_OKX,
"0x6cc5f688a315f3dc28a7781717a9a798a59fda7b": EntityType.CEX_OKX,
# KuCoin
"0xf16e9b0d03470827a95cdfd0cb8a8a3b46969b91": EntityType.CEX_KUCOIN,
"0xd6216fc19db775df9774a6e33526131da7d19a2c": EntityType.CEX_KUCOIN,
# Bybit
"0xf89e6d82be28f5cc97a9e6a94a16a17e5be73e78": EntityType.CEX_BYBIT,
# Crypto.com
"0x6262998ced04146fa42253a5c0af90ca02dfd2a3": EntityType.CEX_CRYPTO_COM,
"0x46340b20830761efd32832a74d7169b29feb9758": EntityType.CEX_CRYPTO_COM,
}
# Bridge contract addresses on Polygon
BRIDGE_ADDRESSES: dict[str, EntityType] = {
# Polygon PoS Bridge (RootChain / Plasma Bridge related)
"0xa0c68c638235ee32657e8f720a23cec1bfc77c77": EntityType.BRIDGE_POLYGON,
"0x401f6c983ea34274ec46f84d70b31c151321188b": EntityType.BRIDGE_POLYGON,
# Multichain (formerly AnySwap)
"0x4f3aff3a747fcade12598081e80c6605a8be192f": EntityType.BRIDGE_MULTICHAIN,
# Stargate
"0x45a01e4e04f14f7a4a6880d0cbaf2c3c1acfbed4": EntityType.BRIDGE_STARGATE,
# Hop Protocol
"0x76b22b8c1079a44f1211b0e72c5d26c5e3b3c3c9": EntityType.BRIDGE_HOP,
}
# DEX router addresses on Polygon
DEX_ADDRESSES: dict[str, EntityType] = {
# Uniswap V3
"0xe592427a0aece92de3edee1f18e0157c05861564": EntityType.DEX_UNISWAP,
"0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45": EntityType.DEX_UNISWAP, # SwapRouter02
# SushiSwap
"0x1b02da8cb0d097eb8d57a175b88c7d8b47997506": EntityType.DEX_SUSHISWAP,
# QuickSwap
"0xa5e0829caced8ffdd4de3c43696c57f7d7a678ff": EntityType.DEX_QUICKSWAP,
# 1inch
"0x1111111254eeb25477b68fb85ed929f73a960582": EntityType.DEX_1INCH,
}
# Token contract addresses on Polygon
TOKEN_ADDRESSES: dict[str, EntityType] = {
# USDC (Bridged)
"0x2791bca1f2de4661ed88a30c99a7a9449aa84174": EntityType.TOKEN_USDC,
# USDC (Native)
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359": EntityType.TOKEN_USDC,
# USDT
"0xc2132d05d31c914a87c6611c10748aeb04b58e8f": EntityType.TOKEN_USDT,
# WETH
"0x7ceb23fd6bc0add59e62ac25578270cff1b9f619": EntityType.TOKEN_WETH,
# WMATIC
"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270": EntityType.TOKEN_WMATIC,
}
# DeFi protocol addresses on Polygon
DEFI_ADDRESSES: dict[str, EntityType] = {
# Aave V3
"0x794a61358d6845594f94dc1db02a252b5b4814ad": EntityType.DEFI_AAVE, # Pool
"0x8145edddf43f50276641b55bd3ad95944510021e": EntityType.DEFI_AAVE, # PoolAddressesProvider
}
def get_all_known_entities() -> dict[str, EntityType]:
"""Get all known entity addresses combined.
Returns:
Dictionary mapping lowercase addresses to their entity types.
"""
all_entities: dict[str, EntityType] = {}
for entities in [
CEX_ADDRESSES,
BRIDGE_ADDRESSES,
DEX_ADDRESSES,
TOKEN_ADDRESSES,
DEFI_ADDRESSES,
]:
for address, entity_type in entities.items():
all_entities[address.lower()] = entity_type
return all_entities