package arbitrum import ( "context" "encoding/hex" "fmt" "math/big" "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/fraktal/mev-beta/internal/logger" ) // RawL2Transaction represents a raw Arbitrum L2 transaction type RawL2Transaction struct { Hash string `json:"hash"` From string `json:"from"` To string `json:"to"` Value string `json:"value"` Gas string `json:"gas"` GasPrice string `json:"gasPrice"` Input string `json:"input"` Nonce string `json:"nonce"` TransactionIndex string `json:"transactionIndex"` Type string `json:"type"` ChainID string `json:"chainId,omitempty"` V string `json:"v,omitempty"` R string `json:"r,omitempty"` S string `json:"s,omitempty"` } // RawL2Block represents a raw Arbitrum L2 block type RawL2Block struct { Hash string `json:"hash"` Number string `json:"number"` Timestamp string `json:"timestamp"` Transactions []RawL2Transaction `json:"transactions"` } // DEXFunctionSignature represents a DEX function signature type DEXFunctionSignature struct { Signature string Name string Protocol string Description string } // ArbitrumL2Parser handles parsing of Arbitrum L2 transactions type ArbitrumL2Parser struct { client *rpc.Client logger *logger.Logger // DEX contract addresses on Arbitrum dexContracts map[common.Address]string // DEX function signatures dexFunctions map[string]DEXFunctionSignature } // NewArbitrumL2Parser creates a new Arbitrum L2 transaction parser func NewArbitrumL2Parser(rpcEndpoint string, logger *logger.Logger) (*ArbitrumL2Parser, error) { client, err := rpc.Dial(rpcEndpoint) if err != nil { return nil, fmt.Errorf("failed to connect to Arbitrum RPC: %v", err) } parser := &ArbitrumL2Parser{ client: client, logger: logger, dexContracts: make(map[common.Address]string), dexFunctions: make(map[string]DEXFunctionSignature), } // Initialize DEX contracts and functions parser.initializeDEXData() return parser, nil } // initializeDEXData initializes known DEX contracts and function signatures func (p *ArbitrumL2Parser) initializeDEXData() { // Official Arbitrum DEX contracts p.dexContracts[common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9")] = "UniswapV2Factory" p.dexContracts[common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")] = "UniswapV3Factory" p.dexContracts[common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4")] = "SushiSwapFactory" p.dexContracts[common.HexToAddress("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24")] = "UniswapV2Router02" p.dexContracts[common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564")] = "UniswapV3Router" p.dexContracts[common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")] = "UniswapV3Router02" p.dexContracts[common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506")] = "SushiSwapRouter" p.dexContracts[common.HexToAddress("0xC36442b4a4522E871399CD717aBDD847Ab11FE88")] = "UniswapV3PositionManager" // CORRECT DEX function signatures verified for Arbitrum (first 4 bytes of keccak256(function_signature)) // Uniswap V2 swap functions p.dexFunctions["0x38ed1739"] = DEXFunctionSignature{ Signature: "0x38ed1739", Name: "swapExactTokensForTokens", Protocol: "UniswapV2", Description: "Swap exact tokens for tokens", } p.dexFunctions["0x8803dbee"] = DEXFunctionSignature{ Signature: "0x8803dbee", Name: "swapTokensForExactTokens", Protocol: "UniswapV2", Description: "Swap tokens for exact tokens", } p.dexFunctions["0x7ff36ab5"] = DEXFunctionSignature{ Signature: "0x7ff36ab5", Name: "swapExactETHForTokens", Protocol: "UniswapV2", Description: "Swap exact ETH for tokens", } p.dexFunctions["0x4a25d94a"] = DEXFunctionSignature{ Signature: "0x4a25d94a", Name: "swapTokensForExactETH", Protocol: "UniswapV2", Description: "Swap tokens for exact ETH", } p.dexFunctions["0x18cbafe5"] = DEXFunctionSignature{ Signature: "0x18cbafe5", Name: "swapExactTokensForETH", Protocol: "UniswapV2", Description: "Swap exact tokens for ETH", } p.dexFunctions["0x791ac947"] = DEXFunctionSignature{ Signature: "0x791ac947", Name: "swapExactTokensForETHSupportingFeeOnTransferTokens", Protocol: "UniswapV2", Description: "Swap exact tokens for ETH supporting fee-on-transfer tokens", } p.dexFunctions["0xb6f9de95"] = DEXFunctionSignature{ Signature: "0xb6f9de95", Name: "swapExactETHForTokensSupportingFeeOnTransferTokens", Protocol: "UniswapV2", Description: "Swap exact ETH for tokens supporting fee-on-transfer tokens", } p.dexFunctions["0x5c11d795"] = DEXFunctionSignature{ Signature: "0x5c11d795", Name: "swapExactTokensForTokensSupportingFeeOnTransferTokens", Protocol: "UniswapV2", Description: "Swap exact tokens for tokens supporting fee-on-transfer tokens", } // Uniswap V2 liquidity functions p.dexFunctions["0xe8e33700"] = DEXFunctionSignature{ Signature: "0xe8e33700", Name: "addLiquidity", Protocol: "UniswapV2", Description: "Add liquidity to pool", } p.dexFunctions["0xf305d719"] = DEXFunctionSignature{ Signature: "0xf305d719", Name: "addLiquidityETH", Protocol: "UniswapV2", Description: "Add liquidity with ETH", } p.dexFunctions["0xbaa2abde"] = DEXFunctionSignature{ Signature: "0xbaa2abde", Name: "removeLiquidity", Protocol: "UniswapV2", Description: "Remove liquidity from pool", } p.dexFunctions["0x02751cec"] = DEXFunctionSignature{ Signature: "0x02751cec", Name: "removeLiquidityETH", Protocol: "UniswapV2", Description: "Remove liquidity with ETH", } // Uniswap V3 swap functions p.dexFunctions["0x414bf389"] = DEXFunctionSignature{ Signature: "0x414bf389", Name: "exactInputSingle", Protocol: "UniswapV3", Description: "Exact input single swap", } p.dexFunctions["0xc04b8d59"] = DEXFunctionSignature{ Signature: "0xc04b8d59", Name: "exactInput", Protocol: "UniswapV3", Description: "Exact input multi-hop swap", } p.dexFunctions["0xdb3e2198"] = DEXFunctionSignature{ Signature: "0xdb3e2198", Name: "exactOutputSingle", Protocol: "UniswapV3", Description: "Exact output single swap", } p.dexFunctions["0xf28c0498"] = DEXFunctionSignature{ Signature: "0xf28c0498", Name: "exactOutput", Protocol: "UniswapV3", Description: "Exact output multi-hop swap", } p.dexFunctions["0xac9650d8"] = DEXFunctionSignature{ Signature: "0xac9650d8", Name: "multicall", Protocol: "UniswapV3", Description: "Batch multiple function calls", } // Uniswap V3 position management functions p.dexFunctions["0x88316456"] = DEXFunctionSignature{ Signature: "0x88316456", Name: "mint", Protocol: "UniswapV3", Description: "Mint new liquidity position", } p.dexFunctions["0xfc6f7865"] = DEXFunctionSignature{ Signature: "0xfc6f7865", Name: "collect", Protocol: "UniswapV3", Description: "Collect fees from position", } p.dexFunctions["0x219f5d17"] = DEXFunctionSignature{ Signature: "0x219f5d17", Name: "increaseLiquidity", Protocol: "UniswapV3", Description: "Increase liquidity in position", } p.dexFunctions["0x0c49ccbe"] = DEXFunctionSignature{ Signature: "0x0c49ccbe", Name: "decreaseLiquidity", Protocol: "UniswapV3", Description: "Decrease liquidity in position", } } // GetBlockByNumber fetches a block with full transaction details using raw RPC func (p *ArbitrumL2Parser) GetBlockByNumber(ctx context.Context, blockNumber uint64) (*RawL2Block, error) { var block RawL2Block blockNumHex := fmt.Sprintf("0x%x", blockNumber) err := p.client.CallContext(ctx, &block, "eth_getBlockByNumber", blockNumHex, true) if err != nil { return nil, fmt.Errorf("failed to get block %d: %v", blockNumber, err) } p.logger.Debug(fmt.Sprintf("Retrieved L2 block %d with %d transactions", blockNumber, len(block.Transactions))) return &block, nil } // ParseDEXTransactions analyzes transactions in a block for DEX interactions func (p *ArbitrumL2Parser) ParseDEXTransactions(ctx context.Context, block *RawL2Block) []DEXTransaction { var dexTransactions []DEXTransaction for _, tx := range block.Transactions { if dexTx := p.parseDEXTransaction(tx); dexTx != nil { dexTransactions = append(dexTransactions, *dexTx) } } if len(dexTransactions) > 0 { p.logger.Info(fmt.Sprintf("Block %s: Found %d DEX transactions", block.Number, len(dexTransactions))) } return dexTransactions } // DEXTransaction represents a parsed DEX transaction type DEXTransaction struct { Hash string From string To string Value *big.Int FunctionSig string FunctionName string Protocol string InputData []byte ContractName string BlockNumber string } // parseDEXTransaction checks if a transaction is a DEX interaction func (p *ArbitrumL2Parser) parseDEXTransaction(tx RawL2Transaction) *DEXTransaction { // Skip transactions without recipient (contract creation) if tx.To == "" || tx.To == "0x" { return nil } // Skip transactions without input data if tx.Input == "" || tx.Input == "0x" || len(tx.Input) < 10 { return nil } toAddr := common.HexToAddress(tx.To) // Check if transaction is to a known DEX contract contractName, isDEXContract := p.dexContracts[toAddr] // Extract function signature (first 4 bytes of input data) functionSig := tx.Input[:10] // "0x" + 8 hex chars = 10 chars // Check if function signature matches known DEX functions if funcInfo, isDEXFunction := p.dexFunctions[functionSig]; isDEXFunction { // Parse value value := big.NewInt(0) if tx.Value != "" && tx.Value != "0x" && tx.Value != "0x0" { value.SetString(strings.TrimPrefix(tx.Value, "0x"), 16) } // Parse input data inputData, err := hex.DecodeString(strings.TrimPrefix(tx.Input, "0x")) if err != nil { p.logger.Debug(fmt.Sprintf("Failed to decode input data for transaction %s: %v", tx.Hash, err)) inputData = []byte{} } // Decode function parameters based on function type swapDetails := p.decodeFunctionData(funcInfo, inputData) p.logger.Info(fmt.Sprintf("DEX Transaction detected: %s -> %s (%s) calling %s (%s), Value: %s ETH%s", tx.From, tx.To, contractName, funcInfo.Name, funcInfo.Protocol, new(big.Float).Quo(new(big.Float).SetInt(value), big.NewFloat(1e18)).String(), swapDetails)) return &DEXTransaction{ Hash: tx.Hash, From: tx.From, To: tx.To, Value: value, FunctionSig: functionSig, FunctionName: funcInfo.Name, Protocol: funcInfo.Protocol, InputData: inputData, ContractName: contractName, BlockNumber: "", // Will be set by caller } } // Check if it's to a known DEX contract but unknown function if isDEXContract { p.logger.Debug(fmt.Sprintf("Unknown DEX function call: %s -> %s (%s), Function: %s", tx.From, tx.To, contractName, functionSig)) } return nil } // decodeFunctionData extracts parameters from transaction input data func (p *ArbitrumL2Parser) decodeFunctionData(funcInfo DEXFunctionSignature, inputData []byte) string { if len(inputData) < 4 { return "" } // Skip the 4-byte function selector params := inputData[4:] switch funcInfo.Name { case "swapExactTokensForTokens": return p.decodeSwapExactTokensForTokens(params) case "swapTokensForExactTokens": return p.decodeSwapTokensForExactTokens(params) case "swapExactETHForTokens": return p.decodeSwapExactETHForTokens(params) case "swapExactTokensForETH": return p.decodeSwapExactTokensForETH(params) case "exactInputSingle": return p.decodeExactInputSingle(params) case "exactInput": return p.decodeExactInput(params) case "exactOutputSingle": return p.decodeExactOutputSingle(params) case "multicall": return p.decodeMulticall(params) default: return fmt.Sprintf(", Raw input: %d bytes", len(inputData)) } } // decodeSwapExactTokensForTokens decodes UniswapV2 swapExactTokensForTokens parameters // function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) func (p *ArbitrumL2Parser) decodeSwapExactTokensForTokens(params []byte) string { if len(params) < 160 { // 5 parameters * 32 bytes each return ", Invalid parameters" } // Decode parameters (simplified - real ABI decoding would be more robust) amountIn := new(big.Int).SetBytes(params[0:32]) amountOutMin := new(big.Int).SetBytes(params[32:64]) // Convert to readable format amountInEth := new(big.Float).Quo(new(big.Float).SetInt(amountIn), big.NewFloat(1e18)) amountOutMinEth := new(big.Float).Quo(new(big.Float).SetInt(amountOutMin), big.NewFloat(1e18)) return fmt.Sprintf(", AmountIn: %s tokens, MinOut: %s tokens", amountInEth.Text('f', 6), amountOutMinEth.Text('f', 6)) } // decodeSwapTokensForExactTokens decodes UniswapV2 swapTokensForExactTokens parameters func (p *ArbitrumL2Parser) decodeSwapTokensForExactTokens(params []byte) string { if len(params) < 160 { return ", Invalid parameters" } amountOut := new(big.Int).SetBytes(params[0:32]) amountInMax := new(big.Int).SetBytes(params[32:64]) amountOutEth := new(big.Float).Quo(new(big.Float).SetInt(amountOut), big.NewFloat(1e18)) amountInMaxEth := new(big.Float).Quo(new(big.Float).SetInt(amountInMax), big.NewFloat(1e18)) return fmt.Sprintf(", AmountOut: %s tokens, MaxIn: %s tokens", amountOutEth.Text('f', 6), amountInMaxEth.Text('f', 6)) } // decodeSwapExactETHForTokens decodes UniswapV2 swapExactETHForTokens parameters func (p *ArbitrumL2Parser) decodeSwapExactETHForTokens(params []byte) string { if len(params) < 32 { return ", Invalid parameters" } amountOutMin := new(big.Int).SetBytes(params[0:32]) amountOutMinEth := new(big.Float).Quo(new(big.Float).SetInt(amountOutMin), big.NewFloat(1e18)) return fmt.Sprintf(", MinOut: %s tokens", amountOutMinEth.Text('f', 6)) } // decodeSwapExactTokensForETH decodes UniswapV2 swapExactTokensForETH parameters func (p *ArbitrumL2Parser) decodeSwapExactTokensForETH(params []byte) string { if len(params) < 64 { return ", Invalid parameters" } amountIn := new(big.Int).SetBytes(params[0:32]) amountOutMin := new(big.Int).SetBytes(params[32:64]) amountInEth := new(big.Float).Quo(new(big.Float).SetInt(amountIn), big.NewFloat(1e18)) amountOutMinEth := new(big.Float).Quo(new(big.Float).SetInt(amountOutMin), big.NewFloat(1e18)) return fmt.Sprintf(", AmountIn: %s tokens, MinETH: %s", amountInEth.Text('f', 6), amountOutMinEth.Text('f', 6)) } // decodeExactInputSingle decodes UniswapV3 exactInputSingle parameters func (p *ArbitrumL2Parser) decodeExactInputSingle(params []byte) string { if len(params) < 160 { // ExactInputSingleParams struct return ", Invalid parameters" } // Simplified decoding - real implementation would parse the struct properly amountIn := new(big.Int).SetBytes(params[128:160]) // approximation amountInEth := new(big.Float).Quo(new(big.Float).SetInt(amountIn), big.NewFloat(1e18)) return fmt.Sprintf(", AmountIn: %s tokens", amountInEth.Text('f', 6)) } // decodeExactInput decodes UniswapV3 exactInput parameters func (p *ArbitrumL2Parser) decodeExactInput(params []byte) string { if len(params) < 128 { return ", Invalid parameters" } amountIn := new(big.Int).SetBytes(params[64:96]) // approximation amountInEth := new(big.Float).Quo(new(big.Float).SetInt(amountIn), big.NewFloat(1e18)) return fmt.Sprintf(", AmountIn: %s tokens (multi-hop)", amountInEth.Text('f', 6)) } // decodeExactOutputSingle decodes UniswapV3 exactOutputSingle parameters func (p *ArbitrumL2Parser) decodeExactOutputSingle(params []byte) string { if len(params) < 160 { return ", Invalid parameters" } amountOut := new(big.Int).SetBytes(params[160:192]) // approximation amountOutEth := new(big.Float).Quo(new(big.Float).SetInt(amountOut), big.NewFloat(1e18)) return fmt.Sprintf(", AmountOut: %s tokens", amountOutEth.Text('f', 6)) } // decodeMulticall decodes UniswapV3 multicall parameters func (p *ArbitrumL2Parser) decodeMulticall(params []byte) string { if len(params) < 32 { return ", Invalid parameters" } // Multicall contains an array of encoded function calls // This is complex to decode without full ABI parsing return fmt.Sprintf(", Multicall with %d bytes of data", len(params)) } // Close closes the RPC connection func (p *ArbitrumL2Parser) Close() { if p.client != nil { p.client.Close() } }