removed the fucking vendor files
This commit is contained in:
283
pkg/contracts/executor.go
Normal file
283
pkg/contracts/executor.go
Normal file
@@ -0,0 +1,283 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user