Files
mev-beta/internal/config/config.go
2025-11-08 10:37:52 -06:00

880 lines
28 KiB
Go

package config
import (
"fmt"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"time"
"gopkg.in/yaml.v3"
)
// Config represents the application configuration
type Config struct {
Arbitrum ArbitrumConfig `yaml:"arbitrum"`
Bot BotConfig `yaml:"bot"`
Uniswap UniswapConfig `yaml:"uniswap"`
Log LogConfig `yaml:"log"`
Database DatabaseConfig `yaml:"database"`
Ethereum EthereumConfig `yaml:"ethereum"`
Contracts ContractsConfig `yaml:"contracts"`
Arbitrage ArbitrageConfig `yaml:"arbitrage"`
Features Features `yaml:"features"`
ArbitrageOptimized ArbitrageOptimizedConfig `yaml:"arbitrage_optimized"`
}
// ArbitrumConfig represents the Arbitrum node configuration
type ArbitrumConfig struct {
// Chain ID for Arbitrum (42161 for mainnet)
ChainID int64 `yaml:"chain_id"`
// Reading endpoints (WSS preferred for real-time monitoring)
ReadingEndpoints []EndpointConfig `yaml:"reading_endpoints"`
// Execution endpoints (HTTP/HTTPS or WSS for transaction submission)
ExecutionEndpoints []EndpointConfig `yaml:"execution_endpoints"`
// Fallback endpoints for failover scenarios
FallbackEndpoints []EndpointConfig `yaml:"fallback_endpoints"`
// Legacy fields for backward compatibility
RPCEndpoint string `yaml:"rpc_endpoint,omitempty"`
WSEndpoint string `yaml:"ws_endpoint,omitempty"`
// Global rate limiting configuration
RateLimit RateLimitConfig `yaml:"rate_limit"`
}
// EndpointConfig represents an RPC endpoint configuration
type EndpointConfig struct {
// RPC endpoint URL
URL string `yaml:"url"`
// Endpoint name for identification
Name string `yaml:"name"`
// Priority (lower number = higher priority)
Priority int `yaml:"priority"`
// Maximum requests per second for this endpoint
MaxRPS int `yaml:"max_rps"`
// Maximum concurrent connections
MaxConcurrent int `yaml:"max_concurrent"`
// Connection timeout in seconds
TimeoutSeconds int `yaml:"timeout_seconds"`
// Health check interval in seconds
HealthCheckInterval int `yaml:"health_check_interval"`
// Rate limiting configuration for this endpoint
RateLimit RateLimitConfig `yaml:"rate_limit"`
}
// RateLimitConfig represents rate limiting configuration
type RateLimitConfig struct {
// Maximum requests per second
RequestsPerSecond int `yaml:"requests_per_second"`
// Maximum concurrent requests
MaxConcurrent int `yaml:"max_concurrent"`
// Burst size for rate limiting
Burst int `yaml:"burst"`
}
// BotConfig represents the bot configuration
type BotConfig struct {
// Enable or disable the bot
Enabled bool `yaml:"enabled"`
// Polling interval in seconds
PollingInterval int `yaml:"polling_interval"`
// Minimum profit threshold in USD
MinProfitThreshold float64 `yaml:"min_profit_threshold"`
// Gas price multiplier (for faster transactions)
GasPriceMultiplier float64 `yaml:"gas_price_multiplier"`
// Maximum number of concurrent workers for processing
MaxWorkers int `yaml:"max_workers"`
// Buffer size for channels
ChannelBufferSize int `yaml:"channel_buffer_size"`
// Timeout for RPC calls in seconds
RPCTimeout int `yaml:"rpc_timeout"`
}
// UniswapConfig represents the Uniswap configuration
type UniswapConfig struct {
// Factory contract address
FactoryAddress string `yaml:"factory_address"`
// Position manager contract address
PositionManagerAddress string `yaml:"position_manager_address"`
// Supported fee tiers
FeeTiers []int64 `yaml:"fee_tiers"`
// Cache configuration for pool data
Cache CacheConfig `yaml:"cache"`
}
// CacheConfig represents caching configuration
type CacheConfig struct {
// Enable or disable caching
Enabled bool `yaml:"enabled"`
// Cache expiration time in seconds
Expiration int `yaml:"expiration"`
// Maximum cache size
MaxSize int `yaml:"max_size"`
}
// LogConfig represents the logging configuration
type LogConfig struct {
// Log level (debug, info, warn, error)
Level string `yaml:"level"`
// Log format (json, text)
Format string `yaml:"format"`
// Log file path (empty for stdout)
File string `yaml:"file"`
}
// DatabaseConfig represents the database configuration
type DatabaseConfig struct {
// Database file path
File string `yaml:"file"`
// Maximum number of open connections
MaxOpenConnections int `yaml:"max_open_connections"`
// Maximum number of idle connections
MaxIdleConnections int `yaml:"max_idle_connections"`
}
// EthereumConfig represents the Ethereum account configuration
type EthereumConfig struct {
// Private key for transaction signing
PrivateKey string `yaml:"private_key"`
// Account address
AccountAddress string `yaml:"account_address"`
// Gas price multiplier (for faster transactions)
GasPriceMultiplier float64 `yaml:"gas_price_multiplier"`
}
// ContractsConfig represents the smart contract addresses
type ContractsConfig struct {
// Arbitrage executor contract address
ArbitrageExecutor string `yaml:"arbitrage_executor"`
// Flash swapper contract address
FlashSwapper string `yaml:"flash_swapper"`
// Flash loan receiver contract address (Balancer flash loans)
FlashLoanReceiver string `yaml:"flash_loan_receiver"`
// Balancer Vault address for flash loans
BalancerVault string `yaml:"balancer_vault"`
// Data fetcher contract address for batch pool data fetching
DataFetcher string `yaml:"data_fetcher"`
// Authorized caller addresses
AuthorizedCallers []string `yaml:"authorized_callers"`
// Authorized DEX addresses
AuthorizedDEXes []string `yaml:"authorized_dexes"`
}
// Load loads the configuration from a file
func Load(filename string) (*Config, error) {
// Read the config file
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
// Expand environment variables in the raw YAML
expandedData := expandEnvVars(string(data))
// Parse the YAML
var config Config
if err := yaml.Unmarshal([]byte(expandedData), &config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
// Override with environment variables if they exist
config.OverrideWithEnv()
return &config, nil
}
// expandEnvVars expands ${VAR} and $VAR patterns in the given string
func expandEnvVars(s string) string {
// Pattern to match ${VAR} and $VAR
envVarPattern := regexp.MustCompile(`\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)`)
return envVarPattern.ReplaceAllStringFunc(s, func(match string) string {
var varName string
// Handle ${VAR} format
if strings.HasPrefix(match, "${") && strings.HasSuffix(match, "}") {
varName = match[2 : len(match)-1]
} else if strings.HasPrefix(match, "$") {
// Handle $VAR format
varName = match[1:]
}
// Get environment variable value
if value := os.Getenv(varName); value != "" {
return value
}
// Return empty string if environment variable is not set
// This prevents invalid YAML when variables are missing
return ""
})
}
// OverrideWithEnv overrides configuration with environment variables
func (c *Config) OverrideWithEnv() {
// Override legacy RPC endpoint (backward compatibility)
if rpcEndpoint := os.Getenv("ARBITRUM_RPC_ENDPOINT"); rpcEndpoint != "" {
c.Arbitrum.RPCEndpoint = rpcEndpoint
// Also add to execution endpoints if not already configured
if len(c.Arbitrum.ExecutionEndpoints) == 0 {
// Determine RPS based on endpoint type
rps := 200
if strings.HasPrefix(rpcEndpoint, "ws") {
rps = 300
}
c.Arbitrum.ExecutionEndpoints = append(c.Arbitrum.ExecutionEndpoints, EndpointConfig{
URL: rpcEndpoint,
Name: "Arbitrum Public HTTP",
Priority: 1,
MaxRPS: rps,
MaxConcurrent: 20,
TimeoutSeconds: 30,
HealthCheckInterval: 60,
RateLimit: RateLimitConfig{
RequestsPerSecond: rps,
MaxConcurrent: 20,
Burst: rps * 2,
},
})
}
}
// Override legacy WebSocket endpoint (backward compatibility)
if wsEndpoint := os.Getenv("ARBITRUM_WS_ENDPOINT"); wsEndpoint != "" {
c.Arbitrum.WSEndpoint = wsEndpoint
// Also add to reading endpoints if not already configured
if len(c.Arbitrum.ReadingEndpoints) == 0 {
c.Arbitrum.ReadingEndpoints = append(c.Arbitrum.ReadingEndpoints, EndpointConfig{
URL: wsEndpoint,
Name: "Arbitrum Public WS",
Priority: 1,
MaxRPS: 300,
MaxConcurrent: 25,
TimeoutSeconds: 60,
HealthCheckInterval: 30,
RateLimit: RateLimitConfig{
RequestsPerSecond: 300,
MaxConcurrent: 25,
Burst: 600,
},
})
}
}
// Override reading endpoints from environment
if readingEndpoints := os.Getenv("ARBITRUM_READING_ENDPOINTS"); readingEndpoints != "" {
c.Arbitrum.ReadingEndpoints = c.parseEndpointsFromEnv(readingEndpoints, "Reading")
}
// Override execution endpoints from environment
if executionEndpoints := os.Getenv("ARBITRUM_EXECUTION_ENDPOINTS"); executionEndpoints != "" {
c.Arbitrum.ExecutionEndpoints = c.parseEndpointsFromEnv(executionEndpoints, "Execution")
}
// Override fallback endpoints from environment (legacy support)
if fallbackEndpoints := os.Getenv("ARBITRUM_FALLBACK_ENDPOINTS"); fallbackEndpoints != "" {
// Add to both reading and execution if they're empty
fallbackConfigs := c.parseEndpointsFromEnv(fallbackEndpoints, "Fallback")
if len(c.Arbitrum.ReadingEndpoints) == 0 {
c.Arbitrum.ReadingEndpoints = append(c.Arbitrum.ReadingEndpoints, fallbackConfigs...)
}
if len(c.Arbitrum.ExecutionEndpoints) == 0 {
c.Arbitrum.ExecutionEndpoints = append(c.Arbitrum.ExecutionEndpoints, fallbackConfigs...)
}
}
// Override rate limit settings
if rps := os.Getenv("RPC_REQUESTS_PER_SECOND"); rps != "" {
if val, err := strconv.Atoi(rps); err == nil {
c.Arbitrum.RateLimit.RequestsPerSecond = val
}
}
if maxConcurrent := os.Getenv("RPC_MAX_CONCURRENT"); maxConcurrent != "" {
if val, err := strconv.Atoi(maxConcurrent); err == nil {
c.Arbitrum.RateLimit.MaxConcurrent = val
}
}
// Override bot settings
if maxWorkers := os.Getenv("BOT_MAX_WORKERS"); maxWorkers != "" {
if val, err := strconv.Atoi(maxWorkers); err == nil {
c.Bot.MaxWorkers = val
}
}
if channelBufferSize := os.Getenv("BOT_CHANNEL_BUFFER_SIZE"); channelBufferSize != "" {
if val, err := strconv.Atoi(channelBufferSize); err == nil {
c.Bot.ChannelBufferSize = val
}
}
// Override Ethereum settings
if privateKey := os.Getenv("ETHEREUM_PRIVATE_KEY"); privateKey != "" {
c.Ethereum.PrivateKey = privateKey
}
if accountAddress := os.Getenv("ETHEREUM_ACCOUNT_ADDRESS"); accountAddress != "" {
c.Ethereum.AccountAddress = accountAddress
}
if gasPriceMultiplier := os.Getenv("ETHEREUM_GAS_PRICE_MULTIPLIER"); gasPriceMultiplier != "" {
if val, err := strconv.ParseFloat(gasPriceMultiplier, 64); err == nil {
c.Ethereum.GasPriceMultiplier = val
}
}
// Override contract addresses
if arbitrageExecutor := os.Getenv("CONTRACT_ARBITRAGE_EXECUTOR"); arbitrageExecutor != "" {
c.Contracts.ArbitrageExecutor = arbitrageExecutor
}
if flashSwapper := os.Getenv("CONTRACT_FLASH_SWAPPER"); flashSwapper != "" {
c.Contracts.FlashSwapper = flashSwapper
}
}
// parseEndpointsFromEnv parses comma-separated endpoint URLs from environment variable
func (c *Config) parseEndpointsFromEnv(endpointsStr, namePrefix string) []EndpointConfig {
if endpointsStr == "" {
return nil
}
urls := strings.Split(endpointsStr, ",")
endpoints := make([]EndpointConfig, 0, len(urls))
for i, url := range urls {
url = strings.TrimSpace(url)
if url == "" {
continue
}
// Determine defaults based on URL scheme
var maxRPS, maxConcurrent, timeoutSeconds, healthCheckInterval int
if strings.HasPrefix(url, "ws") {
// WebSocket endpoints - higher rate limits for real-time data
maxRPS = 300
maxConcurrent = 25
timeoutSeconds = 60
healthCheckInterval = 30
} else {
// HTTP endpoints - conservative rate limits
maxRPS = 200
maxConcurrent = 20
timeoutSeconds = 30
healthCheckInterval = 60
}
endpoint := EndpointConfig{
URL: url,
Name: fmt.Sprintf("%s-%d", namePrefix, i+1),
Priority: i + 1, // Lower number = higher priority
MaxRPS: maxRPS,
MaxConcurrent: maxConcurrent,
TimeoutSeconds: timeoutSeconds,
HealthCheckInterval: healthCheckInterval,
RateLimit: RateLimitConfig{
RequestsPerSecond: maxRPS,
MaxConcurrent: maxConcurrent,
Burst: maxRPS * 2, // Allow burst of 2x normal rate
},
}
endpoints = append(endpoints, endpoint)
}
return endpoints
}
// CreateProviderConfigFile creates a temporary YAML config file for the transport system
func (c *Config) CreateProviderConfigFile(tempPath string) error {
// Convert config to provider format
providerConfig := c.ConvertToProviderConfig()
// Marshal to YAML
yamlData, err := yaml.Marshal(providerConfig)
if err != nil {
return fmt.Errorf("failed to marshal provider config: %w", err)
}
// Write to file
if err := os.WriteFile(tempPath, yamlData, 0644); err != nil {
return fmt.Errorf("failed to write provider config file: %w", err)
}
return nil
}
// ConvertToProviderConfig converts ArbitrumConfig to transport.ProvidersConfig
func (c *Config) createProviderConfig(endpoint EndpointConfig, features []string) map[string]interface{} {
provider := map[string]interface{}{
"name": endpoint.Name,
"type": "standard",
"http_endpoint": "",
"ws_endpoint": "",
"priority": endpoint.Priority,
"rate_limit": map[string]interface{}{
"requests_per_second": endpoint.RateLimit.RequestsPerSecond,
"burst": endpoint.RateLimit.Burst,
"timeout": fmt.Sprintf("%ds", endpoint.TimeoutSeconds),
"retry_delay": "1s",
"max_retries": 3,
},
"features": features,
"health_check": map[string]interface{}{
"enabled": true,
"interval": fmt.Sprintf("%ds", endpoint.HealthCheckInterval),
"timeout": fmt.Sprintf("%ds", endpoint.TimeoutSeconds),
},
}
// Determine endpoint type and assign to appropriate field
if strings.HasPrefix(endpoint.URL, "ws") {
provider["ws_endpoint"] = endpoint.URL
} else {
provider["http_endpoint"] = endpoint.URL
}
return provider
}
func (c *Config) ConvertToProviderConfig() map[string]interface{} {
providerConfigs := make([]map[string]interface{}, 0)
// Handle legacy configuration if new endpoints are not configured
if len(c.Arbitrum.ReadingEndpoints) == 0 && len(c.Arbitrum.ExecutionEndpoints) == 0 {
// Use legacy RPC and WS endpoints
if c.Arbitrum.RPCEndpoint != "" {
// Set default rate limits if zero
rps := c.Arbitrum.RateLimit.RequestsPerSecond
if rps <= 0 {
if strings.HasPrefix(c.Arbitrum.RPCEndpoint, "ws") {
rps = 300 // Default for WebSocket
} else {
rps = 200 // Default for HTTP
}
}
burst := c.Arbitrum.RateLimit.Burst
if burst <= 0 {
burst = rps * 2 // Default burst is 2x RPS
}
provider := map[string]interface{}{
"name": "Legacy-RPC",
"type": "standard",
"priority": 1,
"rate_limit": map[string]interface{}{
"requests_per_second": rps,
"burst": burst,
"timeout": "30s",
"retry_delay": "1s",
"max_retries": 3,
},
"features": []string{"execution", "reading"},
"health_check": map[string]interface{}{
"enabled": true,
"interval": "60s",
"timeout": "30s",
},
}
// Determine endpoint type and assign to appropriate field
if strings.HasPrefix(c.Arbitrum.RPCEndpoint, "ws") {
provider["http_endpoint"] = ""
provider["ws_endpoint"] = c.Arbitrum.RPCEndpoint
} else {
provider["http_endpoint"] = c.Arbitrum.RPCEndpoint
provider["ws_endpoint"] = ""
}
providerConfigs = append(providerConfigs, provider)
}
if c.Arbitrum.WSEndpoint != "" {
// Set default rate limits if zero
rps := c.Arbitrum.RateLimit.RequestsPerSecond
if rps <= 0 {
rps = 300 // Default for WebSocket
}
burst := c.Arbitrum.RateLimit.Burst
if burst <= 0 {
burst = rps * 2 // Default burst is 2x RPS
}
provider := map[string]interface{}{
"name": "Legacy-WSS",
"type": "standard",
"http_endpoint": "",
"ws_endpoint": c.Arbitrum.WSEndpoint,
"priority": 1,
"rate_limit": map[string]interface{}{
"requests_per_second": rps,
"burst": burst,
"timeout": "60s",
"retry_delay": "1s",
"max_retries": 3,
},
"features": []string{"reading", "real_time"},
"health_check": map[string]interface{}{
"enabled": true,
"interval": "30s",
"timeout": "60s",
},
}
providerConfigs = append(providerConfigs, provider)
}
// Create simple pool configuration for legacy mode
providerPools := make(map[string]interface{})
if len(providerConfigs) > 0 {
providerNames := make([]string, 0)
for _, provider := range providerConfigs {
providerNames = append(providerNames, provider["name"].(string))
}
// Use same providers for both reading and execution in legacy mode
providerPools["read_only"] = map[string]interface{}{
"strategy": "priority_based",
"max_concurrent_connections": 25,
"health_check_interval": "30s",
"failover_enabled": true,
"providers": providerNames,
}
providerPools["execution"] = map[string]interface{}{
"strategy": "priority_based",
"max_concurrent_connections": 20,
"health_check_interval": "30s",
"failover_enabled": true,
"providers": providerNames,
}
}
return map[string]interface{}{
"provider_pools": providerPools,
"providers": providerConfigs,
"rotation": map[string]interface{}{
"strategy": "priority_based",
"health_check_required": true,
"fallover_enabled": true,
"retry_failed_after": "5m",
},
"global_limits": map[string]interface{}{
"max_concurrent_connections": 50,
"connection_timeout": "30s",
"read_timeout": "60s",
"write_timeout": "30s",
"idle_timeout": "300s",
},
"monitoring": map[string]interface{}{
"enabled": true,
"metrics_interval": "60s",
"log_slow_requests": true,
"slow_request_threshold": "5s",
"track_provider_performance": true,
},
}
}
// Convert reading endpoints
for _, endpoint := range c.Arbitrum.ReadingEndpoints {
providerConfigs = append(providerConfigs, c.createProviderConfig(endpoint, []string{"reading", "real_time"}))
}
// Convert execution endpoints
for _, endpoint := range c.Arbitrum.ExecutionEndpoints {
providerConfigs = append(providerConfigs, c.createProviderConfig(endpoint, []string{"execution", "transaction_submission"}))
}
// Build provider pool configurations
providerPools := make(map[string]interface{})
// Reading pool configuration
if len(c.Arbitrum.ReadingEndpoints) > 0 {
readingProviders := make([]string, 0)
for _, endpoint := range c.Arbitrum.ReadingEndpoints {
readingProviders = append(readingProviders, endpoint.Name)
}
providerPools["read_only"] = map[string]interface{}{
"strategy": "websocket_preferred",
"max_concurrent_connections": 25,
"health_check_interval": "30s",
"failover_enabled": true,
"providers": readingProviders,
}
}
// Execution pool configuration
if len(c.Arbitrum.ExecutionEndpoints) > 0 {
executionProviders := make([]string, 0)
for _, endpoint := range c.Arbitrum.ExecutionEndpoints {
executionProviders = append(executionProviders, endpoint.Name)
}
providerPools["execution"] = map[string]interface{}{
"strategy": "reliability_first",
"max_concurrent_connections": 20,
"health_check_interval": "30s",
"failover_enabled": true,
"providers": executionProviders,
}
}
// Complete configuration
return map[string]interface{}{
"provider_pools": providerPools,
"providers": providerConfigs,
"rotation": map[string]interface{}{
"strategy": "priority_based",
"health_check_required": true,
"fallover_enabled": true,
"retry_failed_after": "5m",
},
"global_limits": map[string]interface{}{
"max_concurrent_connections": 50,
"connection_timeout": "30s",
"read_timeout": "60s",
"write_timeout": "30s",
"idle_timeout": "300s",
},
"monitoring": map[string]interface{}{
"enabled": true,
"metrics_interval": "60s",
"log_slow_requests": true,
"slow_request_threshold": "5s",
"track_provider_performance": true,
},
}
}
// ValidateEnvironmentVariables validates all required environment variables
func (c *Config) ValidateEnvironmentVariables() error {
// Validate RPC endpoint
if c.Arbitrum.RPCEndpoint == "" {
return fmt.Errorf("ARBITRUM_RPC_ENDPOINT environment variable is required")
}
if err := validateRPCEndpoint(c.Arbitrum.RPCEndpoint); err != nil {
return fmt.Errorf("invalid ARBITRUM_RPC_ENDPOINT: %w", err)
}
// Validate WebSocket endpoint if provided
if c.Arbitrum.WSEndpoint != "" {
if err := validateRPCEndpoint(c.Arbitrum.WSEndpoint); err != nil {
return fmt.Errorf("invalid ARBITRUM_WS_ENDPOINT: %w", err)
}
}
// Validate Ethereum private key
if c.Ethereum.PrivateKey == "" {
return fmt.Errorf("ETHEREUM_PRIVATE_KEY environment variable is required")
}
// Validate account address
if c.Ethereum.AccountAddress == "" {
return fmt.Errorf("ETHEREUM_ACCOUNT_ADDRESS environment variable is required")
}
// Validate contract addresses
if c.Contracts.ArbitrageExecutor == "" {
return fmt.Errorf("CONTRACT_ARBITRAGE_EXECUTOR environment variable is required")
}
if c.Contracts.FlashSwapper == "" {
return fmt.Errorf("CONTRACT_FLASH_SWAPPER environment variable is required")
}
// Validate numeric values
if c.Arbitrum.RateLimit.RequestsPerSecond < 0 {
return fmt.Errorf("RPC_REQUESTS_PER_SECOND must be non-negative")
}
if c.Arbitrum.RateLimit.MaxConcurrent < 0 {
return fmt.Errorf("RPC_MAX_CONCURRENT must be non-negative")
}
if c.Bot.MaxWorkers <= 0 {
return fmt.Errorf("BOT_MAX_WORKERS must be positive")
}
if c.Bot.ChannelBufferSize < 0 {
return fmt.Errorf("BOT_CHANNEL_BUFFER_SIZE must be non-negative")
}
if c.Ethereum.GasPriceMultiplier < 0 {
return fmt.Errorf("ETHEREUM_GAS_PRICE_MULTIPLIER must be non-negative")
}
return nil
}
// validateRPCEndpoint validates RPC endpoint URL for security and format
func validateRPCEndpoint(endpoint string) error {
if endpoint == "" {
return fmt.Errorf("RPC endpoint cannot be empty")
}
u, err := url.Parse(endpoint)
if err != nil {
return fmt.Errorf("invalid RPC endpoint URL: %w", err)
}
// Check for valid schemes
switch u.Scheme {
case "http", "https", "ws", "wss":
// Valid schemes
default:
return fmt.Errorf("invalid RPC scheme: %s (must be http, https, ws, or wss)", u.Scheme)
}
// Check for localhost/private networks in production
if strings.Contains(u.Hostname(), "localhost") || strings.Contains(u.Hostname(), "127.0.0.1") {
// Allow localhost only if explicitly enabled
if os.Getenv("MEV_BOT_ALLOW_LOCALHOST") != "true" {
return fmt.Errorf("localhost RPC endpoints not allowed in production (set MEV_BOT_ALLOW_LOCALHOST=true to override)")
}
}
// Validate hostname is not empty
if u.Hostname() == "" {
return fmt.Errorf("RPC endpoint must have a valid hostname")
}
return nil
}
// ArbitrageConfig represents the arbitrage service configuration
type ArbitrageConfig struct {
// Enable or disable arbitrage service
Enabled bool `yaml:"enabled"`
// Contract addresses
ArbitrageContractAddress string `yaml:"arbitrage_contract_address"`
FlashSwapContractAddress string `yaml:"flash_swap_contract_address"`
// Profitability settings
MinProfitWei int64 `yaml:"min_profit_wei"`
MinROIPercent float64 `yaml:"min_roi_percent"`
MinSignificantSwapSize int64 `yaml:"min_significant_swap_size"`
SlippageTolerance float64 `yaml:"slippage_tolerance"`
// Scanning configuration
MinScanAmountWei int64 `yaml:"min_scan_amount_wei"`
MaxScanAmountWei int64 `yaml:"max_scan_amount_wei"`
// Gas configuration
MaxGasPriceWei int64 `yaml:"max_gas_price_wei"`
// Execution limits
MaxConcurrentExecutions int `yaml:"max_concurrent_executions"`
MaxOpportunitiesPerEvent int `yaml:"max_opportunities_per_event"`
// Timing settings
OpportunityTTL time.Duration `yaml:"opportunity_ttl"`
MaxPathAge time.Duration `yaml:"max_path_age"`
StatsUpdateInterval time.Duration `yaml:"stats_update_interval"`
// Pool discovery configuration
PoolDiscoveryConfig PoolDiscoveryConfig `yaml:"pool_discovery"`
}
// PoolDiscoveryConfig represents pool discovery service configuration
type PoolDiscoveryConfig struct {
// Enable or disable pool discovery
Enabled bool `yaml:"enabled"`
// Block range to scan for new pools
BlockRange uint64 `yaml:"block_range"`
// Polling interval for new pools
PollingInterval time.Duration `yaml:"polling_interval"`
// DEX factory addresses to monitor
FactoryAddresses []string `yaml:"factory_addresses"`
// Minimum liquidity threshold for pools
MinLiquidityWei int64 `yaml:"min_liquidity_wei"`
// Cache configuration
CacheSize int `yaml:"cache_size"`
CacheTTL time.Duration `yaml:"cache_ttl"`
}
// Features represents Layer 2 optimization feature flags
type Features struct {
// Phase 1: Configuration tuning
UseArbitrumOptimizedTimeouts bool `yaml:"use_arbitrum_optimized_timeouts"`
UseDynamicTTL bool `yaml:"use_dynamic_ttl"`
// Phase 2: Transaction filtering
EnableDEXPrefilter bool `yaml:"enable_dex_prefilter"`
// Phase 3: Sequencer optimization
UseDirectSequencerFeed bool `yaml:"use_direct_sequencer_feed"`
// Phase 4-5: Timeboost
EnableTimeboost bool `yaml:"enable_timeboost"`
}
// ArbitrageOptimizedConfig represents Arbitrum-optimized arbitrage timing
type ArbitrageOptimizedConfig struct {
// Opportunity lifecycle (tuned for 250ms blocks)
OpportunityTTL time.Duration `yaml:"opportunity_ttl"`
MaxPathAge time.Duration `yaml:"max_path_age"`
ExecutionDeadline time.Duration `yaml:"execution_deadline"`
// Legacy values for rollback
LegacyOpportunityTTL time.Duration `yaml:"legacy_opportunity_ttl"`
LegacyMaxPathAge time.Duration `yaml:"legacy_max_path_age"`
// Dynamic TTL settings
DynamicTTL DynamicTTLConfig `yaml:"dynamic_ttl"`
}
// DynamicTTLConfig represents dynamic TTL calculation settings
type DynamicTTLConfig struct {
MinTTLBlocks int `yaml:"min_ttl_blocks"`
MaxTTLBlocks int `yaml:"max_ttl_blocks"`
ProfitMultiplier bool `yaml:"profit_multiplier"`
VolatilityAdjustment bool `yaml:"volatility_adjustment"`
}
// GetOpportunityTTL returns the active opportunity TTL based on feature flags
func (c *Config) GetOpportunityTTL() time.Duration {
if c.Features.UseArbitrumOptimizedTimeouts {
return c.ArbitrageOptimized.OpportunityTTL
}
// Fallback to legacy config
if c.Arbitrage.OpportunityTTL > 0 {
return c.Arbitrage.OpportunityTTL
}
// Default fallback
return 30 * time.Second
}
// GetMaxPathAge returns the active max path age based on feature flags
func (c *Config) GetMaxPathAge() time.Duration {
if c.Features.UseArbitrumOptimizedTimeouts {
return c.ArbitrageOptimized.MaxPathAge
}
// Fallback to legacy config
if c.Arbitrage.MaxPathAge > 0 {
return c.Arbitrage.MaxPathAge
}
// Default fallback
return 60 * time.Second
}
// GetExecutionDeadline returns the execution deadline
func (c *Config) GetExecutionDeadline() time.Duration {
if c.Features.UseArbitrumOptimizedTimeouts && c.ArbitrageOptimized.ExecutionDeadline > 0 {
return c.ArbitrageOptimized.ExecutionDeadline
}
// Default fallback for Arbitrum (12 blocks @ 250ms)
return 3 * time.Second
}