package arbitrum import ( "context" "fmt" "math/big" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/fraktal/mev-beta/internal/logger" ) // L2GasEstimator provides Arbitrum-specific gas estimation and optimization type L2GasEstimator struct { client *ArbitrumClient logger *logger.Logger // L2 gas price configuration baseFeeMultiplier float64 priorityFeeMin *big.Int priorityFeeMax *big.Int gasLimitMultiplier float64 } // GasEstimate represents an L2 gas estimate with detailed breakdown type GasEstimate struct { GasLimit uint64 MaxFeePerGas *big.Int MaxPriorityFee *big.Int L1DataFee *big.Int L2ComputeFee *big.Int TotalFee *big.Int Confidence float64 // 0-1 scale } // NewL2GasEstimator creates a new L2 gas estimator func NewL2GasEstimator(client *ArbitrumClient, logger *logger.Logger) *L2GasEstimator { return &L2GasEstimator{ client: client, logger: logger, baseFeeMultiplier: 1.1, // 10% buffer on base fee priorityFeeMin: big.NewInt(100000000), // 0.1 gwei minimum priorityFeeMax: big.NewInt(2000000000), // 2 gwei maximum gasLimitMultiplier: 1.2, // 20% buffer on gas limit } } // EstimateL2Gas provides comprehensive gas estimation for L2 transactions func (g *L2GasEstimator) EstimateL2Gas(ctx context.Context, tx *types.Transaction) (*GasEstimate, error) { // Get current gas price data gasPrice, err := g.client.SuggestGasPrice(ctx) if err != nil { return nil, fmt.Errorf("failed to get gas price: %v", err) } // Estimate gas limit gasLimit, err := g.estimateGasLimit(ctx, tx) if err != nil { return nil, fmt.Errorf("failed to estimate gas limit: %v", err) } // Get L1 data fee (Arbitrum-specific) l1DataFee, err := g.estimateL1DataFee(ctx, tx) if err != nil { g.logger.Warn(fmt.Sprintf("Failed to estimate L1 data fee: %v", err)) l1DataFee = big.NewInt(0) } // Calculate L2 compute fee l2ComputeFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasLimit))) // Calculate priority fee priorityFee := g.calculateOptimalPriorityFee(ctx, gasPrice) // Calculate max fee per gas maxFeePerGas := new(big.Int).Add(gasPrice, priorityFee) // Total fee includes both L1 and L2 components totalFee := new(big.Int).Add(l1DataFee, l2ComputeFee) // Apply gas limit buffer bufferedGasLimit := uint64(float64(gasLimit) * g.gasLimitMultiplier) estimate := &GasEstimate{ GasLimit: bufferedGasLimit, MaxFeePerGas: maxFeePerGas, MaxPriorityFee: priorityFee, L1DataFee: l1DataFee, L2ComputeFee: l2ComputeFee, TotalFee: totalFee, Confidence: g.calculateConfidence(gasPrice, priorityFee), } return estimate, nil } // estimateGasLimit estimates the gas limit for an L2 transaction func (g *L2GasEstimator) estimateGasLimit(ctx context.Context, tx *types.Transaction) (uint64, error) { // Create a call message for gas estimation msg := ethereum.CallMsg{ From: common.Address{}, // Will be overridden To: tx.To(), Value: tx.Value(), Data: tx.Data(), GasPrice: tx.GasPrice(), } // Estimate gas using the client gasLimit, err := g.client.EstimateGas(ctx, msg) if err != nil { // Fallback to default gas limits based on transaction type return g.getDefaultGasLimit(tx), nil } return gasLimit, nil } // estimateL1DataFee calculates the L1 data fee component (Arbitrum-specific) func (g *L2GasEstimator) estimateL1DataFee(ctx context.Context, tx *types.Transaction) (*big.Int, error) { // Arbitrum L1 data fee calculation // This is based on the calldata size and L1 gas price calldata := tx.Data() // Count zero and non-zero bytes (different costs) zeroBytes := 0 nonZeroBytes := 0 for _, b := range calldata { if b == 0 { zeroBytes++ } else { nonZeroBytes++ } } // Arbitrum L1 data fee formula (simplified) // Actual implementation would need to fetch current L1 gas price l1GasPrice := big.NewInt(20000000000) // 20 gwei estimate // Gas cost: 4 per zero byte, 16 per non-zero byte gasCost := int64(zeroBytes*4 + nonZeroBytes*16) // Add base transaction cost gasCost += 21000 l1DataFee := new(big.Int).Mul(l1GasPrice, big.NewInt(gasCost)) return l1DataFee, nil } // calculateOptimalPriorityFee calculates an optimal priority fee for fast inclusion func (g *L2GasEstimator) calculateOptimalPriorityFee(ctx context.Context, baseFee *big.Int) *big.Int { // Try to get recent priority fees from the network priorityFee, err := g.getSuggestedPriorityFee(ctx) if err != nil { // Fallback to base fee percentage priorityFee = new(big.Int).Div(baseFee, big.NewInt(10)) // 10% of base fee } // Ensure within bounds if priorityFee.Cmp(g.priorityFeeMin) < 0 { priorityFee = new(big.Int).Set(g.priorityFeeMin) } if priorityFee.Cmp(g.priorityFeeMax) > 0 { priorityFee = new(big.Int).Set(g.priorityFeeMax) } return priorityFee } // getSuggestedPriorityFee gets suggested priority fee from the network func (g *L2GasEstimator) getSuggestedPriorityFee(ctx context.Context) (*big.Int, error) { // Use eth_maxPriorityFeePerGas if available var result string err := g.client.rpcClient.CallContext(ctx, &result, "eth_maxPriorityFeePerGas") if err != nil { return nil, err } priorityFee := new(big.Int) if _, success := priorityFee.SetString(result[2:], 16); !success { return nil, fmt.Errorf("invalid priority fee response") } return priorityFee, nil } // calculateConfidence calculates confidence level for the gas estimate func (g *L2GasEstimator) calculateConfidence(gasPrice, priorityFee *big.Int) float64 { // Higher priority fee relative to gas price = higher confidence ratio := new(big.Float).Quo(new(big.Float).SetInt(priorityFee), new(big.Float).SetInt(gasPrice)) ratioFloat, _ := ratio.Float64() // Confidence scale: 0.1 ratio = 0.5 confidence, 0.5 ratio = 0.9 confidence confidence := 0.3 + (ratioFloat * 1.2) if confidence > 1.0 { confidence = 1.0 } if confidence < 0.1 { confidence = 0.1 } return confidence } // getDefaultGasLimit returns default gas limits based on transaction type func (g *L2GasEstimator) getDefaultGasLimit(tx *types.Transaction) uint64 { dataSize := len(tx.Data()) switch { case dataSize == 0: // Simple transfer return 21000 case dataSize < 100: // Simple contract interaction return 50000 case dataSize < 1000: // Complex contract interaction return 150000 case dataSize < 5000: // Very complex interaction (e.g., DEX swap) return 300000 default: // Extremely complex interaction return 500000 } } // OptimizeForSpeed adjusts gas parameters for fastest execution func (g *L2GasEstimator) OptimizeForSpeed(estimate *GasEstimate) *GasEstimate { optimized := *estimate // Increase priority fee by 50% speedPriorityFee := new(big.Int).Mul(estimate.MaxPriorityFee, big.NewInt(150)) optimized.MaxPriorityFee = new(big.Int).Div(speedPriorityFee, big.NewInt(100)) // Increase max fee per gas accordingly optimized.MaxFeePerGas = new(big.Int).Add( new(big.Int).Sub(estimate.MaxFeePerGas, estimate.MaxPriorityFee), optimized.MaxPriorityFee, ) // Increase gas limit by 10% more optimized.GasLimit = uint64(float64(estimate.GasLimit) * 1.1) // Recalculate total fee l2Fee := new(big.Int).Mul(optimized.MaxFeePerGas, big.NewInt(int64(optimized.GasLimit))) optimized.TotalFee = new(big.Int).Add(estimate.L1DataFee, l2Fee) // Higher confidence due to aggressive pricing optimized.Confidence = estimate.Confidence * 1.2 if optimized.Confidence > 1.0 { optimized.Confidence = 1.0 } return &optimized } // OptimizeForCost adjusts gas parameters for lowest cost func (g *L2GasEstimator) OptimizeForCost(estimate *GasEstimate) *GasEstimate { optimized := *estimate // Use minimum priority fee optimized.MaxPriorityFee = new(big.Int).Set(g.priorityFeeMin) // Reduce max fee per gas optimized.MaxFeePerGas = new(big.Int).Add( new(big.Int).Sub(estimate.MaxFeePerGas, estimate.MaxPriorityFee), optimized.MaxPriorityFee, ) // Use exact gas limit (no buffer) optimized.GasLimit = uint64(float64(estimate.GasLimit) / g.gasLimitMultiplier) // Recalculate total fee l2Fee := new(big.Int).Mul(optimized.MaxFeePerGas, big.NewInt(int64(optimized.GasLimit))) optimized.TotalFee = new(big.Int).Add(estimate.L1DataFee, l2Fee) // Lower confidence due to minimal gas pricing optimized.Confidence = estimate.Confidence * 0.7 return &optimized } // IsL2TransactionViable checks if an L2 transaction is economically viable func (g *L2GasEstimator) IsL2TransactionViable(estimate *GasEstimate, expectedProfit *big.Int) bool { // Compare total fee to expected profit return estimate.TotalFee.Cmp(expectedProfit) < 0 }