// Package contracts provides integration with MEV smart contracts for arbitrage execution package contracts import ( "context" "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" etypes "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/interfaces" "github.com/fraktal/mev-beta/internal/config" "github.com/fraktal/mev-beta/internal/logger" stypes "github.com/fraktal/mev-beta/pkg/types" ) // ContractExecutor handles execution of arbitrage opportunities through smart contracts type ContractExecutor struct { config *config.BotConfig logger *logger.Logger client *ethclient.Client arbitrage *arbitrage.ArbitrageExecutor flashSwapper *flashswap.BaseFlashSwapper privateKey string accountAddress common.Address chainID *big.Int gasPrice *big.Int pendingNonce uint64 lastNonceUpdate time.Time } // NewContractExecutor creates a new contract executor func NewContractExecutor( cfg *config.Config, logger *logger.Logger, ) (*ContractExecutor, error) { // Connect to Ethereum client client, err := ethclient.Dial(cfg.Arbitrum.RPCEndpoint) if err != nil { return nil, fmt.Errorf("failed to connect to Ethereum node: %w", err) } // Parse contract addresses from config arbitrageAddr := common.HexToAddress(cfg.Contracts.ArbitrageExecutor) flashSwapperAddr := common.HexToAddress(cfg.Contracts.FlashSwapper) // Create contract instances arbitrageContract, err := arbitrage.NewArbitrageExecutor(arbitrageAddr, client) if err != nil { return nil, fmt.Errorf("failed to instantiate arbitrage contract: %w", err) } flashSwapperContract, err := flashswap.NewBaseFlashSwapper(flashSwapperAddr, client) if err != nil { return nil, fmt.Errorf("failed to instantiate flash swapper contract: %w", err) } // Get chain ID chainID, err := client.ChainID(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get chain ID: %w", err) } executor := &ContractExecutor{ config: &cfg.Bot, logger: logger, client: client, arbitrage: arbitrageContract, flashSwapper: flashSwapperContract, privateKey: cfg.Ethereum.PrivateKey, accountAddress: common.HexToAddress(cfg.Ethereum.AccountAddress), chainID: chainID, gasPrice: big.NewInt(0), pendingNonce: 0, } // Initialize gas price if err := executor.updateGasPrice(); err != nil { logger.Warn(fmt.Sprintf("Failed to initialize gas price: %v", err)) } logger.Info("Contract executor initialized successfully") return executor, nil } // ExecuteArbitrage executes a standard arbitrage opportunity func (ce *ContractExecutor) ExecuteArbitrage(ctx context.Context, opportunity stypes.ArbitrageOpportunity) (*etypes.Transaction, error) { ce.logger.Info(fmt.Sprintf("Executing arbitrage opportunity: %+v", opportunity)) // Convert opportunity to contract parameters params := ce.convertToArbitrageParams(opportunity) // Prepare transaction options opts, err := ce.prepareTransactionOpts(ctx) if err != nil { return nil, fmt.Errorf("failed to prepare transaction options: %w", err) } // Execute arbitrage through contract tx, err := ce.arbitrage.ExecuteArbitrage(opts, params) if err != nil { return nil, fmt.Errorf("failed to execute arbitrage: %w", err) } ce.logger.Info(fmt.Sprintf("Arbitrage transaction submitted: %s", tx.Hash().Hex())) return tx, nil } // ExecuteTriangularArbitrage executes a triangular arbitrage opportunity func (ce *ContractExecutor) ExecuteTriangularArbitrage(ctx context.Context, opportunity stypes.ArbitrageOpportunity) (*etypes.Transaction, error) { ce.logger.Info(fmt.Sprintf("Executing triangular arbitrage opportunity: %+v", opportunity)) // Convert opportunity to contract parameters params := ce.convertToTriangularArbitrageParams(opportunity) // Prepare transaction options opts, err := ce.prepareTransactionOpts(ctx) if err != nil { return nil, fmt.Errorf("failed to prepare transaction options: %w", err) } // Execute triangular arbitrage through contract tx, err := ce.arbitrage.ExecuteTriangularArbitrage(opts, params) if err != nil { return nil, fmt.Errorf("failed to execute triangular arbitrage: %w", err) } ce.logger.Info(fmt.Sprintf("Triangular arbitrage transaction submitted: %s", tx.Hash().Hex())) return tx, nil } // convertToArbitrageParams converts a scanner opportunity to contract parameters func (ce *ContractExecutor) convertToArbitrageParams(opportunity stypes.ArbitrageOpportunity) interfaces.IArbitrageArbitrageParams { // Convert token addresses tokens := make([]common.Address, len(opportunity.Path)) for i, token := range opportunity.Path { tokens[i] = common.HexToAddress(token) } // Convert pool addresses pools := make([]common.Address, len(opportunity.Pools)) for i, pool := range opportunity.Pools { pools[i] = common.HexToAddress(pool) } // Convert amounts (simplified for now) amounts := make([]*big.Int, len(pools)) for i := range amounts { // Use a default amount for now - in practice this should be calculated based on optimal trade size amounts[i] = big.NewInt(1000000000000000000) // 1 ETH equivalent } // Convert swap data (empty for now - in practice this would contain encoded swap parameters) swapData := make([][]byte, len(pools)) for i := range swapData { swapData[i] = []byte{} } // Create parameters struct params := interfaces.IArbitrageArbitrageParams{ Tokens: tokens, Pools: pools, Amounts: amounts, SwapData: swapData, MinProfit: opportunity.Profit, // Use estimated profit as minimum required profit } return params } // convertToTriangularArbitrageParams converts a scanner opportunity to triangular arbitrage parameters func (ce *ContractExecutor) convertToTriangularArbitrageParams(opportunity stypes.ArbitrageOpportunity) interfaces.IArbitrageTriangularArbitrageParams { // For triangular arbitrage, we expect exactly 3 tokens forming a triangle if len(opportunity.Path) < 3 { ce.logger.Error("Invalid triangular arbitrage path - insufficient tokens") return interfaces.IArbitrageTriangularArbitrageParams{} } // Extract the three tokens tokenA := common.HexToAddress(opportunity.Path[0]) tokenB := common.HexToAddress(opportunity.Path[1]) tokenC := common.HexToAddress(opportunity.Path[2]) // Extract pools (should be 3 for triangular arbitrage) if len(opportunity.Pools) < 3 { ce.logger.Error("Invalid triangular arbitrage pools - insufficient pools") return interfaces.IArbitrageTriangularArbitrageParams{} } poolAB := common.HexToAddress(opportunity.Pools[0]) poolBC := common.HexToAddress(opportunity.Pools[1]) poolCA := common.HexToAddress(opportunity.Pools[2]) // Create parameters struct params := interfaces.IArbitrageTriangularArbitrageParams{ TokenA: tokenA, TokenB: tokenB, TokenC: tokenC, PoolAB: poolAB, PoolBC: poolBC, PoolCA: poolCA, AmountIn: big.NewInt(1000000000000000000), // 1 ETH equivalent (placeholder) MinProfit: opportunity.Profit, // Use estimated profit as minimum required profit SwapDataAB: []byte{}, // Placeholder for actual swap data SwapDataBC: []byte{}, // Placeholder for actual swap data SwapDataCA: []byte{}, // Placeholder for actual swap data } return params } // prepareTransactionOpts prepares transaction options with proper gas pricing and nonce func (ce *ContractExecutor) prepareTransactionOpts(ctx context.Context) (*bind.TransactOpts, error) { // Update gas price if needed if err := ce.updateGasPrice(); err != nil { ce.logger.Warn(fmt.Sprintf("Failed to update gas price: %v", err)) } // Get current nonce nonce, err := ce.client.PendingNonceAt(ctx, ce.accountAddress) if err != nil { return nil, fmt.Errorf("failed to get account nonce: %w", err) } // Create transaction options opts := &bind.TransactOpts{ From: ce.accountAddress, Nonce: big.NewInt(int64(nonce)), Signer: ce.signTransaction, // Custom signer function Value: big.NewInt(0), // No ETH value for arbitrage transactions GasPrice: ce.gasPrice, GasLimit: 0, // Let the node estimate gas limit Context: ctx, NoSend: false, } return opts, nil } // updateGasPrice updates the gas price estimate func (ce *ContractExecutor) updateGasPrice() error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Get suggested gas price from node gasPrice, err := ce.client.SuggestGasPrice(ctx) if err != nil { return fmt.Errorf("failed to suggest gas price: %w", err) } // Apply gas price multiplier from config (if set) if ce.config.Ethereum.GasPriceMultiplier > 1.0 { multiplier := big.NewFloat(ce.config.Ethereum.GasPriceMultiplier) gasPriceFloat := new(big.Float).SetInt(gasPrice) adjustedGasPriceFloat := new(big.Float).Mul(gasPriceFloat, multiplier) adjustedGasPrice, _ := adjustedGasPriceFloat.Int(nil) ce.gasPrice = adjustedGasPrice } else { ce.gasPrice = gasPrice } return nil } // signTransaction signs a transaction with the configured private key func (ce *ContractExecutor) signTransaction(address common.Address, tx *etypes.Transaction) (*etypes.Transaction, error) { // In a production implementation, you would use the private key to sign the transaction // For now, we'll return the transaction as-is since we're using the client's built-in signing ce.logger.Debug("Signing transaction (placeholder)") return tx, nil } // Close closes the contract executor and releases resources func (ce *ContractExecutor) Close() { if ce.client != nil { ce.client.Close() } }