package parsers import ( "context" "fmt" "math/big" "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/your-org/mev-bot/pkg/cache" mevtypes "github.com/your-org/mev-bot/pkg/types" ) // UniswapV2 Swap event signature: // event Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to) var ( // SwapEventSignature is the event signature for UniswapV2 Swap events SwapEventSignature = crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")) ) // UniswapV2Parser implements the Parser interface for UniswapV2 pools type UniswapV2Parser struct { cache cache.PoolCache logger mevtypes.Logger } // NewUniswapV2Parser creates a new UniswapV2 parser func NewUniswapV2Parser(cache cache.PoolCache, logger mevtypes.Logger) *UniswapV2Parser { return &UniswapV2Parser{ cache: cache, logger: logger, } } // Protocol returns the protocol type this parser handles func (p *UniswapV2Parser) Protocol() mevtypes.ProtocolType { return mevtypes.ProtocolUniswapV2 } // SupportsLog checks if this parser can handle the given log func (p *UniswapV2Parser) SupportsLog(log types.Log) bool { // Check if log has the Swap event signature if len(log.Topics) == 0 { return false } return log.Topics[0] == SwapEventSignature } // ParseLog parses a UniswapV2 Swap event from a log func (p *UniswapV2Parser) ParseLog(ctx context.Context, log types.Log, tx *types.Transaction) (*mevtypes.SwapEvent, error) { // Verify this is a Swap event if !p.SupportsLog(log) { return nil, fmt.Errorf("unsupported log") } // Get pool info from cache to extract token addresses and decimals poolInfo, err := p.cache.GetByAddress(ctx, log.Address) if err != nil { return nil, fmt.Errorf("pool not found in cache: %w", err) } // Parse event data // Data contains: amount0In, amount1In, amount0Out, amount1Out (non-indexed) // Topics contain: [signature, sender, to] (indexed) if len(log.Topics) != 3 { return nil, fmt.Errorf("invalid number of topics: expected 3, got %d", len(log.Topics)) } // Define ABI for data decoding uint256Type, err := abi.NewType("uint256", "", nil) if err != nil { return nil, fmt.Errorf("failed to create uint256 type: %w", err) } arguments := abi.Arguments{ {Type: uint256Type, Name: "amount0In"}, {Type: uint256Type, Name: "amount1In"}, {Type: uint256Type, Name: "amount0Out"}, {Type: uint256Type, Name: "amount1Out"}, } // Decode data values, err := arguments.Unpack(log.Data) if err != nil { return nil, fmt.Errorf("failed to decode event data: %w", err) } if len(values) != 4 { return nil, fmt.Errorf("invalid number of values: expected 4, got %d", len(values)) } // Extract indexed parameters from topics sender := common.BytesToAddress(log.Topics[1].Bytes()) recipient := common.BytesToAddress(log.Topics[2].Bytes()) // Extract amounts from decoded data amount0In := values[0].(*big.Int) amount1In := values[1].(*big.Int) amount0Out := values[2].(*big.Int) amount1Out := values[3].(*big.Int) // Scale amounts to 18 decimals for internal representation amount0InScaled := mevtypes.ScaleToDecimals(amount0In, poolInfo.Token0Decimals, 18) amount1InScaled := mevtypes.ScaleToDecimals(amount1In, poolInfo.Token1Decimals, 18) amount0OutScaled := mevtypes.ScaleToDecimals(amount0Out, poolInfo.Token0Decimals, 18) amount1OutScaled := mevtypes.ScaleToDecimals(amount1Out, poolInfo.Token1Decimals, 18) // Create swap event event := &mevtypes.SwapEvent{ TxHash: tx.Hash(), BlockNumber: log.BlockNumber, LogIndex: uint(log.Index), PoolAddress: log.Address, Protocol: mevtypes.ProtocolUniswapV2, Token0: poolInfo.Token0, Token1: poolInfo.Token1, Token0Decimals: poolInfo.Token0Decimals, Token1Decimals: poolInfo.Token1Decimals, Amount0In: amount0InScaled, Amount1In: amount1InScaled, Amount0Out: amount0OutScaled, Amount1Out: amount1OutScaled, Sender: sender, Recipient: recipient, Fee: big.NewInt(int64(poolInfo.Fee)), } // Validate the parsed event if err := event.Validate(); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } p.logger.Debug("parsed UniswapV2 swap event", "txHash", event.TxHash.Hex(), "pool", event.PoolAddress.Hex(), "token0", event.Token0.Hex(), "token1", event.Token1.Hex(), ) return event, nil } // ParseReceipt parses all UniswapV2 Swap events from a transaction receipt func (p *UniswapV2Parser) ParseReceipt(ctx context.Context, receipt *types.Receipt, tx *types.Transaction) ([]*mevtypes.SwapEvent, error) { var events []*mevtypes.SwapEvent for _, log := range receipt.Logs { if p.SupportsLog(*log) { event, err := p.ParseLog(ctx, *log, tx) if err != nil { // Log error but continue processing other logs p.logger.Warn("failed to parse log", "txHash", tx.Hash().Hex(), "logIndex", log.Index, "error", err, ) continue } events = append(events, event) } } return events, nil }