249 lines
6.2 KiB
Go
249 lines
6.2 KiB
Go
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
|
|
}
|