package dex import ( "context" "fmt" "log/slog" "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/fraktal/mev-beta/pkg/types" ) // MEVBotIntegration integrates the multi-DEX system with the existing MEV bot type MEVBotIntegration struct { registry *Registry analyzer *CrossDEXAnalyzer client *ethclient.Client logger *slog.Logger } // NewMEVBotIntegration creates a new integration instance func NewMEVBotIntegration(client *ethclient.Client, logger *slog.Logger) (*MEVBotIntegration, error) { // Create registry registry := NewRegistry(client) // Initialize Arbitrum DEXes if err := registry.InitializeArbitrumDEXes(); err != nil { return nil, fmt.Errorf("failed to initialize DEXes: %w", err) } // Create analyzer analyzer := NewCrossDEXAnalyzer(registry, client) integration := &MEVBotIntegration{ registry: registry, analyzer: analyzer, client: client, logger: logger, } logger.Info("Multi-DEX integration initialized", "active_dexes", registry.GetActiveDEXCount(), ) return integration, nil } // ConvertToArbitrageOpportunity converts a DEX ArbitragePath to types.ArbitrageOpportunity func (m *MEVBotIntegration) ConvertToArbitrageOpportunity(path *ArbitragePath) *types.ArbitrageOpportunity { if path == nil || len(path.Hops) == 0 { return nil } // Build token path as strings tokenPath := make([]string, len(path.Hops)+1) tokenPath[0] = path.Hops[0].TokenIn.Hex() for i, hop := range path.Hops { tokenPath[i+1] = hop.TokenOut.Hex() } // Build pool addresses pools := make([]string, len(path.Hops)) for i, hop := range path.Hops { pools[i] = hop.PoolAddress.Hex() } // Determine protocol (use first hop's protocol for now, or "Multi-DEX" if different protocols) protocol := path.Hops[0].DEX.String() for i := 1; i < len(path.Hops); i++ { if path.Hops[i].DEX != path.Hops[0].DEX { protocol = "Multi-DEX" break } } // Generate unique ID id := fmt.Sprintf("dex-%s-%d-hops-%d", protocol, len(pools), time.Now().UnixNano()) return &types.ArbitrageOpportunity{ ID: id, Path: tokenPath, Pools: pools, Protocol: protocol, TokenIn: path.Hops[0].TokenIn, TokenOut: path.Hops[len(path.Hops)-1].TokenOut, AmountIn: path.Hops[0].AmountIn, Profit: path.TotalProfit, NetProfit: path.NetProfit, GasEstimate: path.GasCost, GasCost: path.GasCost, EstimatedProfit: path.NetProfit, RequiredAmount: path.Hops[0].AmountIn, PriceImpact: 1.0 - path.Confidence, // Inverse of confidence ROI: path.ROI, Confidence: path.Confidence, Profitable: path.NetProfit.Sign() > 0, Timestamp: time.Now().Unix(), DetectedAt: time.Now(), ExpiresAt: time.Now().Add(5 * time.Minute), ExecutionTime: int64(len(pools) * 100), // Estimate 100ms per hop Risk: 1.0 - path.Confidence, Urgency: 5 + len(pools), // Higher urgency for multi-hop } } // FindOpportunitiesForTokenPair finds arbitrage opportunities for a token pair across all DEXes func (m *MEVBotIntegration) FindOpportunitiesForTokenPair( ctx context.Context, tokenA, tokenB common.Address, amountIn *big.Int, ) ([]*types.ArbitrageOpportunity, error) { // Minimum profit threshold: 0.0001 ETH ($0.25 @ $2500/ETH) minProfitETH := 0.0001 // Find cross-DEX opportunities paths, err := m.analyzer.FindArbitrageOpportunities(ctx, tokenA, tokenB, amountIn, minProfitETH) if err != nil { return nil, fmt.Errorf("failed to find opportunities: %w", err) } // Convert to types.ArbitrageOpportunity opportunities := make([]*types.ArbitrageOpportunity, 0, len(paths)) for _, path := range paths { opp := m.ConvertToArbitrageOpportunity(path) if opp != nil { opportunities = append(opportunities, opp) } } m.logger.Info("Found cross-DEX opportunities", "token_pair", fmt.Sprintf("%s/%s", tokenA.Hex()[:10], tokenB.Hex()[:10]), "opportunities", len(opportunities), ) return opportunities, nil } // FindMultiHopOpportunities finds multi-hop arbitrage opportunities func (m *MEVBotIntegration) FindMultiHopOpportunities( ctx context.Context, startToken common.Address, intermediateTokens []common.Address, amountIn *big.Int, maxHops int, ) ([]*types.ArbitrageOpportunity, error) { minProfitETH := 0.0001 paths, err := m.analyzer.FindMultiHopOpportunities( ctx, startToken, intermediateTokens, amountIn, maxHops, minProfitETH, ) if err != nil { return nil, fmt.Errorf("failed to find multi-hop opportunities: %w", err) } opportunities := make([]*types.ArbitrageOpportunity, 0, len(paths)) for _, path := range paths { opp := m.ConvertToArbitrageOpportunity(path) if opp != nil { opportunities = append(opportunities, opp) } } m.logger.Info("Found multi-hop opportunities", "start_token", startToken.Hex()[:10], "max_hops", maxHops, "opportunities", len(opportunities), ) return opportunities, nil } // GetPriceComparison gets price comparison across all DEXes func (m *MEVBotIntegration) GetPriceComparison( ctx context.Context, tokenIn, tokenOut common.Address, amountIn *big.Int, ) (map[string]float64, error) { quotes, err := m.analyzer.GetPriceComparison(ctx, tokenIn, tokenOut, amountIn) if err != nil { return nil, err } prices := make(map[string]float64) for protocol, quote := range quotes { // Calculate price as expectedOut / amountIn priceFloat := new(big.Float).Quo( new(big.Float).SetInt(quote.ExpectedOut), new(big.Float).SetInt(amountIn), ) price, _ := priceFloat.Float64() prices[protocol.String()] = price } return prices, nil } // GetActiveDEXes returns list of active DEX protocols func (m *MEVBotIntegration) GetActiveDEXes() []string { dexes := m.registry.GetAll() names := make([]string, len(dexes)) for i, dex := range dexes { names[i] = dex.Name } return names } // GetDEXCount returns the number of active DEXes func (m *MEVBotIntegration) GetDEXCount() int { return m.registry.GetActiveDEXCount() }