package mev import ( "context" "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/fraktal/mev-beta/internal/logger" ) // CompetitionAnalyzer analyzes MEV competition and optimizes bidding strategy type CompetitionAnalyzer struct { client *ethclient.Client logger *logger.Logger baseFeeHistory []*big.Int priorityFeeHistory []*big.Int lastUpdate time.Time } // MEVOpportunity represents a detected MEV opportunity with competition analysis type MEVOpportunity struct { TxHash string Block uint64 OpportunityType string EstimatedProfit *big.Int RequiredGas uint64 Competition *CompetitionMetrics RecommendedBid *BiddingStrategy } // CompetitionMetrics tracks MEV competition in the mempool type CompetitionMetrics struct { CompetingBots int HighestPriorityFee *big.Int AveragePriorityFee *big.Int CompetitionIntensity float64 // 0-1 scale TimeToInclusion time.Duration } // BiddingStrategy recommends optimal gas pricing for MEV competition type BiddingStrategy struct { BaseFee *big.Int PriorityFee *big.Int MaxFeePerGas *big.Int GasLimit uint64 TotalCost *big.Int SuccessProbability float64 } // NewCompetitionAnalyzer creates a new MEV competition analyzer func NewCompetitionAnalyzer(client *ethclient.Client, logger *logger.Logger) *CompetitionAnalyzer { return &CompetitionAnalyzer{ client: client, logger: logger, baseFeeHistory: make([]*big.Int, 0, 50), priorityFeeHistory: make([]*big.Int, 0, 50), lastUpdate: time.Now(), } } // AnalyzeCompetition analyzes current MEV competition for an opportunity func (ca *CompetitionAnalyzer) AnalyzeCompetition(ctx context.Context, opportunity *MEVOpportunity) (*CompetitionMetrics, error) { // Update fee history if needed if time.Since(ca.lastUpdate) > 10*time.Second { if err := ca.updateFeeHistory(ctx); err != nil { ca.logger.Warn(fmt.Sprintf("Failed to update fee history: %v", err)) } } // Analyze pending transactions for similar opportunities competingTxs, err := ca.findCompetingTransactions(ctx, opportunity) if err != nil { return nil, fmt.Errorf("failed to analyze competing transactions: %w", err) } // Calculate competition intensity intensity := ca.calculateCompetitionIntensity(competingTxs) // Analyze priority fees of competing transactions highestPriority, avgPriority := ca.analyzePriorityFees(competingTxs) metrics := &CompetitionMetrics{ CompetingBots: len(competingTxs), HighestPriorityFee: highestPriority, AveragePriorityFee: avgPriority, CompetitionIntensity: intensity, TimeToInclusion: ca.estimateInclusionTime(intensity), } ca.logger.Debug(fmt.Sprintf("Competition analysis: %d competing bots, intensity: %.2f, highest priority: %s gwei", metrics.CompetingBots, metrics.CompetitionIntensity, formatGwei(metrics.HighestPriorityFee))) return metrics, nil } // CalculateOptimalBid calculates the optimal gas bid for winning an MEV opportunity func (ca *CompetitionAnalyzer) CalculateOptimalBid(ctx context.Context, opportunity *MEVOpportunity, competition *CompetitionMetrics) (*BiddingStrategy, error) { // Get current base fee latestBlock, err := ca.client.HeaderByNumber(ctx, nil) if err != nil { return nil, fmt.Errorf("failed to get latest block: %w", err) } baseFee := latestBlock.BaseFee if baseFee == nil { baseFee = big.NewInt(1000000000) // 1 gwei fallback } // Calculate competitive priority fee competitivePriorityFee := ca.calculateCompetitivePriorityFee(competition, opportunity.EstimatedProfit) // Calculate max fee per gas (EIP-1559) maxFeePerGas := new(big.Int).Add(baseFee, competitivePriorityFee) // Calculate total gas cost totalCost := new(big.Int).Mul(maxFeePerGas, big.NewInt(int64(opportunity.RequiredGas))) // Estimate success probability based on bid vs competition successProb := ca.estimateSuccessProbability(competitivePriorityFee, competition) strategy := &BiddingStrategy{ BaseFee: baseFee, PriorityFee: competitivePriorityFee, MaxFeePerGas: maxFeePerGas, GasLimit: opportunity.RequiredGas, TotalCost: totalCost, SuccessProbability: successProb, } // Validate profitability after gas costs netProfit := new(big.Int).Sub(opportunity.EstimatedProfit, totalCost) if netProfit.Sign() <= 0 { ca.logger.Warn(fmt.Sprintf("Opportunity not profitable after competitive bidding - Profit: %s, Cost: %s", formatEther(opportunity.EstimatedProfit), formatEther(totalCost))) return strategy, fmt.Errorf("opportunity not profitable with competitive gas pricing") } ca.logger.Info(fmt.Sprintf("Optimal bid calculated - Priority: %s gwei, Success rate: %.1f%%, Net profit: %s ETH", formatGwei(competitivePriorityFee), successProb*100, formatEther(netProfit))) return strategy, nil } // calculateCompetitivePriorityFee calculates a competitive priority fee func (ca *CompetitionAnalyzer) calculateCompetitivePriorityFee(competition *CompetitionMetrics, estimatedProfit *big.Int) *big.Int { // Base priority fee (current network average) basePriority := ca.getAveragePriorityFee() // Competition multiplier based on intensity competitionMultiplier := 1.0 + (competition.CompetitionIntensity * 10.0) // 1x to 11x // If there are competing bots, beat the highest bidder by 20% if competition.HighestPriorityFee != nil && competition.HighestPriorityFee.Sign() > 0 { competitorBid := new(big.Float).SetInt(competition.HighestPriorityFee) competitorBid.Mul(competitorBid, big.NewFloat(1.2)) // 20% higher competitorBidInt, _ := competitorBid.Int(nil) if competitorBidInt.Cmp(basePriority) > 0 { basePriority = competitorBidInt } } // Apply competition multiplier finalPriority := new(big.Float).SetInt(basePriority) finalPriority.Mul(finalPriority, big.NewFloat(competitionMultiplier)) // Cap at 50% of estimated profit to maintain profitability maxPriorityBasedOnProfit := new(big.Int).Div(estimatedProfit, big.NewInt(2)) result, _ := finalPriority.Int(nil) if result.Cmp(maxPriorityBasedOnProfit) > 0 { result = maxPriorityBasedOnProfit } // Minimum floor of 2 gwei for Arbitrum minPriority := big.NewInt(2000000000) // 2 gwei if result.Cmp(minPriority) < 0 { result = minPriority } return result } // updateFeeHistory updates the historical fee data for analysis func (ca *CompetitionAnalyzer) updateFeeHistory(ctx context.Context) error { latestBlock, err := ca.client.HeaderByNumber(ctx, nil) if err != nil { return err } // Add to history (keep last 50 blocks) if latestBlock.BaseFee != nil { ca.baseFeeHistory = append(ca.baseFeeHistory, latestBlock.BaseFee) if len(ca.baseFeeHistory) > 50 { ca.baseFeeHistory = ca.baseFeeHistory[1:] } } ca.lastUpdate = time.Now() return nil } // findCompetingTransactions finds transactions in mempool competing for similar opportunities func (ca *CompetitionAnalyzer) findCompetingTransactions(ctx context.Context, opportunity *MEVOpportunity) ([]*types.Transaction, error) { // In a real implementation, this would analyze the mempool for competing transactions // For now, we'll simulate based on opportunity type and current network conditions // Simulate competition based on opportunity type var competingCount int switch opportunity.OpportunityType { case "arbitrage": competingCount = 3 + int(time.Now().Unix()%5) // 3-7 competing bots case "liquidation": competingCount = 5 + int(time.Now().Unix()%8) // 5-12 competing bots case "sandwich": competingCount = 1 + int(time.Now().Unix()%3) // 1-3 competing bots default: competingCount = 2 } // Create simulated competing transactions var competingTxs []*types.Transaction for i := 0; i < competingCount; i++ { // In a real implementation, these would be actual pending transactions tx := &types.Transaction{} competingTxs = append(competingTxs, tx) } return competingTxs, nil } // calculateCompetitionIntensity calculates competition intensity (0-1 scale) func (ca *CompetitionAnalyzer) calculateCompetitionIntensity(competingTxs []*types.Transaction) float64 { if len(competingTxs) == 0 { return 0.0 } // Base intensity on number of competitors baseIntensity := float64(len(competingTxs)) / 10.0 // Normalize to 10 max competitors if baseIntensity > 1.0 { baseIntensity = 1.0 } // Adjust based on time of day (higher competition during US/EU hours) hour := time.Now().UTC().Hour() timeMultiplier := 1.0 if (hour >= 13 && hour <= 21) || (hour >= 6 && hour <= 14) { // US + EU hours timeMultiplier = 1.3 } intensity := baseIntensity * timeMultiplier if intensity > 1.0 { intensity = 1.0 } return intensity } // analyzePriorityFees analyzes priority fees of competing transactions func (ca *CompetitionAnalyzer) analyzePriorityFees(competingTxs []*types.Transaction) (*big.Int, *big.Int) { if len(competingTxs) == 0 { return big.NewInt(0), big.NewInt(0) } // Simulate priority fee analysis highest := big.NewInt(1000000000) // 1 gwei base total := big.NewInt(0) for i, _ := range competingTxs { // Simulate varying priority fees (1-10 gwei) fee := big.NewInt(1000000000 + int64(i)*1000000000) if fee.Cmp(highest) > 0 { highest = fee } total.Add(total, fee) } average := new(big.Int).Div(total, big.NewInt(int64(len(competingTxs)))) return highest, average } // getAveragePriorityFee returns the recent average priority fee func (ca *CompetitionAnalyzer) getAveragePriorityFee() *big.Int { if len(ca.priorityFeeHistory) == 0 { return big.NewInt(1500000000) // 1.5 gwei default for Arbitrum } total := big.NewInt(0) for _, fee := range ca.priorityFeeHistory { total.Add(total, fee) } return new(big.Int).Div(total, big.NewInt(int64(len(ca.priorityFeeHistory)))) } // estimateSuccessProbability estimates probability of transaction inclusion func (ca *CompetitionAnalyzer) estimateSuccessProbability(priorityFee *big.Int, competition *CompetitionMetrics) float64 { // Base probability on priority fee relative to competition baseProbability := 0.5 if competition.HighestPriorityFee != nil && competition.HighestPriorityFee.Sign() > 0 { ratio := new(big.Float).Quo(new(big.Float).SetInt(priorityFee), new(big.Float).SetInt(competition.HighestPriorityFee)) ratioFloat, _ := ratio.Float64() if ratioFloat > 1.2 { baseProbability = 0.9 // 90% if bidding 20% higher } else if ratioFloat > 1.0 { baseProbability = 0.7 // 70% if bidding slightly higher } else if ratioFloat > 0.8 { baseProbability = 0.4 // 40% if bidding somewhat lower } else { baseProbability = 0.1 // 10% if bidding much lower } } // Adjust for competition intensity adjustedProbability := baseProbability * (1.0 - competition.CompetitionIntensity*0.3) if adjustedProbability < 0.05 { adjustedProbability = 0.05 } return adjustedProbability } // estimateInclusionTime estimates time to transaction inclusion func (ca *CompetitionAnalyzer) estimateInclusionTime(intensity float64) time.Duration { // Base time is ~0.25 seconds for Arbitrum baseTime := 250 * time.Millisecond // Higher competition = longer inclusion time competitionDelay := time.Duration(intensity*2000) * time.Millisecond return baseTime + competitionDelay } // Helper functions for formatting func formatGwei(wei *big.Int) string { if wei == nil { return "0" } gwei := new(big.Float).SetInt(wei) gwei.Quo(gwei, big.NewFloat(1e9)) return fmt.Sprintf("%.2f", gwei) } func formatEther(wei *big.Int) string { if wei == nil { return "0.000000" } eth := new(big.Float).SetInt(wei) eth.Quo(eth, big.NewFloat(1e18)) return fmt.Sprintf("%.6f", eth) }