package events import ( "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" ) // EventType represents the type of DEX event type EventType int const ( Unknown EventType = iota Swap AddLiquidity RemoveLiquidity NewPool ) // String returns a string representation of the event type func (et EventType) String() string { switch et { case Unknown: return "Unknown" case Swap: return "Swap" case AddLiquidity: return "AddLiquidity" case RemoveLiquidity: return "RemoveLiquidity" case NewPool: return "NewPool" default: return "Unknown" } } type Event struct { Type EventType Protocol string // UniswapV2, UniswapV3, SushiSwap, etc. PoolAddress common.Address Token0 common.Address Token1 common.Address Amount0 *big.Int Amount1 *big.Int SqrtPriceX96 *uint256.Int Liquidity *uint256.Int Tick int Timestamp uint64 TransactionHash common.Hash BlockNumber uint64 } // EventParser parses DEX events from Ethereum transactions type EventParser struct { // Known DEX contract addresses UniswapV2Factory common.Address UniswapV3Factory common.Address SushiSwapFactory common.Address // Router addresses UniswapV2Router01 common.Address UniswapV2Router02 common.Address UniswapV3Router common.Address SushiSwapRouter common.Address // Known pool addresses (for quick lookup) knownPools map[common.Address]string // Event signatures for parsing logs swapEventV2Sig common.Hash swapEventV3Sig common.Hash mintEventV2Sig common.Hash mintEventV3Sig common.Hash burnEventV2Sig common.Hash burnEventV3Sig common.Hash } // NewEventParser creates a new event parser with official Arbitrum deployment addresses func NewEventParser() *EventParser { parser := &EventParser{ // Official Arbitrum DEX Factory Addresses UniswapV2Factory: common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9"), // Official Uniswap V2 Factory on Arbitrum UniswapV3Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), // Official Uniswap V3 Factory on Arbitrum SushiSwapFactory: common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"), // Official SushiSwap V2 Factory on Arbitrum // Official Arbitrum DEX Router Addresses UniswapV2Router01: common.HexToAddress("0x0000000000000000000000000000000000000000"), // V2Router01 not deployed on Arbitrum UniswapV2Router02: common.HexToAddress("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24"), // Official Uniswap V2 Router02 on Arbitrum UniswapV3Router: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // Official Uniswap V3 SwapRouter on Arbitrum SushiSwapRouter: common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), // Official SushiSwap Router on Arbitrum knownPools: make(map[common.Address]string), } // Initialize event signatures parser.swapEventV2Sig = crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")) parser.swapEventV3Sig = crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24)")) parser.mintEventV2Sig = crypto.Keccak256Hash([]byte("Mint(address,uint256,uint256)")) parser.mintEventV3Sig = crypto.Keccak256Hash([]byte("Mint(address,address,int24,int24,uint128,uint256,uint256)")) parser.burnEventV2Sig = crypto.Keccak256Hash([]byte("Burn(address,uint256,uint256)")) parser.burnEventV3Sig = crypto.Keccak256Hash([]byte("Burn(address,int24,int24,uint128,uint256,uint256)")) // Pre-populate known Arbitrum pools (high volume pools) parser.knownPools[common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0")] = "UniswapV3" // USDC/WETH 0.05% parser.knownPools[common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d")] = "UniswapV3" // USDC/WETH 0.3% parser.knownPools[common.HexToAddress("0x2f5e87C9312fa29aed5c179E456625D79015299c")] = "UniswapV3" // WBTC/WETH 0.05% parser.knownPools[common.HexToAddress("0x149e36E72726e0BceA5c59d40df2c43F60f5A22D")] = "UniswapV3" // WBTC/WETH 0.3% parser.knownPools[common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d")] = "UniswapV3" // USDT/WETH 0.05% parser.knownPools[common.HexToAddress("0xFe7D6a84287235C7b4b57C4fEb9a44d4C6Ed3BB8")] = "UniswapV3" // ARB/WETH 0.05% parser.knownPools[common.HexToAddress("0x80A9ae39310abf666A87C743d6ebBD0E8C42158E")] = "UniswapV3" // WETH/USDT 0.3% parser.knownPools[common.HexToAddress("0xC82819F72A9e77E2c0c3A69B3196478f44303cf4")] = "UniswapV3" // WETH/USDC 1% // Add SushiSwap pools parser.knownPools[common.HexToAddress("0x905dfCD5649217c42684f23958568e533C711Aa3")] = "SushiSwap" // WETH/USDC parser.knownPools[common.HexToAddress("0x3221022e37029923aCe4235D812273C5A42C322d")] = "SushiSwap" // WETH/USDT // Add GMX pools parser.knownPools[common.HexToAddress("0x70d95587d40A2caf56bd97485aB3Eec10Bee6336")] = "GMX" // GLP Pool parser.knownPools[common.HexToAddress("0x489ee077994B6658eAfA855C308275EAd8097C4A")] = "GMX" // GMX/WETH return parser } // ParseTransactionReceipt parses events from a transaction receipt func (ep *EventParser) ParseTransactionReceipt(receipt *types.Receipt, blockNumber uint64, timestamp uint64) ([]*Event, error) { events := make([]*Event, 0) // Parse logs for DEX events for _, log := range receipt.Logs { // Skip anonymous logs if len(log.Topics) == 0 { continue } // Check if this is a DEX event based on the topic signature eventSig := log.Topics[0] var event *Event var err error switch eventSig { case ep.swapEventV2Sig: event, err = ep.parseUniswapV2Swap(log, blockNumber, timestamp, receipt.TxHash) case ep.swapEventV3Sig: event, err = ep.parseUniswapV3Swap(log, blockNumber, timestamp, receipt.TxHash) case ep.mintEventV2Sig: event, err = ep.parseUniswapV2Mint(log, blockNumber, timestamp, receipt.TxHash) case ep.mintEventV3Sig: event, err = ep.parseUniswapV3Mint(log, blockNumber, timestamp, receipt.TxHash) case ep.burnEventV2Sig: event, err = ep.parseUniswapV2Burn(log, blockNumber, timestamp, receipt.TxHash) case ep.burnEventV3Sig: event, err = ep.parseUniswapV3Burn(log, blockNumber, timestamp, receipt.TxHash) } if err != nil { // Log error but continue parsing other logs continue } if event != nil { events = append(events, event) } } return events, nil } // IsDEXInteraction checks if a transaction interacts with a known DEX contract func (ep *EventParser) IsDEXInteraction(tx *types.Transaction) bool { if tx.To() == nil { return false } to := *tx.To() // Check factory contracts if to == ep.UniswapV2Factory || to == ep.UniswapV3Factory || to == ep.SushiSwapFactory { return true } // Check router contracts if to == ep.UniswapV2Router01 || to == ep.UniswapV2Router02 || to == ep.UniswapV3Router || to == ep.SushiSwapRouter { return true } // Check known pools if _, exists := ep.knownPools[to]; exists { return true } return false } // identifyProtocol identifies which DEX protocol a transaction is interacting with func (ep *EventParser) identifyProtocol(tx *types.Transaction) string { if tx.To() == nil { return "Unknown" } to := *tx.To() // Check factory contracts if to == ep.UniswapV2Factory { return "UniswapV2" } if to == ep.UniswapV3Factory { return "UniswapV3" } if to == ep.SushiSwapFactory { return "SushiSwap" } // Check router contracts if to == ep.UniswapV2Router01 || to == ep.UniswapV2Router02 { return "UniswapV2" } if to == ep.UniswapV3Router { return "UniswapV3" } if to == ep.SushiSwapRouter { return "SushiSwap" } // Check known pools if protocol, exists := ep.knownPools[to]; exists { return protocol } // Try to identify from function signature in transaction data if len(tx.Data()) >= 4 { sig := common.Bytes2Hex(tx.Data()[:4]) switch sig { case "0xac9650d8": // multicall (Uniswap V3) return "UniswapV3" case "0x88316456": // swap (Uniswap V2) return "UniswapV2" case "0x128acb08": // swap (SushiSwap) return "SushiSwap" case "0x38ed1739": // swapExactTokensForTokens (Uniswap V2) return "UniswapV2" case "0x8803dbee": // swapTokensForExactTokens (Uniswap V2) return "UniswapV2" case "0x7ff36ab5": // swapExactETHForTokens (Uniswap V2) return "UniswapV2" case "0xb6f9de95": // swapExactTokensForETH (Uniswap V2) return "UniswapV2" case "0x414bf389": // exactInputSingle (Uniswap V3) return "UniswapV3" case "0xdb3e2198": // exactInput (Uniswap V3) return "UniswapV3" case "0xf305d719": // exactOutputSingle (Uniswap V3) return "UniswapV3" case "0x04e45aaf": // exactOutput (Uniswap V3) return "UniswapV3" case "0x18cbafe5": // swapExactTokensForTokensSupportingFeeOnTransferTokens (Uniswap V2) return "UniswapV2" case "0x18cffa1c": // swapExactETHForTokensSupportingFeeOnTransferTokens (Uniswap V2) return "UniswapV2" case "0x791ac947": // swapExactTokensForETHSupportingFeeOnTransferTokens (Uniswap V2) return "UniswapV2" case "0x5ae401dc": // multicall (Uniswap V3) return "UniswapV3" } } return "Unknown" } // parseUniswapV2Swap parses a Uniswap V2 Swap event func (ep *EventParser) parseUniswapV2Swap(log *types.Log, blockNumber uint64, timestamp uint64, txHash common.Hash) (*Event, error) { if len(log.Topics) != 2 || len(log.Data) != 32*4 { return nil, fmt.Errorf("invalid Uniswap V2 Swap event log") } // Parse the data fields amount0In := new(big.Int).SetBytes(log.Data[0:32]) amount1In := new(big.Int).SetBytes(log.Data[32:64]) amount0Out := new(big.Int).SetBytes(log.Data[64:96]) amount1Out := new(big.Int).SetBytes(log.Data[96:128]) // Determine which token is being swapped in/out var amount0, amount1 *big.Int if amount0In.Cmp(big.NewInt(0)) > 0 { amount0 = amount0In } else { amount0 = new(big.Int).Neg(amount0Out) } if amount1In.Cmp(big.NewInt(0)) > 0 { amount1 = amount1In } else { amount1 = new(big.Int).Neg(amount1Out) } event := &Event{ Type: Swap, Protocol: "UniswapV2", PoolAddress: log.Address, Amount0: amount0, Amount1: amount1, Timestamp: timestamp, TransactionHash: txHash, BlockNumber: blockNumber, } return event, nil } // parseUniswapV3Swap parses a Uniswap V3 Swap event func (ep *EventParser) parseUniswapV3Swap(log *types.Log, blockNumber uint64, timestamp uint64, txHash common.Hash) (*Event, error) { if len(log.Topics) != 3 || len(log.Data) != 32*5 { return nil, fmt.Errorf("invalid Uniswap V3 Swap event log") } // Parse the data fields amount0 := new(big.Int).SetBytes(log.Data[0:32]) amount1 := new(big.Int).SetBytes(log.Data[32:64]) sqrtPriceX96 := new(big.Int).SetBytes(log.Data[64:96]) liquidity := new(big.Int).SetBytes(log.Data[96:128]) tick := new(big.Int).SetBytes(log.Data[128:160]) // Convert to signed values if needed if amount0.Cmp(big.NewInt(0)) > 0x7fffffffffffffff { amount0 = amount0.Sub(amount0, new(big.Int).Lsh(big.NewInt(1), 256)) } if amount1.Cmp(big.NewInt(0)) > 0x7fffffffffffffff { amount1 = amount1.Sub(amount1, new(big.Int).Lsh(big.NewInt(1), 256)) } event := &Event{ Type: Swap, Protocol: "UniswapV3", PoolAddress: log.Address, Amount0: amount0, Amount1: amount1, SqrtPriceX96: uint256.MustFromBig(sqrtPriceX96), Liquidity: uint256.MustFromBig(liquidity), Tick: int(tick.Int64()), Timestamp: timestamp, TransactionHash: txHash, BlockNumber: blockNumber, } return event, nil } // parseUniswapV2Mint parses a Uniswap V2 Mint event func (ep *EventParser) parseUniswapV2Mint(log *types.Log, blockNumber uint64, timestamp uint64, txHash common.Hash) (*Event, error) { if len(log.Topics) != 2 || len(log.Data) != 32*2 { return nil, fmt.Errorf("invalid Uniswap V2 Mint event log") } // Parse the data fields amount0 := new(big.Int).SetBytes(log.Data[0:32]) amount1 := new(big.Int).SetBytes(log.Data[32:64]) event := &Event{ Type: AddLiquidity, Protocol: "UniswapV2", PoolAddress: log.Address, Amount0: amount0, Amount1: amount1, Timestamp: timestamp, TransactionHash: txHash, BlockNumber: blockNumber, } return event, nil } // parseUniswapV3Mint parses a Uniswap V3 Mint event func (ep *EventParser) parseUniswapV3Mint(log *types.Log, blockNumber uint64, timestamp uint64, txHash common.Hash) (*Event, error) { if len(log.Topics) != 3 || len(log.Data) != 32*4 { return nil, fmt.Errorf("invalid Uniswap V3 Mint event log") } // Parse the data fields amount0 := new(big.Int).SetBytes(log.Data[0:32]) amount1 := new(big.Int).SetBytes(log.Data[32:64]) event := &Event{ Type: AddLiquidity, Protocol: "UniswapV3", PoolAddress: log.Address, Amount0: amount0, Amount1: amount1, Timestamp: timestamp, TransactionHash: txHash, BlockNumber: blockNumber, } return event, nil } // parseUniswapV2Burn parses a Uniswap V2 Burn event func (ep *EventParser) parseUniswapV2Burn(log *types.Log, blockNumber uint64, timestamp uint64, txHash common.Hash) (*Event, error) { if len(log.Topics) != 2 || len(log.Data) != 32*2 { return nil, fmt.Errorf("invalid Uniswap V2 Burn event log") } // Parse the data fields amount0 := new(big.Int).SetBytes(log.Data[0:32]) amount1 := new(big.Int).SetBytes(log.Data[32:64]) event := &Event{ Type: RemoveLiquidity, Protocol: "UniswapV2", PoolAddress: log.Address, Amount0: amount0, Amount1: amount1, Timestamp: timestamp, TransactionHash: txHash, BlockNumber: blockNumber, } return event, nil } // parseUniswapV3Burn parses a Uniswap V3 Burn event func (ep *EventParser) parseUniswapV3Burn(log *types.Log, blockNumber uint64, timestamp uint64, txHash common.Hash) (*Event, error) { if len(log.Topics) != 3 || len(log.Data) != 32*4 { return nil, fmt.Errorf("invalid Uniswap V3 Burn event log") } // Parse the data fields amount0 := new(big.Int).SetBytes(log.Data[0:32]) amount1 := new(big.Int).SetBytes(log.Data[32:64]) event := &Event{ Type: RemoveLiquidity, Protocol: "UniswapV3", PoolAddress: log.Address, Amount0: amount0, Amount1: amount1, Timestamp: timestamp, TransactionHash: txHash, BlockNumber: blockNumber, } return event, nil } // AddKnownPool adds a pool address to the known pools map func (ep *EventParser) AddKnownPool(address common.Address, protocol string) { ep.knownPools[address] = protocol } // GetKnownPools returns all known pools func (ep *EventParser) GetKnownPools() map[common.Address]string { return ep.knownPools }