package arbitrum import ( "context" "encoding/hex" "fmt" "math/big" "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/calldata" "github.com/fraktal/mev-beta/pkg/oracle" "github.com/fraktal/mev-beta/pkg/pools" "github.com/fraktal/mev-beta/pkg/security" ) // 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"` } // RawL2BlockWithLogs represents a raw Arbitrum L2 block with logs type RawL2BlockWithLogs struct { Hash string `json:"hash"` Number string `json:"number"` Timestamp string `json:"timestamp"` Transactions []RawL2TransactionWithLogs `json:"transactions"` } // RawL2TransactionWithLogs includes transaction logs for pool discovery type RawL2TransactionWithLogs 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"` Logs []interface{} `json:"logs"` TransactionIndex string `json:"transactionIndex"` Type string `json:"type"` } // 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 oracle *oracle.PriceOracle // DEX contract addresses on Arbitrum dexContracts map[common.Address]string // DEX function signatures dexFunctions map[string]DEXFunctionSignature // Pool discovery system poolDiscovery *pools.PoolDiscovery // ABI decoders for sophisticated parameter parsing uniswapV2ABI abi.ABI uniswapV3ABI abi.ABI sushiSwapABI abi.ABI } // NewArbitrumL2Parser creates a new Arbitrum L2 transaction parser func NewArbitrumL2Parser(rpcEndpoint string, logger *logger.Logger, priceOracle *oracle.PriceOracle) (*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, oracle: priceOracle, dexContracts: make(map[common.Address]string), dexFunctions: make(map[string]DEXFunctionSignature), } // Initialize DEX contracts and functions parser.initializeDEXData() // Initialize ABI decoders for sophisticated parsing if err := parser.initializeABIs(); err != nil { logger.Warn(fmt.Sprintf("Failed to initialize ABI decoders: %v", err)) } // Initialize pool discovery system parser.poolDiscovery = pools.NewPoolDiscovery(client, logger) logger.Info(fmt.Sprintf("Pool discovery system initialized - %d pools, %d exchanges loaded", parser.poolDiscovery.GetPoolCount(), parser.poolDiscovery.GetExchangeCount())) 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" // MISSING HIGH-ACTIVITY DEX CONTRACTS (based on log analysis) p.dexContracts[common.HexToAddress("0xaa78afc926d0df40458ad7b1f7eed37251bd2b5f")] = "SushiSwapRouter_Arbitrum" // 44 transactions p.dexContracts[common.HexToAddress("0x87d66368cd08a7ca42252f5ab44b2fb6d1fb8d15")] = "TraderJoeRouter" // 50 transactions p.dexContracts[common.HexToAddress("0x16e71b13fe6079b4312063f7e81f76d165ad32ad")] = "SushiSwapRouter_V2" // Frequent p.dexContracts[common.HexToAddress("0xaa277cb7914b7e5514946da92cb9de332ce610ef")] = "RamsesExchange" // Multi calls p.dexContracts[common.HexToAddress("0xc873fEcbd354f5A56E00E710B90EF4201db2448d")] = "CamelotRouter" // Camelot DEX p.dexContracts[common.HexToAddress("0x5ffe7FB82894076ECB99A30D6A32e969e6e35E98")] = "CurveAddressProvider" // Curve p.dexContracts[common.HexToAddress("0xba12222222228d8ba445958a75a0704d566bf2c8")] = "BalancerVault" // Balancer V2 // HIGH-ACTIVITY UNISWAP V3 POOLS (detected from logs) p.dexContracts[common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0")] = "UniswapV3Pool_WETH_USDC" // 381 occurrences p.dexContracts[common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d")] = "UniswapV3Pool_WETH_USDT" // 168 occurrences p.dexContracts[common.HexToAddress("0x2f5e87C9312fa29aed5c179E456625D79015299c")] = "UniswapV3Pool_ARB_ETH" // 169 occurrences // 1INCH AGGREGATOR (major MEV source) p.dexContracts[common.HexToAddress("0x1111111254eeb25477b68fb85ed929f73a960582")] = "1InchAggregatorV5" p.dexContracts[common.HexToAddress("0x1111111254fb6c44bac0bed2854e76f90643097d")] = "1InchAggregatorV4" // 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", } // MISSING CRITICAL FUNCTION SIGNATURES (major MEV sources) // Multicall functions (used heavily in V3 and aggregators) p.dexFunctions["0xac9650d8"] = DEXFunctionSignature{ Signature: "0xac9650d8", Name: "multicall", Protocol: "Multicall", Description: "Execute multiple function calls in single transaction", } p.dexFunctions["0x5ae401dc"] = DEXFunctionSignature{ Signature: "0x5ae401dc", Name: "multicall", Protocol: "MultiV2", Description: "Multicall with deadline", } p.dexFunctions["0x1f0464d1"] = DEXFunctionSignature{ Signature: "0x1f0464d1", Name: "multicall", Protocol: "MultiV3", Description: "Multicall with previous blockhash guard", } // 1INCH Aggregator functions (major arbitrage source) p.dexFunctions["0x7c025200"] = DEXFunctionSignature{ Signature: "0x7c025200", Name: "swap", Protocol: "1Inch", Description: "1inch aggregator swap", } p.dexFunctions["0xe449022e"] = DEXFunctionSignature{ Signature: "0xe449022e", Name: "uniswapV3Swap", Protocol: "1Inch", Description: "1inch uniswap v3 swap", } p.dexFunctions["0x12aa3caf"] = DEXFunctionSignature{ Signature: "0x12aa3caf", Name: "ethUnoswap", Protocol: "1Inch", Description: "1inch ETH unoswap", } // Balancer V2 functions p.dexFunctions["0x52bbbe29"] = DEXFunctionSignature{ Signature: "0x52bbbe29", Name: "swap", Protocol: "BalancerV2", Description: "Balancer V2 single swap", } p.dexFunctions["0x945bcec9"] = DEXFunctionSignature{ Signature: "0x945bcec9", Name: "batchSwap", Protocol: "BalancerV2", Description: "Balancer V2 batch swap", } // Curve functions p.dexFunctions["0x3df02124"] = DEXFunctionSignature{ Signature: "0x3df02124", Name: "exchange", Protocol: "Curve", Description: "Curve token exchange", } p.dexFunctions["0xa6417ed6"] = DEXFunctionSignature{ Signature: "0xa6417ed6", Name: "exchange_underlying", Protocol: "Curve", Description: "Curve exchange underlying tokens", } // SushiSwap specific functions p.dexFunctions["0x02751cec"] = DEXFunctionSignature{ Signature: "0x02751cec", Name: "removeLiquidityETH", Protocol: "SushiSwap", Description: "Remove liquidity with ETH", } // TraderJoe functions p.dexFunctions["0x18cbafe5"] = DEXFunctionSignature{ Signature: "0x18cbafe5", Name: "swapExactTokensForETH", Protocol: "TraderJoe", Description: "TraderJoe exact tokens for ETH", } // Universal Router functions (Uniswap's new router) p.dexFunctions["0x3593564c"] = DEXFunctionSignature{ Signature: "0x3593564c", Name: "execute", Protocol: "UniversalRouter", Description: "Universal router execute", } // Generic DEX functions that appear frequently p.dexFunctions["0x022c0d9f"] = DEXFunctionSignature{ Signature: "0x022c0d9f", Name: "swap", Protocol: "Generic", Description: "Generic swap function", } p.dexFunctions["0x128acb08"] = DEXFunctionSignature{ Signature: "0x128acb08", Name: "swapTokensForTokens", Protocol: "Generic", Description: "Generic token to token swap", } } // 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 } // SwapDetails contains detailed information about a DEX swap type SwapDetails struct { AmountIn *big.Int AmountOut *big.Int AmountMin *big.Int TokenIn string TokenOut string Fee uint32 Deadline uint64 Recipient string IsValid bool } // 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 SwapDetails *SwapDetails // Detailed swap information } // 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.decodeFunctionDataStructured(funcInfo, inputData) // Log basic transaction detection (opportunities logged later with actual amounts from events) if swapDetails != nil && swapDetails.IsValid { p.logger.Info(fmt.Sprintf("DEX Transaction detected: %s -> %s (%s) calling %s (%s) - TokenIn: %s, TokenOut: %s", tx.From, tx.To, contractName, funcInfo.Name, funcInfo.Protocol, swapDetails.TokenIn, swapDetails.TokenOut)) } else { // Fallback to simple logging swapDetailsStr := 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(), swapDetailsStr)) } 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 SwapDetails: swapDetails, } } // 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" } // Use sophisticated ABI decoding instead of basic byte slicing fullInputData := append([]byte{0x38, 0xed, 0x17, 0x39}, params...) // Add function selector decoded, err := p.decodeWithABI("UniswapV2", "swapExactTokensForTokens", fullInputData) if err != nil { // Fallback to basic decoding if len(params) >= 64 { 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, MinOut: %s tokens (fallback)", amountInEth.Text('f', 6), amountOutMinEth.Text('f', 6)) } return ", Invalid parameters" } // Extract values from ABI decoded parameters amountIn, ok1 := decoded["amountIn"].(*big.Int) amountOutMin, ok2 := decoded["amountOutMin"].(*big.Int) path, ok3 := decoded["path"].([]common.Address) deadline, ok4 := decoded["deadline"].(*big.Int) if !ok1 || !ok2 || !ok3 || !ok4 { return ", Failed to decode parameters" } // Convert to readable format with enhanced details 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)) // Extract token addresses from path tokenIn := "0x0000000000000000000000000000000000000000" tokenOut := "0x0000000000000000000000000000000000000000" if len(path) >= 2 { tokenIn = path[0].Hex() tokenOut = path[len(path)-1].Hex() } return fmt.Sprintf(", AmountIn: %s (%s), MinOut: %s (%s), Hops: %d, Deadline: %s", amountInEth.Text('f', 6), tokenIn, amountOutMinEth.Text('f', 6), tokenOut, len(path)-1, time.Unix(deadline.Int64(), 0).Format("15:04:05")) } // 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)) } // decodeFunctionDataStructured extracts structured parameters from transaction input data func (p *ArbitrumL2Parser) decodeFunctionDataStructured(funcInfo DEXFunctionSignature, inputData []byte) *SwapDetails { if len(inputData) < 4 { return &SwapDetails{IsValid: false} } // Skip the 4-byte function selector params := inputData[4:] switch funcInfo.Name { case "swapExactTokensForTokens": return p.decodeSwapExactTokensForTokensStructured(params) case "swapTokensForExactTokens": return p.decodeSwapTokensForExactTokensStructured(params) case "swapExactETHForTokens": return p.decodeSwapExactETHForTokensStructured(params) case "swapExactTokensForETH": return p.decodeSwapExactTokensForETHStructured(params) case "exactInputSingle": return p.decodeExactInputSingleStructured(params) case "exactInput": return p.decodeExactInputStructured(params) case "exactOutputSingle": return p.decodeExactOutputSingleStructured(params) case "multicall": return p.decodeMulticallStructured(params) default: return &SwapDetails{IsValid: false} } } // decodeSwapExactTokensForTokensStructured decodes UniswapV2 swapExactTokensForTokens parameters func (p *ArbitrumL2Parser) decodeSwapExactTokensForTokensStructured(params []byte) *SwapDetails { if len(params) < 160 { // 5 parameters * 32 bytes each return &SwapDetails{IsValid: false} } // Extract amounts directly amountIn := new(big.Int).SetBytes(params[0:32]) amountMin := new(big.Int).SetBytes(params[32:64]) // Extract tokens from path array // UniswapV2 encodes path as dynamic array at offset specified in params[64:96] var tokenIn, tokenOut string = "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000" if len(params) >= 96 { pathOffset := new(big.Int).SetBytes(params[64:96]).Uint64() // Ensure we have enough data for path array if pathOffset+32 <= uint64(len(params)) { pathLength := new(big.Int).SetBytes(params[pathOffset : pathOffset+32]).Uint64() // Need at least 2 tokens in path (input and output) if pathLength >= 2 && pathOffset+32+pathLength*32 <= uint64(len(params)) { // Extract first token (input) tokenInStart := pathOffset + 32 if tokenInStart+32 <= uint64(len(params)) { tokenInAddr := common.BytesToAddress(params[tokenInStart+12 : tokenInStart+32]) // Address is in last 20 bytes tokenIn = p.resolveTokenSymbol(tokenInAddr.Hex()) } // Extract last token (output) tokenOutStart := pathOffset + 32 + (pathLength-1)*32 if tokenOutStart+32 <= uint64(len(params)) { tokenOutAddr := common.BytesToAddress(params[tokenOutStart+12 : tokenOutStart+32]) // Address is in last 20 bytes tokenOut = p.resolveTokenSymbol(tokenOutAddr.Hex()) } } } } return &SwapDetails{ AmountIn: amountIn, AmountOut: amountMin, // For UniswapV2, this is actually AmountMin but we display it as expected output AmountMin: amountMin, TokenIn: tokenIn, TokenOut: tokenOut, Deadline: new(big.Int).SetBytes(params[128:160]).Uint64(), Recipient: fmt.Sprintf("0x%x", params[96:128]), // address is last 20 bytes IsValid: true, } } // decodeSwapExactTokensForETHStructured decodes UniswapV2 swapExactTokensForETH parameters func (p *ArbitrumL2Parser) decodeSwapExactTokensForETHStructured(params []byte) *SwapDetails { if len(params) < 64 { return &SwapDetails{IsValid: false} } return &SwapDetails{ AmountIn: new(big.Int).SetBytes(params[0:32]), AmountOut: new(big.Int).SetBytes(params[32:64]), // For UniswapV2, this is actually AmountMin but we display it as expected output AmountMin: new(big.Int).SetBytes(params[32:64]), TokenIn: "0x0000000000000000000000000000000000000000", TokenOut: "ETH", IsValid: true, } } // decodeExactInputSingleStructured decodes UniswapV3 exactInputSingle parameters func (p *ArbitrumL2Parser) decodeExactInputSingleStructured(params []byte) *SwapDetails { if len(params) < 160 { // ExactInputSingleParams struct return &SwapDetails{IsValid: false} } // ExactInputSingleParams structure: // struct ExactInputSingleParams { // address tokenIn; // offset 0, 32 bytes // address tokenOut; // offset 32, 32 bytes // uint24 fee; // offset 64, 32 bytes (padded) // address recipient; // offset 96, 32 bytes // uint256 deadline; // offset 128, 32 bytes // uint256 amountIn; // offset 160, 32 bytes // uint256 amountOutMinimum; // offset 192, 32 bytes // uint160 sqrtPriceLimitX96; // offset 224, 32 bytes // } // Properly extract token addresses (last 20 bytes of each 32-byte slot) tokenIn := common.BytesToAddress(params[12:32]) // Skip first 12 bytes, take last 20 tokenOut := common.BytesToAddress(params[44:64]) // Skip first 12 bytes, take last 20 recipient := common.BytesToAddress(params[108:128]) // Extract amounts and other values rawFee := new(big.Int).SetBytes(params[64:96]).Uint64() fee, err := security.SafeUint64ToUint32(rawFee) if err != nil { p.logger.Error("Fee value exceeds uint32 maximum", "rawFee", rawFee, "error", err) return &SwapDetails{IsValid: false} // Return invalid if fee is invalid } deadline := new(big.Int).SetBytes(params[128:160]).Uint64() amountIn := new(big.Int).SetBytes(params[160:192]) amountOutMin := new(big.Int).SetBytes(params[192:224]) return &SwapDetails{ AmountIn: amountIn, AmountOut: amountOutMin, // For exactInputSingle, we display amountOutMinimum as expected output AmountMin: amountOutMin, TokenIn: p.resolveTokenSymbol(tokenIn.Hex()), TokenOut: p.resolveTokenSymbol(tokenOut.Hex()), Fee: fee, Deadline: deadline, Recipient: recipient.Hex(), IsValid: true, } } // decodeSwapTokensForExactTokensStructured decodes UniswapV2 swapTokensForExactTokens parameters func (p *ArbitrumL2Parser) decodeSwapTokensForExactTokensStructured(params []byte) *SwapDetails { if len(params) < 160 { return &SwapDetails{IsValid: false} } return &SwapDetails{ AmountOut: new(big.Int).SetBytes(params[0:32]), AmountIn: new(big.Int).SetBytes(params[32:64]), // Max amount in TokenIn: "0x0000000000000000000000000000000000000000", TokenOut: "0x0000000000000000000000000000000000000000", IsValid: true, } } // decodeSwapExactETHForTokensStructured decodes UniswapV2 swapExactETHForTokens parameters func (p *ArbitrumL2Parser) decodeSwapExactETHForTokensStructured(params []byte) *SwapDetails { if len(params) < 32 { return &SwapDetails{IsValid: false} } return &SwapDetails{ AmountMin: new(big.Int).SetBytes(params[0:32]), TokenIn: "ETH", TokenOut: "0x0000000000000000000000000000000000000000", IsValid: true, } } // decodeExactInputStructured decodes UniswapV3 exactInput parameters func (p *ArbitrumL2Parser) decodeExactInputStructured(params []byte) *SwapDetails { if len(params) < 128 { return &SwapDetails{IsValid: false} } // ExactInputParams struct: // struct ExactInputParams { // bytes path; // offset 0: pointer to path data // address recipient; // offset 32: 32 bytes // uint256 deadline; // offset 64: 32 bytes // uint256 amountIn; // offset 96: 32 bytes // uint256 amountOutMinimum; // offset 128: 32 bytes // } recipient := common.BytesToAddress(params[44:64]) // Skip padding, take last 20 bytes deadline := new(big.Int).SetBytes(params[64:96]).Uint64() amountIn := new(big.Int).SetBytes(params[96:128]) var amountOutMin *big.Int if len(params) >= 160 { amountOutMin = new(big.Int).SetBytes(params[128:160]) } else { amountOutMin = big.NewInt(0) } return &SwapDetails{ AmountIn: amountIn, AmountOut: amountOutMin, // For exactInput, we display amountOutMinimum as expected output AmountMin: amountOutMin, TokenIn: "0x0000000000000000000000000000000000000000", // Would need to decode path data at offset specified in params[0:32] TokenOut: "0x0000000000000000000000000000000000000000", // Would need to decode path data Deadline: deadline, Recipient: recipient.Hex(), IsValid: true, } } // decodeExactOutputSingleStructured decodes UniswapV3 exactOutputSingle parameters func (p *ArbitrumL2Parser) decodeExactOutputSingleStructured(params []byte) *SwapDetails { if len(params) < 160 { return &SwapDetails{IsValid: false} } return &SwapDetails{ AmountOut: new(big.Int).SetBytes(params[160:192]), TokenIn: fmt.Sprintf("0x%x", params[0:32]), TokenOut: fmt.Sprintf("0x%x", params[32:64]), IsValid: true, } } // decodeMulticallStructured decodes UniswapV3 multicall parameters func (p *ArbitrumL2Parser) decodeMulticallStructured(params []byte) *SwapDetails { if len(params) < 64 { return &SwapDetails{IsValid: false} } // Multicall contains an array of encoded function calls // First 32 bytes is the offset to the array // Next 32 bytes is the array length if len(params) < 64 { return &SwapDetails{IsValid: false} } // Get array length (skip offset, get length) if len(params) < 64+32 { return &SwapDetails{IsValid: false} } arrayLength := new(big.Int).SetBytes(params[32:64]) // Validate array length if arrayLength.Sign() <= 0 || arrayLength.Cmp(big.NewInt(100)) > 0 { return &SwapDetails{IsValid: false} } // Enhanced multicall parsing to handle various router patterns if arrayLength.Cmp(big.NewInt(0)) > 0 && len(params) > 96 { // Try to extract tokens from any function call in the multicall token0, token1 := p.extractTokensFromMulticallData(params) if token0 != "" && token1 != "" { return &SwapDetails{ TokenIn: token0, TokenOut: token1, IsValid: true, } } } // If we can't decode specific parameters, mark as invalid rather than returning zeros // This will trigger fallback processing return &SwapDetails{ TokenIn: "0x0000000000000000000000000000000000000000", TokenOut: "0x0000000000000000000000000000000000000000", IsValid: false, // Mark as invalid so fallback processing can handle it } } // calculateProfitWithOracle calculates profit estimation using the price oracle func (p *ArbitrumL2Parser) calculateProfitWithOracle(swapDetails *SwapDetails) (float64, error) { if p.oracle == nil { return 0.0, fmt.Errorf("price oracle not available") } // Skip calculation for invalid swaps if !swapDetails.IsValid || swapDetails.AmountIn == nil || swapDetails.AmountIn.Sign() == 0 { return 0.0, nil } // Convert token addresses from string to common.Address var tokenIn, tokenOut common.Address // TokenIn is a string, convert to common.Address if !common.IsHexAddress(swapDetails.TokenIn) { return 0.0, fmt.Errorf("invalid tokenIn address: %s", swapDetails.TokenIn) } tokenIn = common.HexToAddress(swapDetails.TokenIn) // TokenOut is a string, convert to common.Address if !common.IsHexAddress(swapDetails.TokenOut) { return 0.0, fmt.Errorf("invalid tokenOut address: %s", swapDetails.TokenOut) } tokenOut = common.HexToAddress(swapDetails.TokenOut) // Create price request priceReq := &oracle.PriceRequest{ TokenIn: tokenIn, TokenOut: tokenOut, AmountIn: swapDetails.AmountIn, Timestamp: time.Now(), } // Get price from oracle with timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() priceResp, err := p.oracle.GetPrice(ctx, priceReq) if err != nil { return 0.0, fmt.Errorf("oracle price lookup failed: %w", err) } if !priceResp.Valid { return 0.0, fmt.Errorf("oracle returned invalid price") } // Convert amounts to float for USD calculation (assuming 18 decimals) amountInFloat := new(big.Float).Quo(new(big.Float).SetInt(swapDetails.AmountIn), big.NewFloat(1e18)) amountOutFloat := new(big.Float).Quo(new(big.Float).SetInt(priceResp.AmountOut), big.NewFloat(1e18)) amountInVal, _ := amountInFloat.Float64() amountOutVal, _ := amountOutFloat.Float64() // Estimate profit based on price difference // This is a simplified calculation - in reality you'd need: // 1. USD prices for both tokens // 2. Gas cost estimation // 3. Market impact analysis // 4. Arbitrage opportunity assessment // For now, calculate potential arbitrage profit as percentage of swap value profitPercentage := 0.0 slippageBps := int64(0) // Initialize slippage variable if amountInVal > 0 { // Simple profit estimation based on price impact slippageBps = priceResp.SlippageBps.Int64() if slippageBps > 0 { // Higher slippage = higher potential arbitrage profit profitPercentage = float64(slippageBps) / 10000.0 * 0.1 // 10% of slippage as profit estimate } } // Convert to USD estimate (simplified - assumes token has $1 base value) estimatedProfitUSD := amountInVal * profitPercentage p.logger.Debug(fmt.Sprintf("Calculated profit for swap %s->%s: amountIn=%.6f, amountOut=%.6f, slippage=%d bps, profit=$%.2f", tokenIn.Hex()[:8], tokenOut.Hex()[:8], amountInVal, amountOutVal, slippageBps, estimatedProfitUSD)) return estimatedProfitUSD, nil } // initializeABIs initializes the ABI decoders for sophisticated parameter parsing func (p *ArbitrumL2Parser) initializeABIs() error { // Uniswap V2 Router ABI (essential functions) uniswapV2JSON := `[ { "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": "swapTokensForExactTokens", "type": "function", "inputs": [ {"name": "amountOut", "type": "uint256"}, {"name": "amountInMax", "type": "uint256"}, {"name": "path", "type": "address[]"}, {"name": "to", "type": "address"}, {"name": "deadline", "type": "uint256"} ] }, { "name": "swapExactETHForTokens", "type": "function", "inputs": [ {"name": "amountOutMin", "type": "uint256"}, {"name": "path", "type": "address[]"}, {"name": "to", "type": "address"}, {"name": "deadline", "type": "uint256"} ] }, { "name": "swapExactTokensForETH", "type": "function", "inputs": [ {"name": "amountIn", "type": "uint256"}, {"name": "amountOutMin", "type": "uint256"}, {"name": "path", "type": "address[]"}, {"name": "to", "type": "address"}, {"name": "deadline", "type": "uint256"} ] } ]` // Uniswap V3 Router ABI (essential functions) uniswapV3JSON := `[ { "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": "exactInput", "type": "function", "inputs": [ { "name": "params", "type": "tuple", "components": [ {"name": "path", "type": "bytes"}, {"name": "recipient", "type": "address"}, {"name": "deadline", "type": "uint256"}, {"name": "amountIn", "type": "uint256"}, {"name": "amountOutMinimum", "type": "uint256"} ] } ] }, { "name": "exactOutputSingle", "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": "amountOut", "type": "uint256"}, {"name": "amountInMaximum", "type": "uint256"}, {"name": "sqrtPriceLimitX96", "type": "uint160"} ] } ] }, { "name": "multicall", "type": "function", "inputs": [ {"name": "data", "type": "bytes[]"} ] } ]` var err error // Parse Uniswap V2 ABI p.uniswapV2ABI, err = abi.JSON(strings.NewReader(uniswapV2JSON)) if err != nil { return fmt.Errorf("failed to parse Uniswap V2 ABI: %w", err) } // Parse Uniswap V3 ABI p.uniswapV3ABI, err = abi.JSON(strings.NewReader(uniswapV3JSON)) if err != nil { return fmt.Errorf("failed to parse Uniswap V3 ABI: %w", err) } // Use same ABI for SushiSwap (same interface as Uniswap V2) p.sushiSwapABI = p.uniswapV2ABI p.logger.Info("ABI decoders initialized successfully for sophisticated transaction parsing") return nil } // decodeWithABI uses proper ABI decoding instead of basic byte slicing func (p *ArbitrumL2Parser) decodeWithABI(protocol, functionName string, inputData []byte) (map[string]interface{}, error) { if len(inputData) < 4 { return nil, fmt.Errorf("input data too short") } // Remove function selector (first 4 bytes) params := inputData[4:] var targetABI abi.ABI switch protocol { case "UniswapV2": targetABI = p.uniswapV2ABI case "UniswapV3": targetABI = p.uniswapV3ABI case "SushiSwap": targetABI = p.sushiSwapABI default: return nil, fmt.Errorf("unsupported protocol: %s", protocol) } // Get the method from ABI method, exists := targetABI.Methods[functionName] if !exists { return nil, fmt.Errorf("method %s not found in %s ABI", functionName, protocol) } // Decode the parameters values, err := method.Inputs.Unpack(params) if err != nil { return nil, fmt.Errorf("failed to unpack parameters: %w", err) } // Convert to map for easier access result := make(map[string]interface{}) for i, input := range method.Inputs { if i < len(values) { result[input.Name] = values[i] } } p.logger.Debug(fmt.Sprintf("Successfully decoded %s.%s with %d parameters", protocol, functionName, len(values))) return result, nil } // GetDetailedSwapInfo extracts detailed swap information from DEXTransaction func (p *ArbitrumL2Parser) GetDetailedSwapInfo(dexTx *DEXTransaction) *DetailedSwapInfo { if dexTx == nil || dexTx.SwapDetails == nil || !dexTx.SwapDetails.IsValid { return &DetailedSwapInfo{IsValid: false} } return &DetailedSwapInfo{ TxHash: dexTx.Hash, From: dexTx.From, To: dexTx.To, MethodName: dexTx.FunctionName, Protocol: dexTx.Protocol, AmountIn: dexTx.SwapDetails.AmountIn, AmountOut: dexTx.SwapDetails.AmountOut, AmountMin: dexTx.SwapDetails.AmountMin, TokenIn: dexTx.SwapDetails.TokenIn, TokenOut: dexTx.SwapDetails.TokenOut, Fee: dexTx.SwapDetails.Fee, Recipient: dexTx.SwapDetails.Recipient, IsValid: true, } } // DetailedSwapInfo represents enhanced swap information for external processing type DetailedSwapInfo struct { TxHash string From string To string MethodName string Protocol string AmountIn *big.Int AmountOut *big.Int AmountMin *big.Int TokenIn string TokenOut string Fee uint32 Recipient string IsValid bool } // Close closes the RPC connection // resolveTokenSymbol converts token address to human-readable symbol func (p *ArbitrumL2Parser) resolveTokenSymbol(tokenAddress string) string { // Convert to lowercase for consistent lookup addr := strings.ToLower(tokenAddress) // Known Arbitrum token mappings tokenMap := map[string]string{ "0x82af49447d8a07e3bd95bd0d56f35241523fbab1": "WETH", "0xaf88d065e77c8cc2239327c5edb3a432268e5831": "USDC", "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8": "USDC.e", "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9": "USDT", "0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f": "WBTC", "0x912ce59144191c1204e64559fe8253a0e49e6548": "ARB", "0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a": "GMX", "0xf97f4df75117a78c1a5a0dbb814af92458539fb4": "LINK", "0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0": "UNI", "0xba5ddd1f9d7f570dc94a51479a000e3bce967196": "AAVE", "0x0de59c86c306b9fead9fb67e65551e2b6897c3f6": "KUMA", "0x6efa9b8883dfb78fd75cd89d8474c44c3cbda469": "DIA", "0x440017a1b021006d556d7fc06a54c32e42eb745b": "G@ARB", "0x11cdb42b0eb46d95f990bedd4695a6e3fa034978": "CRV", "0x040d1edc9569d4bab2d15287dc5a4f10f56a56b8": "BAL", "0x354a6da3fcde098f8389cad84b0182725c6c91de": "COMP", "0x2e9a6df78e42c50b0cefcf9000d0c3a4d34e1dd5": "MKR", "0x539bde0d7dbd336b79148aa742883198bbf60342": "MAGIC", "0x3d9907f9a368ad0a51be60f7da3b97cf940982d8": "GRAIL", "0x6c2c06790b3e3e3c38e12ee22f8183b37a13ee55": "DPX", "0x3082cc23568ea640225c2467653db90e9250aaa0": "RDNT", "0xaaa6c1e32c55a7bfa8066a6fae9b42650f262418": "RAM", "0x0c880f6761f1af8d9aa9c466984b80dab9a8c9e8": "PENDLE", } if symbol, exists := tokenMap[addr]; exists { return symbol } // Return shortened address if not found if len(tokenAddress) > 10 { return tokenAddress[:6] + "..." + tokenAddress[len(tokenAddress)-4:] } return tokenAddress } // extractTokensFromMulticallData extracts token addresses from multicall transaction data func (p *ArbitrumL2Parser) extractTokensFromMulticallData(params []byte) (token0, token1 string) { tokens, err := calldata.ExtractTokensFromMulticallWithContext(params, &calldata.MulticallContext{ Stage: "arbitrum.l2_parser.extractTokensFromMulticallData", Protocol: "unknown", }) if err != nil || len(tokens) == 0 { return "", "" } filtered := make([]string, 0, len(tokens)) for _, token := range tokens { if token == (common.Address{}) { continue } hexAddr := strings.TrimPrefix(strings.ToLower(token.Hex()), "0x") if p.isValidTokenAddress(hexAddr) { filtered = append(filtered, token.Hex()) } } if len(filtered) == 0 { return "", "" } token0 = filtered[0] if len(filtered) > 1 { token1 = filtered[1] } return token0, token1 } // isValidTokenAddress checks if an address looks like a valid token address func (p *ArbitrumL2Parser) isValidTokenAddress(hexAddr string) bool { // Basic validation - more sophisticated validation could be added if len(hexAddr) != 40 { return false } // Check if it's all hex for _, r := range hexAddr { if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) { return false } } // Filter out common non-token addresses (routers, common addresses, etc.) commonNonTokens := []string{ "e592427a0aece92de3edee1f18e0157c05861564", // Uniswap V3 Router "a51afafe0263b40edaef0df8781ea9aa03e381a3", // Universal Router "4752ba5dbc23f44d87826276bf6fd6b1c372ad24", // Uniswap V2 Router "0000000000000000000000000000000000000000", // Zero address "ffffffffffffffffffffffffffffffffffffffff", // Max address "0000000000000000000000000000000000000001", // Common system addresses } for _, nonToken := range commonNonTokens { if strings.EqualFold(hexAddr, nonToken) { return false } } return true } func (p *ArbitrumL2Parser) Close() { if p.client != nil { p.client.Close() } }