package arbitrage import ( "context" "fmt" "log/slog" "math/big" "github.com/your-org/mev-bot/pkg/types" ) // GasEstimatorConfig contains configuration for gas estimation type GasEstimatorConfig struct { BaseGas uint64 // Base gas cost per transaction GasPerPool uint64 // Additional gas per pool/hop V2SwapGas uint64 // Gas for UniswapV2-style swap V3SwapGas uint64 // Gas for UniswapV3 swap CurveSwapGas uint64 // Gas for Curve swap GasPriceMultiplier float64 // Multiplier for gas price (e.g., 1.1 for 10% buffer) } // DefaultGasEstimatorConfig returns default configuration based on observed Arbitrum gas costs func DefaultGasEstimatorConfig() *GasEstimatorConfig { return &GasEstimatorConfig{ BaseGas: 21000, // Base transaction cost GasPerPool: 10000, // Buffer per additional pool V2SwapGas: 120000, // V2 swap V3SwapGas: 180000, // V3 swap (more complex) CurveSwapGas: 150000, // Curve swap GasPriceMultiplier: 1.1, // 10% buffer } } // GasEstimator estimates gas costs for arbitrage opportunities type GasEstimator struct { config *GasEstimatorConfig logger *slog.Logger } // NewGasEstimator creates a new gas estimator func NewGasEstimator(config *GasEstimatorConfig, logger *slog.Logger) *GasEstimator { if config == nil { config = DefaultGasEstimatorConfig() } return &GasEstimator{ config: config, logger: logger.With("component", "gas_estimator"), } } // EstimateGasCost estimates the total gas cost for executing a path func (g *GasEstimator) EstimateGasCost(ctx context.Context, path *Path, gasPrice *big.Int) (*big.Int, error) { if gasPrice == nil || gasPrice.Sign() <= 0 { return nil, fmt.Errorf("invalid gas price") } totalGas := g.config.BaseGas // Estimate gas for each pool in the path for _, pool := range path.Pools { poolGas := g.estimatePoolGas(pool.Protocol) totalGas += poolGas } // Apply multiplier for safety buffer totalGasFloat := float64(totalGas) * g.config.GasPriceMultiplier totalGasWithBuffer := uint64(totalGasFloat) // Calculate cost: totalGas * gasPrice gasCost := new(big.Int).Mul( big.NewInt(int64(totalGasWithBuffer)), gasPrice, ) g.logger.Debug("estimated gas cost", "poolCount", len(path.Pools), "totalGas", totalGasWithBuffer, "gasPrice", gasPrice.String(), "totalCost", gasCost.String(), ) return gasCost, nil } // estimatePoolGas estimates gas cost for a single pool swap func (g *GasEstimator) estimatePoolGas(protocol types.ProtocolType) uint64 { switch protocol { case types.ProtocolUniswapV2, types.ProtocolSushiSwap: return g.config.V2SwapGas case types.ProtocolUniswapV3: return g.config.V3SwapGas case types.ProtocolCurve: return g.config.CurveSwapGas default: // Default to V2 gas cost for unknown protocols return g.config.V2SwapGas } } // EstimateGasLimit estimates the gas limit for executing a path func (g *GasEstimator) EstimateGasLimit(ctx context.Context, path *Path) (uint64, error) { totalGas := g.config.BaseGas for _, pool := range path.Pools { poolGas := g.estimatePoolGas(pool.Protocol) totalGas += poolGas } // Apply buffer totalGasFloat := float64(totalGas) * g.config.GasPriceMultiplier gasLimit := uint64(totalGasFloat) return gasLimit, nil } // EstimateOptimalGasPrice estimates an optimal gas price for execution func (g *GasEstimator) EstimateOptimalGasPrice(ctx context.Context, netProfit *big.Int, path *Path, currentGasPrice *big.Int) (*big.Int, error) { if netProfit == nil || netProfit.Sign() <= 0 { return currentGasPrice, nil } // Calculate gas limit gasLimit, err := g.EstimateGasLimit(ctx, path) if err != nil { return nil, err } // Maximum gas price we can afford while staying profitable // maxGasPrice = netProfit / gasLimit maxGasPrice := new(big.Int).Div(netProfit, big.NewInt(int64(gasLimit))) // Use current gas price if it's lower than max if currentGasPrice.Cmp(maxGasPrice) < 0 { return currentGasPrice, nil } // Use 90% of max gas price to maintain profit margin optimalGasPrice := new(big.Int).Mul(maxGasPrice, big.NewInt(90)) optimalGasPrice.Div(optimalGasPrice, big.NewInt(100)) g.logger.Debug("calculated optimal gas price", "netProfit", netProfit.String(), "gasLimit", gasLimit, "currentGasPrice", currentGasPrice.String(), "maxGasPrice", maxGasPrice.String(), "optimalGasPrice", optimalGasPrice.String(), ) return optimalGasPrice, nil } // CompareGasCosts compares gas costs across different opportunity types func (g *GasEstimator) CompareGasCosts(ctx context.Context, opportunities []*Opportunity, gasPrice *big.Int) ([]*GasCostComparison, error) { comparisons := make([]*GasCostComparison, 0, len(opportunities)) for _, opp := range opportunities { // Reconstruct path for gas estimation path := &Path{ Pools: make([]*types.PoolInfo, len(opp.Path)), Type: opp.Type, } for i, step := range opp.Path { path.Pools[i] = &types.PoolInfo{ Address: step.PoolAddress, Protocol: step.Protocol, } } gasCost, err := g.EstimateGasCost(ctx, path, gasPrice) if err != nil { g.logger.Warn("failed to estimate gas cost", "oppID", opp.ID, "error", err) continue } comparison := &GasCostComparison{ OpportunityID: opp.ID, Type: opp.Type, HopCount: len(opp.Path), EstimatedGas: gasCost, NetProfit: opp.NetProfit, ROI: opp.ROI, } // Calculate efficiency: profit per gas unit if gasCost.Sign() > 0 { efficiency := new(big.Float).Quo( new(big.Float).SetInt(opp.NetProfit), new(big.Float).SetInt(gasCost), ) efficiencyFloat, _ := efficiency.Float64() comparison.Efficiency = efficiencyFloat } comparisons = append(comparisons, comparison) } g.logger.Info("compared gas costs", "opportunityCount", len(opportunities), "comparisonCount", len(comparisons), ) return comparisons, nil } // GasCostComparison contains comparison data for gas costs type GasCostComparison struct { OpportunityID string Type OpportunityType HopCount int EstimatedGas *big.Int NetProfit *big.Int ROI float64 Efficiency float64 // Profit per gas unit } // GetMostEfficientOpportunity returns the opportunity with the best efficiency func (g *GasEstimator) GetMostEfficientOpportunity(comparisons []*GasCostComparison) *GasCostComparison { if len(comparisons) == 0 { return nil } mostEfficient := comparisons[0] for _, comp := range comparisons[1:] { if comp.Efficiency > mostEfficient.Efficiency { mostEfficient = comp } } return mostEfficient }