package arbitrage import ( "context" "fmt" "math/big" "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/arbitrum" "github.com/fraktal/mev-beta/pkg/math" "github.com/fraktal/mev-beta/pkg/security" pkgtypes "github.com/fraktal/mev-beta/pkg/types" ) // FlashSwapExecutor executes arbitrage using flash swaps for capital efficiency type FlashSwapExecutor struct { client *ethclient.Client logger *logger.Logger keyManager *security.KeyManager gasEstimator *arbitrum.L2GasEstimator decimalConverter *math.DecimalConverter // Contract addresses flashSwapContract common.Address arbitrageContract common.Address // Configuration config ExecutionConfig // State tracking pendingExecutions map[common.Hash]*ExecutionState executionHistory []*ExecutionResult totalProfit *math.UniversalDecimal totalGasCost *math.UniversalDecimal } // ExecutionConfig configures the flash swap executor type ExecutionConfig struct { // Risk management MaxSlippage *math.UniversalDecimal MinProfitThreshold *math.UniversalDecimal MaxPositionSize *math.UniversalDecimal MaxDailyVolume *math.UniversalDecimal // Gas settings GasLimitMultiplier float64 MaxGasPrice *math.UniversalDecimal PriorityFeeStrategy string // "conservative", "aggressive", "competitive" // Execution settings ExecutionTimeout time.Duration ConfirmationBlocks uint64 RetryAttempts int RetryDelay time.Duration // MEV protection EnableMEVProtection bool PrivateMempool bool FlashbotsRelay string // Monitoring EnableDetailedLogs bool TrackPerformance bool } // ExecutionState tracks the state of an ongoing execution type ExecutionState struct { Opportunity *pkgtypes.ArbitrageOpportunity TransactionHash common.Hash Status ExecutionStatus StartTime time.Time SubmissionTime time.Time ConfirmationTime time.Time GasUsed uint64 EffectiveGasPrice *big.Int ActualProfit *math.UniversalDecimal Error error } // ExecutionStatus represents the current status of an execution type ExecutionStatus string const ( StatusPending ExecutionStatus = "pending" StatusSubmitted ExecutionStatus = "submitted" StatusConfirmed ExecutionStatus = "confirmed" StatusFailed ExecutionStatus = "failed" StatusReverted ExecutionStatus = "reverted" ) // FlashSwapCalldata represents the data needed for a flash swap execution type FlashSwapCalldata struct { InitiatorPool common.Address TokenPath []common.Address Pools []common.Address AmountIn *big.Int MinAmountOut *big.Int Recipient common.Address Data []byte } // NewFlashSwapExecutor creates a new flash swap executor func NewFlashSwapExecutor( client *ethclient.Client, logger *logger.Logger, keyManager *security.KeyManager, gasEstimator *arbitrum.L2GasEstimator, flashSwapContract, arbitrageContract common.Address, config ExecutionConfig, ) *FlashSwapExecutor { executor := &FlashSwapExecutor{ client: client, logger: logger, keyManager: keyManager, gasEstimator: gasEstimator, decimalConverter: math.NewDecimalConverter(), flashSwapContract: flashSwapContract, arbitrageContract: arbitrageContract, config: config, pendingExecutions: make(map[common.Hash]*ExecutionState), executionHistory: make([]*ExecutionResult, 0), } // Initialize counters executor.totalProfit, _ = executor.decimalConverter.FromString("0", 18, "ETH") executor.totalGasCost, _ = executor.decimalConverter.FromString("0", 18, "ETH") // Set default configuration executor.setDefaultConfig() return executor } // setDefaultConfig sets default configuration values func (executor *FlashSwapExecutor) setDefaultConfig() { if executor.config.MaxSlippage == nil { executor.config.MaxSlippage, _ = executor.decimalConverter.FromString("1", 4, "PERCENT") // 1% } if executor.config.MinProfitThreshold == nil { executor.config.MinProfitThreshold, _ = executor.decimalConverter.FromString("0.01", 18, "ETH") } if executor.config.MaxPositionSize == nil { executor.config.MaxPositionSize, _ = executor.decimalConverter.FromString("10", 18, "ETH") } if executor.config.GasLimitMultiplier == 0 { executor.config.GasLimitMultiplier = 1.2 // 20% buffer } if executor.config.ExecutionTimeout == 0 { executor.config.ExecutionTimeout = 30 * time.Second } if executor.config.ConfirmationBlocks == 0 { executor.config.ConfirmationBlocks = 1 // Arbitrum has fast finality } if executor.config.RetryAttempts == 0 { executor.config.RetryAttempts = 3 } if executor.config.RetryDelay == 0 { executor.config.RetryDelay = 2 * time.Second } if executor.config.PriorityFeeStrategy == "" { executor.config.PriorityFeeStrategy = "competitive" } } // ExecuteArbitrage executes an arbitrage opportunity using flash swaps func (executor *FlashSwapExecutor) ExecuteArbitrage(ctx context.Context, opportunity *pkgtypes.ArbitrageOpportunity) (*ExecutionResult, error) { executor.logger.Info(fmt.Sprintf("🚀 Executing arbitrage opportunity: %s profit expected", opportunity.NetProfit.String())) // Validate opportunity before execution if err := executor.validateOpportunity(opportunity); err != nil { return nil, fmt.Errorf("opportunity validation failed: %w", err) } // Create execution state executionState := &ExecutionState{ Opportunity: opportunity, Status: StatusPending, StartTime: time.Now(), } // Prepare flash swap transaction flashSwapData, err := executor.prepareFlashSwap(opportunity) if err != nil { result := executor.createFailedResult(executionState, fmt.Errorf("failed to prepare flash swap: %w", err)) return result, nil } // Get transaction options with dynamic gas pricing transactOpts, err := executor.getTransactionOptions(ctx, flashSwapData) if err != nil { return executor.createFailedResult(executionState, fmt.Errorf("failed to get transaction options: %w", err)), nil } // Execute with retry logic var result *ExecutionResult for attempt := 0; attempt <= executor.config.RetryAttempts; attempt++ { if attempt > 0 { executor.logger.Info(fmt.Sprintf("Retrying execution attempt %d/%d", attempt, executor.config.RetryAttempts)) time.Sleep(executor.config.RetryDelay) } result = executor.executeWithTimeout(ctx, executionState, flashSwapData, transactOpts) // If successful or non-retryable error, break errorMsg := "" if result.Error != nil { errorMsg = result.Error.Error() } if result.Success || !executor.isRetryableError(errorMsg) { break } // Update gas price for retry if attempt < executor.config.RetryAttempts { transactOpts, err = executor.updateGasPriceForRetry(ctx, transactOpts, attempt) if err != nil { executor.logger.Warn(fmt.Sprintf("Failed to update gas price for retry: %v", err)) } } } // Update statistics executor.updateExecutionStats(result) status := "Unknown" if result.Success { status = "Success" } else if result.Error != nil { status = "Failed" } else { status = "Incomplete" } executor.logger.Info(fmt.Sprintf("✅ Arbitrage execution completed: %s", status)) if result.Success && result.ProfitRealized != nil { // Note: opportunity.NetProfit is not directly accessible through ExecutionResult structure // So we just log that execution was successful with actual profit executor.logger.Info(fmt.Sprintf("💰 Actual profit: %s ETH", formatEther(result.ProfitRealized))) } return result, nil } // validateOpportunity validates an opportunity before execution func (executor *FlashSwapExecutor) validateOpportunity(opportunity *pkgtypes.ArbitrageOpportunity) error { // Check minimum profit threshold minProfitWei := big.NewInt(10000000000000000) // 0.01 ETH in wei if opportunity.NetProfit.Cmp(minProfitWei) < 0 { return fmt.Errorf("profit %s below minimum threshold %s", opportunity.NetProfit.String(), minProfitWei.String()) } // Check maximum position size maxPositionWei := big.NewInt(1000000000000000000) // 1 ETH in wei if opportunity.AmountIn.Cmp(maxPositionWei) > 0 { return fmt.Errorf("position size %s exceeds maximum %s", opportunity.AmountIn.String(), maxPositionWei.String()) } // Check price impact maxPriceImpact := 5.0 // 5% max if opportunity.PriceImpact > maxPriceImpact { return fmt.Errorf("price impact %.2f%% too high", opportunity.PriceImpact) } // Check confidence level if opportunity.Confidence < 0.7 { return fmt.Errorf("confidence level %.1f%% too low", opportunity.Confidence*100) } // Check execution path if len(opportunity.Path) < 2 { return fmt.Errorf("empty execution path") } // Basic validation for path if len(opportunity.Path) < 2 { return fmt.Errorf("path must have at least 2 tokens") } return nil } // prepareFlashSwap prepares the flash swap transaction data func (executor *FlashSwapExecutor) prepareFlashSwap(opportunity *pkgtypes.ArbitrageOpportunity) (*FlashSwapCalldata, error) { if len(opportunity.Path) < 2 { return nil, fmt.Errorf("path must have at least 2 tokens") } // Convert path strings to token addresses tokenPath := make([]common.Address, 0, len(opportunity.Path)) for _, tokenAddr := range opportunity.Path { tokenPath = append(tokenPath, common.HexToAddress(tokenAddr)) } // Use pool addresses from opportunity if available poolAddresses := make([]common.Address, 0, len(opportunity.Pools)) for _, poolAddr := range opportunity.Pools { poolAddresses = append(poolAddresses, common.HexToAddress(poolAddr)) } // Calculate minimum output with slippage protection expectedOutput := opportunity.Profit // Calculate minimum output with slippage protection using basic math slippagePercent := opportunity.MaxSlippage / 100.0 // Convert percentage to decimal slippageFactor := big.NewFloat(1.0 - slippagePercent) expectedFloat := new(big.Float).SetInt(expectedOutput) minOutputFloat := new(big.Float).Mul(expectedFloat, slippageFactor) minAmountOut, _ := minOutputFloat.Int(nil) // Ensure minAmountOut is not negative if minAmountOut.Sign() < 0 { minAmountOut = big.NewInt(0) } // Create flash swap data calldata := &FlashSwapCalldata{ InitiatorPool: poolAddresses[0], // First pool initiates the flash swap TokenPath: tokenPath, Pools: poolAddresses, AmountIn: opportunity.AmountIn, MinAmountOut: minAmountOut, Recipient: executor.arbitrageContract, // Our arbitrage contract Data: executor.encodeArbitrageData(opportunity), } return calldata, nil } // encodeArbitrageData encodes the arbitrage execution data func (executor *FlashSwapExecutor) encodeArbitrageData(opportunity *pkgtypes.ArbitrageOpportunity) []byte { // In production, this would properly ABI-encode the arbitrage parameters // For demonstration, we'll create a simple encoding that includes key parameters // This is a simplified approach - real implementation would use proper ABI encoding // with go-ethereum's abi package data := []byte(fmt.Sprintf("arbitrage:%s:%s:%s:%s", opportunity.TokenIn, opportunity.TokenOut, opportunity.AmountIn.String(), opportunity.Profit.String())) if len(data) > 1024 { // Limit the size data = data[:1024] } return data } // getTransactionOptions prepares transaction options with dynamic gas pricing func (executor *FlashSwapExecutor) getTransactionOptions(ctx context.Context, flashSwapData *FlashSwapCalldata) (*bind.TransactOpts, error) { // Get active private key privateKey, err := executor.keyManager.GetActivePrivateKey() if err != nil { return nil, fmt.Errorf("failed to get private key: %w", err) } // Get chain ID chainID, err := executor.client.ChainID(ctx) if err != nil { return nil, fmt.Errorf("failed to get chain ID: %w", err) } // Create transaction options transactOpts, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) if err != nil { return nil, fmt.Errorf("failed to create transactor: %w", err) } // For gas estimation, we would normally call the contract method with callMsg // Since we're using a mock implementation, we'll use a reasonable default // In production, you'd do: gasLimit, err := client.EstimateGas(ctx, callMsg) // For demonstration purposes, we'll use a reasonable default gas limit estimatedGas := uint64(800000) // Standard for complex flash swaps // Apply gas limit multiplier adjustedGasLimit := uint64(float64(estimatedGas) * executor.config.GasLimitMultiplier) transactOpts.GasLimit = adjustedGasLimit // Get gas price from network for proper EIP-1559 transaction suggestedTip, err := executor.client.SuggestGasTipCap(ctx) if err != nil { // Default priority fee suggestedTip = big.NewInt(100000000) // 0.1 gwei } baseFee, err := executor.client.HeaderByNumber(ctx, nil) if err != nil || baseFee.BaseFee == nil { // For networks that don't support EIP-1559 or on error defaultBaseFee := big.NewInt(1000000000) // 1 gwei transactOpts.GasFeeCap = new(big.Int).Add(defaultBaseFee, suggestedTip) } else { // EIP-1559 gas pricing: FeeCap = baseFee*2 + priorityFee transactOpts.GasFeeCap = new(big.Int).Add( new(big.Int).Mul(baseFee.BaseFee, big.NewInt(2)), suggestedTip, ) } transactOpts.GasTipCap = suggestedTip executor.logger.Debug(fmt.Sprintf("Gas estimate - Limit: %d, MaxFee: %s, Priority: %s", adjustedGasLimit, transactOpts.GasFeeCap.String(), transactOpts.GasTipCap.String())) // Apply priority fee strategy executor.applyPriorityFeeStrategy(transactOpts) return transactOpts, nil } // applyPriorityFeeStrategy adjusts gas pricing based on strategy func (executor *FlashSwapExecutor) applyPriorityFeeStrategy(transactOpts *bind.TransactOpts) { switch executor.config.PriorityFeeStrategy { case "aggressive": // Increase priority fee by 50% if transactOpts.GasTipCap != nil { newTip := new(big.Int).Mul(transactOpts.GasTipCap, big.NewInt(150)) transactOpts.GasTipCap = new(big.Int).Div(newTip, big.NewInt(100)) } case "competitive": // Increase priority fee by 25% if transactOpts.GasTipCap != nil { newTip := new(big.Int).Mul(transactOpts.GasTipCap, big.NewInt(125)) transactOpts.GasTipCap = new(big.Int).Div(newTip, big.NewInt(100)) } case "conservative": // Use default priority fee (no change) } // Ensure we don't exceed maximum gas price if executor.config.MaxGasPrice != nil && transactOpts.GasFeeCap != nil { if transactOpts.GasFeeCap.Cmp(big.NewInt(50000000000)) > 0 { transactOpts.GasFeeCap = new(big.Int).Set(big.NewInt(50000000000)) } } } // executeWithTimeout executes the flash swap with timeout protection func (executor *FlashSwapExecutor) executeWithTimeout( ctx context.Context, executionState *ExecutionState, flashSwapData *FlashSwapCalldata, transactOpts *bind.TransactOpts, ) *ExecutionResult { // Create timeout context timeoutCtx, cancel := context.WithTimeout(ctx, executor.config.ExecutionTimeout) defer cancel() // Submit transaction tx, err := executor.submitTransaction(timeoutCtx, flashSwapData, transactOpts) if err != nil { return executor.createFailedResult(executionState, fmt.Errorf("transaction submission failed: %w", err)) } executionState.TransactionHash = tx.Hash() executionState.Status = StatusSubmitted executionState.SubmissionTime = time.Now() executor.pendingExecutions[tx.Hash()] = executionState executor.logger.Info(fmt.Sprintf("📤 Transaction submitted: %s", tx.Hash().Hex())) // Wait for confirmation receipt, err := executor.waitForConfirmation(timeoutCtx, tx.Hash()) if err != nil { return executor.createFailedResult(executionState, fmt.Errorf("confirmation failed: %w", err)) } executionState.ConfirmationTime = time.Now() executionState.GasUsed = receipt.GasUsed executionState.EffectiveGasPrice = receipt.EffectiveGasPrice // Check transaction status if receipt.Status == types.ReceiptStatusFailed { executionState.Status = StatusReverted return executor.createFailedResult(executionState, fmt.Errorf("transaction reverted")) } executionState.Status = StatusConfirmed // Calculate actual results actualProfit, err := executor.calculateActualProfit(receipt, executionState.Opportunity) if err != nil { executor.logger.Warn(fmt.Sprintf("Failed to calculate actual profit: %v", err)) actualProfit = executionState.Opportunity.NetProfit // Use expected as fallback } executionState.ActualProfit = actualProfit // Create successful result return executor.createSuccessfulResult(executionState, receipt) } // submitTransaction submits the flash swap transaction func (executor *FlashSwapExecutor) submitTransaction( ctx context.Context, flashSwapData *FlashSwapCalldata, transactOpts *bind.TransactOpts, ) (*types.Transaction, error) { // This is a simplified implementation // Production would call the actual flash swap contract executor.logger.Debug("Submitting flash swap transaction...") executor.logger.Debug(fmt.Sprintf(" Initiator Pool: %s", flashSwapData.InitiatorPool.Hex())) executor.logger.Debug(fmt.Sprintf(" Amount In: %s", flashSwapData.AmountIn.String())) executor.logger.Debug(fmt.Sprintf(" Min Amount Out: %s", flashSwapData.MinAmountOut.String())) executor.logger.Debug(fmt.Sprintf(" Token Path: %d tokens", len(flashSwapData.TokenPath))) executor.logger.Debug(fmt.Sprintf(" Pool Path: %d pools", len(flashSwapData.Pools))) // For demonstration, create a mock transaction // Production would interact with actual contracts // This is where we would actually call the flash swap contract method // For now, we'll simulate creating a transaction that would call the flash swap function // In production, you'd call the actual contract function like: // tx, err := executor.flashSwapContract.FlashSwap(transactOpts, flashSwapData.InitiatorPool, ...) // For this mock implementation, we'll return a transaction that would call the mock contract nonce, err := executor.client.PendingNonceAt(context.Background(), transactOpts.From) if err != nil { nonce = 0 // fallback } // Create a mock transaction tx := types.NewTransaction( nonce, flashSwapData.InitiatorPool, // Flash swap contract address big.NewInt(0), // Value - no direct ETH transfer in flash swaps transactOpts.GasLimit, transactOpts.GasFeeCap, flashSwapData.Data, // Encoded flash swap data ) // In a real implementation, you'd need to sign and send the transaction // For now, return a transaction object for the simulation return tx, nil } // waitForConfirmation waits for transaction confirmation func (executor *FlashSwapExecutor) waitForConfirmation(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { executor.logger.Debug(fmt.Sprintf("Waiting for confirmation of transaction: %s", txHash.Hex())) // For demonstration, simulate a successful transaction // Production would poll for actual transaction receipt select { case <-ctx.Done(): return nil, fmt.Errorf("timeout waiting for confirmation") case <-time.After(3 * time.Second): // Simulate network delay // Create mock receipt receipt := &types.Receipt{ TxHash: txHash, Status: types.ReceiptStatusSuccessful, GasUsed: 750000, EffectiveGasPrice: big.NewInt(100000000), // 0.1 gwei BlockNumber: big.NewInt(1000000), } return receipt, nil } } // calculateActualProfit calculates the actual profit from the transaction func (executor *FlashSwapExecutor) calculateActualProfit(receipt *types.Receipt, opportunity *pkgtypes.ArbitrageOpportunity) (*math.UniversalDecimal, error) { // Calculate actual gas cost gasCost := new(big.Int).Mul(big.NewInt(int64(receipt.GasUsed)), receipt.EffectiveGasPrice) gasCostDecimal, err := math.NewUniversalDecimal(gasCost, 18, "ETH") if err != nil { return nil, err } // For demonstration, assume we got the expected output // Production would parse the transaction logs to get actual amounts expectedOutput := opportunity.Profit // Use the decimal converter to convert to ETH equivalent // For simplicity, assume both input and output are already in compatible formats // In real implementation, you'd need actual price data netProfit, err := executor.decimalConverter.Subtract(expectedOutput, opportunity.AmountIn) if err != nil { return nil, err } // Subtract gas costs from net profit netProfit, err = executor.decimalConverter.Subtract(netProfit, gasCostDecimal) if err != nil { return nil, err } return netProfit, nil } // createSuccessfulResult creates a successful execution result func (executor *FlashSwapExecutor) createSuccessfulResult(state *ExecutionState, receipt *types.Receipt) *ExecutionResult { // Convert UniversalDecimal to big.Int for ProfitRealized profitRealized := big.NewInt(0) if state.ActualProfit != nil { profitRealized = state.ActualProfit } // Create a minimal ArbitragePath based on the opportunity path := &ArbitragePath{ Tokens: []common.Address{state.Opportunity.TokenIn, state.Opportunity.TokenOut}, // Basic 2-token path Pools: []*PoolInfo{}, // Empty for now Protocols: []string{}, // Empty for now Fees: []int64{}, // Empty for now EstimatedGas: big.NewInt(0), // To be calculated NetProfit: profitRealized, ROI: 0, // To be calculated LastUpdated: time.Now(), } gasCost := new(big.Int).Mul(big.NewInt(int64(receipt.GasUsed)), receipt.EffectiveGasPrice) gasCostDecimal, _ := math.NewUniversalDecimal(gasCost, 18, "ETH") return &ExecutionResult{ TransactionHash: state.TransactionHash, GasUsed: receipt.GasUsed, GasPrice: receipt.EffectiveGasPrice, GasCost: gasCostDecimal, ProfitRealized: profitRealized, Success: true, Error: nil, ErrorMessage: "", Status: "Success", ExecutionTime: time.Since(state.StartTime), Path: path, } } // createFailedResult creates a failed execution result func (executor *FlashSwapExecutor) createFailedResult(state *ExecutionState, err error) *ExecutionResult { // Create a minimal ArbitragePath based on the opportunity path := &ArbitragePath{ Tokens: []common.Address{state.Opportunity.TokenIn, state.Opportunity.TokenOut}, // Basic 2-token path Pools: []*PoolInfo{}, // Empty for now Protocols: []string{}, // Empty for now Fees: []int64{}, // Empty for now EstimatedGas: big.NewInt(0), // To be calculated NetProfit: big.NewInt(0), ROI: 0, // To be calculated LastUpdated: time.Now(), } gasCostDecimal, _ := math.NewUniversalDecimal(big.NewInt(0), 18, "ETH") return &ExecutionResult{ TransactionHash: state.TransactionHash, GasUsed: 0, GasPrice: big.NewInt(0), GasCost: gasCostDecimal, ProfitRealized: big.NewInt(0), Success: false, Error: err, ErrorMessage: err.Error(), Status: "Failed", ExecutionTime: time.Since(state.StartTime), Path: path, } } // isRetryableError determines if an error is retryable func (executor *FlashSwapExecutor) isRetryableError(errorMsg string) bool { retryableErrors := []string{ "gas price too low", "nonce too low", "timeout", "network error", "connection refused", "transaction underpriced", "replacement transaction underpriced", "known transaction", } for _, retryable := range retryableErrors { if strings.Contains(strings.ToLower(errorMsg), strings.ToLower(retryable)) { return true } } return false } // updateGasPriceForRetry updates gas price for retry attempts func (executor *FlashSwapExecutor) updateGasPriceForRetry( ctx context.Context, transactOpts *bind.TransactOpts, attempt int, ) (*bind.TransactOpts, error) { // Increase gas price by 20% for each retry multiplier := 1.0 + float64(attempt)*0.2 if transactOpts.GasFeeCap != nil { newGasFeeCap := new(big.Float).Mul( new(big.Float).SetInt(transactOpts.GasFeeCap), big.NewFloat(multiplier), ) newGasFeeCapInt, _ := newGasFeeCap.Int(nil) transactOpts.GasFeeCap = newGasFeeCapInt } if transactOpts.GasTipCap != nil { newGasTipCap := new(big.Float).Mul( new(big.Float).SetInt(transactOpts.GasTipCap), big.NewFloat(multiplier), ) newGasTipCapInt, _ := newGasTipCap.Int(nil) transactOpts.GasTipCap = newGasTipCapInt } executor.logger.Debug(fmt.Sprintf("Updated gas prices for retry %d: MaxFee=%s, Priority=%s", attempt, transactOpts.GasFeeCap.String(), transactOpts.GasTipCap.String())) return transactOpts, nil } // updateExecutionStats updates execution statistics func (executor *FlashSwapExecutor) updateExecutionStats(result *ExecutionResult) { executor.executionHistory = append(executor.executionHistory, result) if result.Success && result.ProfitRealized != nil { profitDecimal, _ := math.NewUniversalDecimal(result.ProfitRealized, 18, "ETH") executor.totalProfit, _ = executor.decimalConverter.Add(executor.totalProfit, profitDecimal) } if result.GasCost != nil { executor.totalGasCost, _ = executor.decimalConverter.Add(executor.totalGasCost, result.GasCost) } // Clean up pending executions delete(executor.pendingExecutions, result.TransactionHash) // Keep only last 100 execution results if len(executor.executionHistory) > 100 { executor.executionHistory = executor.executionHistory[len(executor.executionHistory)-100:] } } // GetExecutionStats returns execution statistics func (executor *FlashSwapExecutor) GetExecutionStats() ExecutionStats { successCount := 0 totalExecutions := len(executor.executionHistory) for _, result := range executor.executionHistory { if result.Success { successCount++ } } successRate := 0.0 if totalExecutions > 0 { successRate = float64(successCount) / float64(totalExecutions) * 100 } return ExecutionStats{ TotalExecutions: totalExecutions, SuccessfulExecutions: successCount, SuccessRate: successRate, TotalProfit: executor.totalProfit, TotalGasCost: executor.totalGasCost, PendingExecutions: len(executor.pendingExecutions), } } // ExecutionStats contains execution statistics type ExecutionStats struct { TotalExecutions int SuccessfulExecutions int SuccessRate float64 TotalProfit *math.UniversalDecimal TotalGasCost *math.UniversalDecimal PendingExecutions int } // GetPendingExecutions returns currently pending executions func (executor *FlashSwapExecutor) GetPendingExecutions() map[common.Hash]*ExecutionState { return executor.pendingExecutions } // GetExecutionHistory returns recent execution history func (executor *FlashSwapExecutor) GetExecutionHistory(limit int) []*ExecutionResult { if limit <= 0 || limit > len(executor.executionHistory) { limit = len(executor.executionHistory) } start := len(executor.executionHistory) - limit return executor.executionHistory[start:] }