- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing - Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives - Added LRU caching system for address validation with 10-minute TTL - Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures - Fixed duplicate function declarations and import conflicts across multiple files - Added error recovery mechanisms with multiple fallback strategies - Updated tests to handle new validation behavior for suspicious addresses - Fixed parser test expectations for improved validation system - Applied gofmt formatting fixes to ensure code style compliance - Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot - Resolved critical security vulnerabilities in heuristic address extraction - Progress: Updated TODO audit from 10% to 35% complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
598 lines
21 KiB
Go
598 lines
21 KiB
Go
package arbitrum
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
arbitrumCommon "github.com/fraktal/mev-beta/pkg/arbitrum/common"
|
|
)
|
|
|
|
// ArbitrumProtocolRegistry manages all DEX protocols on Arbitrum
|
|
type ArbitrumProtocolRegistry struct {
|
|
logger *logger.Logger
|
|
protocols map[string]*DEXProtocol
|
|
mu sync.RWMutex
|
|
|
|
// Event logging
|
|
swapLogger *os.File
|
|
liquidationLogger *os.File
|
|
liquidityLogger *os.File
|
|
}
|
|
|
|
// DEXProtocol represents a complete DEX protocol configuration
|
|
type DEXProtocol struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"` // "uniswap_v2", "uniswap_v3", "curve", "balancer", etc.
|
|
Routers []common.Address `json:"routers"`
|
|
Factories []common.Address `json:"factories"`
|
|
SwapFunctions map[string]string `json:"swap_functions"`
|
|
EventSignatures map[string]common.Hash `json:"event_signatures"`
|
|
PoolTypes []string `json:"pool_types"`
|
|
FeeStructure map[string]interface{} `json:"fee_structure"`
|
|
Active bool `json:"active"`
|
|
Priority int `json:"priority"` // Higher = more important for MEV
|
|
}
|
|
|
|
// SwapEvent represents a detected swap for logging
|
|
type SwapEvent struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
BlockNumber uint64 `json:"block_number"`
|
|
TxHash string `json:"tx_hash"`
|
|
Protocol string `json:"protocol"`
|
|
Router string `json:"router"`
|
|
Pool string `json:"pool"`
|
|
TokenIn string `json:"token_in"`
|
|
TokenOut string `json:"token_out"`
|
|
AmountIn string `json:"amount_in"`
|
|
AmountOut string `json:"amount_out"`
|
|
Sender string `json:"sender"`
|
|
Recipient string `json:"recipient"`
|
|
GasPrice string `json:"gas_price"`
|
|
GasUsed uint64 `json:"gas_used"`
|
|
PriceImpact float64 `json:"price_impact"`
|
|
MEVScore float64 `json:"mev_score"`
|
|
Profitable bool `json:"profitable"`
|
|
}
|
|
|
|
// LiquidationEvent represents a detected liquidation
|
|
type LiquidationEvent struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
BlockNumber uint64 `json:"block_number"`
|
|
TxHash string `json:"tx_hash"`
|
|
Protocol string `json:"protocol"`
|
|
Liquidator string `json:"liquidator"`
|
|
Borrower string `json:"borrower"`
|
|
CollateralToken string `json:"collateral_token"`
|
|
DebtToken string `json:"debt_token"`
|
|
CollateralAmount string `json:"collateral_amount"`
|
|
DebtAmount string `json:"debt_amount"`
|
|
Bonus string `json:"liquidation_bonus"`
|
|
HealthFactor float64 `json:"health_factor"`
|
|
MEVOpportunity bool `json:"mev_opportunity"`
|
|
EstimatedProfit string `json:"estimated_profit"`
|
|
}
|
|
|
|
// LiquidityEvent represents a liquidity change event
|
|
type LiquidityEvent struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
BlockNumber uint64 `json:"block_number"`
|
|
TxHash string `json:"tx_hash"`
|
|
Protocol string `json:"protocol"`
|
|
Pool string `json:"pool"`
|
|
EventType string `json:"event_type"` // "add", "remove", "sync"
|
|
Token0 string `json:"token0"`
|
|
Token1 string `json:"token1"`
|
|
Amount0 string `json:"amount0"`
|
|
Amount1 string `json:"amount1"`
|
|
Liquidity string `json:"liquidity"`
|
|
PriceAfter string `json:"price_after"`
|
|
ImpactSize float64 `json:"impact_size"`
|
|
ArbitrageOpp bool `json:"arbitrage_opportunity"`
|
|
}
|
|
|
|
// NewArbitrumProtocolRegistry creates a new protocol registry
|
|
func NewArbitrumProtocolRegistry(logger *logger.Logger) (*ArbitrumProtocolRegistry, error) {
|
|
registry := &ArbitrumProtocolRegistry{
|
|
logger: logger,
|
|
protocols: make(map[string]*DEXProtocol),
|
|
}
|
|
|
|
// Initialize event logging files
|
|
if err := registry.initializeEventLogging(); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize event logging: %w", err)
|
|
}
|
|
|
|
// Load all Arbitrum DEX protocols
|
|
if err := registry.loadArbitrumProtocols(); err != nil {
|
|
return nil, fmt.Errorf("failed to load protocols: %w", err)
|
|
}
|
|
|
|
return registry, nil
|
|
}
|
|
|
|
// initializeEventLogging sets up JSONL logging files
|
|
func (r *ArbitrumProtocolRegistry) initializeEventLogging() error {
|
|
// Create logs directory if it doesn't exist
|
|
if err := os.MkdirAll("logs", 0755); err != nil {
|
|
return fmt.Errorf("failed to create logs directory: %w", err)
|
|
}
|
|
|
|
// Open swap events log file
|
|
swapFile, err := os.OpenFile("logs/swaps.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open swap log file: %w", err)
|
|
}
|
|
r.swapLogger = swapFile
|
|
|
|
// Open liquidation events log file
|
|
liquidationFile, err := os.OpenFile("logs/liquidations.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open liquidation log file: %w", err)
|
|
}
|
|
r.liquidationLogger = liquidationFile
|
|
|
|
// Open liquidity events log file
|
|
liquidityFile, err := os.OpenFile("logs/liquidity.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open liquidity log file: %w", err)
|
|
}
|
|
r.liquidityLogger = liquidityFile
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadArbitrumProtocols loads all major DEX protocols on Arbitrum
|
|
func (r *ArbitrumProtocolRegistry) loadArbitrumProtocols() error {
|
|
// Uniswap V3 - Highest priority for MEV
|
|
r.protocols["uniswap_v3"] = &DEXProtocol{
|
|
Name: "Uniswap V3",
|
|
Type: "uniswap_v3",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // SwapRouter
|
|
common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), // SwapRouter02
|
|
},
|
|
Factories: []common.Address{
|
|
common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), // Factory
|
|
},
|
|
SwapFunctions: map[string]string{
|
|
"0x414bf389": "exactInputSingle",
|
|
"0xc04b8d59": "exactInput",
|
|
"0xdb3e2198": "exactOutputSingle",
|
|
"0xf28c0498": "exactOutput",
|
|
"0x5ae401dc": "multicall",
|
|
"0x1f0464d1": "multicall",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"Swap": crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24)")),
|
|
"Mint": crypto.Keccak256Hash([]byte("Mint(address,address,int24,int24,uint128,uint256,uint256)")),
|
|
"Burn": crypto.Keccak256Hash([]byte("Burn(address,int24,int24,uint128,uint256,uint256)")),
|
|
},
|
|
PoolTypes: []string{"concentrated"},
|
|
FeeStructure: map[string]interface{}{"type": "tiered", "fees": []int{500, 3000, 10000}},
|
|
Active: true,
|
|
Priority: 100,
|
|
}
|
|
|
|
// Uniswap V2 - High MEV potential
|
|
r.protocols["uniswap_v2"] = &DEXProtocol{
|
|
Name: "Uniswap V2",
|
|
Type: "uniswap_v2",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), // Router02
|
|
},
|
|
Factories: []common.Address{
|
|
common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), // Factory
|
|
},
|
|
SwapFunctions: map[string]string{
|
|
"0x38ed1739": "swapExactTokensForTokens",
|
|
"0x8803dbee": "swapTokensForExactTokens",
|
|
"0x7ff36ab5": "swapExactETHForTokens",
|
|
"0x4a25d94a": "swapTokensForExactETH",
|
|
"0x791ac947": "swapExactTokensForETH",
|
|
"0xfb3bdb41": "swapETHForExactTokens",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"Swap": crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")),
|
|
"Mint": crypto.Keccak256Hash([]byte("Mint(address,uint256,uint256)")),
|
|
"Burn": crypto.Keccak256Hash([]byte("Burn(address,uint256,uint256,address)")),
|
|
"Sync": crypto.Keccak256Hash([]byte("Sync(uint112,uint112)")),
|
|
},
|
|
PoolTypes: []string{"constant_product"},
|
|
FeeStructure: map[string]interface{}{"type": "fixed", "fee": 3000},
|
|
Active: true,
|
|
Priority: 90,
|
|
}
|
|
|
|
// SushiSwap - High volume on Arbitrum
|
|
r.protocols["sushiswap"] = &DEXProtocol{
|
|
Name: "SushiSwap",
|
|
Type: "uniswap_v2",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), // SushiRouter
|
|
},
|
|
Factories: []common.Address{
|
|
common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"), // Factory
|
|
},
|
|
SwapFunctions: map[string]string{
|
|
"0x38ed1739": "swapExactTokensForTokens",
|
|
"0x8803dbee": "swapTokensForExactTokens",
|
|
"0x7ff36ab5": "swapExactETHForTokens",
|
|
"0x4a25d94a": "swapTokensForExactETH",
|
|
"0x791ac947": "swapExactTokensForETH",
|
|
"0xfb3bdb41": "swapETHForExactTokens",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"Swap": crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")),
|
|
"Mint": crypto.Keccak256Hash([]byte("Mint(address,uint256,uint256)")),
|
|
"Burn": crypto.Keccak256Hash([]byte("Burn(address,uint256,uint256,address)")),
|
|
"Sync": crypto.Keccak256Hash([]byte("Sync(uint112,uint112)")),
|
|
},
|
|
PoolTypes: []string{"constant_product"},
|
|
FeeStructure: map[string]interface{}{"type": "fixed", "fee": 3000},
|
|
Active: true,
|
|
Priority: 85,
|
|
}
|
|
|
|
// Camelot V3 - Arbitrum native DEX with high activity
|
|
r.protocols["camelot_v3"] = &DEXProtocol{
|
|
Name: "Camelot V3",
|
|
Type: "algebra", // Camelot uses Algebra protocol
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0x1F721E2E82F6676FCE4eA07A5958cF098D339e18"), // SwapRouter
|
|
},
|
|
Factories: []common.Address{
|
|
common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"), // Factory
|
|
},
|
|
SwapFunctions: map[string]string{
|
|
"0x414bf389": "exactInputSingle",
|
|
"0xc04b8d59": "exactInput",
|
|
"0xdb3e2198": "exactOutputSingle",
|
|
"0xf28c0498": "exactOutput",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"Swap": crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24)")),
|
|
"Mint": crypto.Keccak256Hash([]byte("Mint(address,int24,int24,uint128,uint256,uint256)")),
|
|
"Burn": crypto.Keccak256Hash([]byte("Burn(address,int24,int24,uint128,uint256,uint256)")),
|
|
},
|
|
PoolTypes: []string{"concentrated"},
|
|
FeeStructure: map[string]interface{}{"type": "dynamic", "base_fee": 500},
|
|
Active: true,
|
|
Priority: 80,
|
|
}
|
|
|
|
// Balancer V2 - Good for large swaps
|
|
r.protocols["balancer_v2"] = &DEXProtocol{
|
|
Name: "Balancer V2",
|
|
Type: "balancer_v2",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), // Vault
|
|
},
|
|
Factories: []common.Address{
|
|
common.HexToAddress("0x8E9aa87E45f6a460D4448f8154F1CA8C5C8a63b5"), // WeightedPoolFactory
|
|
common.HexToAddress("0x751A0bC0e3f75b38e01Cf25bFCE7fF36DE1C87DE"), // StablePoolFactory
|
|
},
|
|
SwapFunctions: map[string]string{
|
|
"0x52bbbe29": "swap",
|
|
"0x945bcec9": "batchSwap",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"Swap": crypto.Keccak256Hash([]byte("Swap(bytes32,address,address,uint256,uint256)")),
|
|
"PoolBalanceChanged": crypto.Keccak256Hash([]byte("PoolBalanceChanged(bytes32,address,address[],int256[],uint256[])")),
|
|
},
|
|
PoolTypes: []string{"weighted", "stable", "meta_stable"},
|
|
FeeStructure: map[string]interface{}{"type": "variable", "min": 100, "max": 10000},
|
|
Active: true,
|
|
Priority: 70,
|
|
}
|
|
|
|
// Curve Finance - Stablecoins and similar assets
|
|
r.protocols["curve"] = &DEXProtocol{
|
|
Name: "Curve Finance",
|
|
Type: "curve",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0xA72C85C258A81761433B4e8da60505Fe3Dd551CC"), // 2Pool
|
|
common.HexToAddress("0x960ea3e3C7FB317332d990873d354E18d7645590"), // Tricrypto
|
|
common.HexToAddress("0x85E8Fc6B3cb1aB51E13A1Eb7b22b3F42E66B1BBB"), // CurveAavePool
|
|
},
|
|
Factories: []common.Address{
|
|
common.HexToAddress("0xb17b674D9c5CB2e441F8e196a2f048A81355d031"), // StableFactory
|
|
common.HexToAddress("0x9AF14D26075f142eb3F292D5065EB3faa646167b"), // CryptoFactory
|
|
},
|
|
SwapFunctions: map[string]string{
|
|
"0x3df02124": "exchange",
|
|
"0xa6417ed6": "exchange_underlying",
|
|
"0x5b41b908": "exchange(int128,int128,uint256,uint256)",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"TokenExchange": crypto.Keccak256Hash([]byte("TokenExchange(address,int128,uint256,int128,uint256)")),
|
|
"TokenExchangeUnderlying": crypto.Keccak256Hash([]byte("TokenExchangeUnderlying(address,int128,uint256,int128,uint256)")),
|
|
"AddLiquidity": crypto.Keccak256Hash([]byte("AddLiquidity(address,uint256[],uint256[],uint256,uint256)")),
|
|
"RemoveLiquidity": crypto.Keccak256Hash([]byte("RemoveLiquidity(address,uint256[],uint256)")),
|
|
},
|
|
PoolTypes: []string{"stable", "crypto", "meta"},
|
|
FeeStructure: map[string]interface{}{"type": "fixed", "fee": 400},
|
|
Active: true,
|
|
Priority: 65,
|
|
}
|
|
|
|
// 1inch - Aggregator with MEV opportunities
|
|
r.protocols["1inch"] = &DEXProtocol{
|
|
Name: "1inch",
|
|
Type: "aggregator",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0x1111111254EEB25477B68fb85Ed929f73A960582"), // AggregationRouterV5
|
|
common.HexToAddress("0x111111125421cA6dc452d289314280a0f8842A65"), // AggregationRouterV4
|
|
},
|
|
Factories: []common.Address{},
|
|
SwapFunctions: map[string]string{
|
|
"0x7c025200": "swap",
|
|
"0x12aa3caf": "unoswap",
|
|
"0x0502b1c5": "uniswapV3Swap",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"Swapped": crypto.Keccak256Hash([]byte("Swapped(address,address,address,uint256,uint256)")),
|
|
},
|
|
PoolTypes: []string{"aggregated"},
|
|
FeeStructure: map[string]interface{}{"type": "variable"},
|
|
Active: true,
|
|
Priority: 75,
|
|
}
|
|
|
|
// GMX - Perpetuals with liquidation opportunities
|
|
r.protocols["gmx"] = &DEXProtocol{
|
|
Name: "GMX",
|
|
Type: "perpetual",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064"), // Router
|
|
common.HexToAddress("0xA906F338CB21815cBc4Bc87ace9e68c87eF8d8F1"), // PositionRouter
|
|
},
|
|
Factories: []common.Address{
|
|
common.HexToAddress("0x489ee077994B6658eAfA855C308275EAd8097C4A"), // Vault
|
|
},
|
|
SwapFunctions: map[string]string{
|
|
"0x0a5bc6f8": "swap",
|
|
"0x7fc0c104": "increasePosition",
|
|
"0xf3bf7c12": "decreasePosition",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"Swap": crypto.Keccak256Hash([]byte("Swap(address,address,address,uint256,uint256,uint256,uint256)")),
|
|
"LiquidatePosition": crypto.Keccak256Hash([]byte("LiquidatePosition(bytes32,address,address,address,bool,uint256,uint256,uint256,int256,uint256)")),
|
|
"IncreasePosition": crypto.Keccak256Hash([]byte("IncreasePosition(bytes32,address,address,address,uint256,uint256,bool,uint256,uint256)")),
|
|
"DecreasePosition": crypto.Keccak256Hash([]byte("DecreasePosition(bytes32,address,address,address,uint256,uint256,bool,uint256,uint256)")),
|
|
},
|
|
PoolTypes: []string{"perpetual"},
|
|
FeeStructure: map[string]interface{}{"type": "position_based"},
|
|
Active: true,
|
|
Priority: 90, // High priority due to liquidation MEV
|
|
}
|
|
|
|
// Radiant Capital - Lending protocol with liquidations
|
|
r.protocols["radiant"] = &DEXProtocol{
|
|
Name: "Radiant Capital",
|
|
Type: "lending",
|
|
Routers: []common.Address{
|
|
common.HexToAddress("0x2032b9A8e9F7e76768CA9271003d3e43E1616B1F"), // LendingPool
|
|
},
|
|
Factories: []common.Address{},
|
|
SwapFunctions: map[string]string{
|
|
"0x630d4904": "liquidationCall",
|
|
"0xa415bcad": "deposit",
|
|
"0x69328dec": "withdraw",
|
|
"0xc858f5f9": "borrow",
|
|
"0x573ade81": "repay",
|
|
},
|
|
EventSignatures: map[string]common.Hash{
|
|
"LiquidationCall": crypto.Keccak256Hash([]byte("LiquidationCall(address,address,address,uint256,uint256,address,bool)")),
|
|
"Deposit": crypto.Keccak256Hash([]byte("Deposit(address,address,address,uint256,uint16)")),
|
|
"Withdraw": crypto.Keccak256Hash([]byte("Withdraw(address,address,address,uint256)")),
|
|
"Borrow": crypto.Keccak256Hash([]byte("Borrow(address,address,address,uint256,uint256,uint16)")),
|
|
"Repay": crypto.Keccak256Hash([]byte("Repay(address,address,address,uint256)")),
|
|
},
|
|
PoolTypes: []string{"lending"},
|
|
FeeStructure: map[string]interface{}{"type": "interest_based"},
|
|
Active: true,
|
|
Priority: 85, // High priority for liquidation MEV
|
|
}
|
|
|
|
r.logger.Info(fmt.Sprintf("Loaded %d DEX protocols for Arbitrum MEV detection", len(r.protocols)))
|
|
return nil
|
|
}
|
|
|
|
// LogSwapEvent logs a swap event to JSONL file
|
|
func (r *ArbitrumProtocolRegistry) LogSwapEvent(event *SwapEvent) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
data, err := json.Marshal(event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal swap event: %w", err)
|
|
}
|
|
|
|
if _, err := r.swapLogger.Write(append(data, '\n')); err != nil {
|
|
return fmt.Errorf("failed to write swap event: %w", err)
|
|
}
|
|
|
|
return r.swapLogger.Sync()
|
|
}
|
|
|
|
// LogLiquidationEvent logs a liquidation event to JSONL file
|
|
func (r *ArbitrumProtocolRegistry) LogLiquidationEvent(event *LiquidationEvent) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
data, err := json.Marshal(event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal liquidation event: %w", err)
|
|
}
|
|
|
|
if _, err := r.liquidationLogger.Write(append(data, '\n')); err != nil {
|
|
return fmt.Errorf("failed to write liquidation event: %w", err)
|
|
}
|
|
|
|
return r.liquidationLogger.Sync()
|
|
}
|
|
|
|
// LogLiquidityEvent logs a liquidity event to JSONL file
|
|
func (r *ArbitrumProtocolRegistry) LogLiquidityEvent(event *LiquidityEvent) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
data, err := json.Marshal(event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal liquidity event: %w", err)
|
|
}
|
|
|
|
if _, err := r.liquidityLogger.Write(append(data, '\n')); err != nil {
|
|
return fmt.Errorf("failed to write liquidity event: %w", err)
|
|
}
|
|
|
|
return r.liquidityLogger.Sync()
|
|
}
|
|
|
|
// GetProtocol returns a protocol by name
|
|
func (r *ArbitrumProtocolRegistry) GetProtocol(name string) (*DEXProtocol, bool) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
protocol, exists := r.protocols[name]
|
|
return protocol, exists
|
|
}
|
|
|
|
// GetActiveProtocols returns all active protocols sorted by priority
|
|
func (r *ArbitrumProtocolRegistry) GetActiveProtocols() []*DEXProtocol {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
var protocols []*DEXProtocol
|
|
for _, protocol := range r.protocols {
|
|
if protocol.Active {
|
|
protocols = append(protocols, protocol)
|
|
}
|
|
}
|
|
|
|
// Sort by priority (highest first)
|
|
for i := 0; i < len(protocols)-1; i++ {
|
|
for j := i + 1; j < len(protocols); j++ {
|
|
if protocols[i].Priority < protocols[j].Priority {
|
|
protocols[i], protocols[j] = protocols[j], protocols[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
return protocols
|
|
}
|
|
|
|
// GetFactoryAddresses returns all factory addresses for a protocol
|
|
func (r *ArbitrumProtocolRegistry) GetFactoryAddresses(protocol arbitrumCommon.Protocol) []common.Address {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
if p, exists := r.protocols[string(protocol)]; exists {
|
|
return p.Factories
|
|
}
|
|
|
|
return []common.Address{}
|
|
}
|
|
|
|
// GetContractAddresses returns all contract addresses for a protocol
|
|
func (r *ArbitrumProtocolRegistry) GetContractAddresses(protocol arbitrumCommon.Protocol) []common.Address {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
if p, exists := r.protocols[string(protocol)]; exists {
|
|
return p.Routers
|
|
}
|
|
|
|
return []common.Address{}
|
|
}
|
|
|
|
// IsKnownRouter checks if an address is a known DEX router
|
|
func (r *ArbitrumProtocolRegistry) IsKnownRouter(address common.Address) (string, bool) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
for name, protocol := range r.protocols {
|
|
if !protocol.Active {
|
|
continue
|
|
}
|
|
for _, router := range protocol.Routers {
|
|
if router == address {
|
|
return name, true
|
|
}
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// IsSwapFunction checks if a function signature is a known swap function
|
|
func (r *ArbitrumProtocolRegistry) IsSwapFunction(sig string) (string, string, bool) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
for protocolName, protocol := range r.protocols {
|
|
if !protocol.Active {
|
|
continue
|
|
}
|
|
if functionName, exists := protocol.SwapFunctions[sig]; exists {
|
|
return protocolName, functionName, true
|
|
}
|
|
}
|
|
return "", "", false
|
|
}
|
|
|
|
// LogArbitrageExecution logs arbitrage execution results (success or failure)
|
|
func (r *ArbitrumProtocolRegistry) LogArbitrageExecution(executionData map[string]interface{}) error {
|
|
// Add to arbitrage execution log file (reusing liquidation logger for now)
|
|
data, err := json.Marshal(executionData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal arbitrage execution data: %w", err)
|
|
}
|
|
|
|
// Write to log file with newline
|
|
if r.liquidationLogger != nil {
|
|
_, err = r.liquidationLogger.Write(append(data, '\n'))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write arbitrage execution log: %w", err)
|
|
}
|
|
return r.liquidationLogger.Sync()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes all log files
|
|
func (r *ArbitrumProtocolRegistry) Close() error {
|
|
var errors []error
|
|
|
|
if r.swapLogger != nil {
|
|
if err := r.swapLogger.Close(); err != nil {
|
|
errors = append(errors, err)
|
|
}
|
|
}
|
|
|
|
if r.liquidationLogger != nil {
|
|
if err := r.liquidationLogger.Close(); err != nil {
|
|
errors = append(errors, err)
|
|
}
|
|
}
|
|
|
|
if r.liquidityLogger != nil {
|
|
if err := r.liquidityLogger.Close(); err != nil {
|
|
errors = append(errors, err)
|
|
}
|
|
}
|
|
|
|
if len(errors) > 0 {
|
|
return fmt.Errorf("errors closing log files: %v", errors)
|
|
}
|
|
|
|
return nil
|
|
}
|