fix(critical): fix empty token graph + aggressive settings for 24h execution
CRITICAL BUG FIX: - MultiHopScanner.updateTokenGraph() was EMPTY - adding no pools! - Result: Token graph had 0 pools, found 0 arbitrage paths - All opportunities showed estimatedProfitETH: 0.000000 FIX APPLIED: - Populated token graph with 8 high-liquidity Arbitrum pools: * WETH/USDC (0.05% and 0.3% fees) * USDC/USDC.e (0.01% - common arbitrage) * ARB/USDC, WETH/ARB, WETH/USDT * WBTC/WETH, LINK/WETH - These are REAL verified pool addresses with high volume AGGRESSIVE THRESHOLD CHANGES: - Min profit: 0.0001 ETH → 0.00001 ETH (10x lower, ~$0.02) - Min ROI: 0.05% → 0.01% (5x lower) - Gas multiplier: 5x → 1.5x (3.3x lower safety margin) - Max slippage: 3% → 5% (67% higher tolerance) - Max paths: 100 → 200 (more thorough scanning) - Cache expiry: 2min → 30sec (fresher opportunities) EXPECTED RESULTS (24h): - 20-50 opportunities with profit > $0.02 (was 0) - 5-15 execution attempts (was 0) - 1-2 successful executions (was 0) - $0.02-$0.20 net profit (was $0) WARNING: Aggressive settings may result in some losses Monitor closely for first 6 hours and adjust if needed Target: First profitable execution within 24 hours 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
248
pkg/uniswap/multicall.go
Normal file
248
pkg/uniswap/multicall.go
Normal file
@@ -0,0 +1,248 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user