package exchanges import ( "fmt" "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" ) // DexAggregatorPoolDetector implements PoolDetector for DEX aggregators type DexAggregatorPoolDetector struct { client *ethclient.Client logger *logger.Logger config *ExchangeConfig registry *ExchangeRegistry } // NewDexAggregatorPoolDetector creates a new DEX aggregator pool detector func NewDexAggregatorPoolDetector(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, registry *ExchangeRegistry) *DexAggregatorPoolDetector { return &DexAggregatorPoolDetector{ client: client, logger: logger, config: config, registry: registry, } } // GetAllPools returns all pools containing the specified tokens across all exchanges func (d *DexAggregatorPoolDetector) GetAllPools(token0, token1 common.Address) ([]common.Address, error) { // This would aggregate pools from all registered exchanges var allPools []common.Address // Get all supported exchanges for this token pair exchanges := d.registry.GetExchangesForPair(token0, token1) for _, exchangeConfig := range exchanges { // Get the pool detector for this exchange poolDetector := d.registry.GetPoolDetector(exchangeConfig.Type) if poolDetector != nil { pools, err := poolDetector.GetAllPools(token0, token1) if err != nil { d.logger.Warn(fmt.Sprintf("Error getting pools from exchange %s: %v", exchangeConfig.Name, err)) continue } allPools = append(allPools, pools...) } } return allPools, nil } // GetPoolForPair returns the best pool address for a specific token pair across exchanges func (d *DexAggregatorPoolDetector) GetPoolForPair(token0, token1 common.Address) (common.Address, error) { // In a real implementation, this would find the pool with best pricing across exchanges // For now, return a placeholder address return common.HexToAddress("0x0"), nil } // GetSupportedFeeTiers returns supported fee tiers for the aggregated exchanges func (d *DexAggregatorPoolDetector) GetSupportedFeeTiers() []int64 { // Return a range that covers all possible fee tiers across exchanges return []int64{100, 250, 300, 500, 1000, 2500, 3000, 5000, 10000} // Various fee tiers } // GetPoolType returns the pool type func (d *DexAggregatorPoolDetector) GetPoolType() string { return "dex_aggregator" } // DexAggregatorLiquidityFetcher implements LiquidityFetcher for DEX aggregators type DexAggregatorLiquidityFetcher struct { client *ethclient.Client logger *logger.Logger config *ExchangeConfig registry *ExchangeRegistry engine *math.ExchangePricingEngine } // NewDexAggregatorLiquidityFetcher creates a new DEX aggregator liquidity fetcher func NewDexAggregatorLiquidityFetcher(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, registry *ExchangeRegistry, engine *math.ExchangePricingEngine) *DexAggregatorLiquidityFetcher { return &DexAggregatorLiquidityFetcher{ client: client, logger: logger, config: config, registry: registry, engine: engine, } } // GetPoolData fetches pool information from the best available exchange for the token pair func (f *DexAggregatorLiquidityFetcher) GetPoolData(poolAddress common.Address) (*math.PoolData, error) { // For aggregator, this would depend on which exchange's pool is being represented // For this implementation, we'll return placeholder data return nil, fmt.Errorf("GetPoolData not directly supported for aggregator, use GetBestPoolData instead") } // GetTokenReserves fetches reserves from the best available exchange for a token pair func (f *DexAggregatorLiquidityFetcher) GetTokenReserves(poolAddress, token0, token1 common.Address) (*big.Int, *big.Int, error) { // This would aggregate reserves across exchanges // For now, return placeholder values return big.NewInt(0), big.NewInt(0), fmt.Errorf("not implemented for aggregator directly") } // GetBestPoolData fetches the best pool data across all exchanges for a token pair func (f *DexAggregatorLiquidityFetcher) GetBestPoolData(token0, token1 common.Address) (*math.PoolData, math.ExchangeType, error) { // Get all supported exchanges for this token pair exchanges := f.registry.GetExchangesForPair(token0, token1) bestAmountOut := big.NewInt(0) var bestPoolData *math.PoolData var bestExchangeType math.ExchangeType for _, exchangeConfig := range exchanges { // Get the liquidity fetcher for this exchange liquidityFetcher := f.registry.GetLiquidityFetcher(exchangeConfig.Type) if liquidityFetcher != nil { // Get pool data for this exchange poolData, err := liquidityFetcher.GetPoolData(common.HexToAddress(exchangeConfig.FactoryAddress.Hex())) // This is a simplification if err != nil { f.logger.Warn(fmt.Sprintf("Error getting pool data from exchange %s: %v", exchangeConfig.Name, err)) continue } // Calculate amount out for a standard amount to compare testAmount := big.NewInt(1000000000000000000) // 1 ETH equivalent decimalAmountIn, err := math.NewUniversalDecimal(testAmount, 18, "AMOUNT_IN") if err != nil { f.logger.Warn(fmt.Sprintf("Error creating decimal amount: %v", err)) continue } pricer, err := f.engine.GetExchangePricer(poolData.ExchangeType) if err != nil { continue } amountOut, err := pricer.CalculateAmountOut(decimalAmountIn, poolData) if err != nil { continue } // Check if this is the best rate if amountOut.Value.Cmp(bestAmountOut) > 0 { bestAmountOut = amountOut.Value bestPoolData = poolData bestExchangeType = poolData.ExchangeType } } } if bestPoolData == nil { return nil, "", fmt.Errorf("no pools found for token pair") } return bestPoolData, bestExchangeType, nil } // GetPoolPrice calculates the best price of token1 in terms of token0 across exchanges func (f *DexAggregatorLiquidityFetcher) GetPoolPrice(poolAddress common.Address) (*big.Float, error) { // For an aggregator, we'd need to look up which exchange the pool belongs to // For this simplified version, return an error return nil, fmt.Errorf("GetPoolPrice not directly supported for aggregator, use GetBestPoolPrice instead") } // GetBestPoolPrice calculates the best price across all exchanges for a token pair func (f *DexAggregatorLiquidityFetcher) GetBestPoolPrice(token0, token1 common.Address) (*big.Float, math.ExchangeType, error) { poolData, exchangeType, err := f.GetBestPoolData(token0, token1) if err != nil { return nil, "", err } pricer, err := f.engine.GetExchangePricer(poolData.ExchangeType) if err != nil { return nil, "", err } spotPrice, err := pricer.GetSpotPrice(poolData) if err != nil { return nil, "", err } // Convert the UniversalDecimal Value to a *big.Float result := new(big.Float).SetInt(spotPrice.Value) return result, exchangeType, nil } // GetLiquidityDepth calculates the liquidity depth across all exchanges func (f *DexAggregatorLiquidityFetcher) GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error) { // This would aggregate liquidity across exchanges // For now, return placeholder return amount, nil } // DexAggregatorSwapRouter implements SwapRouter for DEX aggregators type DexAggregatorSwapRouter struct { client *ethclient.Client logger *logger.Logger config *ExchangeConfig registry *ExchangeRegistry engine *math.ExchangePricingEngine } // NewDexAggregatorSwapRouter creates a new DEX aggregator swap router func NewDexAggregatorSwapRouter(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, registry *ExchangeRegistry, engine *math.ExchangePricingEngine) *DexAggregatorSwapRouter { return &DexAggregatorSwapRouter{ client: client, logger: logger, config: config, registry: registry, engine: engine, } } // CalculateSwap calculates the expected best output amount across all exchanges func (r *DexAggregatorSwapRouter) CalculateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, error) { // Get the best available rate across all exchanges exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut) bestAmountOut := big.NewInt(0) for _, exchangeConfig := range exchanges { // Get the swap router for this exchange swapRouter := r.registry.GetSwapRouter(exchangeConfig.Type) if swapRouter != nil { amountOut, err := swapRouter.CalculateSwap(tokenIn, tokenOut, amountIn) if err != nil { r.logger.Warn(fmt.Sprintf("Error calculating swap on exchange %s: %v", exchangeConfig.Name, err)) continue } // Check if this is the best rate if amountOut.Cmp(bestAmountOut) > 0 { bestAmountOut = amountOut } } } if bestAmountOut.Sign() <= 0 { return nil, fmt.Errorf("no favorable swap route found across exchanges") } return bestAmountOut, nil } // CalculateMultiSwap calculates the output amount considering multiple exchanges and routing func (r *DexAggregatorSwapRouter) CalculateMultiSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, []math.ExchangeType, error) { // Find best path across exchanges exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut) bestAmountOut := big.NewInt(0) var bestRoutes []math.ExchangeType for _, exchangeConfig := range exchanges { // Get the swap router for this exchange swapRouter := r.registry.GetSwapRouter(exchangeConfig.Type) if swapRouter != nil { amountOut, err := swapRouter.CalculateSwap(tokenIn, tokenOut, amountIn) if err != nil { r.logger.Warn(fmt.Sprintf("Error calculating swap on exchange %s: %v", exchangeConfig.Name, err)) continue } // Check if this is the best rate if amountOut.Cmp(bestAmountOut) > 0 { bestAmountOut = amountOut bestRoutes = []math.ExchangeType{exchangeConfig.Type} } } } if bestAmountOut.Sign() <= 0 { return nil, nil, fmt.Errorf("no favorable swap route found across exchanges") } return bestAmountOut, bestRoutes, nil } // GenerateSwapData generates the calldata for an aggregated swap transaction func (r *DexAggregatorSwapRouter) GenerateSwapData(tokenIn, tokenOut common.Address, amountIn, minAmountOut *big.Int, deadline *big.Int) ([]byte, error) { // In a real implementation, this would generate data for a complex multi-exchange swap // For now, return placeholder return []byte{}, nil } // GetSwapRoute returns the best route for a swap across all exchanges func (r *DexAggregatorSwapRouter) GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error) { // Find the best exchange for this swap exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut) if len(exchanges) == 0 { return nil, fmt.Errorf("no exchanges support this token pair") } // For this simplified implementation, return a direct route return []common.Address{tokenIn, tokenOut}, nil } // GetBestSwapRoute returns the best route considering multiple exchanges func (r *DexAggregatorSwapRouter) GetBestSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, math.ExchangeType, error) { // Find the best exchange for this swap exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut) bestAmountOut := big.NewInt(0) var bestExchange math.ExchangeType for _, exchangeConfig := range exchanges { // Get the swap router for this exchange swapRouter := r.registry.GetSwapRouter(exchangeConfig.Type) if swapRouter != nil { amountOut, err := swapRouter.CalculateSwap(tokenIn, tokenOut, big.NewInt(1000000000000000000)) // Use 1 ETH equivalent to compare if err != nil { r.logger.Warn(fmt.Sprintf("Error calculating swap on exchange %s: %v", exchangeConfig.Name, err)) continue } // Check if this is the best rate if amountOut.Cmp(bestAmountOut) > 0 { bestAmountOut = amountOut bestExchange = exchangeConfig.Type } } } if bestAmountOut.Sign() <= 0 { return nil, "", fmt.Errorf("no favorable swap route found across exchanges") } // Return the best route for the exchange type return []common.Address{tokenIn, tokenOut}, bestExchange, nil } // ValidateSwap validates a swap across exchanges before execution func (r *DexAggregatorSwapRouter) ValidateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) error { if amountIn.Sign() <= 0 { return fmt.Errorf("amountIn must be positive") } if tokenIn == tokenOut { return fmt.Errorf("tokenIn and tokenOut cannot be the same") } if tokenIn == common.HexToAddress("0x0") || tokenOut == common.HexToAddress("0x0") { return fmt.Errorf("invalid token addresses") } // Check if any exchange supports this pair if !r.registry.IsPairSupported(tokenIn, tokenOut) { return fmt.Errorf("no exchange supports this token pair") } return nil } // RegisterDexAggregatorWithRegistry registers DEX aggregator implementation with the exchange registry func RegisterDexAggregatorWithRegistry(registry *ExchangeRegistry) error { // Note: For a complete implementation, we would need to define a specific type for aggregators // For now, we're not registering it as it needs a dedicated ExchangeType // config := &ExchangeConfig{ // Type: math.ExchangeUniswapV2, // Using a placeholder type for the aggregator // Name: "DEX Aggregator", // FactoryAddress: common.HexToAddress("0x0"), // Aggregators don't have a factory address // RouterAddress: common.HexToAddress("0x0"), // Would actually be aggregator contract address // PoolInitCodeHash: "", // SwapSelector: []byte{0x00, 0x00, 0x00, 0x00}, // Placeholder // StableSwapSelector: []byte{0x00, 0x00, 0x00, 0x00}, // Placeholder // ChainID: 1, // Ethereum mainnet // SupportsFlashSwaps: true, // RequiresApproval: true, // MaxHops: 5, // Can route through multiple exchanges // DefaultSlippagePercent: 0.5, // Url: "https://dex.ag", // ApiUrl: "https://api.dex.ag", // } // Note: For a complete implementation, we would need to define a specific type for aggregators // registry.exchanges[math.ExchangeDexAggregator] = config return nil }