package arbitrum import ( "context" "fmt" "math/big" "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rpc" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/security" ) // BaseProtocolParser provides common functionality for all protocol parsers type BaseProtocolParser struct { client *rpc.Client logger *logger.Logger protocol Protocol abi abi.ABI // Contract addresses for this protocol contracts map[ContractType][]common.Address // Function and event signatures functionSigs map[string]*FunctionSignature eventSigs map[common.Hash]*EventSignature } // NewBaseProtocolParser creates a new base protocol parser func NewBaseProtocolParser(client *rpc.Client, logger *logger.Logger, protocol Protocol) *BaseProtocolParser { return &BaseProtocolParser{ client: client, logger: logger, protocol: protocol, contracts: make(map[ContractType][]common.Address), functionSigs: make(map[string]*FunctionSignature), eventSigs: make(map[common.Hash]*EventSignature), } } // Common interface methods implementation func (p *BaseProtocolParser) GetProtocol() Protocol { return p.protocol } func (p *BaseProtocolParser) IsKnownContract(address common.Address) bool { for _, contracts := range p.contracts { for _, contract := range contracts { if contract == address { return true } } } return false } func (p *BaseProtocolParser) ValidateEvent(event *EnhancedDEXEvent) error { if event == nil { return fmt.Errorf("event is nil") } if event.Protocol != p.protocol { return fmt.Errorf("event protocol %s does not match parser protocol %s", event.Protocol, p.protocol) } if event.AmountIn != nil && event.AmountIn.Sign() < 0 { return fmt.Errorf("negative amount in") } if event.AmountOut != nil && event.AmountOut.Sign() < 0 { return fmt.Errorf("negative amount out") } return nil } // Helper methods func (p *BaseProtocolParser) decodeLogData(log *types.Log, eventABI *abi.Event) (map[string]interface{}, error) { // Decode indexed topics indexed := make(map[string]interface{}) nonIndexed := make(map[string]interface{}) topicIndex := 1 // Skip topic[0] which is the event signature for _, input := range eventABI.Inputs { if input.Indexed { if topicIndex < len(log.Topics) { value := abi.ConvertType(log.Topics[topicIndex], input.Type) indexed[input.Name] = value topicIndex++ } } } // Decode non-indexed data if len(log.Data) > 0 { nonIndexedInputs := abi.Arguments{} for _, input := range eventABI.Inputs { if !input.Indexed { nonIndexedInputs = append(nonIndexedInputs, input) } } if len(nonIndexedInputs) > 0 { values, err := nonIndexedInputs.Unpack(log.Data) if err != nil { return nil, fmt.Errorf("failed to decode log data: %w", err) } for i, input := range nonIndexedInputs { if i < len(values) { nonIndexed[input.Name] = values[i] } } } } // Merge indexed and non-indexed result := make(map[string]interface{}) for k, v := range indexed { result[k] = v } for k, v := range nonIndexed { result[k] = v } return result, nil } func (p *BaseProtocolParser) decodeFunctionData(data []byte, functionABI *abi.Method) (map[string]interface{}, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short") } // Remove function selector paramData := data[4:] values, err := functionABI.Inputs.Unpack(paramData) if err != nil { return nil, fmt.Errorf("failed to unpack function data: %w", err) } result := make(map[string]interface{}) for i, input := range functionABI.Inputs { if i < len(values) { result[input.Name] = values[i] } } return result, nil } // UniswapV2Parser implements DEXParserInterface for Uniswap V2 type UniswapV2Parser struct { *BaseProtocolParser } // NewUniswapV2Parser creates a new Uniswap V2 parser func NewUniswapV2Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolUniswapV2) parser := &UniswapV2Parser{BaseProtocolParser: base} // Initialize Uniswap V2 specific data parser.initializeUniswapV2() return parser } func (p *UniswapV2Parser) initializeUniswapV2() { // Contract addresses p.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9"), // Uniswap V2 Factory } p.contracts[ContractTypeRouter] = []common.Address{ common.HexToAddress("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24"), // Uniswap V2 Router } // Function signatures p.functionSigs["0x38ed1739"] = &FunctionSignature{ Selector: [4]byte{0x38, 0xed, 0x17, 0x39}, Name: "swapExactTokensForTokens", Protocol: ProtocolUniswapV2, ContractType: ContractTypeRouter, EventType: EventTypeSwap, Description: "Swap exact tokens for tokens", } p.functionSigs["0x8803dbee"] = &FunctionSignature{ Selector: [4]byte{0x88, 0x03, 0xdb, 0xee}, Name: "swapTokensForExactTokens", Protocol: ProtocolUniswapV2, ContractType: ContractTypeRouter, EventType: EventTypeSwap, Description: "Swap tokens for exact tokens", } // Event signatures swapTopic := crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")) p.eventSigs[swapTopic] = &EventSignature{ Topic0: swapTopic, Name: "Swap", Protocol: ProtocolUniswapV2, EventType: EventTypeSwap, Description: "Uniswap V2 swap event", } pairCreatedTopic := crypto.Keccak256Hash([]byte("PairCreated(address,address,address,uint256)")) p.eventSigs[pairCreatedTopic] = &EventSignature{ Topic0: pairCreatedTopic, Name: "PairCreated", Protocol: ProtocolUniswapV2, EventType: EventTypePoolCreated, Description: "Uniswap V2 pair created event", } // Load ABI p.loadUniswapV2ABI() } func (p *UniswapV2Parser) loadUniswapV2ABI() { abiJSON := `[ { "name": "swapExactTokensForTokens", "type": "function", "inputs": [ {"name": "amountIn", "type": "uint256"}, {"name": "amountOutMin", "type": "uint256"}, {"name": "path", "type": "address[]"}, {"name": "to", "type": "address"}, {"name": "deadline", "type": "uint256"} ] }, { "name": "Swap", "type": "event", "inputs": [ {"name": "sender", "type": "address", "indexed": true}, {"name": "amount0In", "type": "uint256", "indexed": false}, {"name": "amount1In", "type": "uint256", "indexed": false}, {"name": "amount0Out", "type": "uint256", "indexed": false}, {"name": "amount1Out", "type": "uint256", "indexed": false}, {"name": "to", "type": "address", "indexed": true} ] }, { "name": "PairCreated", "type": "event", "inputs": [ {"name": "token0", "type": "address", "indexed": true}, {"name": "token1", "type": "address", "indexed": true}, {"name": "pair", "type": "address", "indexed": false}, {"name": "allPairsLength", "type": "uint256", "indexed": false} ] } ]` var err error p.abi, err = abi.JSON(strings.NewReader(abiJSON)) if err != nil { p.logger.Error(fmt.Sprintf("Failed to load Uniswap V2 ABI: %v", err)) } } func (p *UniswapV2Parser) GetSupportedEventTypes() []EventType { return []EventType{EventTypeSwap, EventTypeLiquidityAdd, EventTypeLiquidityRemove, EventTypePoolCreated} } func (p *UniswapV2Parser) GetSupportedContractTypes() []ContractType { return []ContractType{ContractTypeRouter, ContractTypeFactory, ContractTypePool} } func (p *UniswapV2Parser) GetContractInfo(address common.Address) (*ContractInfo, error) { for contractType, addresses := range p.contracts { for _, addr := range addresses { if addr == address { return &ContractInfo{ Address: address, ContractType: contractType, Protocol: ProtocolUniswapV2, Name: fmt.Sprintf("Uniswap V2 %s", contractType), }, nil } } } return nil, fmt.Errorf("unknown Uniswap V2 contract: %s", address.Hex()) } func (p *UniswapV2Parser) ParseTransactionLogs(tx *types.Transaction, receipt *types.Receipt) ([]*EnhancedDEXEvent, error) { var events []*EnhancedDEXEvent for _, log := range receipt.Logs { if event, err := p.ParseLog(log); err == nil && event != nil { events = append(events, event) } } return events, nil } func (p *UniswapV2Parser) ParseLog(log *types.Log) (*EnhancedDEXEvent, error) { if len(log.Topics) == 0 { return nil, fmt.Errorf("log has no topics") } eventSig, exists := p.eventSigs[log.Topics[0]] if !exists { return nil, fmt.Errorf("unknown event signature") } event := &EnhancedDEXEvent{ Protocol: p.protocol, EventType: eventSig.EventType, ContractAddress: log.Address, RawLogData: log.Data, RawTopics: log.Topics, IsValid: true, } switch eventSig.Name { case "Swap": return p.parseSwapEvent(log, event) case "PairCreated": return p.parsePairCreatedEvent(log, event) default: return nil, fmt.Errorf("unsupported event: %s", eventSig.Name) } } func (p *UniswapV2Parser) parseSwapEvent(log *types.Log, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { swapEvent := p.abi.Events["Swap"] decoded, err := p.decodeLogData(log, &swapEvent) if err != nil { return nil, fmt.Errorf("failed to decode swap event: %w", err) } event.PoolAddress = log.Address event.DecodedParams = decoded // Extract sender from indexed topics if len(log.Topics) > 1 { event.Sender = common.BytesToAddress(log.Topics[1].Bytes()) } // Extract token addresses from pool contract (need to query pool for token0/token1) if err := p.enrichPoolTokens(event); err != nil { p.logger.Debug(fmt.Sprintf("Failed to get pool tokens: %v", err)) } // Extract factory address (standard V2 factory) event.FactoryAddress = common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9") // Set router address if called through router event.RouterAddress = common.HexToAddress("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24") // Extract amounts with proper logic if amount0In, ok := decoded["amount0In"].(*big.Int); ok { if amount1In, ok := decoded["amount1In"].(*big.Int); ok { if amount0In.Sign() > 0 { event.AmountIn = amount0In event.TokenIn = event.Token0 } else if amount1In.Sign() > 0 { event.AmountIn = amount1In event.TokenIn = event.Token1 } } } if amount0Out, ok := decoded["amount0Out"].(*big.Int); ok { if amount1Out, ok := decoded["amount1Out"].(*big.Int); ok { if amount0Out.Sign() > 0 { event.AmountOut = amount0Out event.TokenOut = event.Token0 } else if amount1Out.Sign() > 0 { event.AmountOut = amount1Out event.TokenOut = event.Token1 } } } // Set recipient if to, ok := decoded["to"].(common.Address); ok { event.Recipient = to } // Uniswap V2 has 0.3% fee (30 basis points) event.PoolFee = 30 return event, nil } // enrichPoolTokens gets token addresses from the pool contract func (p *UniswapV2Parser) enrichPoolTokens(event *EnhancedDEXEvent) error { // For V2, token addresses need to be queried from the pool contract // This is a placeholder - in production you'd call the pool contract // For now, we'll populate from known pool data return nil } func (p *UniswapV2Parser) parsePairCreatedEvent(log *types.Log, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { pairEvent := p.abi.Events["PairCreated"] decoded, err := p.decodeLogData(log, &pairEvent) if err != nil { return nil, fmt.Errorf("failed to decode pair created event: %w", err) } event.DecodedParams = decoded // Extract token addresses if token0, ok := decoded["token0"].(common.Address); ok { event.TokenIn = token0 } if token1, ok := decoded["token1"].(common.Address); ok { event.TokenOut = token1 } if pair, ok := decoded["pair"].(common.Address); ok { event.PoolAddress = pair } event.PoolType = PoolTypeConstantProduct return event, nil } func (p *UniswapV2Parser) ParseTransactionData(tx *types.Transaction) (*EnhancedDEXEvent, error) { if tx.To() == nil || len(tx.Data()) < 4 { return nil, fmt.Errorf("invalid transaction data") } // Check if this is a known contract if !p.IsKnownContract(*tx.To()) { return nil, fmt.Errorf("unknown contract") } // Extract function selector selector := fmt.Sprintf("0x%x", tx.Data()[:4]) funcSig, exists := p.functionSigs[selector] if !exists { return nil, fmt.Errorf("unknown function signature") } event := &EnhancedDEXEvent{ Protocol: p.protocol, EventType: funcSig.EventType, ContractAddress: *tx.To(), IsValid: true, } switch funcSig.Name { case "swapExactTokensForTokens": return p.parseSwapExactTokensForTokens(tx.Data(), event) default: return nil, fmt.Errorf("unsupported function: %s", funcSig.Name) } } func (p *UniswapV2Parser) parseSwapExactTokensForTokens(data []byte, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { method := p.abi.Methods["swapExactTokensForTokens"] decoded, err := p.decodeFunctionData(data, &method) if err != nil { return nil, fmt.Errorf("failed to decode function data: %w", err) } event.DecodedParams = decoded // Extract parameters if amountIn, ok := decoded["amountIn"].(*big.Int); ok { event.AmountIn = amountIn } if amountOutMin, ok := decoded["amountOutMin"].(*big.Int); ok { event.AmountOut = amountOutMin } if path, ok := decoded["path"].([]common.Address); ok && len(path) >= 2 { event.TokenIn = path[0] event.TokenOut = path[len(path)-1] } if to, ok := decoded["to"].(common.Address); ok { event.Recipient = to } if deadline, ok := decoded["deadline"].(*big.Int); ok { event.Deadline = deadline.Uint64() } return event, nil } func (p *UniswapV2Parser) DecodeFunctionCall(data []byte) (*EnhancedDEXEvent, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short for function selector") } selector := fmt.Sprintf("0x%x", data[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolUniswapV2, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // For common Uniswap V2 functions, we can decode the parameters switch selector { case "0x38ed1739": // swapExactTokensForTokens if len(data) >= 132 { // 4 + 32*4 bytes minimum amountIn := new(big.Int).SetBytes(data[4:36]) amountOutMin := new(big.Int).SetBytes(data[36:68]) event.AmountIn = amountIn event.AmountOut = amountOutMin // This is minimum, actual will be in logs event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMin"] = amountOutMin } case "0x7ff36ab5": // swapExactETHForTokens if len(data) >= 68 { // 4 + 32*2 bytes minimum amountOutMin := new(big.Int).SetBytes(data[4:36]) event.AmountOut = amountOutMin event.DecodedParams["amountOutMin"] = amountOutMin } case "0x18cbafe5": // swapExactTokensForETH if len(data) >= 100 { // 4 + 32*3 bytes minimum amountIn := new(big.Int).SetBytes(data[4:36]) amountOutMin := new(big.Int).SetBytes(data[36:68]) event.AmountIn = amountIn event.AmountOut = amountOutMin event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMin"] = amountOutMin } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *UniswapV2Parser) DiscoverPools(fromBlock, toBlock uint64) ([]*PoolInfo, error) { var pools []*PoolInfo // PairCreated event signature for Uniswap V2 factory pairCreatedTopic := crypto.Keccak256Hash([]byte("PairCreated(address,address,address,uint256)")) // Query logs from factory contract factoryAddresses := p.contracts[ContractTypeFactory] if len(factoryAddresses) == 0 { return nil, fmt.Errorf("no factory addresses configured") } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() for _, factoryAddr := range factoryAddresses { // Query PairCreated events var logs []interface{} err := p.client.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": fmt.Sprintf("0x%x", fromBlock), "toBlock": fmt.Sprintf("0x%x", toBlock), "address": factoryAddr.Hex(), "topics": []interface{}{pairCreatedTopic.Hex()}, }) if err != nil { p.logger.Debug(fmt.Sprintf("Failed to query PairCreated events: %v", err)) continue } // Parse each PairCreated event for _, logData := range logs { logMap, ok := logData.(map[string]interface{}) if !ok { continue } topics, ok := logMap["topics"].([]interface{}) if !ok || len(topics) < 4 { continue } // Extract token addresses from topics[1] and topics[2] token0 := common.HexToAddress(topics[1].(string)) token1 := common.HexToAddress(topics[2].(string)) pairAddr := common.HexToAddress(topics[3].(string)) // Extract block number blockNumHex, ok := logMap["blockNumber"].(string) if !ok { continue } blockNum := common.HexToHash(blockNumHex).Big().Uint64() pool := &PoolInfo{ Address: pairAddr, Protocol: ProtocolUniswapV2, PoolType: "UniswapV2", Token0: token0, Token1: token1, Fee: 30, // Uniswap V2 has 0.3% fee CreatedBlock: blockNum, IsActive: true, LastUpdated: time.Now(), } pools = append(pools, pool) } } return pools, nil } func (p *UniswapV2Parser) GetPoolInfo(poolAddress common.Address) (*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create pool contract ABI for basic queries poolABI := `[ {"name": "token0", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "token1", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]} ]` parsedABI, err := abi.JSON(strings.NewReader(poolABI)) if err != nil { return nil, fmt.Errorf("failed to parse pool ABI: %w", err) } // Query token0 token0Data, err := parsedABI.Pack("token0") if err != nil { return nil, fmt.Errorf("failed to pack token0 call: %w", err) } var token0Result string err = p.client.CallContext(ctx, &token0Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token0Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token0: %w", err) } // Query token1 token1Data, err := parsedABI.Pack("token1") if err != nil { return nil, fmt.Errorf("failed to pack token1 call: %w", err) } var token1Result string err = p.client.CallContext(ctx, &token1Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token1Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token1: %w", err) } // Decode token0 result using ABI token0ResultBytes := common.FromHex(token0Result) token0Results, err := parsedABI.Unpack("token0", token0ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token0 result: %w", err) } if len(token0Results) == 0 { return nil, fmt.Errorf("empty token0 result") } token0, ok := token0Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token0 result is not an address") } // Decode token1 result using ABI token1ResultBytes := common.FromHex(token1Result) token1Results, err := parsedABI.Unpack("token1", token1ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token1 result: %w", err) } if len(token1Results) == 0 { return nil, fmt.Errorf("empty token1 result") } token1, ok := token1Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token1 result is not an address") } return &PoolInfo{ Address: poolAddress, Protocol: ProtocolUniswapV2, PoolType: "UniswapV2", Token0: token0, Token1: token1, Fee: 30, // Uniswap V2 has 0.3% fee IsActive: true, LastUpdated: time.Now(), }, nil } func (p *UniswapV2Parser) EnrichEventData(event *EnhancedDEXEvent) error { // Implementation would add additional metadata return nil } // UniswapV3Parser implements DEXParserInterface for Uniswap V3 type UniswapV3Parser struct { *BaseProtocolParser } // NewUniswapV3Parser creates a new Uniswap V3 parser func NewUniswapV3Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolUniswapV3) parser := &UniswapV3Parser{BaseProtocolParser: base} // Initialize Uniswap V3 specific data parser.initializeUniswapV3() return parser } func (p *UniswapV3Parser) initializeUniswapV3() { // Contract addresses p.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), // Uniswap V3 Factory } p.contracts[ContractTypeRouter] = []common.Address{ common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // SwapRouter common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), // SwapRouter02 } p.contracts[ContractTypeManager] = []common.Address{ common.HexToAddress("0xC36442b4a4522E871399CD717aBDD847Ab11FE88"), // NonfungiblePositionManager } // Function signatures p.functionSigs["0x414bf389"] = &FunctionSignature{ Selector: [4]byte{0x41, 0x4b, 0xf3, 0x89}, Name: "exactInputSingle", Protocol: ProtocolUniswapV3, ContractType: ContractTypeRouter, EventType: EventTypeSwap, Description: "Exact input single pool swap", } // Event signatures swapTopic := crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24)")) p.eventSigs[swapTopic] = &EventSignature{ Topic0: swapTopic, Name: "Swap", Protocol: ProtocolUniswapV3, EventType: EventTypeSwap, Description: "Uniswap V3 swap event", } poolCreatedTopic := crypto.Keccak256Hash([]byte("PoolCreated(address,address,uint24,int24,address)")) p.eventSigs[poolCreatedTopic] = &EventSignature{ Topic0: poolCreatedTopic, Name: "PoolCreated", Protocol: ProtocolUniswapV3, EventType: EventTypePoolCreated, Description: "Uniswap V3 pool created event", } // Load ABI p.loadUniswapV3ABI() } func (p *UniswapV3Parser) loadUniswapV3ABI() { abiJSON := `[ { "name": "exactInputSingle", "type": "function", "inputs": [ { "name": "params", "type": "tuple", "components": [ {"name": "tokenIn", "type": "address"}, {"name": "tokenOut", "type": "address"}, {"name": "fee", "type": "uint24"}, {"name": "recipient", "type": "address"}, {"name": "deadline", "type": "uint256"}, {"name": "amountIn", "type": "uint256"}, {"name": "amountOutMinimum", "type": "uint256"}, {"name": "sqrtPriceLimitX96", "type": "uint160"} ] } ] }, { "name": "Swap", "type": "event", "inputs": [ {"name": "sender", "type": "address", "indexed": true}, {"name": "recipient", "type": "address", "indexed": true}, {"name": "amount0", "type": "int256", "indexed": false}, {"name": "amount1", "type": "int256", "indexed": false}, {"name": "sqrtPriceX96", "type": "uint160", "indexed": false}, {"name": "liquidity", "type": "uint128", "indexed": false}, {"name": "tick", "type": "int24", "indexed": false} ] }, { "name": "PoolCreated", "type": "event", "inputs": [ {"name": "token0", "type": "address", "indexed": true}, {"name": "token1", "type": "address", "indexed": true}, {"name": "fee", "type": "uint24", "indexed": true}, {"name": "tickSpacing", "type": "int24", "indexed": false}, {"name": "pool", "type": "address", "indexed": false} ] } ]` var err error p.abi, err = abi.JSON(strings.NewReader(abiJSON)) if err != nil { p.logger.Error(fmt.Sprintf("Failed to load Uniswap V3 ABI: %v", err)) } } // Implement the same interface methods as UniswapV2Parser but with V3-specific logic func (p *UniswapV3Parser) GetSupportedEventTypes() []EventType { return []EventType{EventTypeSwap, EventTypeLiquidityAdd, EventTypeLiquidityRemove, EventTypePoolCreated, EventTypePositionUpdate} } func (p *UniswapV3Parser) GetSupportedContractTypes() []ContractType { return []ContractType{ContractTypeRouter, ContractTypeFactory, ContractTypePool, ContractTypeManager} } func (p *UniswapV3Parser) GetContractInfo(address common.Address) (*ContractInfo, error) { for contractType, addresses := range p.contracts { for _, addr := range addresses { if addr == address { return &ContractInfo{ Address: address, ContractType: contractType, Protocol: ProtocolUniswapV3, Name: fmt.Sprintf("Uniswap V3 %s", contractType), }, nil } } } return nil, fmt.Errorf("unknown Uniswap V3 contract: %s", address.Hex()) } func (p *UniswapV3Parser) ParseTransactionLogs(tx *types.Transaction, receipt *types.Receipt) ([]*EnhancedDEXEvent, error) { var events []*EnhancedDEXEvent for _, log := range receipt.Logs { if event, err := p.ParseLog(log); err == nil && event != nil { events = append(events, event) } } return events, nil } func (p *UniswapV3Parser) ParseLog(log *types.Log) (*EnhancedDEXEvent, error) { if len(log.Topics) == 0 { return nil, fmt.Errorf("log has no topics") } eventSig, exists := p.eventSigs[log.Topics[0]] if !exists { return nil, fmt.Errorf("unknown event signature") } event := &EnhancedDEXEvent{ Protocol: p.protocol, EventType: eventSig.EventType, ContractAddress: log.Address, RawLogData: log.Data, RawTopics: log.Topics, IsValid: true, } switch eventSig.Name { case "Swap": return p.parseSwapEvent(log, event) case "PoolCreated": return p.parsePoolCreatedEvent(log, event) default: return nil, fmt.Errorf("unsupported event: %s", eventSig.Name) } } func (p *UniswapV3Parser) parseSwapEvent(log *types.Log, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { swapEvent := p.abi.Events["Swap"] decoded, err := p.decodeLogData(log, &swapEvent) if err != nil { return nil, fmt.Errorf("failed to decode swap event: %w", err) } event.PoolAddress = log.Address event.DecodedParams = decoded // Extract sender and recipient from indexed topics if len(log.Topics) > 1 { event.Sender = common.BytesToAddress(log.Topics[1].Bytes()) } if len(log.Topics) > 2 { event.Recipient = common.BytesToAddress(log.Topics[2].Bytes()) } // Extract token addresses and fee from pool contract if err := p.enrichPoolData(event); err != nil { p.logger.Debug(fmt.Sprintf("Failed to get pool data: %v", err)) } // Extract factory address (standard V3 factory) event.FactoryAddress = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984") // Determine router address from context (could be SwapRouter or SwapRouter02) event.RouterAddress = p.determineRouter(event) // Extract amounts (V3 uses signed integers) if amount0, ok := decoded["amount0"].(*big.Int); ok { if amount1, ok := decoded["amount1"].(*big.Int); ok { // In V3, negative means outgoing, positive means incoming if amount0.Sign() < 0 { event.AmountOut = new(big.Int).Abs(amount0) event.TokenOut = event.Token0 event.AmountIn = amount1 event.TokenIn = event.Token1 } else { event.AmountIn = amount0 event.TokenIn = event.Token0 event.AmountOut = new(big.Int).Abs(amount1) event.TokenOut = event.Token1 } } } // Extract V3-specific data if sqrtPriceX96, ok := decoded["sqrtPriceX96"].(*big.Int); ok { event.SqrtPriceX96 = sqrtPriceX96 } if liquidity, ok := decoded["liquidity"].(*big.Int); ok { event.Liquidity = liquidity } if tick, ok := decoded["tick"].(*big.Int); ok { event.PoolTick = tick } return event, nil } // enrichPoolData gets comprehensive pool data including tokens and fee func (p *UniswapV3Parser) enrichPoolData(event *EnhancedDEXEvent) error { // For V3, we need to query the pool contract for token0, token1, and fee // This is a placeholder - in production you'd call the pool contract return nil } // determineRouter determines which router was used based on context func (p *UniswapV3Parser) determineRouter(event *EnhancedDEXEvent) common.Address { // Default to SwapRouter02 which is more commonly used return common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45") } func (p *UniswapV3Parser) parsePoolCreatedEvent(log *types.Log, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { poolEvent := p.abi.Events["PoolCreated"] decoded, err := p.decodeLogData(log, &poolEvent) if err != nil { return nil, fmt.Errorf("failed to decode pool created event: %w", err) } event.DecodedParams = decoded // Extract token addresses if token0, ok := decoded["token0"].(common.Address); ok { event.TokenIn = token0 } if token1, ok := decoded["token1"].(common.Address); ok { event.TokenOut = token1 } if pool, ok := decoded["pool"].(common.Address); ok { event.PoolAddress = pool } if fee, ok := decoded["fee"].(*big.Int); ok { event.PoolFee = uint32(fee.Uint64()) } event.PoolType = PoolTypeConcentrated return event, nil } func (p *UniswapV3Parser) ParseTransactionData(tx *types.Transaction) (*EnhancedDEXEvent, error) { if tx.To() == nil || len(tx.Data()) < 4 { return nil, fmt.Errorf("invalid transaction data") } // Check if this is a known contract if !p.IsKnownContract(*tx.To()) { return nil, fmt.Errorf("unknown contract") } // Extract function selector selector := fmt.Sprintf("0x%x", tx.Data()[:4]) funcSig, exists := p.functionSigs[selector] if !exists { return nil, fmt.Errorf("unknown function signature") } event := &EnhancedDEXEvent{ Protocol: p.protocol, EventType: funcSig.EventType, ContractAddress: *tx.To(), IsValid: true, } switch funcSig.Name { case "exactInputSingle": return p.parseExactInputSingle(tx.Data(), event) default: return nil, fmt.Errorf("unsupported function: %s", funcSig.Name) } } func (p *UniswapV3Parser) parseExactInputSingle(data []byte, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { method := p.abi.Methods["exactInputSingle"] decoded, err := p.decodeFunctionData(data, &method) if err != nil { return nil, fmt.Errorf("failed to decode function data: %w", err) } event.DecodedParams = decoded // Extract parameters from tuple if params, ok := decoded["params"].(struct { TokenIn common.Address TokenOut common.Address Fee *big.Int Recipient common.Address Deadline *big.Int AmountIn *big.Int AmountOutMinimum *big.Int SqrtPriceLimitX96 *big.Int }); ok { event.TokenIn = params.TokenIn event.TokenOut = params.TokenOut event.PoolFee = uint32(params.Fee.Uint64()) event.Recipient = params.Recipient event.Deadline = params.Deadline.Uint64() event.AmountIn = params.AmountIn event.AmountOut = params.AmountOutMinimum } return event, nil } func (p *UniswapV3Parser) DecodeFunctionCall(data []byte) (*EnhancedDEXEvent, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short for function selector") } selector := fmt.Sprintf("0x%x", data[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolUniswapV3, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // For common Uniswap V3 functions, we can decode the parameters switch selector { case "0x414bf389": // exactInputSingle if len(data) >= 164 { // 4 + 32*5 bytes minimum // Decode ExactInputSingleParams struct tokenIn := common.BytesToAddress(data[4:36]) tokenOut := common.BytesToAddress(data[36:68]) fee := new(big.Int).SetBytes(data[68:100]) amountIn := new(big.Int).SetBytes(data[132:164]) event.TokenIn = tokenIn event.TokenOut = tokenOut event.AmountIn = amountIn event.PoolFee = uint32(fee.Uint64()) event.DecodedParams["tokenIn"] = tokenIn event.DecodedParams["tokenOut"] = tokenOut event.DecodedParams["fee"] = fee event.DecodedParams["amountIn"] = amountIn } case "0x09b81346": // exactInput (multi-hop) if len(data) >= 68 { // 4 + 32*2 bytes minimum amountIn := new(big.Int).SetBytes(data[4:36]) amountOutMin := new(big.Int).SetBytes(data[36:68]) event.AmountIn = amountIn event.AmountOut = amountOutMin event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMinimum"] = amountOutMin } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *UniswapV3Parser) DiscoverPools(fromBlock, toBlock uint64) ([]*PoolInfo, error) { var pools []*PoolInfo // PoolCreated event signature for Uniswap V3 factory poolCreatedTopic := crypto.Keccak256Hash([]byte("PoolCreated(address,address,uint24,int24,address)")) // Query logs from factory contract factoryAddresses := p.contracts[ContractTypeFactory] if len(factoryAddresses) == 0 { return nil, fmt.Errorf("no factory addresses configured") } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() for _, factoryAddr := range factoryAddresses { // Query PoolCreated events var logs []interface{} err := p.client.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": fmt.Sprintf("0x%x", fromBlock), "toBlock": fmt.Sprintf("0x%x", toBlock), "address": factoryAddr.Hex(), "topics": []interface{}{poolCreatedTopic.Hex()}, }) if err != nil { p.logger.Debug(fmt.Sprintf("Failed to query PoolCreated events: %v", err)) continue } // Parse each PoolCreated event for _, logData := range logs { logMap, ok := logData.(map[string]interface{}) if !ok { continue } topics, ok := logMap["topics"].([]interface{}) if !ok || len(topics) < 4 { continue } // Extract token addresses from topics[1] and topics[2] token0 := common.HexToAddress(topics[1].(string)) token1 := common.HexToAddress(topics[2].(string)) // Extract fee from topics[3] (uint24) feeHex := topics[3].(string) fee := common.HexToHash(feeHex).Big().Uint64() // Extract pool address from data data, ok := logMap["data"].(string) if !ok || len(data) < 66 { continue } poolAddr := common.HexToAddress(data[26:66]) // Skip first 32 bytes (tick), take next 20 bytes // Extract block number blockNumHex, ok := logMap["blockNumber"].(string) if !ok { continue } blockNum := common.HexToHash(blockNumHex).Big().Uint64() pool := &PoolInfo{ Address: poolAddr, Protocol: ProtocolUniswapV3, PoolType: "UniswapV3", Token0: token0, Token1: token1, Fee: uint32(fee), CreatedBlock: blockNum, IsActive: true, LastUpdated: time.Now(), } pools = append(pools, pool) } } return pools, nil } func (p *UniswapV3Parser) GetPoolInfo(poolAddress common.Address) (*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create pool contract ABI for basic queries poolABI := `[ {"name": "token0", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "token1", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "fee", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "uint24"}]} ]` parsedABI, err := abi.JSON(strings.NewReader(poolABI)) if err != nil { return nil, fmt.Errorf("failed to parse pool ABI: %w", err) } // Query token0 token0Data, err := parsedABI.Pack("token0") if err != nil { return nil, fmt.Errorf("failed to pack token0 call: %w", err) } var token0Result string err = p.client.CallContext(ctx, &token0Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token0Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token0: %w", err) } // Query token1 token1Data, err := parsedABI.Pack("token1") if err != nil { return nil, fmt.Errorf("failed to pack token1 call: %w", err) } var token1Result string err = p.client.CallContext(ctx, &token1Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token1Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token1: %w", err) } // Query fee feeData, err := parsedABI.Pack("fee") if err != nil { return nil, fmt.Errorf("failed to pack fee call: %w", err) } var feeResult string err = p.client.CallContext(ctx, &feeResult, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", feeData), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query fee: %w", err) } // Decode token0 result using ABI token0ResultBytes := common.FromHex(token0Result) token0Results, err := parsedABI.Unpack("token0", token0ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token0 result: %w", err) } if len(token0Results) == 0 { return nil, fmt.Errorf("empty token0 result") } token0, ok := token0Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token0 result is not an address") } // Decode token1 result using ABI token1ResultBytes := common.FromHex(token1Result) token1Results, err := parsedABI.Unpack("token1", token1ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token1 result: %w", err) } if len(token1Results) == 0 { return nil, fmt.Errorf("empty token1 result") } token1, ok := token1Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token1 result is not an address") } // Decode fee result using ABI (uint24) feeResultBytes := common.FromHex(feeResult) feeResults, err := parsedABI.Unpack("fee", feeResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack fee result: %w", err) } if len(feeResults) == 0 { return nil, fmt.Errorf("empty fee result") } feeValue, ok := feeResults[0].(*big.Int) if !ok { return nil, fmt.Errorf("fee result is not a big.Int") } fee := feeValue.Uint64() return &PoolInfo{ Address: poolAddress, Protocol: ProtocolUniswapV3, PoolType: "UniswapV3", Token0: token0, Token1: token1, Fee: uint32(fee), IsActive: true, LastUpdated: time.Now(), }, nil } func (p *UniswapV3Parser) EnrichEventData(event *EnhancedDEXEvent) error { return nil } // Placeholder parsers for other protocols // In a full implementation, each would have complete parsing logic // SushiSwapV2Parser - Real implementation for SushiSwap V2 type SushiSwapV2Parser struct { *BaseProtocolParser contractABI abi.ABI } func NewSushiSwapV2Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolSushiSwapV2) parser := &SushiSwapV2Parser{BaseProtocolParser: base} parser.initializeSushiSwapV2() return parser } func (p *SushiSwapV2Parser) initializeSushiSwapV2() { // SushiSwap V2 Factory and Router addresses on Arbitrum p.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"), // SushiSwap V2 Factory } p.contracts[ContractTypeRouter] = []common.Address{ common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), // SushiSwap V2 Router } // Event signatures - same as Uniswap V2 but different contracts swapTopic := crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")) p.eventSigs[swapTopic] = &EventSignature{ Topic0: swapTopic, Name: "Swap", Protocol: ProtocolSushiSwapV2, EventType: EventTypeSwap, Description: "SushiSwap V2 swap event", } p.loadSushiSwapV2ABI() } func (p *SushiSwapV2Parser) loadSushiSwapV2ABI() { abiJSON := `[ { "anonymous": false, "inputs": [ {"indexed": true, "name": "sender", "type": "address"}, {"indexed": false, "name": "amount0In", "type": "uint256"}, {"indexed": false, "name": "amount1In", "type": "uint256"}, {"indexed": false, "name": "amount0Out", "type": "uint256"}, {"indexed": false, "name": "amount1Out", "type": "uint256"}, {"indexed": true, "name": "to", "type": "address"} ], "name": "Swap", "type": "event" } ]` var err error p.contractABI, err = abi.JSON(strings.NewReader(abiJSON)) if err != nil { p.logger.Error(fmt.Sprintf("Failed to parse SushiSwap V2 ABI: %v", err)) } } func (p *SushiSwapV2Parser) GetSupportedEventTypes() []EventType { return []EventType{EventTypeSwap, EventTypeLiquidityAdd, EventTypeLiquidityRemove} } func (p *SushiSwapV2Parser) GetSupportedContractTypes() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *SushiSwapV2Parser) GetContractInfo(address common.Address) (*ContractInfo, error) { for contractType, addresses := range p.contracts { for _, addr := range addresses { if addr == address { return &ContractInfo{ Address: address, ContractType: contractType, Protocol: ProtocolSushiSwapV2, Name: fmt.Sprintf("SushiSwap V2 %s", contractType), }, nil } } } return nil, fmt.Errorf("unknown SushiSwap V2 contract: %s", address.Hex()) } func (p *SushiSwapV2Parser) ParseTransactionLogs(tx *types.Transaction, receipt *types.Receipt) ([]*EnhancedDEXEvent, error) { var events []*EnhancedDEXEvent for _, log := range receipt.Logs { if event, err := p.ParseLog(log); err == nil && event != nil { event.BlockNumber = receipt.BlockNumber.Uint64() events = append(events, event) } } return events, nil } func (p *SushiSwapV2Parser) ParseLog(log *types.Log) (*EnhancedDEXEvent, error) { if !p.IsKnownContract(log.Address) { return nil, fmt.Errorf("unknown contract address: %s", log.Address.Hex()) } event := &EnhancedDEXEvent{ Protocol: ProtocolSushiSwapV2, EventType: EventTypeSwap, DecodedParams: make(map[string]interface{}), } if len(log.Topics) > 0 { if sig, exists := p.eventSigs[log.Topics[0]]; exists { switch sig.Name { case "Swap": return p.parseSwapEvent(log, event) default: return nil, fmt.Errorf("unsupported event: %s", sig.Name) } } } return nil, fmt.Errorf("unrecognized event signature") } func (p *SushiSwapV2Parser) parseSwapEvent(log *types.Log, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { // Extract indexed parameters from topics if len(log.Topics) > 1 { event.Sender = common.BytesToAddress(log.Topics[1].Bytes()) } if len(log.Topics) > 2 { event.Recipient = common.BytesToAddress(log.Topics[2].Bytes()) } // Decode log data swapEvent := p.contractABI.Events["Swap"] decoded, err := p.decodeLogData(log, &swapEvent) if err != nil { return nil, fmt.Errorf("failed to decode SushiSwap swap event: %w", err) } event.DecodedParams = decoded event.PoolAddress = log.Address event.Factory = common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4") event.Router = common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506") // Extract amounts - SushiSwap V2 uses same logic as Uniswap V2 if amount0In, ok := decoded["amount0In"].(*big.Int); ok && amount0In.Sign() > 0 { event.AmountIn = amount0In } if amount1In, ok := decoded["amount1In"].(*big.Int); ok && amount1In.Sign() > 0 { event.AmountIn = amount1In } if amount0Out, ok := decoded["amount0Out"].(*big.Int); ok && amount0Out.Sign() > 0 { event.AmountOut = amount0Out } if amount1Out, ok := decoded["amount1Out"].(*big.Int); ok && amount1Out.Sign() > 0 { event.AmountOut = amount1Out } // SushiSwap V2 has 0.3% fee = 30 basis points event.FeeBps = 30 event.PoolFee = 30 return event, nil } func (p *SushiSwapV2Parser) ParseTransactionData(tx *types.Transaction) (*EnhancedDEXEvent, error) { if len(tx.Data()) < 4 { return nil, fmt.Errorf("transaction data too short") } selector := fmt.Sprintf("0x%x", tx.Data()[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolSushiSwapV2, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Decode common SushiSwap V2 functions (same as Uniswap V2) switch selector { case "0x38ed1739": // swapExactTokensForTokens if len(tx.Data()) >= 132 { amountIn := new(big.Int).SetBytes(tx.Data()[4:36]) amountOutMin := new(big.Int).SetBytes(tx.Data()[36:68]) event.AmountIn = amountIn event.AmountOut = amountOutMin event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMin"] = amountOutMin } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *SushiSwapV2Parser) DecodeFunctionCall(data []byte) (*EnhancedDEXEvent, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short for function selector") } selector := fmt.Sprintf("0x%x", data[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolSushiSwapV2, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Same function decoding as Uniswap V2 switch selector { case "0x38ed1739": // swapExactTokensForTokens if len(data) >= 132 { amountIn := new(big.Int).SetBytes(data[4:36]) amountOutMin := new(big.Int).SetBytes(data[36:68]) event.AmountIn = amountIn event.AmountOut = amountOutMin event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMin"] = amountOutMin } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *SushiSwapV2Parser) DiscoverPools(fromBlock, toBlock uint64) ([]*PoolInfo, error) { var pools []*PoolInfo // PairCreated event signature (same as Uniswap V2) pairCreatedTopic := crypto.Keccak256Hash([]byte("PairCreated(address,address,address,uint256)")) // Query logs from SushiSwap factory contract factoryAddresses := p.contracts[ContractTypeFactory] if len(factoryAddresses) == 0 { return nil, fmt.Errorf("no factory addresses configured") } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() for _, factoryAddr := range factoryAddresses { // Query PairCreated events var logs []interface{} err := p.client.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": fmt.Sprintf("0x%x", fromBlock), "toBlock": fmt.Sprintf("0x%x", toBlock), "address": factoryAddr.Hex(), "topics": []interface{}{pairCreatedTopic.Hex()}, }) if err != nil { p.logger.Debug(fmt.Sprintf("Failed to query SushiSwap PairCreated events: %v", err)) continue } // Parse each PairCreated event for _, logData := range logs { logMap, ok := logData.(map[string]interface{}) if !ok { continue } topics, ok := logMap["topics"].([]interface{}) if !ok || len(topics) < 4 { continue } // Extract token addresses and pair address token0 := common.HexToAddress(topics[1].(string)) token1 := common.HexToAddress(topics[2].(string)) pairAddr := common.HexToAddress(topics[3].(string)) // Extract block number blockNumHex, ok := logMap["blockNumber"].(string) if !ok { continue } blockNum := common.HexToHash(blockNumHex).Big().Uint64() pool := &PoolInfo{ Address: pairAddr, Protocol: ProtocolSushiSwapV2, PoolType: "UniswapV2", Token0: token0, Token1: token1, Fee: 30, // SushiSwap V2 has 0.3% fee CreatedBlock: blockNum, IsActive: true, LastUpdated: time.Now(), } pools = append(pools, pool) } } return pools, nil } func (p *SushiSwapV2Parser) GetPoolInfo(poolAddress common.Address) (*PoolInfo, error) { // Query the pool contract for token information ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create pool contract interface poolABI := `[ {"name": "token0", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "token1", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "getReserves", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "_reserve0", "type": "uint112"}, {"name": "_reserve1", "type": "uint112"}, {"name": "_blockTimestampLast", "type": "uint32"}]} ]` parsedABI, err := abi.JSON(strings.NewReader(poolABI)) if err != nil { return nil, fmt.Errorf("failed to parse pool ABI: %w", err) } // Query token0 token0Data, err := parsedABI.Pack("token0") if err != nil { return nil, fmt.Errorf("failed to pack token0 call: %w", err) } var token0Result string err = p.client.CallContext(ctx, &token0Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token0Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token0: %w", err) } // Query token1 token1Data, err := parsedABI.Pack("token1") if err != nil { return nil, fmt.Errorf("failed to pack token1 call: %w", err) } var token1Result string err = p.client.CallContext(ctx, &token1Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token1Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token1: %w", err) } // Decode token0 result using ABI token0ResultBytes := common.FromHex(token0Result) token0Results, err := parsedABI.Unpack("token0", token0ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token0 result: %w", err) } if len(token0Results) == 0 { return nil, fmt.Errorf("empty token0 result") } token0, ok := token0Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token0 result is not an address") } // Decode token1 result using ABI token1ResultBytes := common.FromHex(token1Result) token1Results, err := parsedABI.Unpack("token1", token1ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token1 result: %w", err) } if len(token1Results) == 0 { return nil, fmt.Errorf("empty token1 result") } token1, ok := token1Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token1 result is not an address") } return &PoolInfo{ Address: poolAddress, Protocol: ProtocolSushiSwapV2, PoolType: "UniswapV2", Token0: token0, Token1: token1, Fee: 30, // SushiSwap V2 uses 0.3% fee IsActive: true, LastUpdated: time.Now(), }, nil } func (p *SushiSwapV2Parser) EnrichEventData(event *EnhancedDEXEvent) error { return nil } func NewSushiSwapV3Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { // SushiSwap V3 uses similar interface to Uniswap V3 but with different addresses base := NewBaseProtocolParser(client, logger, ProtocolSushiSwapV3) parser := &UniswapV3Parser{BaseProtocolParser: base} // Override with SushiSwap V3 specific addresses parser.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0x1af415a1EbA07a4986a52B6f2e7dE7003D82231e"), // SushiSwap V3 Factory } parser.protocol = ProtocolSushiSwapV3 return parser } // CamelotV2Parser - Real implementation for Camelot V2 type CamelotV2Parser struct { *BaseProtocolParser contractABI abi.ABI } // CamelotV3Parser - Implements CamelotV3 (similar to Uniswap V3 but with Camelot-specific features) type CamelotV3Parser struct { *BaseProtocolParser contractABI abi.ABI } // TraderJoeV2Parser - Implements TraderJoe V2 with liquidity bins (LB) and concentrated liquidity type TraderJoeV2Parser struct { *BaseProtocolParser contractABI abi.ABI } // KyberElasticParser - Implements KyberSwap Elastic with concentrated liquidity (V3-style) type KyberElasticParser struct { *BaseProtocolParser contractABI abi.ABI } func NewCamelotV2Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolCamelotV2) parser := &CamelotV2Parser{BaseProtocolParser: base} parser.initializeCamelotV2() return parser } func (p *CamelotV2Parser) initializeCamelotV2() { // Camelot V2 contracts on Arbitrum p.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0x6EcCab422D763aC031210895C81787E87B91B678"), // Camelot V2 Factory } p.contracts[ContractTypeRouter] = []common.Address{ common.HexToAddress("0xc873fEcbd354f5A56E00E710B90EF4201db2448d"), // Camelot V2 Router } // Camelot uses same event signature as Uniswap V2 swapTopic := crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")) p.eventSigs[swapTopic] = &EventSignature{ Topic0: swapTopic, Name: "Swap", Protocol: ProtocolCamelotV2, EventType: EventTypeSwap, Description: "Camelot V2 swap event", } p.loadCamelotV2ABI() } func (p *CamelotV2Parser) loadCamelotV2ABI() { abiJSON := `[ { "anonymous": false, "inputs": [ {"indexed": true, "name": "sender", "type": "address"}, {"indexed": false, "name": "amount0In", "type": "uint256"}, {"indexed": false, "name": "amount1In", "type": "uint256"}, {"indexed": false, "name": "amount0Out", "type": "uint256"}, {"indexed": false, "name": "amount1Out", "type": "uint256"}, {"indexed": true, "name": "to", "type": "address"} ], "name": "Swap", "type": "event" } ]` var err error p.contractABI, err = abi.JSON(strings.NewReader(abiJSON)) if err != nil { p.logger.Error(fmt.Sprintf("Failed to parse Camelot V2 ABI: %v", err)) } } func (p *CamelotV2Parser) GetSupportedEventTypes() []EventType { return []EventType{EventTypeSwap, EventTypeLiquidityAdd, EventTypeLiquidityRemove} } func (p *CamelotV2Parser) GetSupportedContractTypes() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *CamelotV2Parser) GetContractInfo(address common.Address) (*ContractInfo, error) { for contractType, addresses := range p.contracts { for _, addr := range addresses { if addr == address { return &ContractInfo{ Address: address, ContractType: contractType, Protocol: ProtocolCamelotV2, Name: fmt.Sprintf("Camelot V2 %s", contractType), }, nil } } } return nil, fmt.Errorf("unknown Camelot V2 contract: %s", address.Hex()) } func (p *CamelotV2Parser) ParseTransactionLogs(tx *types.Transaction, receipt *types.Receipt) ([]*EnhancedDEXEvent, error) { var events []*EnhancedDEXEvent for _, log := range receipt.Logs { if event, err := p.ParseLog(log); err == nil && event != nil { event.BlockNumber = receipt.BlockNumber.Uint64() events = append(events, event) } } return events, nil } func (p *CamelotV2Parser) ParseLog(log *types.Log) (*EnhancedDEXEvent, error) { if !p.IsKnownContract(log.Address) { return nil, fmt.Errorf("unknown contract address: %s", log.Address.Hex()) } event := &EnhancedDEXEvent{ Protocol: ProtocolCamelotV2, EventType: EventTypeSwap, DecodedParams: make(map[string]interface{}), } if len(log.Topics) > 0 { if sig, exists := p.eventSigs[log.Topics[0]]; exists { switch sig.Name { case "Swap": return p.parseSwapEvent(log, event) default: return nil, fmt.Errorf("unsupported event: %s", sig.Name) } } } return nil, fmt.Errorf("unrecognized event signature") } func (p *CamelotV2Parser) parseSwapEvent(log *types.Log, event *EnhancedDEXEvent) (*EnhancedDEXEvent, error) { if len(log.Topics) > 1 { event.Sender = common.BytesToAddress(log.Topics[1].Bytes()) } if len(log.Topics) > 2 { event.Recipient = common.BytesToAddress(log.Topics[2].Bytes()) } swapEvent := p.contractABI.Events["Swap"] decoded, err := p.decodeLogData(log, &swapEvent) if err != nil { return nil, fmt.Errorf("failed to decode Camelot swap event: %w", err) } event.DecodedParams = decoded event.PoolAddress = log.Address event.Factory = common.HexToAddress("0x6EcCab422D763aC031210895C81787E87B91B678") event.Router = common.HexToAddress("0xc873fEcbd354f5A56E00E710B90EF4201db2448d") // Extract amounts if amount0In, ok := decoded["amount0In"].(*big.Int); ok && amount0In.Sign() > 0 { event.AmountIn = amount0In } if amount1In, ok := decoded["amount1In"].(*big.Int); ok && amount1In.Sign() > 0 { event.AmountIn = amount1In } if amount0Out, ok := decoded["amount0Out"].(*big.Int); ok && amount0Out.Sign() > 0 { event.AmountOut = amount0Out } if amount1Out, ok := decoded["amount1Out"].(*big.Int); ok && amount1Out.Sign() > 0 { event.AmountOut = amount1Out } // Camelot V2 uses dynamic fees, default 0.3% event.FeeBps = 30 event.PoolFee = 30 return event, nil } func (p *CamelotV2Parser) ParseTransactionData(tx *types.Transaction) (*EnhancedDEXEvent, error) { if len(tx.Data()) < 4 { return nil, fmt.Errorf("transaction data too short") } selector := fmt.Sprintf("0x%x", tx.Data()[:4]) if sig, exists := p.functionSigs[selector]; exists { return &EnhancedDEXEvent{ Protocol: ProtocolCamelotV2, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), }, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *CamelotV2Parser) DecodeFunctionCall(data []byte) (*EnhancedDEXEvent, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short for function selector") } selector := fmt.Sprintf("0x%x", data[:4]) if sig, exists := p.functionSigs[selector]; exists { return &EnhancedDEXEvent{ Protocol: ProtocolCamelotV2, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), }, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *CamelotV2Parser) DiscoverPools(fromBlock, toBlock uint64) ([]*PoolInfo, error) { var pools []*PoolInfo // PairCreated event signature (same as Uniswap V2) pairCreatedTopic := crypto.Keccak256Hash([]byte("PairCreated(address,address,address,uint256)")) // Query logs from Camelot factory contract factoryAddresses := p.contracts[ContractTypeFactory] if len(factoryAddresses) == 0 { return nil, fmt.Errorf("no factory addresses configured") } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() for _, factoryAddr := range factoryAddresses { var logs []interface{} err := p.client.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": fmt.Sprintf("0x%x", fromBlock), "toBlock": fmt.Sprintf("0x%x", toBlock), "address": factoryAddr.Hex(), "topics": []interface{}{pairCreatedTopic.Hex()}, }) if err != nil { continue } // Parse each PairCreated event (same logic as other V2 parsers) for _, logData := range logs { logMap, ok := logData.(map[string]interface{}) if !ok { continue } topics, ok := logMap["topics"].([]interface{}) if !ok || len(topics) < 4 { continue } token0 := common.HexToAddress(topics[1].(string)) token1 := common.HexToAddress(topics[2].(string)) pairAddr := common.HexToAddress(topics[3].(string)) blockNumHex, ok := logMap["blockNumber"].(string) if !ok { continue } blockNum := common.HexToHash(blockNumHex).Big().Uint64() pools = append(pools, &PoolInfo{ Address: pairAddr, Protocol: ProtocolCamelotV2, PoolType: "UniswapV2", Token0: token0, Token1: token1, Fee: 30, // Camelot V2 dynamic fees, default 0.3% CreatedBlock: blockNum, IsActive: true, LastUpdated: time.Now(), }) } } return pools, nil } func (p *CamelotV2Parser) GetPoolInfo(poolAddress common.Address) (*PoolInfo, error) { // Query the pool contract for token information ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Camelot V2 uses same interface as Uniswap V2 poolABI := `[ {"name": "token0", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "token1", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "getReserves", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "_reserve0", "type": "uint112"}, {"name": "_reserve1", "type": "uint112"}, {"name": "_blockTimestampLast", "type": "uint32"}]} ]` parsedABI, err := abi.JSON(strings.NewReader(poolABI)) if err != nil { return nil, fmt.Errorf("failed to parse pool ABI: %w", err) } // Query token0 token0Data, err := parsedABI.Pack("token0") if err != nil { return nil, fmt.Errorf("failed to pack token0 call: %w", err) } var token0Result string err = p.client.CallContext(ctx, &token0Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token0Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token0: %w", err) } // Query token1 token1Data, err := parsedABI.Pack("token1") if err != nil { return nil, fmt.Errorf("failed to pack token1 call: %w", err) } var token1Result string err = p.client.CallContext(ctx, &token1Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token1Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token1: %w", err) } // Decode token0 result using ABI token0ResultBytes := common.FromHex(token0Result) token0Results, err := parsedABI.Unpack("token0", token0ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token0 result: %w", err) } if len(token0Results) == 0 { return nil, fmt.Errorf("empty token0 result") } token0, ok := token0Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token0 result is not an address") } // Decode token1 result using ABI token1ResultBytes := common.FromHex(token1Result) token1Results, err := parsedABI.Unpack("token1", token1ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token1 result: %w", err) } if len(token1Results) == 0 { return nil, fmt.Errorf("empty token1 result") } token1, ok := token1Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token1 result is not an address") } return &PoolInfo{ Address: poolAddress, Protocol: ProtocolCamelotV2, PoolType: "UniswapV2", Token0: token0, Token1: token1, Fee: 30, // Camelot V2 uses dynamic fees, default 0.3% IsActive: true, LastUpdated: time.Now(), }, nil } func (p *CamelotV2Parser) EnrichEventData(event *EnhancedDEXEvent) error { return nil } // CamelotV3Parser Implementation func (p *CamelotV3Parser) initializeCamelotV3() { // Camelot V3 contract addresses on Arbitrum p.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"), // Camelot V3 Factory } p.contracts[ContractTypeRouter] = []common.Address{ common.HexToAddress("0x1F721E2E82F6676FCE4eA07A5958cF098D339e18"), // Camelot V3 Router } // Camelot V3 uses similar events to Uniswap V3 but with different signatures swapTopic := crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24)")) poolCreatedTopic := crypto.Keccak256Hash([]byte("PoolCreated(address,address,uint24,int24,address)")) p.eventSigs[swapTopic] = &EventSignature{ Topic0: swapTopic, Name: "Swap", Protocol: ProtocolCamelotV3, EventType: EventTypeSwap, Description: "Camelot V3 swap event", } p.eventSigs[poolCreatedTopic] = &EventSignature{ Topic0: poolCreatedTopic, Name: "PoolCreated", Protocol: ProtocolCamelotV3, EventType: EventTypePoolCreated, Description: "Camelot V3 pool creation event", } p.loadCamelotV3ABI() } func (p *CamelotV3Parser) loadCamelotV3ABI() { // Simplified Camelot V3 ABI - key functions and events abiJSON := `[ {"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":true,"name":"recipient","type":"address"},{"indexed":false,"name":"amount0","type":"int256"},{"indexed":false,"name":"amount1","type":"int256"},{"indexed":false,"name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"name":"liquidity","type":"uint128"},{"indexed":false,"name":"tick","type":"int24"}],"name":"Swap","type":"event"}, {"inputs":[{"name":"tokenA","type":"address"},{"name":"tokenB","type":"address"},{"name":"fee","type":"uint24"},{"name":"amountIn","type":"uint256"},{"name":"amountOutMin","type":"uint256"},{"name":"path","type":"bytes"},{"name":"to","type":"address"},{"name":"deadline","type":"uint256"}],"name":"exactInputSingle","outputs":[{"name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"} ]` parsedABI, err := abi.JSON(strings.NewReader(abiJSON)) if err != nil { p.logger.Error("Failed to parse Camelot V3 ABI:", err) return } p.contractABI = parsedABI // Function signatures for common Camelot V3 functions exactInputSelector := "0x414bf389" exactInputBytes := [4]byte{0x41, 0x4b, 0xf3, 0x89} p.functionSigs[exactInputSelector] = &FunctionSignature{ Selector: exactInputBytes, Name: "exactInputSingle", Protocol: ProtocolCamelotV3, EventType: EventTypeSwap, Description: "Camelot V3 exact input single swap", } } func (p *CamelotV3Parser) GetSupportedContracts() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *CamelotV3Parser) GetSupportedContractTypes() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *CamelotV3Parser) GetSupportedEventTypes() []EventType { return []EventType{EventTypeSwap, EventTypeLiquidityAdd, EventTypeLiquidityRemove, EventTypePoolCreated, EventTypePositionUpdate} } func (p *CamelotV3Parser) GetContractInfo(address common.Address) (*ContractInfo, error) { for contractType, addresses := range p.contracts { for _, addr := range addresses { if addr == address { return &ContractInfo{ Address: address, ContractType: contractType, Protocol: ProtocolCamelotV3, Name: fmt.Sprintf("Camelot V3 %s", contractType), }, nil } } } return nil, fmt.Errorf("unknown Camelot V3 contract: %s", address.Hex()) } func (p *CamelotV3Parser) IsKnownContract(address common.Address) bool { _, err := p.GetContractInfo(address) return err == nil } func (p *CamelotV3Parser) ParseLog(log *types.Log) (*EnhancedDEXEvent, error) { if !p.IsKnownContract(log.Address) { return nil, fmt.Errorf("unknown contract address: %s", log.Address.Hex()) } event := &EnhancedDEXEvent{ Protocol: ProtocolCamelotV3, EventType: EventTypeSwap, DecodedParams: make(map[string]interface{}), } if len(log.Topics) > 0 { if sig, exists := p.eventSigs[log.Topics[0]]; exists { event.EventType = sig.EventType // Parse Camelot V3 Swap event if sig.Name == "Swap" && len(log.Topics) >= 3 { // Extract indexed parameters event.DecodedParams["sender"] = common.HexToAddress(log.Topics[1].Hex()) event.DecodedParams["recipient"] = common.HexToAddress(log.Topics[2].Hex()) // Decode non-indexed parameters from data if len(log.Data) >= 160 { // 5 * 32 bytes 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]) event.DecodedParams["amount0"] = amount0 event.DecodedParams["amount1"] = amount1 event.DecodedParams["sqrtPriceX96"] = sqrtPriceX96 event.DecodedParams["liquidity"] = liquidity event.DecodedParams["tick"] = tick } } } } return event, nil } func (p *CamelotV3Parser) ParseTransactionData(tx *types.Transaction) (*EnhancedDEXEvent, error) { if len(tx.Data()) < 4 { return nil, fmt.Errorf("transaction data too short") } selector := fmt.Sprintf("0x%x", tx.Data()[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolCamelotV3, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Decode exactInputSingle function if sig.Name == "exactInputSingle" && len(tx.Data()) >= 260 { // Decode parameters (simplified) data := tx.Data()[4:] // Skip function selector if len(data) >= 256 { tokenIn := common.BytesToAddress(data[12:32]) tokenOut := common.BytesToAddress(data[44:64]) fee := new(big.Int).SetBytes(data[64:96]) amountIn := new(big.Int).SetBytes(data[96:128]) amountOutMin := new(big.Int).SetBytes(data[128:160]) event.DecodedParams["tokenIn"] = tokenIn event.DecodedParams["tokenOut"] = tokenOut event.DecodedParams["fee"] = fee event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMin"] = amountOutMin } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *CamelotV3Parser) DecodeFunctionCall(data []byte) (*EnhancedDEXEvent, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short for function selector") } selector := fmt.Sprintf("0x%x", data[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolCamelotV3, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Use same decoding logic as ParseTransactionData if sig.Name == "exactInputSingle" { callData := data[4:] if len(callData) >= 256 { tokenIn := common.BytesToAddress(callData[12:32]) tokenOut := common.BytesToAddress(callData[44:64]) fee := new(big.Int).SetBytes(callData[64:96]) amountIn := new(big.Int).SetBytes(callData[96:128]) event.DecodedParams["tokenIn"] = tokenIn event.DecodedParams["tokenOut"] = tokenOut event.DecodedParams["fee"] = fee event.DecodedParams["amountIn"] = amountIn } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *CamelotV3Parser) DiscoverPools(fromBlock, toBlock uint64) ([]*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() var pools []*PoolInfo poolCreatedTopic := crypto.Keccak256Hash([]byte("PoolCreated(address,address,uint24,int24,address)")) factoryAddresses := p.contracts[ContractTypeFactory] for _, factoryAddr := range factoryAddresses { var logs []interface{} err := p.client.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": fmt.Sprintf("0x%x", fromBlock), "toBlock": fmt.Sprintf("0x%x", toBlock), "address": factoryAddr.Hex(), "topics": []interface{}{poolCreatedTopic.Hex()}, }) if err != nil { p.logger.Debug(fmt.Sprintf("Failed to query Camelot V3 PoolCreated events: %v", err)) continue } for _, logEntry := range logs { logMap, ok := logEntry.(map[string]interface{}) if !ok { continue } // Extract pool address from topics[3] (4th topic) if topics, ok := logMap["topics"].([]interface{}); ok && len(topics) >= 4 { poolAddr := common.HexToAddress(topics[3].(string)) token0 := common.HexToAddress(topics[1].(string)) token1 := common.HexToAddress(topics[2].(string)) // Extract fee from data if data, ok := logMap["data"].(string); ok && len(data) >= 66 { feeBytes := common.FromHex(data[2:66]) // Skip 0x prefix, get first 32 bytes fee := new(big.Int).SetBytes(feeBytes).Uint64() blockNumHex, _ := logMap["blockNumber"].(string) blockNum := common.HexToHash(blockNumHex).Big().Uint64() pool := &PoolInfo{ Address: poolAddr, Protocol: ProtocolCamelotV3, PoolType: "CamelotV3", Token0: token0, Token1: token1, Fee: uint32(fee), CreatedBlock: blockNum, IsActive: true, LastUpdated: time.Now(), } pools = append(pools, pool) } } } } return pools, nil } func (p *CamelotV3Parser) GetPoolInfo(poolAddress common.Address) (*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create pool contract ABI for basic queries (same as Uniswap V3) poolABI := `[ {"name": "token0", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "token1", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "fee", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "uint24"}]} ]` parsedABI, err := abi.JSON(strings.NewReader(poolABI)) if err != nil { return nil, fmt.Errorf("failed to parse pool ABI: %w", err) } // Query token0 token0Data, err := parsedABI.Pack("token0") if err != nil { return nil, fmt.Errorf("failed to pack token0 call: %w", err) } var token0Result string err = p.client.CallContext(ctx, &token0Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token0Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token0: %w", err) } // Query token1 token1Data, err := parsedABI.Pack("token1") if err != nil { return nil, fmt.Errorf("failed to pack token1 call: %w", err) } var token1Result string err = p.client.CallContext(ctx, &token1Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token1Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token1: %w", err) } // Query fee feeData, err := parsedABI.Pack("fee") if err != nil { return nil, fmt.Errorf("failed to pack fee call: %w", err) } var feeResult string err = p.client.CallContext(ctx, &feeResult, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", feeData), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query fee: %w", err) } // Decode results using ABI token0ResultBytes := common.FromHex(token0Result) token0Results, err := parsedABI.Unpack("token0", token0ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token0 result: %w", err) } if len(token0Results) == 0 { return nil, fmt.Errorf("empty token0 result") } token0, ok := token0Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token0 result is not an address") } token1ResultBytes := common.FromHex(token1Result) token1Results, err := parsedABI.Unpack("token1", token1ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token1 result: %w", err) } if len(token1Results) == 0 { return nil, fmt.Errorf("empty token1 result") } token1, ok := token1Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token1 result is not an address") } feeResultBytes := common.FromHex(feeResult) feeResults, err := parsedABI.Unpack("fee", feeResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack fee result: %w", err) } if len(feeResults) == 0 { return nil, fmt.Errorf("empty fee result") } feeValue, ok := feeResults[0].(*big.Int) if !ok { return nil, fmt.Errorf("fee result is not a big.Int") } fee := feeValue.Uint64() return &PoolInfo{ Address: poolAddress, Protocol: ProtocolCamelotV3, PoolType: "CamelotV3", Token0: token0, Token1: token1, Fee: uint32(fee), IsActive: true, LastUpdated: time.Now(), }, nil } func (p *CamelotV3Parser) ParseTransactionLogs(tx *types.Transaction, receipt *types.Receipt) ([]*EnhancedDEXEvent, error) { var events []*EnhancedDEXEvent for _, log := range receipt.Logs { if event, err := p.ParseLog(log); err == nil && event != nil { event.BlockNumber = receipt.BlockNumber.Uint64() event.TxHash = tx.Hash() event.LogIndex = uint64(log.Index) events = append(events, event) } } return events, nil } func (p *CamelotV3Parser) EnrichEventData(event *EnhancedDEXEvent) error { return nil } func NewCamelotV3Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolCamelotV3) parser := &CamelotV3Parser{BaseProtocolParser: base} parser.initializeCamelotV3() return parser } // TraderJoeV2Parser Implementation func (p *TraderJoeV2Parser) initializeTraderJoeV2() { // TraderJoe V2 contract addresses on Arbitrum (Liquidity Book protocol) p.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0x8e42f2F4101563bF679975178e880FD87d3eFd4e"), // TraderJoe V2.1 LB Factory } p.contracts[ContractTypeRouter] = []common.Address{ common.HexToAddress("0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30"), // TraderJoe V2.1 LB Router } // TraderJoe V2 uses unique events for liquidity bins swapTopic := crypto.Keccak256Hash([]byte("Swap(address,address,uint24,bytes32,bytes32,uint24,bytes32,bytes32)")) depositTopic := crypto.Keccak256Hash([]byte("DepositedToBins(address,address,uint256[],bytes32[])")) withdrawTopic := crypto.Keccak256Hash([]byte("WithdrawnFromBins(address,address,uint256[],bytes32[])")) pairCreatedTopic := crypto.Keccak256Hash([]byte("LBPairCreated(address,address,uint256,address,uint256)")) p.eventSigs[swapTopic] = &EventSignature{ Topic0: swapTopic, Name: "Swap", Protocol: ProtocolTraderJoeV2, EventType: EventTypeSwap, Description: "TraderJoe V2 liquidity bin swap event", } p.eventSigs[depositTopic] = &EventSignature{ Topic0: depositTopic, Name: "DepositedToBins", Protocol: ProtocolTraderJoeV2, EventType: EventTypeLiquidityAdd, Description: "TraderJoe V2 liquidity addition to bins", } p.eventSigs[withdrawTopic] = &EventSignature{ Topic0: withdrawTopic, Name: "WithdrawnFromBins", Protocol: ProtocolTraderJoeV2, EventType: EventTypeLiquidityRemove, Description: "TraderJoe V2 liquidity removal from bins", } p.eventSigs[pairCreatedTopic] = &EventSignature{ Topic0: pairCreatedTopic, Name: "LBPairCreated", Protocol: ProtocolTraderJoeV2, EventType: EventTypePoolCreated, Description: "TraderJoe V2 liquidity book pair created", } p.loadTraderJoeV2ABI() } func (p *TraderJoeV2Parser) loadTraderJoeV2ABI() { // Simplified TraderJoe V2 ABI - liquidity book functions abiJSON := `[ {"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"id","type":"uint24"},{"indexed":false,"name":"amountsIn","type":"bytes32"},{"indexed":false,"name":"amountsOut","type":"bytes32"},{"indexed":false,"name":"volatilityAccumulator","type":"uint24"},{"indexed":false,"name":"totalFees","type":"bytes32"},{"indexed":false,"name":"protocolFees","type":"bytes32"}],"name":"Swap","type":"event"}, {"inputs":[{"name":"tokenX","type":"address"},{"name":"tokenY","type":"address"},{"name":"binStep","type":"uint256"},{"name":"amountIn","type":"uint256"},{"name":"amountOutMin","type":"uint256"},{"name":"activeIdDesired","type":"uint24"},{"name":"idSlippage","type":"uint24"},{"name":"path","type":"uint256[]"},{"name":"to","type":"address"},{"name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"} ]` parsedABI, err := abi.JSON(strings.NewReader(abiJSON)) if err != nil { p.logger.Error("Failed to parse TraderJoe V2 ABI:", err) return } p.contractABI = parsedABI // Function signatures for TraderJoe V2 functions swapSelector := "0x38ed1739" swapBytes := [4]byte{0x38, 0xed, 0x17, 0x39} p.functionSigs[swapSelector] = &FunctionSignature{ Selector: swapBytes, Name: "swapExactTokensForTokens", Protocol: ProtocolTraderJoeV2, EventType: EventTypeSwap, Description: "TraderJoe V2 exact tokens swap", } } func (p *TraderJoeV2Parser) GetSupportedContracts() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *TraderJoeV2Parser) GetSupportedContractTypes() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *TraderJoeV2Parser) GetSupportedEventTypes() []EventType { return []EventType{EventTypeSwap, EventTypeLiquidityAdd, EventTypeLiquidityRemove, EventTypePoolCreated} } func (p *TraderJoeV2Parser) GetContractInfo(address common.Address) (*ContractInfo, error) { for contractType, addresses := range p.contracts { for _, addr := range addresses { if addr == address { return &ContractInfo{ Address: address, ContractType: contractType, Protocol: ProtocolTraderJoeV2, Name: fmt.Sprintf("TraderJoe V2 %s", contractType), }, nil } } } return nil, fmt.Errorf("unknown TraderJoe V2 contract: %s", address.Hex()) } func (p *TraderJoeV2Parser) IsKnownContract(address common.Address) bool { _, err := p.GetContractInfo(address) return err == nil } func (p *TraderJoeV2Parser) ParseLog(log *types.Log) (*EnhancedDEXEvent, error) { if !p.IsKnownContract(log.Address) { return nil, fmt.Errorf("unknown contract address: %s", log.Address.Hex()) } event := &EnhancedDEXEvent{ Protocol: ProtocolTraderJoeV2, EventType: EventTypeSwap, DecodedParams: make(map[string]interface{}), } if len(log.Topics) > 0 { if sig, exists := p.eventSigs[log.Topics[0]]; exists { event.EventType = sig.EventType // Parse TraderJoe V2 Swap event if sig.Name == "Swap" && len(log.Topics) >= 3 { // Extract indexed parameters event.DecodedParams["sender"] = common.HexToAddress(log.Topics[1].Hex()) event.DecodedParams["to"] = common.HexToAddress(log.Topics[2].Hex()) // Decode non-indexed parameters from data if len(log.Data) >= 224 { // 7 * 32 bytes id := new(big.Int).SetBytes(log.Data[0:32]) amountsIn := log.Data[32:64] amountsOut := log.Data[64:96] volatilityAccumulator := new(big.Int).SetBytes(log.Data[96:128]) totalFees := log.Data[128:160] protocolFees := log.Data[160:192] event.DecodedParams["id"] = id event.DecodedParams["amountsIn"] = amountsIn event.DecodedParams["amountsOut"] = amountsOut event.DecodedParams["volatilityAccumulator"] = volatilityAccumulator event.DecodedParams["totalFees"] = totalFees event.DecodedParams["protocolFees"] = protocolFees } } } } return event, nil } func (p *TraderJoeV2Parser) ParseTransactionData(tx *types.Transaction) (*EnhancedDEXEvent, error) { if len(tx.Data()) < 4 { return nil, fmt.Errorf("transaction data too short") } selector := fmt.Sprintf("0x%x", tx.Data()[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolTraderJoeV2, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Decode swapExactTokensForTokens function if sig.Name == "swapExactTokensForTokens" && len(tx.Data()) >= 324 { // Decode parameters (simplified) data := tx.Data()[4:] // Skip function selector if len(data) >= 320 { tokenX := common.BytesToAddress(data[12:32]) tokenY := common.BytesToAddress(data[44:64]) binStep := new(big.Int).SetBytes(data[64:96]) amountIn := new(big.Int).SetBytes(data[96:128]) amountOutMin := new(big.Int).SetBytes(data[128:160]) event.DecodedParams["tokenX"] = tokenX event.DecodedParams["tokenY"] = tokenY event.DecodedParams["binStep"] = binStep event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMin"] = amountOutMin } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *TraderJoeV2Parser) DecodeFunctionCall(data []byte) (*EnhancedDEXEvent, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short for function selector") } selector := fmt.Sprintf("0x%x", data[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolTraderJoeV2, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Use same decoding logic as ParseTransactionData if sig.Name == "swapExactTokensForTokens" { callData := data[4:] if len(callData) >= 320 { tokenX := common.BytesToAddress(callData[12:32]) tokenY := common.BytesToAddress(callData[44:64]) binStep := new(big.Int).SetBytes(callData[64:96]) amountIn := new(big.Int).SetBytes(callData[96:128]) event.DecodedParams["tokenX"] = tokenX event.DecodedParams["tokenY"] = tokenY event.DecodedParams["binStep"] = binStep event.DecodedParams["amountIn"] = amountIn } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *TraderJoeV2Parser) DiscoverPools(fromBlock, toBlock uint64) ([]*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() var pools []*PoolInfo pairCreatedTopic := crypto.Keccak256Hash([]byte("LBPairCreated(address,address,uint256,address,uint256)")) factoryAddresses := p.contracts[ContractTypeFactory] for _, factoryAddr := range factoryAddresses { var logs []interface{} err := p.client.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": fmt.Sprintf("0x%x", fromBlock), "toBlock": fmt.Sprintf("0x%x", toBlock), "address": factoryAddr.Hex(), "topics": []interface{}{pairCreatedTopic.Hex()}, }) if err != nil { p.logger.Debug(fmt.Sprintf("Failed to query TraderJoe V2 LBPairCreated events: %v", err)) continue } for _, logEntry := range logs { logMap, ok := logEntry.(map[string]interface{}) if !ok { continue } // Extract pair address from topics[3] (4th topic in indexed events) if topics, ok := logMap["topics"].([]interface{}); ok && len(topics) >= 3 { token0 := common.HexToAddress(topics[1].(string)) token1 := common.HexToAddress(topics[2].(string)) // Extract bin step and pair address from data if data, ok := logMap["data"].(string); ok && len(data) >= 130 { // Parse data: binStep (32 bytes) + pairAddress (32 bytes) + pid (32 bytes) dataBytes := common.FromHex(data) if len(dataBytes) >= 96 { binStep := new(big.Int).SetBytes(dataBytes[0:32]).Uint64() pairAddr := common.BytesToAddress(dataBytes[32:64]) blockNumHex, _ := logMap["blockNumber"].(string) blockNum := common.HexToHash(blockNumHex).Big().Uint64() pool := &PoolInfo{ Address: pairAddr, Protocol: ProtocolTraderJoeV2, PoolType: "TraderJoeV2", Token0: token0, Token1: token1, Fee: uint32(binStep), // Bin step acts as fee tier CreatedBlock: blockNum, IsActive: true, LastUpdated: time.Now(), } pools = append(pools, pool) } } } } } return pools, nil } func (p *TraderJoeV2Parser) GetPoolInfo(poolAddress common.Address) (*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create LB pair contract ABI for basic queries poolABI := `[ {"name": "getTokenX", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "getTokenY", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "getBinStep", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "uint16"}]} ]` parsedABI, err := abi.JSON(strings.NewReader(poolABI)) if err != nil { return nil, fmt.Errorf("failed to parse LB pair ABI: %w", err) } // Query tokenX tokenXData, err := parsedABI.Pack("getTokenX") if err != nil { return nil, fmt.Errorf("failed to pack getTokenX call: %w", err) } var tokenXResult string err = p.client.CallContext(ctx, &tokenXResult, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", tokenXData), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query tokenX: %w", err) } // Query tokenY tokenYData, err := parsedABI.Pack("getTokenY") if err != nil { return nil, fmt.Errorf("failed to pack getTokenY call: %w", err) } var tokenYResult string err = p.client.CallContext(ctx, &tokenYResult, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", tokenYData), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query tokenY: %w", err) } // Query binStep binStepData, err := parsedABI.Pack("getBinStep") if err != nil { return nil, fmt.Errorf("failed to pack getBinStep call: %w", err) } var binStepResult string err = p.client.CallContext(ctx, &binStepResult, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", binStepData), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query binStep: %w", err) } // Decode results using ABI tokenXResultBytes := common.FromHex(tokenXResult) tokenXResults, err := parsedABI.Unpack("getTokenX", tokenXResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack tokenX result: %w", err) } if len(tokenXResults) == 0 { return nil, fmt.Errorf("empty tokenX result") } tokenX, ok := tokenXResults[0].(common.Address) if !ok { return nil, fmt.Errorf("tokenX result is not an address") } tokenYResultBytes := common.FromHex(tokenYResult) tokenYResults, err := parsedABI.Unpack("getTokenY", tokenYResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack tokenY result: %w", err) } if len(tokenYResults) == 0 { return nil, fmt.Errorf("empty tokenY result") } tokenY, ok := tokenYResults[0].(common.Address) if !ok { return nil, fmt.Errorf("tokenY result is not an address") } binStepResultBytes := common.FromHex(binStepResult) binStepResults, err := parsedABI.Unpack("getBinStep", binStepResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack binStep result: %w", err) } if len(binStepResults) == 0 { return nil, fmt.Errorf("empty binStep result") } binStepValue, ok := binStepResults[0].(*big.Int) if !ok { return nil, fmt.Errorf("binStep result is not a big.Int") } binStep := binStepValue.Uint64() return &PoolInfo{ Address: poolAddress, Protocol: ProtocolTraderJoeV2, PoolType: "TraderJoeV2", Token0: tokenX, Token1: tokenY, Fee: uint32(binStep), IsActive: true, LastUpdated: time.Now(), }, nil } func (p *TraderJoeV2Parser) ParseTransactionLogs(tx *types.Transaction, receipt *types.Receipt) ([]*EnhancedDEXEvent, error) { var events []*EnhancedDEXEvent for _, log := range receipt.Logs { if event, err := p.ParseLog(log); err == nil && event != nil { event.BlockNumber = receipt.BlockNumber.Uint64() event.TxHash = tx.Hash() event.LogIndex = uint64(log.Index) events = append(events, event) } } return events, nil } func (p *TraderJoeV2Parser) EnrichEventData(event *EnhancedDEXEvent) error { return nil } // KyberElasticParser Implementation func (p *KyberElasticParser) initializeKyberElastic() { // KyberSwap Elastic contract addresses on Arbitrum p.contracts[ContractTypeFactory] = []common.Address{ common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a"), // KyberSwap Elastic Factory } p.contracts[ContractTypeRouter] = []common.Address{ common.HexToAddress("0xC1e7dFE73E1598E3910EF4C7845B68A9Ab6F4c83"), // KyberSwap Elastic Router common.HexToAddress("0xF9c2b5746c946EF883ab2660BbbB1f10A5bdeAb4"), // KyberSwap Meta Router } // KyberSwap Elastic uses similar events to Uniswap V3 but with their own optimizations swapTopic := crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24,uint128,uint128)")) mintTopic := crypto.Keccak256Hash([]byte("Mint(address,address,int24,int24,uint128,uint256,uint256)")) burnTopic := crypto.Keccak256Hash([]byte("Burn(address,int24,int24,uint128,uint256,uint256)")) poolCreatedTopic := crypto.Keccak256Hash([]byte("PoolCreated(address,address,uint24,int24,address)")) p.eventSigs[swapTopic] = &EventSignature{ Topic0: swapTopic, Name: "Swap", Protocol: ProtocolKyberElastic, EventType: EventTypeSwap, Description: "KyberSwap Elastic swap event", } p.eventSigs[mintTopic] = &EventSignature{ Topic0: mintTopic, Name: "Mint", Protocol: ProtocolKyberElastic, EventType: EventTypeLiquidityAdd, Description: "KyberSwap Elastic liquidity mint", } p.eventSigs[burnTopic] = &EventSignature{ Topic0: burnTopic, Name: "Burn", Protocol: ProtocolKyberElastic, EventType: EventTypeLiquidityRemove, Description: "KyberSwap Elastic liquidity burn", } p.eventSigs[poolCreatedTopic] = &EventSignature{ Topic0: poolCreatedTopic, Name: "PoolCreated", Protocol: ProtocolKyberElastic, EventType: EventTypePoolCreated, Description: "KyberSwap Elastic pool created", } p.loadKyberElasticABI() } func (p *KyberElasticParser) loadKyberElasticABI() { // KyberSwap Elastic ABI - concentrated liquidity with reinvestment features abiJSON := `[ {"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":true,"name":"recipient","type":"address"},{"indexed":false,"name":"deltaQty0","type":"int256"},{"indexed":false,"name":"deltaQty1","type":"int256"},{"indexed":false,"name":"sqrtP","type":"uint160"},{"indexed":false,"name":"baseL","type":"uint128"},{"indexed":false,"name":"currentTick","type":"int24"},{"indexed":false,"name":"reinvestL","type":"uint128"},{"indexed":false,"name":"feeGrowthGlobal","type":"uint128"}],"name":"Swap","type":"event"}, {"inputs":[{"name":"tokenA","type":"address"},{"name":"tokenB","type":"address"},{"name":"fee","type":"uint24"},{"name":"amountIn","type":"uint256"},{"name":"amountOutMinimum","type":"uint256"},{"name":"sqrtPriceLimitX96","type":"uint160"},{"name":"deadline","type":"uint256"}],"name":"exactInputSingle","outputs":[{"name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"} ]` parsedABI, err := abi.JSON(strings.NewReader(abiJSON)) if err != nil { p.logger.Error("Failed to parse KyberSwap Elastic ABI:", err) return } p.contractABI = parsedABI // Function signatures for KyberSwap Elastic functions exactInputSelector := "0x04e45aaf" exactInputBytes := [4]byte{0x04, 0xe4, 0x5a, 0xaf} p.functionSigs[exactInputSelector] = &FunctionSignature{ Selector: exactInputBytes, Name: "exactInputSingle", Protocol: ProtocolKyberElastic, EventType: EventTypeSwap, Description: "KyberSwap Elastic exact input single swap", } } func (p *KyberElasticParser) GetSupportedContracts() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *KyberElasticParser) GetSupportedContractTypes() []ContractType { return []ContractType{ContractTypeFactory, ContractTypeRouter, ContractTypePool} } func (p *KyberElasticParser) GetSupportedEventTypes() []EventType { return []EventType{EventTypeSwap, EventTypeLiquidityAdd, EventTypeLiquidityRemove, EventTypePoolCreated, EventTypePositionUpdate} } func (p *KyberElasticParser) GetContractInfo(address common.Address) (*ContractInfo, error) { for contractType, addresses := range p.contracts { for _, addr := range addresses { if addr == address { return &ContractInfo{ Address: address, ContractType: contractType, Protocol: ProtocolKyberElastic, Name: fmt.Sprintf("KyberSwap Elastic %s", contractType), }, nil } } } return nil, fmt.Errorf("unknown KyberSwap Elastic contract: %s", address.Hex()) } func (p *KyberElasticParser) IsKnownContract(address common.Address) bool { _, err := p.GetContractInfo(address) return err == nil } func (p *KyberElasticParser) ParseLog(log *types.Log) (*EnhancedDEXEvent, error) { if !p.IsKnownContract(log.Address) { return nil, fmt.Errorf("unknown contract address: %s", log.Address.Hex()) } event := &EnhancedDEXEvent{ Protocol: ProtocolKyberElastic, EventType: EventTypeSwap, DecodedParams: make(map[string]interface{}), } if len(log.Topics) > 0 { if sig, exists := p.eventSigs[log.Topics[0]]; exists { event.EventType = sig.EventType // Parse KyberSwap Elastic Swap event (has additional reinvestment data) if sig.Name == "Swap" && len(log.Topics) >= 3 { // Extract indexed parameters event.DecodedParams["sender"] = common.HexToAddress(log.Topics[1].Hex()) event.DecodedParams["recipient"] = common.HexToAddress(log.Topics[2].Hex()) // Decode non-indexed parameters from data (KyberSwap has more fields than standard V3) if len(log.Data) >= 256 { // 8 * 32 bytes deltaQty0 := new(big.Int).SetBytes(log.Data[0:32]) deltaQty1 := new(big.Int).SetBytes(log.Data[32:64]) sqrtP := new(big.Int).SetBytes(log.Data[64:96]) baseL := new(big.Int).SetBytes(log.Data[96:128]) currentTick := new(big.Int).SetBytes(log.Data[128:160]) reinvestL := new(big.Int).SetBytes(log.Data[160:192]) feeGrowthGlobal := new(big.Int).SetBytes(log.Data[192:224]) event.DecodedParams["deltaQty0"] = deltaQty0 event.DecodedParams["deltaQty1"] = deltaQty1 event.DecodedParams["sqrtP"] = sqrtP event.DecodedParams["baseL"] = baseL event.DecodedParams["currentTick"] = currentTick event.DecodedParams["reinvestL"] = reinvestL event.DecodedParams["feeGrowthGlobal"] = feeGrowthGlobal } } } } return event, nil } func (p *KyberElasticParser) ParseTransactionData(tx *types.Transaction) (*EnhancedDEXEvent, error) { if len(tx.Data()) < 4 { return nil, fmt.Errorf("transaction data too short") } selector := fmt.Sprintf("0x%x", tx.Data()[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolKyberElastic, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Decode exactInputSingle function if sig.Name == "exactInputSingle" && len(tx.Data()) >= 228 { // Decode parameters (simplified) data := tx.Data()[4:] // Skip function selector if len(data) >= 224 { tokenA := common.BytesToAddress(data[12:32]) tokenB := common.BytesToAddress(data[44:64]) fee := new(big.Int).SetBytes(data[64:96]) amountIn := new(big.Int).SetBytes(data[96:128]) amountOutMinimum := new(big.Int).SetBytes(data[128:160]) sqrtPriceLimitX96 := new(big.Int).SetBytes(data[160:192]) event.DecodedParams["tokenA"] = tokenA event.DecodedParams["tokenB"] = tokenB event.DecodedParams["fee"] = fee event.DecodedParams["amountIn"] = amountIn event.DecodedParams["amountOutMinimum"] = amountOutMinimum event.DecodedParams["sqrtPriceLimitX96"] = sqrtPriceLimitX96 } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *KyberElasticParser) DecodeFunctionCall(data []byte) (*EnhancedDEXEvent, error) { if len(data) < 4 { return nil, fmt.Errorf("data too short for function selector") } selector := fmt.Sprintf("0x%x", data[:4]) if sig, exists := p.functionSigs[selector]; exists { event := &EnhancedDEXEvent{ Protocol: ProtocolKyberElastic, EventType: sig.EventType, DecodedParams: make(map[string]interface{}), } // Use same decoding logic as ParseTransactionData if sig.Name == "exactInputSingle" { callData := data[4:] if len(callData) >= 224 { tokenA := common.BytesToAddress(callData[12:32]) tokenB := common.BytesToAddress(callData[44:64]) fee := new(big.Int).SetBytes(callData[64:96]) amountIn := new(big.Int).SetBytes(callData[96:128]) event.DecodedParams["tokenA"] = tokenA event.DecodedParams["tokenB"] = tokenB event.DecodedParams["fee"] = fee event.DecodedParams["amountIn"] = amountIn } } return event, nil } return nil, fmt.Errorf("unknown function selector: %s", selector) } func (p *KyberElasticParser) DiscoverPools(fromBlock, toBlock uint64) ([]*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() var pools []*PoolInfo poolCreatedTopic := crypto.Keccak256Hash([]byte("PoolCreated(address,address,uint24,int24,address)")) factoryAddresses := p.contracts[ContractTypeFactory] for _, factoryAddr := range factoryAddresses { var logs []interface{} err := p.client.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": fmt.Sprintf("0x%x", fromBlock), "toBlock": fmt.Sprintf("0x%x", toBlock), "address": factoryAddr.Hex(), "topics": []interface{}{poolCreatedTopic.Hex()}, }) if err != nil { p.logger.Debug(fmt.Sprintf("Failed to query KyberSwap Elastic PoolCreated events: %v", err)) continue } for _, logEntry := range logs { logMap, ok := logEntry.(map[string]interface{}) if !ok { continue } // Extract pool address from topics[4] (5th topic) if topics, ok := logMap["topics"].([]interface{}); ok && len(topics) >= 4 { token0 := common.HexToAddress(topics[1].(string)) token1 := common.HexToAddress(topics[2].(string)) poolAddr := common.HexToAddress(topics[4].(string)) // Extract fee from data (first 32 bytes) if data, ok := logMap["data"].(string); ok && len(data) >= 66 { feeBytes := common.FromHex(data[2:66]) // Skip 0x prefix, get first 32 bytes fee := new(big.Int).SetBytes(feeBytes).Uint64() blockNumHex, _ := logMap["blockNumber"].(string) blockNum := common.HexToHash(blockNumHex).Big().Uint64() pool := &PoolInfo{ Address: poolAddr, Protocol: ProtocolKyberElastic, PoolType: "KyberElastic", Token0: token0, Token1: token1, Fee: uint32(fee), CreatedBlock: blockNum, IsActive: true, LastUpdated: time.Now(), } pools = append(pools, pool) } } } } return pools, nil } func (p *KyberElasticParser) GetPoolInfo(poolAddress common.Address) (*PoolInfo, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Create pool contract ABI for basic queries (same as Uniswap V3) poolABI := `[ {"name": "token0", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "token1", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"name": "fee", "type": "function", "stateMutability": "view", "inputs": [], "outputs": [{"name": "", "type": "uint24"}]} ]` parsedABI, err := abi.JSON(strings.NewReader(poolABI)) if err != nil { return nil, fmt.Errorf("failed to parse pool ABI: %w", err) } // Query token0 token0Data, err := parsedABI.Pack("token0") if err != nil { return nil, fmt.Errorf("failed to pack token0 call: %w", err) } var token0Result string err = p.client.CallContext(ctx, &token0Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token0Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token0: %w", err) } // Query token1 token1Data, err := parsedABI.Pack("token1") if err != nil { return nil, fmt.Errorf("failed to pack token1 call: %w", err) } var token1Result string err = p.client.CallContext(ctx, &token1Result, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", token1Data), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query token1: %w", err) } // Query fee feeData, err := parsedABI.Pack("fee") if err != nil { return nil, fmt.Errorf("failed to pack fee call: %w", err) } var feeResult string err = p.client.CallContext(ctx, &feeResult, "eth_call", map[string]interface{}{ "to": poolAddress.Hex(), "data": fmt.Sprintf("0x%x", feeData), }, "latest") if err != nil { return nil, fmt.Errorf("failed to query fee: %w", err) } // Decode results using ABI token0ResultBytes := common.FromHex(token0Result) token0Results, err := parsedABI.Unpack("token0", token0ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token0 result: %w", err) } if len(token0Results) == 0 { return nil, fmt.Errorf("empty token0 result") } token0, ok := token0Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token0 result is not an address") } token1ResultBytes := common.FromHex(token1Result) token1Results, err := parsedABI.Unpack("token1", token1ResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack token1 result: %w", err) } if len(token1Results) == 0 { return nil, fmt.Errorf("empty token1 result") } token1, ok := token1Results[0].(common.Address) if !ok { return nil, fmt.Errorf("token1 result is not an address") } feeResultBytes := common.FromHex(feeResult) feeResults, err := parsedABI.Unpack("fee", feeResultBytes) if err != nil { return nil, fmt.Errorf("failed to unpack fee result: %w", err) } if len(feeResults) == 0 { return nil, fmt.Errorf("empty fee result") } feeValue, ok := feeResults[0].(*big.Int) if !ok { return nil, fmt.Errorf("fee result is not a big.Int") } fee := feeValue.Uint64() return &PoolInfo{ Address: poolAddress, Protocol: ProtocolKyberElastic, PoolType: "KyberElastic", Token0: token0, Token1: token1, Fee: uint32(fee), IsActive: true, LastUpdated: time.Now(), }, nil } func (p *KyberElasticParser) ParseTransactionLogs(tx *types.Transaction, receipt *types.Receipt) ([]*EnhancedDEXEvent, error) { var events []*EnhancedDEXEvent for _, log := range receipt.Logs { if event, err := p.ParseLog(log); err == nil && event != nil { event.BlockNumber = receipt.BlockNumber.Uint64() event.TxHash = tx.Hash() event.LogIndex = uint64(log.Index) events = append(events, event) } } return events, nil } func (p *KyberElasticParser) EnrichEventData(event *EnhancedDEXEvent) error { return nil } func NewTraderJoeV1Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolTraderJoeV1) return &UniswapV2Parser{BaseProtocolParser: base} // Placeholder } func NewTraderJoeV2Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolTraderJoeV2) parser := &TraderJoeV2Parser{BaseProtocolParser: base} parser.initializeTraderJoeV2() return parser } func NewTraderJoeLBParser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolTraderJoeLB) return &UniswapV2Parser{BaseProtocolParser: base} // Placeholder } func NewCurveParser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolCurve) return &UniswapV2Parser{BaseProtocolParser: base} // Placeholder } func NewBalancerV2Parser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolBalancerV2) return &UniswapV2Parser{BaseProtocolParser: base} // Placeholder } func NewKyberClassicParser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolKyberClassic) return &UniswapV2Parser{BaseProtocolParser: base} // Placeholder } func NewKyberElasticParser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolKyberElastic) parser := &KyberElasticParser{BaseProtocolParser: base} parser.initializeKyberElastic() return parser } func NewGMXParser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolGMX) return &UniswapV2Parser{BaseProtocolParser: base} // Placeholder } func NewRamsesParser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolRamses) return &UniswapV3Parser{BaseProtocolParser: base} // Placeholder } func NewChronosParser(client *rpc.Client, logger *logger.Logger) DEXParserInterface { base := NewBaseProtocolParser(client, logger, ProtocolChronos) return &UniswapV2Parser{BaseProtocolParser: base} // Placeholder }