Files
mev-beta/pkg/arbitrum/arbitrum_protocols.go
2025-10-04 09:31:02 -05:00

596 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",
},
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
}