package uniswap import ( "context" "fmt" "strings" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" ) // Multicall3 address on Arbitrum var Multicall3Address = common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11") // Multicall3 ABI (simplified - only aggregate3 function) const Multicall3ABI = `[{ "inputs": [{ "components": [{ "internalType": "address", "name": "target", "type": "address" }, { "internalType": "bool", "name": "allowFailure", "type": "bool" }, { "internalType": "bytes", "name": "callData", "type": "bytes" }], "internalType": "struct Multicall3.Call3[]", "name": "calls", "type": "tuple[]" }], "name": "aggregate3", "outputs": [{ "components": [{ "internalType": "bool", "name": "success", "type": "bool" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" }], "internalType": "struct Multicall3.Result[]", "name": "returnData", "type": "tuple[]" }], "stateMutability": "payable", "type": "function" }]` // Call3 represents a single call in Multicall3 type Call3 struct { Target common.Address AllowFailure bool CallData []byte } // Result3 represents the result of a Multicall3 call type Result3 struct { Success bool ReturnData []byte } // MulticallBatcher batches multiple RPC calls into a single Multicall3 transaction type MulticallBatcher struct { client *ethclient.Client multicallABI abi.ABI } // NewMulticallBatcher creates a new multicall batcher func NewMulticallBatcher(client *ethclient.Client) (*MulticallBatcher, error) { parsedABI, err := abi.JSON(strings.NewReader(Multicall3ABI)) if err != nil { return nil, fmt.Errorf("failed to parse Multicall3 ABI: %w", err) } return &MulticallBatcher{ client: client, multicallABI: parsedABI, }, nil } // ExecuteMulticall executes multiple calls in a single transaction func (m *MulticallBatcher) ExecuteMulticall(ctx context.Context, calls []Call3) ([]Result3, error) { if len(calls) == 0 { return []Result3{}, nil } // Pack the aggregate3 call data, err := m.multicallABI.Pack("aggregate3", calls) if err != nil { return nil, fmt.Errorf("failed to pack multicall data: %w", err) } // Create the call message msg := ethereum.CallMsg{ To: &Multicall3Address, Data: data, } // Execute the call result, err := m.client.CallContract(ctx, msg, nil) if err != nil { return nil, fmt.Errorf("failed to execute multicall: %w", err) } // Unpack the results var results []Result3 err = m.multicallABI.UnpackIntoInterface(&results, "aggregate3", result) if err != nil { return nil, fmt.Errorf("failed to unpack multicall results: %w", err) } return results, nil } // BatchPoolDataCalls batches slot0, liquidity, token0, token1, and fee calls for a pool func (m *MulticallBatcher) BatchPoolDataCalls(ctx context.Context, poolAddress common.Address, poolABI abi.ABI) ( slot0Data []byte, liquidityData []byte, token0Data []byte, token1Data []byte, feeData []byte, err error, ) { // Prepare call data for each function slot0CallData, err := poolABI.Pack("slot0") if err != nil { return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack slot0: %w", err) } liquidityCallData, err := poolABI.Pack("liquidity") if err != nil { return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack liquidity: %w", err) } token0CallData, err := poolABI.Pack("token0") if err != nil { return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack token0: %w", err) } token1CallData, err := poolABI.Pack("token1") if err != nil { return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack token1: %w", err) } feeCallData, err := poolABI.Pack("fee") if err != nil { return nil, nil, nil, nil, nil, fmt.Errorf("failed to pack fee: %w", err) } // Create multicall calls calls := []Call3{ {Target: poolAddress, AllowFailure: true, CallData: slot0CallData}, {Target: poolAddress, AllowFailure: true, CallData: liquidityCallData}, {Target: poolAddress, AllowFailure: true, CallData: token0CallData}, {Target: poolAddress, AllowFailure: true, CallData: token1CallData}, {Target: poolAddress, AllowFailure: true, CallData: feeCallData}, } // Execute multicall results, err := m.ExecuteMulticall(ctx, calls) if err != nil { return nil, nil, nil, nil, nil, err } // Extract results if len(results) != 5 { return nil, nil, nil, nil, nil, fmt.Errorf("expected 5 results, got %d", len(results)) } // Return raw data (caller will unpack based on success flags) return results[0].ReturnData, results[1].ReturnData, results[2].ReturnData, results[3].ReturnData, results[4].ReturnData, nil } // BatchMultiplePoolData batches pool data calls for multiple pools func (m *MulticallBatcher) BatchMultiplePoolData(ctx context.Context, pools []common.Address, poolABI abi.ABI) ( results map[common.Address]map[string][]byte, err error, ) { results = make(map[common.Address]map[string][]byte) if len(pools) == 0 { return results, nil } // Prepare all calls var allCalls []Call3 callMap := make(map[int]struct { poolAddr common.Address funcName string }) functions := []string{"slot0", "liquidity", "token0", "token1", "fee"} callIndex := 0 for _, poolAddr := range pools { for _, funcName := range functions { callData, err := poolABI.Pack(funcName) if err != nil { // Skip this call if packing fails continue } allCalls = append(allCalls, Call3{ Target: poolAddr, AllowFailure: true, CallData: callData, }) callMap[callIndex] = struct { poolAddr common.Address funcName string }{poolAddr, funcName} callIndex++ } } // Execute multicall multicallResults, err := m.ExecuteMulticall(ctx, allCalls) if err != nil { return nil, fmt.Errorf("failed to execute multicall: %w", err) } // Parse results for i, result := range multicallResults { if info, ok := callMap[i]; ok { if _, exists := results[info.poolAddr]; !exists { results[info.poolAddr] = make(map[string][]byte) } if result.Success { results[info.poolAddr][info.funcName] = result.ReturnData } } } return results, nil }