""" SENTINEL - Multi-Chain Registry & Configuration ================================================ Centralized chain definitions, RPC endpoints, explorer APIs, and data source configurations for all 13 supported chains. Chains: solana, ethereum, base, bsc, arbitrum, polygon, avalanche, optimism, fantom, linea, zksync, scroll, mantle """ import os from dataclasses import dataclass, field from enum import Enum class ChainFamily(Enum): """Blockchain family classification.""" SOLANA = "solana" # Solana SPL tokens EVM = "evm" # Ethereum Virtual Machine chains COSMOS = "cosmos" # Not yet supported @dataclass class ChainConfig: """Full configuration for a supported blockchain.""" name: str # Short name (e.g. "ethereum", "base") chain_id: str # Chain ID ("1", "8453", "solana") family: ChainFamily = ChainFamily.EVM display_name: str = "" # Human name ("Ethereum", "Base") # RPC endpoints (primary, fallback, public) rpc_endpoints: list[str] = field(default_factory=list) # Block explorer API explorer_url: str = "" explorer_api_url: str = "" explorer_api_key_env: str = "" # Env var for API key # Native token native_token_symbol: str = "ETH" native_token_decimals: int = 18 # DEX data sources (DexScreener supports all) dexscreener_chain_id: str = "" # Chain ID as DexScreener knows it # Known lockers / factory addresses known_lockers: dict[str, str] = field(default_factory=dict) # CEX hot wallets for this chain cex_hot_wallets: dict[str, list[str]] = field(default_factory=dict) # Block explorer API key env var api_key_env: str = "" def get_explorer_api_key(self) -> str: """Get the block explorer API key from environment.""" return os.getenv(self.api_key_env, "") @property def is_solana(self) -> bool: return self.family == ChainFamily.SOLANA @property def is_evm(self) -> bool: return self.family == ChainFamily.EVM # ─── Chain Registry ──────────────────────────────────────────────── CHAINS: dict[str, ChainConfig] = { "solana": ChainConfig( name="solana", chain_id="solana", family=ChainFamily.SOLANA, display_name="Solana", rpc_endpoints=[ "https://solana-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", "https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}", "https://solana.drpc.org", "https://solana-rpc.publicnode.com", "https://rpc.ankr.com/solana", ], explorer_url="https://solscan.io", explorer_api_url="https://public-api.solscan.io", explorer_api_key_env="SOLSCAN_API_KEY", native_token_symbol="SOL", native_token_decimals=9, dexscreener_chain_id="solana", known_lockers={}, cex_hot_wallets={ "Binance": [ "5tzF4VG5DB9R4PJJZdE3EGX6MHcY7K6uAcAFN7b7zoyF", "DRpbCBMxVnDK7maPM4Gqt5iQJ3U1QorZ3Nz8g7Aeu9p", "9WzDXMPQefAPQgxPaMkr2Fi8nY91fMjJY4kMN7AhN2qh", "QC4kUxtjAy5r1LFf6kPHF7GvNACm7UV2BcGmdv7E4Jj", "E5jRGgPnG4F7tHSyLQyazL3rbc8zP9G1NYJNx2GV5yRT", "8fXs4GQcZGJSJiMxH1L5tNp9R2VkS6wY7Z3A1BqFcD0E", "GJRs4FwHtemZ5ZEKxJ8x4D2gN8qFgXSP1M2wQrLmVk7h", ], "Coinbase": [ "2AQ7xRF2Jq5k2C8RNqiP95jRskQk91nR6Nj4Q5xqG3x7", "9x2YQfGjg1Q7Q7Q1Q1Q1Q1Q1Q1Q1Q1Q1Q1Q1Q1Q1Q1", ], "OKX": [ "5YK5YK5YK5YK5YK5YK5YK5YK5YK5YK5YK5YK5YK5YK5Y", "5uNpdhHDuBoXd7RUAB4SzvqP5vQGrnQJn9f7xVU5RoXL", ], "Bybit": [ "FcnWJ9y2xV6AKP5BrVK3hLmTM82QwpE4qXq7NgTsHDBP", "AC5RDfQFmDS1deWZos921JfqbQJzuyqqM21f7qNeYLkN", ], "Kraken": [ "FWznbcNXWQuHTaweGHAb3k3MMw7gfX7Y9dZH3XPYkbWY", ], "KuCoin": [ "BmFdpraXjbNqTGHeQ8QwPXy7sL9VLsKHvkSEWcYMYxVQ", "H8sMJSCNmYGCSVbY9u6VEU8SG7Qk2rPqFn3JntVnDpTE", ], "Gate.io": [ "u6PJ8DtNzmFZ3rG3TzZioV5EqPbJYExq1En8HVNDjxR", ], "MEXC": [ "ASTy5S1R2dq4WHCpVhBMbb4vLgWJdPBF5kG6zMTZBQsg", ], "Bitget": [ "Aobp2URFvJk4iCBsiJfLkCxQ89rXHMLDR3MvUtJcNYrF", ], "Crypto.com": [ "HyCHiyEqFGt3S1MJCqQyZ2mDn7rFQ8GWLN4Xx6VPoBRk", ], "HTX": [ "GR4NVErnTnR3dYxGpVfPm1K9JVfZwGSmujqBBFLVxRzH", ], "Upbit": [ "6zcmsePHoGpK6WjK7VGaMhhEu1QSGEdfSWd1qzvJJF5N", "3YfFWpQZfC1mJgGht6wWuXLgp6nQsTEtqQtEWQHJzp1d", ], "Bithumb": [ "7BZsKJvQLcKHx8LMuN9M8rqpPmNr4qWGM9R8RcwWcTq1", ], "Bitfinex": [ "FXxGxnmr58cJSiy4drkLeA41td4QtEby9pRZcxPYvPnN", ], "BitMEX": [ "8ZPuSkQC9cETSWpNRuQYJHbZvbKmpPWYyduk8Ns1e9BX", ], "Korbit": [ "DQiuRWA57ZyRsG6LEtQNkFV6JUCa6kTEKNfRvGJLQD4", ], }, api_key_env="", ), "ethereum": ChainConfig( name="ethereum", chain_id="1", family=ChainFamily.EVM, display_name="Ethereum", rpc_endpoints=[ "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", "https://ethereum-rpc.publicnode.com", "https://eth.llamarpc.com", "https://rpc.ankr.com/eth", "https://cloudflare-eth.com", ], explorer_url="https://etherscan.io", explorer_api_url="https://api.etherscan.io/api", explorer_api_key_env="ETHERSCAN_API_KEY", native_token_symbol="ETH", native_token_decimals=18, dexscreener_chain_id="ethereum", known_lockers={ "0x663A5C229c09b049E36dCc11a9B0d4a8c9c3dBC2": "UNCX Network", "0xe2fE530C047f2d85298b07D9333C05737f1435fB": "Team Finance", "0x7ee058420e5937496f5a2096f04caa7721cf70cc": "PinkLock", }, cex_hot_wallets={ "Binance": [ "0x28C6c06298d514Db089934071355E5743bf21d60", "0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8", "0xDFd5293D8e347dFe59E90eFd55b2956a1343963d", "0xF977814e90dA44bFA03b6295A0616a897441aceC", "0x21a31Ee1afC51d94C2eFcCAa2092aD1028285549", "0x4976A4A02f38326660D17bf34b431dC6e2eb2327", "0x56Eddb7aa87536c09CCc2793473599fD21A8b17F", "0x5a52E96BAcdaBb82fd05763E25335261B270Efcb", "0x9696f59E4d72E237BE84fFD425DCaD154Bf96976", "0x61189Da79177950A7272c88c6058B96D4Bcd6bE2", ], "Coinbase": [ "0x503828976D22510aad0201ac7EC88293211D23Da", "0xdD2F458a4b46251B1dB5E2ed56bDDf8DB4Ed7E8e", "0xbf3EEfAC1073b93a2639fB1eD4aB7bC997d8732E", "0x71660c4005BA85C37ccec55d0C4493E3Fe753dBf", "0xA9D1e08C7793af67e9d92fe308d5697FB81d3E43", "0x3CD751E6b0078Be39333c1fbFDeC3a2b87Ab9C82", ], "OKX": [ "0x6cC5F688a315f3dC28A7781717a9A798a59fDA7b", "0x236F9F97e0E62388479bf9E5BA4889E46B0273c3", "0x8a612aB4cC1CBf5b37Bd6a0E8A0AcE5AF0e5B00F", ], "Bybit": [ "0xf89d7b9c864f589bbF53a82105107622B35EaA40", "0x1Db92e2EeBC8E0c075a02BeA49a2935BcD2dFC73", ], "Kraken": [ "0x2910543Af39abA0Cd09dBb2D50200b3E800A63D2", "0x0A869d79a7052C7f1b55a8EbAbbEa3420f0D1E13", "0xE853c56864A2ebe4576a807D26Fdc4A0adA51919", "0x267be1C1D684F78cb4F6a176C4911b741E4Ffdc0", ], "KuCoin": [ "0x2B5634C42055806a59e9107ED44D43c426E58258", "0x679040D7223FdE9363AA2cF68B4C8A7394cc83C5", ], "Gate.io": [ "0x0D0707963952f2fBA59dD06f2b425ace40b492Fe", "0x7793cD85c11A924478cF358d2b5DeC8C4f41acB0", ], "Bitfinex": [ "0x876EabF441B2EE5B5b0554Fd502a8E0600950cFa", "0x1151314c646Ce4E0eFD76d1aF4760aE66a2Fe30e", "0x742d35Cc6634C0532925a3b844Bc9e7596bBDEdD", ], "Bitget": [ "0x0639556F03714A74a5fEEaF5736a4A64f70Df206", "0x97dE9E01a1c95d3D09D9b29333dF4e5A50A1B61d", ], "MEXC": [ "0x75e89d5979E4f6Fba9F97c104c2F0AFB3F1dcB88", "0x3CC936b795A188F0e246cBB2D74C5Bd190aeCF18", ], "HTX": [ "0xeB2d2F1b8c558a40207669291Fda468E50c8a0Bb", "0x1871e9B1C47c0FF6FeA5bAAcB3A04F8068D60E7e", ], "Crypto.com": [ "0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3", "0x46340b20830761efd32832A74d7169B29FEB9758", ], "Gemini": [ "0xd24400ae8BfEBb18cA49Be86258a3C749cf46853", "0x61EDCDf5bb737ADffE5043706e7C5bb1f1a56eEA", "0x07ee55aA48Bb72DCc597523F4A2632811C1bE3a4", ], "Korbit": [ "0xF977814e90dA44bFA03b6295A0616a897441aceC", ], "Bittrex": [ "0x94b1B6eB67A7D14b2ad2Ee93FeBA5457c5451eF2", "0x66F820a414680B5bcda5eAFc3d1F3C99b9F9B11f", ], "Bitstamp": [ "0x00bdb5699745f5b860228c8f939abF1b9Ae374eD", "0x1522900B6daFac587d499a862861C0869Be6E428", ], "Upbit": [ "0x390De26d772D2e2005C6d1D24afC902baC3ad635", "0x5e032243d507C743b061Ef021E2eC7bfA1Ec5277", ], "OKEx": [ "0x6cC5F688a315f3dC28A7781717a9A798a59fDA7b", ], "BinanceUS": [ "0x61189Da79177950A7272c88c6058B96D4Bcd6bE2", ], "Poloniex": [ "0x32Be343B94f860124dC4fEe278FDCBD38C102D88", "0x209c4784AB1E8183Cf58cA33cb740dcbF2fD3bF7", ], "Bithumb": [ "0x44E9d0Bc5f3F4Bc54d9F6c1bd9fA09fD0a1e633c", ], "Coinone": [ "0x167a9333BF582556f35Bd4d16a7E80a9525A9c3B", ], "LBank": [ "0x1205E4F0D2f0225Fd4b3d8d7C1C9c7b9A1B3D5F7", ], "BitMart": [ "0xE93381fB4c4F14bDa253907b18faD305D799241a", ], "WooX": [ "0x0d83F81c79E8e93b30C4f5e9a4bDf4D3C5E7F9A1", ], }, api_key_env="ETHERSCAN_API_KEY", ), "base": ChainConfig( name="base", chain_id="8453", family=ChainFamily.EVM, display_name="Base", rpc_endpoints=[ "https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", "https://mainnet.base.org", "https://base-rpc.publicnode.com", "https://rpc.ankr.com/base", ], explorer_url="https://basescan.org", explorer_api_url="https://api.basescan.org/api", explorer_api_key_env="BASESCAN_API_KEY", native_token_symbol="ETH", native_token_decimals=18, dexscreener_chain_id="base", known_lockers={ "0x663A5C229c09b049E36dCc11a9B0d4a8c9c3dBC2": "UNCX Network", "0x7ee058420e5937496f5a2096f04caa7721cf70cc": "PinkLock", }, cex_hot_wallets={ "Coinbase": [ "0x3304E22DDaa22bCdC5fCa2269b418046aE7b566A", "0x3154B747c4bFd35f2e42dF08c28c1A7c8C4f71D7", ], "Binance": [ "0x3304E22DDaa22bCdC5fCa2269b418046aE7b566A", ], "OKX": [ "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", ], }, api_key_env="BASESCAN_API_KEY", ), "bsc": ChainConfig( name="bsc", chain_id="56", family=ChainFamily.EVM, display_name="BSC", rpc_endpoints=[ "https://bsc-dataseed.binance.org", "https://rpc.ankr.com/bsc", "https://bsc-rpc.publicnode.com", "https://bsc-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", ], explorer_url="https://bscscan.com", explorer_api_url="https://api.bscscan.com/api", explorer_api_key_env="BSCSCAN_API_KEY", native_token_symbol="BNB", native_token_decimals=18, dexscreener_chain_id="bsc", known_lockers={ "0x663A5C229c09b049E36dCc11a9B0d4a8c9c3dBC2": "UNCX Network", "0x7ee058420e5937496f5a2096f04caa7721cf70cc": "PinkLock", "0x2D8E3580C6E428d4EaA069198D0B76ae0f98e43a": "Mudra", }, cex_hot_wallets={ "Binance": [ "0xB38e8c17e38363aF6EbdCb3dAE12e0243582891D", "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", ], "MEXC": ["0x51e6D27FA57373d8d4C256231241053a70Cb1d93"], "OKX": [ "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3", ], "KuCoin": [ "0xD6216fc19DB775Df92A4Dba0f1E18b85a4c623F0", ], "Gate.io": [ "0x2A4EdF32D2b0ce008FeEB8EE2dA93B2dF34c3a4D", ], "Bybit": [ "0xE2Fc0d06Fe01466fDDb948Cf38A8Ee98f36793B0", ], "Bitget": [ "0x6B6a3E94FEb2B5FaF40bfD71B39C4D6A4E6F8e7c", ], }, api_key_env="BSCSCAN_API_KEY", ), "arbitrum": ChainConfig( name="arbitrum", chain_id="42161", family=ChainFamily.EVM, display_name="Arbitrum", rpc_endpoints=[ "https://arb1.arbitrum.io/rpc", "https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", "https://arbitrum-one-rpc.publicnode.com", ], explorer_url="https://arbiscan.io", explorer_api_url="https://api.arbiscan.io/api", explorer_api_key_env="ARBISCAN_API_KEY", native_token_symbol="ETH", native_token_decimals=18, dexscreener_chain_id="arbitrum", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", "0xF977814e90dA44bFA03b6295A0616a897441aceC", ], "Coinbase": [ "0x3154B747c4bFd35f2e42dF08c28c1A7c8C4f71D7", ], "Bybit": [ "0xf89d7b9c864f589bbF53a82105107622B35EaA40", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], "Kraken": [ "0x2910543Af39abA0Cd09dBb2D50200b3E800A63D2", ], }, api_key_env="ARBISCAN_API_KEY", ), "polygon": ChainConfig( name="polygon", chain_id="137", family=ChainFamily.EVM, display_name="Polygon", rpc_endpoints=[ "https://polygon-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", "https://polygon-rpc.com", "https://polygon-bor-rpc.publicnode.com", "https://rpc.ankr.com/polygon", ], explorer_url="https://polygonscan.com", explorer_api_url="https://api.polygonscan.com/api", explorer_api_key_env="POLYGONSCAN_API_KEY", native_token_symbol="MATIC", native_token_decimals=18, dexscreener_chain_id="polygon", cex_hot_wallets={ "Binance": [ "0xe7804c37c13166fF0b37F5aE0BB07A3aEbb6e245", "0xF977814e90dA44bFA03b6295A0616a897441aceC", ], "Coinbase": [ "0x3154B747c4bFd35f2e42dF08c28c1A7c8C4f71D7", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], "Bybit": [ "0xf89d7b9c864f589bbF53a82105107622B35EaA40", ], "KuCoin": [ "0xD6216fc19DB775Df92A4Dba0f1E18b85a4c623F0", ], "Gate.io": [ "0x2A4EdF32D2b0ce008FeEB8EE2dA93B2dF34c3a4D", ], }, api_key_env="POLYGONSCAN_API_KEY", ), "avalanche": ChainConfig( name="avalanche", chain_id="43114", family=ChainFamily.EVM, display_name="Avalanche", rpc_endpoints=[ "https://api.avax.network/ext/bc/C/rpc", "https://avalanche-c-chain-rpc.publicnode.com", "https://rpc.ankr.com/avalanche", ], explorer_url="https://snowtrace.io", explorer_api_url="https://api.snowtrace.io/api", explorer_api_key_env="SNOWTRACE_API_KEY", native_token_symbol="AVAX", native_token_decimals=18, dexscreener_chain_id="avalanche", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", "0xF977814e90dA44bFA03b6295A0616a897441aceC", ], "Coinbase": [ "0x3154B747c4bFd35f2e42dF08c28c1A7c8C4f71D7", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], "Bybit": [ "0xf89d7b9c864f589bbF53a82105107622B35EaA40", ], "KuCoin": [ "0xD6216fc19DB775Df92A4Dba0f1E18b85a4c623F0", ], }, api_key_env="SNOWTRACE_API_KEY", ), "optimism": ChainConfig( name="optimism", chain_id="10", family=ChainFamily.EVM, display_name="Optimism", rpc_endpoints=[ "https://mainnet.optimism.io", "https://opt-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", "https://optimism-rpc.publicnode.com", ], explorer_url="https://optimistic.etherscan.io", explorer_api_url="https://api-optimistic.etherscan.io/api", explorer_api_key_env="ETHERSCAN_API_KEY", # Shares Etherscan key native_token_symbol="ETH", native_token_decimals=18, dexscreener_chain_id="optimism", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", ], "Coinbase": [ "0x3154B747c4bFd35f2e42dF08c28c1A7c8C4f71D7", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], "Bybit": [ "0xf89d7b9c864f589bbF53a82105107622B35EaA40", ], }, api_key_env="ETHERSCAN_API_KEY", ), "fantom": ChainConfig( name="fantom", chain_id="250", family=ChainFamily.EVM, display_name="Fantom", rpc_endpoints=[ "https://rpc.fantom.network", "https://fantom-rpc.publicnode.com", "https://rpc.ankr.com/fantom", ], explorer_url="https://ftmscan.com", explorer_api_url="https://api.ftmscan.com/api", explorer_api_key_env="FTMSCAN_API_KEY", native_token_symbol="FTM", native_token_decimals=18, dexscreener_chain_id="fantom", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], "KuCoin": [ "0xD6216fc19DB775Df92A4Dba0f1E18b85a4c623F0", ], }, api_key_env="FTMSCAN_API_KEY", ), "linea": ChainConfig( name="linea", chain_id="59144", family=ChainFamily.EVM, display_name="Linea", rpc_endpoints=[ "https://rpc.linea.build", "https://linea-rpc.publicnode.com", ], explorer_url="https://lineascan.build", explorer_api_url="https://api.lineascan.build/api", explorer_api_key_env="", native_token_symbol="ETH", native_token_decimals=18, dexscreener_chain_id="linea", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], }, api_key_env="", ), "zksync": ChainConfig( name="zksync", chain_id="324", family=ChainFamily.EVM, display_name="zkSync Era", rpc_endpoints=[ "https://mainnet.era.zksync.io", "https://zksync-era.blockpi.network/v1/rpc/public", ], explorer_url="https://explorer.zksync.io", explorer_api_url="https://block-explorer-api.mainnet.zksync.io/api", explorer_api_key_env="", native_token_symbol="ETH", native_token_decimals=18, dexscreener_chain_id="zksync", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], "Bybit": [ "0xf89d7b9c864f589bbF53a82105107622B35EaA40", ], }, api_key_env="", ), "scroll": ChainConfig( name="scroll", chain_id="534352", family=ChainFamily.EVM, display_name="Scroll", rpc_endpoints=[ "https://rpc.scroll.io", "https://scroll-rpc.publicnode.com", ], explorer_url="https://scrollscan.com", explorer_api_url="https://api.scrollscan.com/api", explorer_api_key_env="", native_token_symbol="ETH", native_token_decimals=18, dexscreener_chain_id="scroll", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", ], "OKX": [ "0x06959153B974D0D5fDfd87D561dF1cA9aA5E7f05", ], }, api_key_env="ETHERSCAN_API_KEY", ), "mantle": ChainConfig( name="mantle", chain_id="5000", family=ChainFamily.EVM, display_name="Mantle", rpc_endpoints=[ "https://rpc.mantle.xyz", ], explorer_url="https://mantlescan.xyz", explorer_api_url="https://api.mantlescan.xyz/api", explorer_api_key_env="", native_token_symbol="MNT", native_token_decimals=18, dexscreener_chain_id="mantle", cex_hot_wallets={ "Binance": [ "0x3C7832B91d940a7C87e0119f60A106C6dB2CcC60", ], "Bybit": [ "0xf89d7b9c864f589bbF53a82105107622B35EaA40", ], }, api_key_env="", ), } # ─── Convenience functions ───────────────────────────────────────── def get_chain(chain_name: str) -> ChainConfig | None: """Get chain config by name (case-insensitive).""" return CHAINS.get(chain_name.lower()) def get_chain_family(chain_name: str) -> ChainFamily: """Determine if a chain is Solana, EVM, or other.""" cfg = CHAINS.get(chain_name.lower()) return cfg.family if cfg else ChainFamily.EVM def is_solana(chain_name: str) -> bool: return get_chain_family(chain_name) == ChainFamily.SOLANA def is_evm(chain_name: str) -> bool: return get_chain_family(chain_name) == ChainFamily.EVM def resolve_chain_id(chain_name: str) -> str: """Resolve chain name to numeric or canonical ID.""" cfg = CHAINS.get(chain_name.lower()) return cfg.chain_id if cfg else chain_name def list_chains() -> list[str]: """List all supported chain names.""" return list(CHAINS.keys()) def get_chain_display(chain_name: str) -> str: """Get human-readable chain display name.""" cfg = CHAINS.get(chain_name.lower()) return cfg.display_name if cfg else chain_name.title() def get_explorer_url(chain_name: str, address: str, tx: str = "") -> str: """Build explorer URL for an address or transaction.""" cfg = CHAINS.get(chain_name.lower()) if not cfg: return "" if tx: return f"{cfg.explorer_url}/tx/{tx}" if cfg.is_evm else f"{cfg.explorer_url}/tx/{tx}" # noqa: RUF034 if cfg.is_solana: return f"{cfg.explorer_url}/account/{address}" return f"{cfg.explorer_url}/address/{address}" def get_rpc_urls(chain_name: str) -> list[str]: """Get list of RPC URLs for a chain (with env vars resolved).""" cfg = CHAINS.get(chain_name.lower()) if not cfg: return [] urls = [] for url in cfg.rpc_endpoints: # Resolve ${VAR} references from environment import re resolved = re.sub(r"\$\{(\w+)\}", lambda m: os.getenv(m.group(1), m.group(1)), url) urls.append(resolved) return urls def get_explorer_api_config(chain_name: str) -> tuple[str, str]: """Get (api_url, api_key) for a chain's block explorer.""" cfg = CHAINS.get(chain_name.lower()) if not cfg: return ("", "") api_key = "" if cfg.api_key_env: api_key = os.getenv(cfg.api_key_env, "") return (cfg.explorer_api_url, api_key) def get_cex_wallets(chain_name: str) -> dict[str, list[str]]: """Get known CEX hot wallets for a chain.""" cfg = CHAINS.get(chain_name.lower()) return cfg.cex_hot_wallets if cfg else {} def get_known_lockers(chain_name: str) -> dict[str, str]: """Get known liquidity locker contracts for a chain (keys are lowercase-normalized).""" cfg = CHAINS.get(chain_name.lower()) if not cfg: return {} return {k.lower(): v for k, v in cfg.known_lockers.items()}