package exchanges import ( "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/math" ) // TokenInfo represents token information type TokenInfo struct { Address string Symbol string Name string Decimals uint8 } // TokenPair represents a pair of tokens type TokenPair struct { Token0 TokenInfo Token1 TokenInfo } // Use ExchangeType from math package to avoid duplication // ExchangeConfig holds configuration for an exchange type ExchangeConfig struct { Type math.ExchangeType Name string FactoryAddress common.Address RouterAddress common.Address PoolInitCodeHash string SwapSelector []byte StableSwapSelector []byte ChainID int64 SupportsFlashSwaps bool RequiresApproval bool MaxHops int DefaultSlippagePercent float64 Url string ApiUrl string } // PoolDetector interface for detecting pools type PoolDetector interface { GetAllPools(token0, token1 common.Address) ([]common.Address, error) GetPoolForPair(token0, token1 common.Address) (common.Address, error) GetSupportedFeeTiers() []int64 GetPoolType() string } // LiquidityFetcher interface for fetching liquidity data type LiquidityFetcher interface { GetPoolData(poolAddress common.Address) (*math.PoolData, error) GetTokenReserves(poolAddress, token0, token1 common.Address) (*big.Int, *big.Int, error) GetPoolPrice(poolAddress common.Address) (*big.Float, error) GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error) } // SwapRouter interface for executing swaps type SwapRouter interface { CalculateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, error) GenerateSwapData(tokenIn, tokenOut common.Address, amountIn, minAmountOut *big.Int, deadline *big.Int) ([]byte, error) GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error) ValidateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) error } // ExchangeRegistry manages multiple exchanges type ExchangeRegistry struct { client *ethclient.Client logger *logger.Logger exchanges map[math.ExchangeType]*ExchangeConfig poolDetectors map[math.ExchangeType]PoolDetector liquidityFetchers map[math.ExchangeType]LiquidityFetcher swapRouters map[math.ExchangeType]SwapRouter marketScanner *MarketScanner } // MarketScanner interface for scanning markets type MarketScanner interface { GetPoolsForTokenPair(token0, token1 common.Address) []common.Address GetPoolInfo(poolAddress common.Address) (*math.PoolData, error) } // NewExchangeRegistry creates a new exchange registry func NewExchangeRegistry(client *ethclient.Client, logger *logger.Logger) *ExchangeRegistry { return &ExchangeRegistry{ client: client, logger: logger, exchanges: make(map[math.ExchangeType]*ExchangeConfig), poolDetectors: make(map[math.ExchangeType]PoolDetector), liquidityFetchers: make(map[math.ExchangeType]LiquidityFetcher), swapRouters: make(map[math.ExchangeType]SwapRouter), } } // LoadArbitrumExchanges loads all supported exchanges on Arbitrum func (er *ExchangeRegistry) LoadArbitrumExchanges() error { // Load Uniswap V3 uniswapV3Config := &ExchangeConfig{ Type: math.ExchangeUniswapV3, Name: "Uniswap V3", FactoryAddress: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), RouterAddress: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), PoolInitCodeHash: "0xe34f199b19b2b4f47d6b5b39c7bde33e2a0a4c36149e248e847a490f543b7f3f", SwapSelector: []byte{0x38, 0xed, 0x17, 0x39}, // exactInputSingle StableSwapSelector: []byte{}, ChainID: 42161, SupportsFlashSwaps: true, RequiresApproval: true, MaxHops: 2, DefaultSlippagePercent: 0.5, Url: "https://uniswap.org", ApiUrl: "https://api.uniswap.org", } er.exchanges[math.ExchangeUniswapV3] = uniswapV3Config // Load SushiSwap sushiSwapConfig := &ExchangeConfig{ Type: math.ExchangeSushiSwap, Name: "SushiSwap", FactoryAddress: common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"), RouterAddress: common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), PoolInitCodeHash: "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303", SwapSelector: []byte{0x18, 0x2d, 0x2e, 0xdb}, // swapExactTokensForTokens StableSwapSelector: []byte{}, ChainID: 42161, SupportsFlashSwaps: true, RequiresApproval: true, MaxHops: 3, DefaultSlippagePercent: 0.5, Url: "https://sushi.com", ApiUrl: "https://api.sushi.com", } er.exchanges[math.ExchangeSushiSwap] = sushiSwapConfig // Load Curve curveConfig := &ExchangeConfig{ Type: math.ExchangeCurve, Name: "Curve", FactoryAddress: common.HexToAddress("0xb1748c7970d18f7d0ebf0f89e1c3f3b98e65e8ad"), RouterAddress: common.HexToAddress("0x4d7c19704623c38f54ec0d144c0ac63d0b76106e"), PoolInitCodeHash: "", SwapSelector: []byte{0x5b, 0x40, 0x2d, 0x3c}, // exchange StableSwapSelector: []byte{0x79, 0x1a, 0xc9, 0x47}, // exchange_underlying ChainID: 42161, SupportsFlashSwaps: false, RequiresApproval: true, MaxHops: 2, DefaultSlippagePercent: 0.1, Url: "https://curve.fi", ApiUrl: "https://api.curve.fi", } er.exchanges[math.ExchangeCurve] = curveConfig // Load Balancer balancerConfig := &ExchangeConfig{ Type: math.ExchangeBalancer, Name: "Balancer", FactoryAddress: common.HexToAddress("0xb33c178f5e23f3abf72a0dbfe955f1e69f2e47a6"), RouterAddress: common.HexToAddress("0xba12222222228d8ba445958a75a0704d566bf2c8"), PoolInitCodeHash: "", SwapSelector: []byte{0x3e, 0x2, 0x76, 0x5e}, // swap StableSwapSelector: []byte{}, ChainID: 42161, SupportsFlashSwaps: true, RequiresApproval: true, MaxHops: 3, DefaultSlippagePercent: 0.3, Url: "https://balancer.fi", ApiUrl: "https://api.balancer.fi", } er.exchanges[math.ExchangeBalancer] = balancerConfig return nil } // GetExchangeByType returns an exchange configuration by type func (er *ExchangeRegistry) GetExchangeByType(exchangeType math.ExchangeType) *ExchangeConfig { config, exists := er.exchanges[exchangeType] if !exists { return nil } return config } // GetExchangesForPair returns exchanges that support a token pair func (er *ExchangeRegistry) GetExchangesForPair(token0, token1 common.Address) []*ExchangeConfig { var supported []*ExchangeConfig for _, config := range er.exchanges { // In a real implementation, this would check if pools exist for this pair // For now, we'll assume all exchanges support all pairs for testing supported = append(supported, config) } return supported } // GetAllExchanges returns all registered exchanges func (er *ExchangeRegistry) GetAllExchanges() []*ExchangeConfig { var configs []*ExchangeConfig for _, config := range er.exchanges { configs = append(configs, config) } return configs } // GetAllExchangesMap returns all registered exchanges as a map func (er *ExchangeRegistry) GetAllExchangesMap() map[math.ExchangeType]*ExchangeConfig { return er.exchanges } // GetHighPriorityTokens returns high-priority tokens for scanning func (er *ExchangeRegistry) GetHighPriorityTokens(limit int) []TokenInfo { // Define high-priority tokens (ETH, USDC, USDT, WBTC, etc.) highPriorityTokens := []TokenInfo{ {Address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", Symbol: "WETH", Name: "Wrapped Ether", Decimals: 18}, {Address: "0xFF970A61D0f7e23A93789578a9F1fF23f7331277", Symbol: "USDC", Name: "USD Coin", Decimals: 6}, {Address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", Symbol: "USDT", Name: "Tether USD", Decimals: 6}, {Address: "0x2f2a2543B76A4166549F855b5b02C90Ea8b4b417", Symbol: "WBTC", Name: "Wrapped BTC", Decimals: 8}, {Address: "0x82e3A8F066a696Da855e363b7f374e5c8E4a79B9", Symbol: "LINK", Name: "ChainLink Token", Decimals: 18}, {Address: "0x3a283d9c08E4B7C5Ea6D7d3625b1aE0d89F9fA37", Symbol: "CRV", Name: "Curve DAO Token", Decimals: 18}, } if limit > len(highPriorityTokens) { limit = len(highPriorityTokens) } return highPriorityTokens[:limit] } // IsPairSupported checks if a token pair is supported by any exchange func (er *ExchangeRegistry) IsPairSupported(token0, token1 common.Address) bool { supportedExchanges := er.GetExchangesForPair(token0, token1) return len(supportedExchanges) > 0 } // ArbitragePath represents a simple arbitrage path for the exchanges package type ArbitragePath struct { TokenIn common.Address TokenOut common.Address Exchanges []math.ExchangeType Pools []common.Address } // GetPoolDetector returns the pool detector for a specific exchange type func (er *ExchangeRegistry) GetPoolDetector(exchangeType math.ExchangeType) PoolDetector { return er.poolDetectors[exchangeType] } // GetLiquidityFetcher returns the liquidity fetcher for a specific exchange type func (er *ExchangeRegistry) GetLiquidityFetcher(exchangeType math.ExchangeType) LiquidityFetcher { return er.liquidityFetchers[exchangeType] } // FindAllPaths finds all possible arbitrage paths between two tokens func (er *ExchangeRegistry) FindAllPaths(tokenA, tokenB common.Address, maxHops int) ([]*ArbitragePath, error) { // Simplified implementation for compilation - would be enhanced with actual path finding paths := []*ArbitragePath{} // For now, create a simple direct path if er.IsPairSupported(tokenA, tokenB) { path := &ArbitragePath{ TokenIn: tokenA, TokenOut: tokenB, Exchanges: []math.ExchangeType{math.ExchangeUniswapV3}, Pools: []common.Address{}, // Would be populated with actual pool addresses } paths = append(paths, path) } return paths, nil }