package sequencer import ( "encoding/base64" "encoding/hex" "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" "github.com/your-org/mev-bot/pkg/validation" ) // L2MessageKind represents the type of L2 message type L2MessageKind uint8 const ( L2MessageKind_SignedTx L2MessageKind = 4 L2MessageKind_Batch L2MessageKind = 3 L2MessageKind_SignedCompressedTx L2MessageKind = 7 ) // ArbitrumMessage represents a decoded Arbitrum sequencer message type ArbitrumMessage struct { SequenceNumber uint64 Kind uint8 BlockNumber uint64 Timestamp uint64 L2MsgRaw string // Base64 encoded Transaction *DecodedTransaction } // DecodedTransaction represents a decoded Arbitrum transaction type DecodedTransaction struct { Hash common.Hash From common.Address To *common.Address Value *big.Int Data []byte Nonce uint64 GasPrice *big.Int GasLimit uint64 RawBytes []byte // RLP-encoded transaction bytes for reconstruction } // DecodeArbitrumMessage decodes an Arbitrum sequencer feed message func DecodeArbitrumMessage(msgMap map[string]interface{}) (*ArbitrumMessage, error) { msg := &ArbitrumMessage{} // Extract sequence number if seqNum, ok := msgMap["sequenceNumber"].(float64); ok { msg.SequenceNumber = uint64(seqNum) } // Extract nested message structure messageWrapper, ok := msgMap["message"].(map[string]interface{}) if !ok { return nil, fmt.Errorf("missing message wrapper") } message, ok := messageWrapper["message"].(map[string]interface{}) if !ok { return nil, fmt.Errorf("missing inner message") } // Extract header if header, ok := message["header"].(map[string]interface{}); ok { if kind, ok := header["kind"].(float64); ok { msg.Kind = uint8(kind) } if blockNum, ok := header["blockNumber"].(float64); ok { msg.BlockNumber = uint64(blockNum) } if timestamp, ok := header["timestamp"].(float64); ok { msg.Timestamp = uint64(timestamp) } } // Extract l2Msg l2MsgBase64, ok := message["l2Msg"].(string) if !ok { return nil, fmt.Errorf("missing l2Msg") } msg.L2MsgRaw = l2MsgBase64 // Decode transaction if it's a signed transaction (kind 3 from header means L1MessageType_L2Message) if msg.Kind == 3 { tx, err := DecodeL2Transaction(l2MsgBase64) if err != nil { // Not all messages are transactions, just skip return msg, nil } msg.Transaction = tx } return msg, nil } // DecodeL2Transaction decodes a base64-encoded L2 transaction func DecodeL2Transaction(l2MsgBase64 string) (*DecodedTransaction, error) { // Step 1: Base64 decode decoded, err := base64.StdEncoding.DecodeString(l2MsgBase64) if err != nil { return nil, fmt.Errorf("base64 decode failed: %w", err) } if len(decoded) == 0 { return nil, fmt.Errorf("empty decoded message") } // Step 2: First byte is L2MessageKind l2Kind := L2MessageKind(decoded[0]) // Only process signed transactions if l2Kind != L2MessageKind_SignedTx { return nil, fmt.Errorf("not a signed transaction (kind=%d)", l2Kind) } // Step 3: Strip first byte and RLP decode the transaction txBytes := decoded[1:] if len(txBytes) == 0 { return nil, fmt.Errorf("empty transaction bytes") } // Try to decode as Ethereum transaction tx := new(types.Transaction) if err := rlp.DecodeBytes(txBytes, tx); err != nil { return nil, fmt.Errorf("RLP decode failed: %w", err) } // Calculate transaction hash txHash := crypto.Keccak256Hash(txBytes) // Extract sender (requires chainID for EIP-155) // For now, we'll skip sender recovery as it requires the chain ID // and signature verification. We're mainly interested in To and Data. result := &DecodedTransaction{ Hash: txHash, To: tx.To(), Value: tx.Value(), Data: tx.Data(), Nonce: tx.Nonce(), GasPrice: tx.GasPrice(), GasLimit: tx.Gas(), RawBytes: txBytes, // Store for later reconstruction } return result, nil } // ToEthereumTransaction converts a DecodedTransaction back to *types.Transaction // This is a reusable utility for converting our decoded format to go-ethereum format func (dt *DecodedTransaction) ToEthereumTransaction() (*types.Transaction, error) { if len(dt.RawBytes) == 0 { return nil, fmt.Errorf("no raw transaction bytes available") } tx := new(types.Transaction) if err := rlp.DecodeBytes(dt.RawBytes, tx); err != nil { return nil, fmt.Errorf("failed to decode transaction: %w", err) } return tx, nil } // IsSwapTransaction checks if the transaction data is a DEX swap func IsSwapTransaction(data []byte) bool { if len(data) < 4 { return false } // Extract function selector (first 4 bytes) selector := hex.EncodeToString(data[0:4]) // Common DEX swap function selectors swapSelectors := map[string]string{ // UniswapV2 Router "38ed1739": "swapExactTokensForTokens", "8803dbee": "swapTokensForExactTokens", "7ff36ab5": "swapExactETHForTokens", "fb3bdb41": "swapETHForExactTokens", "18cbafe5": "swapExactTokensForETH", "4a25d94a": "swapTokensForExactETH", // UniswapV3 Router "414bf389": "exactInputSingle", "c04b8d59": "exactInput", "db3e2198": "exactOutputSingle", "f28c0498": "exactOutput", // UniswapV2 Pair (direct swap) "022c0d9f": "swap", // Curve "3df02124": "exchange", "a6417ed6": "exchange_underlying", // 1inch "7c025200": "swap", "e449022e": "uniswapV3Swap", // 0x Protocol "d9627aa4": "sellToUniswap", "415565b0": "fillRfqOrder", } _, isSwap := swapSelectors[selector] return isSwap } // DEXProtocol represents a DEX protocol type DEXProtocol struct { Name string Version string Type string // "router" or "pool" } // GetSwapProtocol identifies the DEX protocol from transaction data func GetSwapProtocol(to *common.Address, data []byte) *DEXProtocol { if to == nil || len(data) < 4 { return &DEXProtocol{Name: "unknown", Version: "", Type: ""} } // Validate address is not zero if err := validation.ValidateAddressPtr(to); err != nil { return &DEXProtocol{Name: "unknown", Version: "", Type: ""} } selector := hex.EncodeToString(data[0:4]) toAddr := to.Hex() // Map known router addresses (Arbitrum mainnet) knownRouters := map[string]*DEXProtocol{ // UniswapV2/V3 "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506": {Name: "SushiSwap", Version: "V2", Type: "router"}, "0xE592427A0AEce92De3Edee1F18E0157C05861564": {Name: "UniswapV3", Version: "V1", Type: "router"}, "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45": {Name: "UniswapV3", Version: "V2", Type: "router"}, "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B": {Name: "UniswapUniversal", Version: "V1", Type: "router"}, // Camelot "0xc873fEcbd354f5A56E00E710B90EF4201db2448d": {Name: "Camelot", Version: "V2", Type: "router"}, "0x1F721E2E82F6676FCE4eA07A5958cF098D339e18": {Name: "Camelot", Version: "V3", Type: "router"}, // Balancer "0xBA12222222228d8Ba445958a75a0704d566BF2C8": {Name: "Balancer", Version: "V2", Type: "vault"}, // Curve "0x7544Fe3d184b6B55D6B36c3FCA1157eE0Ba30287": {Name: "Curve", Version: "V1", Type: "router"}, // Kyber "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5": {Name: "KyberSwap", Version: "V1", Type: "router"}, "0xC1e7dFE73E1598E3910EF4C7845B68A19f0e8c6F": {Name: "KyberSwap", Version: "V2", Type: "router"}, // Aggregators "0x1111111254EEB25477B68fb85Ed929f73A960582": {Name: "1inch", Version: "V5", Type: "router"}, "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57": {Name: "Paraswap", Version: "V5", Type: "router"}, } // Check if it's a known router if protocol, ok := knownRouters[toAddr]; ok { return protocol } // Try to identify by function selector switch selector { // UniswapV2-style swap case "022c0d9f": return &DEXProtocol{Name: "UniswapV2", Version: "", Type: "pool"} // UniswapV2 Router case "38ed1739", "8803dbee", "7ff36ab5", "fb3bdb41", "18cbafe5", "4a25d94a": return &DEXProtocol{Name: "UniswapV2", Version: "", Type: "router"} // UniswapV3 Router case "414bf389", "c04b8d59", "db3e2198", "f28c0498", "5ae401dc", "ac9650d8": return &DEXProtocol{Name: "UniswapV3", Version: "", Type: "router"} // Curve case "3df02124", "a6417ed6", "394747c5", "5b41b908": return &DEXProtocol{Name: "Curve", Version: "", Type: "pool"} // Balancer case "52bbbe29": // swap return &DEXProtocol{Name: "Balancer", Version: "V2", Type: "vault"} // Camelot V3 swap case "128acb08": // exactInputSingle for Camelot V3 return &DEXProtocol{Name: "Camelot", Version: "V3", Type: "router"} default: return &DEXProtocol{Name: "unknown", Version: "", Type: ""} } } // IsSupportedDEX checks if the protocol is one we want to track func IsSupportedDEX(protocol *DEXProtocol) bool { if protocol == nil { return false } supportedDEXes := map[string]bool{ "UniswapV2": true, "UniswapV3": true, "UniswapUniversal": true, "SushiSwap": true, "Camelot": true, "Balancer": true, "Curve": true, "KyberSwap": true, } return supportedDEXes[protocol.Name] }