package arbitrage import ( "context" "crypto/ecdsa" "fmt" "math/big" "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/bindings/arbitrage" "github.com/fraktal/mev-beta/bindings/flashswap" "github.com/fraktal/mev-beta/bindings/tokens" "github.com/fraktal/mev-beta/bindings/uniswap" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/mev" "github.com/fraktal/mev-beta/pkg/security" ) // ArbitrageExecutor manages the execution of arbitrage opportunities using smart contracts type ArbitrageExecutor struct { client *ethclient.Client logger *logger.Logger keyManager *security.KeyManager competitionAnalyzer *mev.CompetitionAnalyzer // Contract instances arbitrageContract *arbitrage.ArbitrageExecutor flashSwapContract *flashswap.BaseFlashSwapper // Contract addresses arbitrageAddress common.Address flashSwapAddress common.Address // Configuration maxGasPrice *big.Int maxGasLimit uint64 slippageTolerance float64 minProfitThreshold *big.Int // Transaction options transactOpts *bind.TransactOpts callOpts *bind.CallOpts } // ExecutionResult represents the result of an arbitrage execution type ExecutionResult struct { TransactionHash common.Hash GasUsed uint64 GasPrice *big.Int ProfitRealized *big.Int Success bool Error error ExecutionTime time.Duration Path *ArbitragePath } // ArbitrageParams contains parameters for arbitrage execution type ArbitrageParams struct { Path *ArbitragePath InputAmount *big.Int MinOutputAmount *big.Int Deadline *big.Int FlashSwapData []byte } // NewArbitrageExecutor creates a new arbitrage executor func NewArbitrageExecutor( client *ethclient.Client, logger *logger.Logger, keyManager *security.KeyManager, arbitrageAddr common.Address, flashSwapAddr common.Address, ) (*ArbitrageExecutor, error) { logger.Info(fmt.Sprintf("Creating arbitrage contract instance at %s", arbitrageAddr.Hex())) // Create contract instances arbitrageContract, err := arbitrage.NewArbitrageExecutor(arbitrageAddr, client) if err != nil { return nil, fmt.Errorf("failed to create arbitrage contract instance: %w", err) } logger.Info("Arbitrage contract instance created successfully") logger.Info(fmt.Sprintf("Creating flash swap contract instance at %s", flashSwapAddr.Hex())) flashSwapContract, err := flashswap.NewBaseFlashSwapper(flashSwapAddr, client) if err != nil { return nil, fmt.Errorf("failed to create flash swap contract instance: %w", err) } logger.Info("Flash swap contract instance created successfully") logger.Info("Creating MEV competition analyzer...") // Initialize MEV competition analyzer for profitable bidding competitionAnalyzer := mev.NewCompetitionAnalyzer(client, logger) logger.Info("MEV competition analyzer created successfully") logger.Info("Getting active private key from key manager...") // Use a timeout to prevent hanging type keyResult struct { key *ecdsa.PrivateKey err error } keyChannel := make(chan keyResult, 1) go func() { key, err := keyManager.GetActivePrivateKey() keyChannel <- keyResult{key, err} }() var privateKey *ecdsa.PrivateKey select { case result := <-keyChannel: if result.err != nil { logger.Warn("⚠️ Could not get private key, will run in monitoring mode only") // For now, just continue without transaction capabilities return &ArbitrageExecutor{ client: client, logger: logger, keyManager: keyManager, competitionAnalyzer: competitionAnalyzer, arbitrageAddress: arbitrageAddr, flashSwapAddress: flashSwapAddr, maxGasPrice: big.NewInt(5000000000), // 5 gwei maxGasLimit: 800000, slippageTolerance: 0.01, // 1% minProfitThreshold: big.NewInt(10000000000000000), // 0.01 ETH }, nil } privateKey = result.key case <-time.After(5 * time.Second): logger.Warn("⚠️ Key retrieval timed out, will run in monitoring mode only") return &ArbitrageExecutor{ client: client, logger: logger, keyManager: keyManager, competitionAnalyzer: competitionAnalyzer, arbitrageAddress: arbitrageAddr, flashSwapAddress: flashSwapAddr, maxGasPrice: big.NewInt(5000000000), // 5 gwei maxGasLimit: 800000, slippageTolerance: 0.01, // 1% minProfitThreshold: big.NewInt(10000000000000000), // 0.01 ETH }, nil } logger.Info("Active private key retrieved successfully") logger.Info("Getting network ID...") // Create transaction options chainID, err := client.NetworkID(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get chain ID: %w", err) } transactOpts, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) if err != nil { return nil, fmt.Errorf("failed to create transactor: %w", err) } // Set default gas parameters transactOpts.GasLimit = 800000 // 800k gas limit transactOpts.GasPrice = big.NewInt(2000000000) // 2 gwei return &ArbitrageExecutor{ client: client, logger: logger, keyManager: keyManager, competitionAnalyzer: competitionAnalyzer, // CRITICAL: MEV competition analysis arbitrageContract: arbitrageContract, flashSwapContract: flashSwapContract, arbitrageAddress: arbitrageAddr, flashSwapAddress: flashSwapAddr, maxGasPrice: big.NewInt(5000000000), // 5 gwei max (realistic for Arbitrum) maxGasLimit: 1500000, // 1.5M gas max (realistic for complex arbitrage) slippageTolerance: 0.003, // 0.3% slippage tolerance (tight for profit) minProfitThreshold: big.NewInt(50000000000000000), // 0.05 ETH minimum profit (realistic after gas) transactOpts: transactOpts, callOpts: &bind.CallOpts{}, }, nil } // ExecuteArbitrage executes an arbitrage opportunity using flash swaps with MEV competition analysis func (ae *ArbitrageExecutor) ExecuteArbitrage(ctx context.Context, params *ArbitrageParams) (*ExecutionResult, error) { // Create MEV opportunity for competition analysis opportunity := &mev.MEVOpportunity{ TxHash: "", // Will be filled after execution Block: 0, // Current block OpportunityType: "arbitrage", EstimatedProfit: big.NewInt(1000000000000000000), // 1 ETH default, will be calculated properly RequiredGas: 800000, // Estimated gas for arbitrage } // Analyze MEV competition competition, err := ae.competitionAnalyzer.AnalyzeCompetition(ctx, opportunity) if err != nil { ae.logger.Warn(fmt.Sprintf("Competition analysis failed, proceeding with default strategy: %v", err)) // Continue with default execution } // Calculate optimal bidding strategy var biddingStrategy *mev.BiddingStrategy if competition != nil { biddingStrategy, err = ae.competitionAnalyzer.CalculateOptimalBid(ctx, opportunity, competition) if err != nil { ae.logger.Error(fmt.Sprintf("Failed to calculate optimal bid: %v", err)) return nil, fmt.Errorf("arbitrage not profitable with competitive gas pricing: %w", err) } // Update transaction options with competitive gas pricing ae.transactOpts.GasPrice = biddingStrategy.PriorityFee ae.transactOpts.GasLimit = biddingStrategy.GasLimit ae.logger.Info(fmt.Sprintf("MEV Strategy: Priority fee: %s gwei, Success rate: %.1f%%, Net profit expected: %s ETH", formatGweiFromWei(biddingStrategy.PriorityFee), biddingStrategy.SuccessProbability*100, formatEtherFromWei(new(big.Int).Sub(opportunity.EstimatedProfit, biddingStrategy.TotalCost)))) } start := time.Now() ae.logger.Info(fmt.Sprintf("Starting arbitrage execution for path with %d hops, expected profit: %s ETH", len(params.Path.Pools), formatEther(params.Path.NetProfit))) result := &ExecutionResult{ Path: params.Path, ExecutionTime: 0, Success: false, } // Pre-execution validation if err := ae.validateExecution(ctx, params); err != nil { result.Error = fmt.Errorf("validation failed: %w", err) return result, result.Error } // Update gas price based on network conditions if err := ae.updateGasPrice(ctx); err != nil { ae.logger.Warn(fmt.Sprintf("Failed to update gas price: %v", err)) } // Prepare flash swap parameters flashSwapParams, err := ae.prepareFlashSwapParams(params) if err != nil { result.Error = fmt.Errorf("failed to prepare flash swap parameters: %w", err) return result, result.Error } // Execute the flash swap arbitrage tx, err := ae.executeFlashSwapArbitrage(ctx, flashSwapParams) if err != nil { result.Error = fmt.Errorf("flash swap execution failed: %w", err) return result, result.Error } result.TransactionHash = tx.Hash() // Wait for transaction confirmation receipt, err := ae.waitForConfirmation(ctx, tx.Hash()) if err != nil { result.Error = fmt.Errorf("transaction confirmation failed: %w", err) return result, result.Error } // Process execution results result.GasUsed = receipt.GasUsed result.GasPrice = tx.GasPrice() result.Success = receipt.Status == types.ReceiptStatusSuccessful if result.Success { // Calculate actual profit actualProfit, err := ae.calculateActualProfit(ctx, receipt) if err != nil { ae.logger.Warn(fmt.Sprintf("Failed to calculate actual profit: %v", err)) actualProfit = params.Path.NetProfit // Fallback to estimated } result.ProfitRealized = actualProfit ae.logger.Info(fmt.Sprintf("Arbitrage execution successful! TX: %s, Gas used: %d, Profit: %s ETH", result.TransactionHash.Hex(), result.GasUsed, formatEther(result.ProfitRealized))) } else { result.Error = fmt.Errorf("transaction failed with status %d", receipt.Status) ae.logger.Error(fmt.Sprintf("Arbitrage execution failed! TX: %s, Gas used: %d", result.TransactionHash.Hex(), result.GasUsed)) } result.ExecutionTime = time.Since(start) return result, result.Error } // validateExecution validates the arbitrage execution parameters func (ae *ArbitrageExecutor) validateExecution(ctx context.Context, params *ArbitrageParams) error { // Check minimum profit threshold if params.Path.NetProfit.Cmp(ae.minProfitThreshold) < 0 { return fmt.Errorf("profit %s below minimum threshold %s", formatEther(params.Path.NetProfit), formatEther(ae.minProfitThreshold)) } // Validate path has at least 2 hops if len(params.Path.Pools) < 2 { return fmt.Errorf("arbitrage path must have at least 2 hops") } // Check token balances if needed for i, pool := range params.Path.Pools { if err := ae.validatePoolLiquidity(ctx, pool, params.InputAmount); err != nil { return fmt.Errorf("pool %d validation failed: %w", i, err) } } // Check gas price is reasonable currentGasPrice, err := ae.client.SuggestGasPrice(ctx) if err != nil { return fmt.Errorf("failed to get current gas price: %w", err) } if currentGasPrice.Cmp(ae.maxGasPrice) > 0 { return fmt.Errorf("gas price too high: %s > %s", currentGasPrice.String(), ae.maxGasPrice.String()) } return nil } // validatePoolLiquidity validates that a pool has sufficient liquidity func (ae *ArbitrageExecutor) validatePoolLiquidity(ctx context.Context, pool *PoolInfo, amount *big.Int) error { // Create ERC20 contract instance to check pool reserves token0Contract, err := tokens.NewIERC20(pool.Token0, ae.client) if err != nil { return fmt.Errorf("failed to create token0 contract: %w", err) } token1Contract, err := tokens.NewIERC20(pool.Token1, ae.client) if err != nil { return fmt.Errorf("failed to create token1 contract: %w", err) } // Check balances of the pool balance0, err := token0Contract.BalanceOf(ae.callOpts, pool.Address) if err != nil { return fmt.Errorf("failed to get token0 balance: %w", err) } balance1, err := token1Contract.BalanceOf(ae.callOpts, pool.Address) if err != nil { return fmt.Errorf("failed to get token1 balance: %w", err) } // Ensure sufficient liquidity (at least 10x the swap amount) minLiquidity := new(big.Int).Mul(amount, big.NewInt(10)) if balance0.Cmp(minLiquidity) < 0 && balance1.Cmp(minLiquidity) < 0 { return fmt.Errorf("insufficient liquidity: balance0=%s, balance1=%s, required=%s", balance0.String(), balance1.String(), minLiquidity.String()) } return nil } // prepareFlashSwapParams prepares parameters for the flash swap execution func (ae *ArbitrageExecutor) prepareFlashSwapParams(params *ArbitrageParams) (*FlashSwapParams, error) { // Build the swap path for the flash swap contract path := make([]common.Address, len(params.Path.Tokens)) copy(path, params.Path.Tokens) // Build pool addresses pools := make([]common.Address, len(params.Path.Pools)) for i, pool := range params.Path.Pools { pools[i] = pool.Address } // Build fee array fees := make([]*big.Int, len(params.Path.Fees)) for i, fee := range params.Path.Fees { fees[i] = big.NewInt(fee) } // Calculate minimum output with slippage tolerance slippageMultiplier := big.NewFloat(1.0 - ae.slippageTolerance) expectedOutputFloat := new(big.Float).SetInt(params.MinOutputAmount) minOutputFloat := new(big.Float).Mul(expectedOutputFloat, slippageMultiplier) minOutput := new(big.Int) minOutputFloat.Int(minOutput) return &FlashSwapParams{ TokenPath: path, PoolPath: pools, Fees: fees, AmountIn: params.InputAmount, MinAmountOut: minOutput, Deadline: params.Deadline, FlashSwapData: params.FlashSwapData, }, nil } // FlashSwapParams contains parameters for flash swap execution type FlashSwapParams struct { TokenPath []common.Address PoolPath []common.Address Fees []*big.Int AmountIn *big.Int MinAmountOut *big.Int Deadline *big.Int FlashSwapData []byte } // executeFlashSwapArbitrage executes the flash swap arbitrage transaction func (ae *ArbitrageExecutor) executeFlashSwapArbitrage(ctx context.Context, params *FlashSwapParams) (*types.Transaction, error) { // Set deadline if not provided (5 minutes from now) if params.Deadline == nil { params.Deadline = big.NewInt(time.Now().Add(5 * time.Minute).Unix()) } // Estimate gas limit gasLimit, err := ae.estimateGasForArbitrage(ctx, params) if err != nil { ae.logger.Warn(fmt.Sprintf("Gas estimation failed, using default: %v", err)) gasLimit = ae.maxGasLimit } ae.transactOpts.GasLimit = gasLimit ae.transactOpts.Context = ctx ae.logger.Debug(fmt.Sprintf("Executing flash swap with params: tokens=%v, pools=%v, amountIn=%s, minOut=%s, gasLimit=%d", params.TokenPath, params.PoolPath, params.AmountIn.String(), params.MinAmountOut.String(), gasLimit)) // Execute the arbitrage through the deployed Uniswap V3 pool using flash swap // We'll use the Uniswap V3 pool directly for flash swaps since it's already deployed // Get the pool address for the first pair in the path poolAddress := params.PoolPath[0] // Create flash swap parameters flashSwapParams := flashswap.IFlashSwapperFlashSwapParams{ Token0: params.TokenPath[0], Token1: params.TokenPath[1], Amount0: params.AmountIn, Amount1: big.NewInt(0), // We only need one token for flash swap To: ae.transactOpts.From, // Send back to our account Data: []byte{}, // Encode arbitrage data if needed } // Execute flash swap using Uniswap V3 pool tx, err := ae.executeUniswapV3FlashSwap(ctx, poolAddress, flashSwapParams) if err != nil { return nil, fmt.Errorf("failed to execute Uniswap V3 flash swap: %w", err) } ae.logger.Info(fmt.Sprintf("Flash swap transaction submitted: %s", tx.Hash().Hex())) return tx, nil } // estimateGasForArbitrage estimates gas needed for the arbitrage transaction func (ae *ArbitrageExecutor) estimateGasForArbitrage(ctx context.Context, params *FlashSwapParams) (uint64, error) { // For now, return a conservative estimate // In production, this would call the contract's estimateGas method estimatedGas := uint64(500000) // 500k gas conservative estimate // Add 20% buffer to estimated gas gasWithBuffer := estimatedGas + (estimatedGas * 20 / 100) if gasWithBuffer > ae.maxGasLimit { gasWithBuffer = ae.maxGasLimit } return gasWithBuffer, nil } // updateGasPrice updates gas price based on network conditions func (ae *ArbitrageExecutor) updateGasPrice(ctx context.Context) error { gasPrice, err := ae.client.SuggestGasPrice(ctx) if err != nil { return err } // Apply 10% premium for faster execution premiumGasPrice := new(big.Int).Add(gasPrice, new(big.Int).Div(gasPrice, big.NewInt(10))) if premiumGasPrice.Cmp(ae.maxGasPrice) > 0 { premiumGasPrice = ae.maxGasPrice } ae.transactOpts.GasPrice = premiumGasPrice ae.logger.Debug(fmt.Sprintf("Updated gas price to %s wei", premiumGasPrice.String())) return nil } // waitForConfirmation waits for transaction confirmation func (ae *ArbitrageExecutor) waitForConfirmation(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { timeout := 30 * time.Second ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() for { select { case <-ctx.Done(): return nil, fmt.Errorf("transaction confirmation timeout") case <-ticker.C: receipt, err := ae.client.TransactionReceipt(ctx, txHash) if err == nil { return receipt, nil } // Continue waiting if transaction is not yet mined } } } // calculateActualProfit calculates the actual profit from transaction receipt func (ae *ArbitrageExecutor) calculateActualProfit(ctx context.Context, receipt *types.Receipt) (*big.Int, error) { // Parse logs to find profit events for _, log := range receipt.Logs { if log.Address == ae.arbitrageAddress { // Parse arbitrage execution event event, err := ae.arbitrageContract.ParseArbitrageExecuted(*log) if err != nil { continue // Not the event we're looking for } return event.Profit, nil } } // If no event found, calculate from balance changes return ae.calculateProfitFromBalanceChange(ctx, receipt) } // calculateProfitFromBalanceChange calculates REAL profit from balance changes func (ae *ArbitrageExecutor) calculateProfitFromBalanceChange(ctx context.Context, receipt *types.Receipt) (*big.Int, error) { // Parse ArbitrageExecuted event from transaction receipt for _, log := range receipt.Logs { if len(log.Topics) >= 4 && log.Topics[0].Hex() == "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925" { // ArbitrageExecuted event signature // topic[1] = tokenA, topic[2] = tokenB // data contains: amountIn, profit, gasUsed if len(log.Data) >= 96 { // 3 * 32 bytes amountIn := new(big.Int).SetBytes(log.Data[0:32]) profit := new(big.Int).SetBytes(log.Data[32:64]) gasUsed := new(big.Int).SetBytes(log.Data[64:96]) ae.logger.Info(fmt.Sprintf("Arbitrage executed - AmountIn: %s, Profit: %s, Gas: %s", amountIn.String(), profit.String(), gasUsed.String())) // Verify profit covers gas costs gasCost := new(big.Int).Mul(gasUsed, receipt.EffectiveGasPrice) netProfit := new(big.Int).Sub(profit, gasCost) if netProfit.Sign() > 0 { ae.logger.Info(fmt.Sprintf("PROFITABLE ARBITRAGE - Net profit: %s ETH", formatEther(netProfit))) return netProfit, nil } else { ae.logger.Warn(fmt.Sprintf("UNPROFITABLE ARBITRAGE - Loss: %s ETH", formatEther(new(big.Int).Neg(netProfit)))) return big.NewInt(0), nil } } } } // If no event found, this means the arbitrage failed or was unprofitable ae.logger.Warn("No ArbitrageExecuted event found - transaction likely failed") return big.NewInt(0), fmt.Errorf("no arbitrage execution detected in transaction") } // formatEther converts wei to ETH string with 6 decimal places 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) } // formatEtherFromWei is an alias for formatEther for consistency func formatEtherFromWei(wei *big.Int) string { return formatEther(wei) } // formatGweiFromWei converts wei to gwei string func formatGweiFromWei(wei *big.Int) string { if wei == nil { return "0.0" } gwei := new(big.Float).SetInt(wei) gwei.Quo(gwei, big.NewFloat(1e9)) return fmt.Sprintf("%.2f", gwei) } // GetArbitrageHistory retrieves historical arbitrage executions by parsing contract events func (ae *ArbitrageExecutor) GetArbitrageHistory(ctx context.Context, fromBlock, toBlock *big.Int) ([]*ArbitrageEvent, error) { ae.logger.Info(fmt.Sprintf("Fetching arbitrage history from block %s to %s", fromBlock.String(), toBlock.String())) // Create filter options for arbitrage events filterOpts := &bind.FilterOpts{ Start: fromBlock.Uint64(), End: &[]uint64{toBlock.Uint64()}[0], Context: ctx, } var allEvents []*ArbitrageEvent // Fetch ArbitrageExecuted events - using proper filter signature executeIter, err := ae.arbitrageContract.FilterArbitrageExecuted(filterOpts, nil) if err != nil { return nil, fmt.Errorf("failed to filter arbitrage executed events: %w", err) } defer executeIter.Close() for executeIter.Next() { event := executeIter.Event arbitrageEvent := &ArbitrageEvent{ TransactionHash: event.Raw.TxHash, BlockNumber: event.Raw.BlockNumber, TokenIn: event.Tokens[0], // First token in tokens array TokenOut: event.Tokens[len(event.Tokens)-1], // Last token in tokens array AmountIn: event.Amounts[0], // First amount in amounts array AmountOut: event.Amounts[len(event.Amounts)-1], // Last amount in amounts array Profit: event.Profit, Timestamp: time.Now(), // Would parse from block timestamp in production } allEvents = append(allEvents, arbitrageEvent) } if err := executeIter.Error(); err != nil { return nil, fmt.Errorf("error iterating arbitrage executed events: %w", err) } // Fetch FlashSwapExecuted events - using proper filter signature flashIter, err := ae.flashSwapContract.FilterFlashSwapExecuted(filterOpts, nil, nil, nil) if err != nil { ae.logger.Warn(fmt.Sprintf("Failed to filter flash swap events: %v", err)) } else { defer flashIter.Close() for flashIter.Next() { event := flashIter.Event flashEvent := &ArbitrageEvent{ TransactionHash: event.Raw.TxHash, BlockNumber: event.Raw.BlockNumber, TokenIn: event.Token0, // Flash swap token 0 TokenOut: event.Token1, // Flash swap token 1 AmountIn: event.Amount0, AmountOut: event.Amount1, Profit: big.NewInt(0), // Flash swaps don't directly show profit Timestamp: time.Now(), } allEvents = append(allEvents, flashEvent) } if err := flashIter.Error(); err != nil { return nil, fmt.Errorf("error iterating flash swap events: %w", err) } } ae.logger.Info(fmt.Sprintf("Retrieved %d arbitrage events from blocks %s to %s", len(allEvents), fromBlock.String(), toBlock.String())) return allEvents, nil } // ArbitrageEvent represents a historical arbitrage event type ArbitrageEvent struct { TransactionHash common.Hash BlockNumber uint64 TokenIn common.Address TokenOut common.Address AmountIn *big.Int AmountOut *big.Int Profit *big.Int Timestamp time.Time } // Helper method to check if execution is profitable after gas costs func (ae *ArbitrageExecutor) IsProfitableAfterGas(path *ArbitragePath, gasPrice *big.Int) bool { gasCost := new(big.Int).Mul(gasPrice, big.NewInt(int64(path.EstimatedGas.Uint64()))) netProfit := new(big.Int).Sub(path.NetProfit, gasCost) return netProfit.Cmp(ae.minProfitThreshold) > 0 } // SetConfiguration updates executor configuration func (ae *ArbitrageExecutor) SetConfiguration(config *ExecutorConfig) { if config.MaxGasPrice != nil { ae.maxGasPrice = config.MaxGasPrice } if config.MaxGasLimit > 0 { ae.maxGasLimit = config.MaxGasLimit } if config.SlippageTolerance > 0 { ae.slippageTolerance = config.SlippageTolerance } if config.MinProfitThreshold != nil { ae.minProfitThreshold = config.MinProfitThreshold } } // executeUniswapV3FlashSwap executes a flash swap directly on a Uniswap V3 pool func (ae *ArbitrageExecutor) executeUniswapV3FlashSwap(ctx context.Context, poolAddress common.Address, params flashswap.IFlashSwapperFlashSwapParams) (*types.Transaction, error) { ae.logger.Debug(fmt.Sprintf("Executing Uniswap V3 flash swap on pool %s", poolAddress.Hex())) // Create pool contract instance using IUniswapV3PoolActions interface poolContract, err := uniswap.NewIUniswapV3PoolActions(poolAddress, ae.client) if err != nil { return nil, fmt.Errorf("failed to create pool contract instance: %w", err) } // For Uniswap V3, we use the pool's flash function directly // The callback will handle the arbitrage logic // Encode the arbitrage data that will be passed to the callback arbitrageData, err := ae.encodeArbitrageData(params) if err != nil { return nil, fmt.Errorf("failed to encode arbitrage data: %w", err) } // Execute flash swap on the pool // amount0 > 0 means we're borrowing token0, amount1 > 0 means we're borrowing token1 tx, err := poolContract.Flash(ae.transactOpts, ae.transactOpts.From, params.Amount0, params.Amount1, arbitrageData) if err != nil { return nil, fmt.Errorf("flash swap transaction failed: %w", err) } ae.logger.Info(fmt.Sprintf("Uniswap V3 flash swap initiated: %s", tx.Hash().Hex())) return tx, nil } // encodeArbitrageData encodes the arbitrage parameters for the flash callback func (ae *ArbitrageExecutor) encodeArbitrageData(params flashswap.IFlashSwapperFlashSwapParams) ([]byte, error) { // For now, we'll encode basic parameters // In production, this would include the full arbitrage path and swap details data := struct { Token0 common.Address Token1 common.Address Amount0 *big.Int Amount1 *big.Int To common.Address }{ Token0: params.Token0, Token1: params.Token1, Amount0: params.Amount0, Amount1: params.Amount1, To: params.To, } // For simplicity, we'll just return the data as JSON bytes // In production, you'd use proper ABI encoding return []byte(fmt.Sprintf(`{"token0":"%s","token1":"%s","amount0":"%s","amount1":"%s","to":"%s"}`, data.Token0.Hex(), data.Token1.Hex(), data.Amount0.String(), data.Amount1.String(), data.To.Hex())), nil } // ExecutorConfig contains configuration for the arbitrage executor type ExecutorConfig struct { MaxGasPrice *big.Int MaxGasLimit uint64 SlippageTolerance float64 MinProfitThreshold *big.Int }