Merge pull request #43 from pselamy/fix/12

feat: add known entity registry for CEX and bridge detection (#12)
This commit is contained in:
Patrick Selamy
2026-01-04 16:43:29 -05:00
committed by GitHub
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
+369
View File
@@ -0,0 +1,369 @@
"""Tests for known entity registry."""
import pytest
from polymarket_insider_tracker.profiler.entities import EntityRegistry
from polymarket_insider_tracker.profiler.entity_data import (
BRIDGE_ADDRESSES,
CEX_ADDRESSES,
DEFI_ADDRESSES,
DEX_ADDRESSES,
TOKEN_ADDRESSES,
EntityType,
get_all_known_entities,
)
# ============================================================================
# EntityType Tests
# ============================================================================
class TestEntityType:
"""Tests for EntityType enum."""
def test_cex_types_exist(self) -> None:
"""Test that CEX entity types are defined."""
assert EntityType.CEX_BINANCE.value == "cex_binance"
assert EntityType.CEX_COINBASE.value == "cex_coinbase"
assert EntityType.CEX_OTHER.value == "cex_other"
def test_bridge_types_exist(self) -> None:
"""Test that bridge entity types are defined."""
assert EntityType.BRIDGE_POLYGON.value == "bridge_polygon"
assert EntityType.BRIDGE_MULTICHAIN.value == "bridge_multichain"
def test_dex_types_exist(self) -> None:
"""Test that DEX entity types are defined."""
assert EntityType.DEX_UNISWAP.value == "dex_uniswap"
assert EntityType.DEX_SUSHISWAP.value == "dex_sushiswap"
def test_token_types_exist(self) -> None:
"""Test that token entity types are defined."""
assert EntityType.TOKEN_USDC.value == "token_usdc"
assert EntityType.TOKEN_WETH.value == "token_weth"
def test_unknown_type(self) -> None:
"""Test unknown entity type."""
assert EntityType.UNKNOWN.value == "unknown"
# ============================================================================
# Entity Data Tests
# ============================================================================
class TestEntityData:
"""Tests for entity data mappings."""
def test_cex_addresses_populated(self) -> None:
"""Test that CEX addresses are populated."""
assert len(CEX_ADDRESSES) > 0
# Check Binance address is present
binance_found = any(
entity == EntityType.CEX_BINANCE for entity in CEX_ADDRESSES.values()
)
assert binance_found
def test_bridge_addresses_populated(self) -> None:
"""Test that bridge addresses are populated."""
assert len(BRIDGE_ADDRESSES) > 0
def test_dex_addresses_populated(self) -> None:
"""Test that DEX addresses are populated."""
assert len(DEX_ADDRESSES) > 0
# Check Uniswap is present
uniswap_found = any(
entity == EntityType.DEX_UNISWAP for entity in DEX_ADDRESSES.values()
)
assert uniswap_found
def test_token_addresses_include_usdc(self) -> None:
"""Test that USDC address is in token addresses."""
usdc_address = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
assert usdc_address in TOKEN_ADDRESSES
assert TOKEN_ADDRESSES[usdc_address] == EntityType.TOKEN_USDC
def test_get_all_known_entities(self) -> None:
"""Test combining all entity mappings."""
all_entities = get_all_known_entities()
total_expected = (
len(CEX_ADDRESSES)
+ len(BRIDGE_ADDRESSES)
+ len(DEX_ADDRESSES)
+ len(TOKEN_ADDRESSES)
+ len(DEFI_ADDRESSES)
)
assert len(all_entities) == total_expected
def test_addresses_are_lowercase(self) -> None:
"""Test that all addresses in get_all_known_entities are lowercase."""
all_entities = get_all_known_entities()
for address in all_entities:
assert address == address.lower()
# ============================================================================
# EntityRegistry Tests
# ============================================================================
class TestEntityRegistryInit:
"""Tests for EntityRegistry initialization."""
def test_default_initialization(self) -> None:
"""Test registry initializes with default entities."""
registry = EntityRegistry()
assert len(registry) > 0
def test_without_defaults(self) -> None:
"""Test registry without default entities."""
registry = EntityRegistry(include_defaults=False)
assert len(registry) == 0
def test_with_custom_entities(self) -> None:
"""Test registry with custom entities."""
custom = {"0x1234": EntityType.CEX_OTHER}
registry = EntityRegistry(custom_entities=custom, include_defaults=False)
assert len(registry) == 1
assert registry.classify("0x1234") == EntityType.CEX_OTHER
def test_custom_entities_override_defaults(self) -> None:
"""Test that custom entities can override defaults."""
# USDC address is in defaults as TOKEN_USDC
usdc_address = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
custom = {usdc_address: EntityType.CONTRACT}
registry = EntityRegistry(custom_entities=custom)
assert registry.classify(usdc_address) == EntityType.CONTRACT
class TestEntityRegistryClassify:
"""Tests for EntityRegistry.classify method."""
@pytest.fixture
def registry(self) -> EntityRegistry:
"""Create a registry for testing."""
return EntityRegistry()
def test_classify_known_cex(self, registry: EntityRegistry) -> None:
"""Test classifying a known CEX address."""
# Binance address
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.classify(binance) == EntityType.CEX_BINANCE
def test_classify_case_insensitive(self, registry: EntityRegistry) -> None:
"""Test that classification is case-insensitive."""
binance_lower = "0x28c6c06298d514db089934071355e5743bf21d60"
binance_mixed = "0x28C6c06298D514db089934071355E5743bf21d60"
assert registry.classify(binance_lower) == registry.classify(binance_mixed)
def test_classify_unknown(self, registry: EntityRegistry) -> None:
"""Test classifying an unknown address."""
unknown = "0x0000000000000000000000000000000000000000"
assert registry.classify(unknown) == EntityType.UNKNOWN
def test_classify_usdc(self, registry: EntityRegistry) -> None:
"""Test classifying USDC token contract."""
usdc = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
assert registry.classify(usdc) == EntityType.TOKEN_USDC
class TestEntityRegistryChecks:
"""Tests for EntityRegistry type check methods."""
@pytest.fixture
def registry(self) -> EntityRegistry:
"""Create a registry for testing."""
return EntityRegistry()
def test_is_known_entity_true(self, registry: EntityRegistry) -> None:
"""Test is_known_entity returns True for known addresses."""
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.is_known_entity(binance) is True
def test_is_known_entity_false(self, registry: EntityRegistry) -> None:
"""Test is_known_entity returns False for unknown addresses."""
unknown = "0x0000000000000000000000000000000000000000"
assert registry.is_known_entity(unknown) is False
def test_is_cex_true(self, registry: EntityRegistry) -> None:
"""Test is_cex returns True for CEX addresses."""
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.is_cex(binance) is True
def test_is_cex_false(self, registry: EntityRegistry) -> None:
"""Test is_cex returns False for non-CEX addresses."""
usdc = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
assert registry.is_cex(usdc) is False
def test_is_bridge_true(self, registry: EntityRegistry) -> None:
"""Test is_bridge returns True for bridge addresses."""
polygon_bridge = "0xa0c68c638235ee32657e8f720a23cec1bfc77c77"
assert registry.is_bridge(polygon_bridge) is True
def test_is_bridge_false(self, registry: EntityRegistry) -> None:
"""Test is_bridge returns False for non-bridge addresses."""
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.is_bridge(binance) is False
def test_is_dex_true(self, registry: EntityRegistry) -> None:
"""Test is_dex returns True for DEX addresses."""
uniswap = "0xe592427a0aece92de3edee1f18e0157c05861564"
assert registry.is_dex(uniswap) is True
def test_is_dex_false(self, registry: EntityRegistry) -> None:
"""Test is_dex returns False for non-DEX addresses."""
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.is_dex(binance) is False
class TestEntityRegistryTerminal:
"""Tests for EntityRegistry.is_terminal method."""
@pytest.fixture
def registry(self) -> EntityRegistry:
"""Create a registry for testing."""
return EntityRegistry()
def test_cex_is_terminal(self, registry: EntityRegistry) -> None:
"""Test that CEX addresses are terminal."""
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.is_terminal(binance) is True
def test_bridge_is_terminal(self, registry: EntityRegistry) -> None:
"""Test that bridge addresses are terminal."""
polygon_bridge = "0xa0c68c638235ee32657e8f720a23cec1bfc77c77"
assert registry.is_terminal(polygon_bridge) is True
def test_dex_is_not_terminal(self, registry: EntityRegistry) -> None:
"""Test that DEX addresses are not terminal."""
uniswap = "0xe592427a0aece92de3edee1f18e0157c05861564"
assert registry.is_terminal(uniswap) is False
def test_token_is_not_terminal(self, registry: EntityRegistry) -> None:
"""Test that token addresses are not terminal."""
usdc = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
assert registry.is_terminal(usdc) is False
def test_unknown_is_not_terminal(self, registry: EntityRegistry) -> None:
"""Test that unknown addresses are not terminal."""
unknown = "0x0000000000000000000000000000000000000000"
assert registry.is_terminal(unknown) is False
class TestEntityRegistryCategory:
"""Tests for EntityRegistry.get_entity_category method."""
@pytest.fixture
def registry(self) -> EntityRegistry:
"""Create a registry for testing."""
return EntityRegistry()
def test_category_cex(self, registry: EntityRegistry) -> None:
"""Test CEX category."""
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.get_entity_category(binance) == "cex"
def test_category_bridge(self, registry: EntityRegistry) -> None:
"""Test bridge category."""
polygon_bridge = "0xa0c68c638235ee32657e8f720a23cec1bfc77c77"
assert registry.get_entity_category(polygon_bridge) == "bridge"
def test_category_dex(self, registry: EntityRegistry) -> None:
"""Test DEX category."""
uniswap = "0xe592427a0aece92de3edee1f18e0157c05861564"
assert registry.get_entity_category(uniswap) == "dex"
def test_category_token(self, registry: EntityRegistry) -> None:
"""Test token category."""
usdc = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
assert registry.get_entity_category(usdc) == "token"
def test_category_defi(self, registry: EntityRegistry) -> None:
"""Test DeFi category."""
aave = "0x794a61358d6845594f94dc1db02a252b5b4814ad"
assert registry.get_entity_category(aave) == "defi"
def test_category_unknown(self, registry: EntityRegistry) -> None:
"""Test unknown category."""
unknown = "0x0000000000000000000000000000000000000000"
assert registry.get_entity_category(unknown) == "unknown"
class TestEntityRegistryMutations:
"""Tests for EntityRegistry mutation methods."""
def test_add_entity(self) -> None:
"""Test adding an entity."""
registry = EntityRegistry(include_defaults=False)
registry.add_entity("0x1234", EntityType.CEX_OTHER)
assert registry.classify("0x1234") == EntityType.CEX_OTHER
def test_add_entity_normalizes_address(self) -> None:
"""Test that add_entity normalizes addresses to lowercase."""
registry = EntityRegistry(include_defaults=False)
registry.add_entity("0xABCD", EntityType.CEX_OTHER)
assert registry.classify("0xabcd") == EntityType.CEX_OTHER
def test_remove_entity(self) -> None:
"""Test removing an entity."""
registry = EntityRegistry(include_defaults=False)
registry.add_entity("0x1234", EntityType.CEX_OTHER)
assert registry.remove_entity("0x1234") is True
assert registry.classify("0x1234") == EntityType.UNKNOWN
def test_remove_nonexistent(self) -> None:
"""Test removing a non-existent entity."""
registry = EntityRegistry(include_defaults=False)
assert registry.remove_entity("0x1234") is False
class TestEntityRegistryDunder:
"""Tests for EntityRegistry dunder methods."""
def test_len(self) -> None:
"""Test __len__ returns count of entities."""
registry = EntityRegistry(include_defaults=False)
registry.add_entity("0x1234", EntityType.CEX_OTHER)
registry.add_entity("0x5678", EntityType.DEX_OTHER)
assert len(registry) == 2
def test_contains(self) -> None:
"""Test __contains__ for membership testing."""
registry = EntityRegistry()
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert binance in registry
assert "0x0000000000000000000000000000000000000000" not in registry
class TestEntityRegistryContract:
"""Tests for EntityRegistry.is_contract method."""
@pytest.fixture
def registry(self) -> EntityRegistry:
"""Create a registry for testing."""
return EntityRegistry()
def test_dex_is_contract(self, registry: EntityRegistry) -> None:
"""Test that DEX addresses are contracts."""
uniswap = "0xe592427a0aece92de3edee1f18e0157c05861564"
assert registry.is_contract(uniswap) is True
def test_token_is_contract(self, registry: EntityRegistry) -> None:
"""Test that token addresses are contracts."""
usdc = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
assert registry.is_contract(usdc) is True
def test_defi_is_contract(self, registry: EntityRegistry) -> None:
"""Test that DeFi protocol addresses are contracts."""
aave = "0x794a61358d6845594f94dc1db02a252b5b4814ad"
assert registry.is_contract(aave) is True
def test_cex_is_not_contract(self, registry: EntityRegistry) -> None:
"""Test that CEX addresses are not contracts."""
binance = "0x28c6c06298d514db089934071355e5743bf21d60"
assert registry.is_contract(binance) is False
def test_unknown_is_not_contract(self, registry: EntityRegistry) -> None:
"""Test that unknown addresses are not contracts."""
unknown = "0x0000000000000000000000000000000000000000"
assert registry.is_contract(unknown) is False