package registry import ( "context" "fmt" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/fraktal/mev-beta/internal/contracts" "github.com/fraktal/mev-beta/internal/logger" ) // ContractInfo contains comprehensive information about a contract type ContractInfo struct { Address common.Address Type contracts.ContractType Name string Symbol string Decimals uint8 Token0 common.Address // For pools Token1 common.Address // For pools Fee uint32 // For V3 pools (in basis points) Factory common.Address Protocol string IsVerified bool LastUpdated time.Time Confidence float64 } // ContractRegistry provides authoritative mapping of contract addresses to their types and metadata type ContractRegistry struct { mu sync.RWMutex contracts map[common.Address]*ContractInfo tokensBySymbol map[string]common.Address poolsByTokenPair map[string][]common.Address // "token0:token1" -> pool addresses detector *contracts.ContractDetector logger *logger.Logger updateInterval time.Duration lastFullUpdate time.Time } // NewContractRegistry creates a new contract registry func NewContractRegistry(detector *contracts.ContractDetector, logger *logger.Logger) *ContractRegistry { registry := &ContractRegistry{ contracts: make(map[common.Address]*ContractInfo), tokensBySymbol: make(map[string]common.Address), poolsByTokenPair: make(map[string][]common.Address), detector: detector, logger: logger, updateInterval: 24 * time.Hour, // Update every 24 hours lastFullUpdate: time.Time{}, } // Initialize with known Arbitrum contracts registry.initializeKnownContracts() return registry } // initializeKnownContracts populates the registry with well-known Arbitrum contracts func (cr *ContractRegistry) initializeKnownContracts() { cr.mu.Lock() defer cr.mu.Unlock() now := time.Now() // Major ERC-20 tokens on Arbitrum knownTokens := map[common.Address]*ContractInfo{ common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"): { Address: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), Type: contracts.ContractTypeERC20Token, Name: "Wrapped Ether", Symbol: "WETH", Decimals: 18, Protocol: "Arbitrum", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"): { Address: common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"), Type: contracts.ContractTypeERC20Token, Name: "USD Coin", Symbol: "USDC", Decimals: 6, Protocol: "Arbitrum", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"): { Address: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), Type: contracts.ContractTypeERC20Token, Name: "Tether USD", Symbol: "USDT", Decimals: 6, Protocol: "Arbitrum", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"): { Address: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), Type: contracts.ContractTypeERC20Token, Name: "Wrapped BTC", Symbol: "WBTC", Decimals: 8, Protocol: "Arbitrum", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"): { Address: common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"), Type: contracts.ContractTypeERC20Token, Name: "Arbitrum", Symbol: "ARB", Decimals: 18, Protocol: "Arbitrum", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, } // Major Uniswap V3 pools on Arbitrum knownPools := map[common.Address]*ContractInfo{ common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"): { Address: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"), Type: contracts.ContractTypeUniswapV3Pool, Name: "USDC/WETH 0.05%", Token0: common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"), // USDC Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH Fee: 500, // 0.05% Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), Protocol: "UniswapV3", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d"): { Address: common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d"), Type: contracts.ContractTypeUniswapV3Pool, Name: "USDC/WETH 0.3%", Token0: common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"), // USDC Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH Fee: 3000, // 0.3% Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), Protocol: "UniswapV3", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0x2f5e87C9312fa29aed5c179E456625D79015299c"): { Address: common.HexToAddress("0x2f5e87C9312fa29aed5c179E456625D79015299c"), Type: contracts.ContractTypeUniswapV3Pool, Name: "WBTC/WETH 0.05%", Token0: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), // WBTC Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH Fee: 500, // 0.05% Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), Protocol: "UniswapV3", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d"): { Address: common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d"), Type: contracts.ContractTypeUniswapV3Pool, Name: "USDT/WETH 0.05%", Token0: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), // USDT Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH Fee: 500, // 0.05% Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), Protocol: "UniswapV3", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0xFe7D6a84287235C7b4b57C4fEb9a44d4C6Ed3BB8"): { Address: common.HexToAddress("0xFe7D6a84287235C7b4b57C4fEb9a44d4C6Ed3BB8"), Type: contracts.ContractTypeUniswapV3Pool, Name: "ARB/WETH 0.05%", Token0: common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"), // ARB Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH Fee: 500, // 0.05% Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), Protocol: "UniswapV3", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, } // Major routers on Arbitrum knownRouters := map[common.Address]*ContractInfo{ common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"): { Address: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), Type: contracts.ContractTypeUniswapV3Router, Name: "Uniswap V3 Router", Protocol: "UniswapV3", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"): { Address: common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), Type: contracts.ContractTypeUniswapV3Router, Name: "Uniswap V3 Router 2", Protocol: "UniswapV3", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"): { Address: common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), Type: contracts.ContractTypeUniswapV2Router, Name: "SushiSwap Router", Protocol: "SushiSwap", IsVerified: true, LastUpdated: now, Confidence: 1.0, }, } // Add all known contracts for addr, info := range knownTokens { cr.contracts[addr] = info cr.tokensBySymbol[info.Symbol] = addr } for addr, info := range knownPools { cr.contracts[addr] = info // Index pools by token pair if info.Token0 != (common.Address{}) && info.Token1 != (common.Address{}) { pairKey := cr.makeTokenPairKey(info.Token0, info.Token1) cr.poolsByTokenPair[pairKey] = append(cr.poolsByTokenPair[pairKey], addr) } } for addr, info := range knownRouters { cr.contracts[addr] = info } cr.logger.Info("Contract registry initialized", "tokens", len(knownTokens), "pools", len(knownPools), "routers", len(knownRouters)) } // GetContractInfo retrieves contract information, using cache first then detection func (cr *ContractRegistry) GetContractInfo(ctx context.Context, address common.Address) (*ContractInfo, error) { // Try cache first if info := cr.getCachedInfo(address); info != nil { return info, nil } // Not in cache, use detector detection := cr.detector.DetectContractType(ctx, address) if detection.Error != nil { return nil, fmt.Errorf("contract detection failed: %w", detection.Error) } // Create contract info from detection info := &ContractInfo{ Address: address, Type: detection.ContractType, Name: fmt.Sprintf("Unknown %s", detection.ContractType.String()), Protocol: "Unknown", IsVerified: false, LastUpdated: time.Now(), Confidence: detection.Confidence, } // Cache the result cr.cacheContractInfo(info) return info, nil } // getCachedInfo safely retrieves cached contract info func (cr *ContractRegistry) getCachedInfo(address common.Address) *ContractInfo { cr.mu.RLock() defer cr.mu.RUnlock() return cr.contracts[address] } // cacheContractInfo safely caches contract info func (cr *ContractRegistry) cacheContractInfo(info *ContractInfo) { cr.mu.Lock() defer cr.mu.Unlock() cr.contracts[info.Address] = info } // IsKnownToken checks if an address is a known ERC-20 token func (cr *ContractRegistry) IsKnownToken(address common.Address) bool { cr.mu.RLock() defer cr.mu.RUnlock() if info, exists := cr.contracts[address]; exists { return info.Type == contracts.ContractTypeERC20Token } return false } // IsKnownPool checks if an address is a known pool func (cr *ContractRegistry) IsKnownPool(address common.Address) bool { cr.mu.RLock() defer cr.mu.RUnlock() if info, exists := cr.contracts[address]; exists { return info.Type == contracts.ContractTypeUniswapV2Pool || info.Type == contracts.ContractTypeUniswapV3Pool } return false } // IsKnownRouter checks if an address is a known router func (cr *ContractRegistry) IsKnownRouter(address common.Address) bool { cr.mu.RLock() defer cr.mu.RUnlock() if info, exists := cr.contracts[address]; exists { return info.Type == contracts.ContractTypeUniswapV2Router || info.Type == contracts.ContractTypeUniswapV3Router || info.Type == contracts.ContractTypeUniversalRouter } return false } // GetTokenBySymbol retrieves a token address by symbol func (cr *ContractRegistry) GetTokenBySymbol(symbol string) (common.Address, bool) { cr.mu.RLock() defer cr.mu.RUnlock() addr, exists := cr.tokensBySymbol[symbol] return addr, exists } // GetPoolsForTokenPair retrieves pools for a given token pair func (cr *ContractRegistry) GetPoolsForTokenPair(token0, token1 common.Address) []common.Address { cr.mu.RLock() defer cr.mu.RUnlock() pairKey := cr.makeTokenPairKey(token0, token1) return cr.poolsByTokenPair[pairKey] } // makeTokenPairKey creates a consistent key for token pairs func (cr *ContractRegistry) makeTokenPairKey(token0, token1 common.Address) string { // Ensure consistent ordering if token0.Big().Cmp(token1.Big()) > 0 { token0, token1 = token1, token0 } return fmt.Sprintf("%s:%s", token0.Hex(), token1.Hex()) } // GetKnownContracts returns all cached contracts func (cr *ContractRegistry) GetKnownContracts() map[common.Address]*ContractInfo { cr.mu.RLock() defer cr.mu.RUnlock() // Return a copy to prevent external modification result := make(map[common.Address]*ContractInfo) for addr, info := range cr.contracts { result[addr] = info } return result } // UpdateContractInfo updates contract information (for dynamic discovery) func (cr *ContractRegistry) UpdateContractInfo(info *ContractInfo) { cr.mu.Lock() defer cr.mu.Unlock() info.LastUpdated = time.Now() cr.contracts[info.Address] = info // Update indexes if info.Type == contracts.ContractTypeERC20Token && info.Symbol != "" { cr.tokensBySymbol[info.Symbol] = info.Address } if (info.Type == contracts.ContractTypeUniswapV2Pool || info.Type == contracts.ContractTypeUniswapV3Pool) && info.Token0 != (common.Address{}) && info.Token1 != (common.Address{}) { pairKey := cr.makeTokenPairKey(info.Token0, info.Token1) // Check if already exists in slice pools := cr.poolsByTokenPair[pairKey] exists := false for _, pool := range pools { if pool == info.Address { exists = true break } } if !exists { cr.poolsByTokenPair[pairKey] = append(pools, info.Address) } } } // GetCacheStats returns statistics about the cached contracts func (cr *ContractRegistry) GetCacheStats() map[string]int { cr.mu.RLock() defer cr.mu.RUnlock() stats := map[string]int{ "total": len(cr.contracts), "tokens": 0, "v2_pools": 0, "v3_pools": 0, "routers": 0, "verified": 0, } for _, info := range cr.contracts { switch info.Type { case contracts.ContractTypeERC20Token: stats["tokens"]++ case contracts.ContractTypeUniswapV2Pool: stats["v2_pools"]++ case contracts.ContractTypeUniswapV3Pool: stats["v3_pools"]++ case contracts.ContractTypeUniswapV2Router, contracts.ContractTypeUniswapV3Router: stats["routers"]++ } if info.IsVerified { stats["verified"]++ } } return stats } // GetPoolInfo returns pool information for a given address func (cr *ContractRegistry) GetPoolInfo(address common.Address) *ContractInfo { cr.mu.RLock() defer cr.mu.RUnlock() if info, exists := cr.contracts[address]; exists { if info.Type == contracts.ContractTypeUniswapV2Pool || info.Type == contracts.ContractTypeUniswapV3Pool { return info } } return nil } // AddPool adds a pool to the registry func (cr *ContractRegistry) AddPool(poolAddress, token0, token1 common.Address, protocol string) { cr.mu.Lock() defer cr.mu.Unlock() // Determine pool type based on protocol var poolType contracts.ContractType switch protocol { case "UniswapV2": poolType = contracts.ContractTypeUniswapV2Pool case "UniswapV3": poolType = contracts.ContractTypeUniswapV3Pool default: poolType = contracts.ContractTypeUniswapV2Pool // Default to V2 } // Add or update pool info info := &ContractInfo{ Address: poolAddress, Type: poolType, Token0: token0, Token1: token1, Protocol: protocol, IsVerified: false, // Runtime discovered pools are not pre-verified LastUpdated: time.Now(), Confidence: 0.8, // High confidence for runtime discovered pools } cr.contracts[poolAddress] = info // Update token pair mapping pairKey := cr.makeTokenPairKey(token0, token1) if pools, exists := cr.poolsByTokenPair[pairKey]; exists { // Check if pool already exists in the list for _, existingPool := range pools { if existingPool == poolAddress { return // Already exists } } cr.poolsByTokenPair[pairKey] = append(pools, poolAddress) } else { cr.poolsByTokenPair[pairKey] = []common.Address{poolAddress} } }