- Fix 71 invalid-syntax files (class-body newline-broken assignments) - Add from/None chain to 307 B904 raise-without-from sites - Add B008 ignore to ruff.toml (already in pyproject.toml) - Noqa F401 on __init__.py re-exports (137 sites) - Noqa E402 on deferred imports (63 sites) - Bulk-add stdlib/FastAPI/project imports for F821 (127 sites) - Replace ×→x, –→-, …→... in docstrings (4093 chars) - Manual refactor of 5 SIM103/SIM116 patterns Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py) Co-authored-by: opencode <opencode@rugmunch.io>
738 lines
26 KiB
Python
738 lines
26 KiB
Python
"""
|
|
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()}
|