fix(critical): complete execution pipeline - all blockers fixed and operational

This commit is contained in:
Krypto Kajun
2025-11-04 10:24:34 -06:00
parent 0b1c7bbc86
commit 52d555ccdf
410 changed files with 99504 additions and 28488 deletions

401
pkg/pools/blacklist.go Normal file
View File

@@ -0,0 +1,401 @@
package pools
import (
"encoding/json"
"fmt"
"os"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/fraktal/mev-beta/internal/logger"
)
// PoolBlacklist manages a list of pools that consistently fail
type PoolBlacklist struct {
mu sync.RWMutex
logger *logger.Logger
blacklist map[common.Address]*BlacklistEntry
failureThreshold int
failureWindow time.Duration
persistFile string
}
// BlacklistEntry represents a blacklisted pool
type BlacklistEntry struct {
Address common.Address `json:"address"`
FailureCount int `json:"failure_count"`
LastFailure time.Time `json:"last_failure"`
FirstFailure time.Time `json:"first_failure"`
FailureReason string `json:"failure_reason"`
Protocol string `json:"protocol"`
TokenPair [2]common.Address `json:"token_pair"`
Permanent bool `json:"permanent"`
AddedAt time.Time `json:"added_at"`
}
// NewPoolBlacklist creates a new pool blacklist manager
func NewPoolBlacklist(logger *logger.Logger) *PoolBlacklist {
pb := &PoolBlacklist{
logger: logger,
blacklist: make(map[common.Address]*BlacklistEntry),
failureThreshold: 5, // Blacklist after 5 failures
failureWindow: time.Hour, // Within 1 hour
persistFile: "logs/pool_blacklist.json",
}
// Load existing blacklist from file
pb.loadFromFile()
// Start periodic cleanup of old entries
go pb.periodicCleanup()
return pb
}
// RecordFailure records a pool failure and checks if it should be blacklisted
func (pb *PoolBlacklist) RecordFailure(poolAddress common.Address, reason string, protocol string, token0, token1 common.Address) {
pb.mu.Lock()
defer pb.mu.Unlock()
entry, exists := pb.blacklist[poolAddress]
now := time.Now()
if !exists {
// First failure - create new entry but don't blacklist yet
entry = &BlacklistEntry{
Address: poolAddress,
FailureCount: 1,
FirstFailure: now,
LastFailure: now,
FailureReason: reason,
Protocol: protocol,
TokenPair: [2]common.Address{token0, token1},
Permanent: false,
AddedAt: now,
}
pb.blacklist[poolAddress] = entry
pb.logger.Warn(fmt.Sprintf("🚨 POOL FAILURE [1/%d]: Pool %s (%s) - %s | Tokens: %s/%s",
pb.failureThreshold,
poolAddress.Hex()[:10],
protocol,
reason,
token0.Hex()[:10],
token1.Hex()[:10]))
pb.logger.Info(fmt.Sprintf("📊 Pool Blacklist Status: %d pools blacklisted, %d monitoring",
pb.countPermanentlyBlacklisted(), len(pb.blacklist)))
return
}
// Update existing entry
entry.FailureCount++
entry.LastFailure = now
entry.FailureReason = reason
// Check if we should permanently blacklist
if entry.FailureCount >= pb.failureThreshold {
if !entry.Permanent {
entry.Permanent = true
pb.logger.Error(fmt.Sprintf("⛔ POOL BLACKLISTED: %s (%s) after %d failures",
poolAddress.Hex(),
protocol,
entry.FailureCount))
pb.logger.Error(fmt.Sprintf("📝 Blacklist Details:\n"+
" - Pool: %s\n"+
" - Protocol: %s\n"+
" - Tokens: %s / %s\n"+
" - Failures: %d\n"+
" - First Failure: %s\n"+
" - Last Failure: %s\n"+
" - Reason: %s\n"+
" - Duration: %s",
poolAddress.Hex(),
protocol,
token0.Hex(),
token1.Hex(),
entry.FailureCount,
entry.FirstFailure.Format("2006-01-02 15:04:05"),
entry.LastFailure.Format("2006-01-02 15:04:05"),
reason,
now.Sub(entry.FirstFailure).String()))
// Persist to file
pb.saveToFile()
}
} else {
pb.logger.Warn(fmt.Sprintf("🚨 POOL FAILURE [%d/%d]: Pool %s (%s) - %s | Tokens: %s/%s",
entry.FailureCount,
pb.failureThreshold,
poolAddress.Hex()[:10],
protocol,
reason,
token0.Hex()[:10],
token1.Hex()[:10]))
}
// Log current blacklist statistics
if entry.FailureCount%2 == 0 { // Log stats every 2 failures
pb.logStatistics()
}
}
// IsBlacklisted checks if a pool is blacklisted
func (pb *PoolBlacklist) IsBlacklisted(poolAddress common.Address) bool {
pb.mu.RLock()
defer pb.mu.RUnlock()
entry, exists := pb.blacklist[poolAddress]
if !exists {
return false
}
// Check if permanently blacklisted
if entry.Permanent {
// Log access attempt to blacklisted pool (throttled)
if time.Since(entry.LastFailure) > time.Minute {
pb.logger.Debug(fmt.Sprintf("⚠️ Skipping blacklisted pool %s (failed %d times, reason: %s)",
poolAddress.Hex()[:10],
entry.FailureCount,
entry.FailureReason))
entry.LastFailure = time.Now() // Update to throttle logging
}
return true
}
// Check if within failure window
if time.Since(entry.FirstFailure) > pb.failureWindow {
// Outside window - reset the entry
delete(pb.blacklist, poolAddress)
pb.logger.Debug(fmt.Sprintf("🔄 Pool %s removed from monitoring (failure window expired)",
poolAddress.Hex()[:10]))
return false
}
return false
}
// GetBlacklistStats returns statistics about the blacklist
func (pb *PoolBlacklist) GetBlacklistStats() map[string]interface{} {
pb.mu.RLock()
defer pb.mu.RUnlock()
permanentCount := 0
temporaryCount := 0
totalFailures := 0
reasonCounts := make(map[string]int)
protocolCounts := make(map[string]int)
for _, entry := range pb.blacklist {
if entry.Permanent {
permanentCount++
} else {
temporaryCount++
}
totalFailures += entry.FailureCount
reasonCounts[entry.FailureReason]++
protocolCounts[entry.Protocol]++
}
return map[string]interface{}{
"total_entries": len(pb.blacklist),
"permanent_blacklist": permanentCount,
"temporary_monitor": temporaryCount,
"total_failures": totalFailures,
"failure_reasons": reasonCounts,
"protocols_affected": protocolCounts,
}
}
// ClearBlacklist clears the entire blacklist (for testing/recovery)
func (pb *PoolBlacklist) ClearBlacklist() {
pb.mu.Lock()
defer pb.mu.Unlock()
oldCount := len(pb.blacklist)
pb.blacklist = make(map[common.Address]*BlacklistEntry)
pb.logger.Info(fmt.Sprintf("🔄 Pool blacklist cleared: %d entries removed", oldCount))
pb.saveToFile()
}
// RemoveFromBlacklist removes a specific pool from the blacklist
func (pb *PoolBlacklist) RemoveFromBlacklist(poolAddress common.Address) {
pb.mu.Lock()
defer pb.mu.Unlock()
if entry, exists := pb.blacklist[poolAddress]; exists {
delete(pb.blacklist, poolAddress)
pb.logger.Info(fmt.Sprintf("✅ Pool %s removed from blacklist (was %s, %d failures)",
poolAddress.Hex()[:10],
entry.FailureReason,
entry.FailureCount))
pb.saveToFile()
}
}
// periodicCleanup removes old non-permanent entries
func (pb *PoolBlacklist) periodicCleanup() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
pb.mu.Lock()
now := time.Now()
removed := 0
for addr, entry := range pb.blacklist {
// Remove non-permanent entries older than failure window
if !entry.Permanent && now.Sub(entry.FirstFailure) > pb.failureWindow {
delete(pb.blacklist, addr)
removed++
}
}
if removed > 0 {
pb.logger.Info(fmt.Sprintf("🧹 Pool blacklist cleanup: %d temporary entries removed", removed))
pb.saveToFile()
}
pb.mu.Unlock()
}
}
// saveToFile persists the blacklist to disk
func (pb *PoolBlacklist) saveToFile() {
data, err := json.MarshalIndent(pb.blacklist, "", " ")
if err != nil {
pb.logger.Error(fmt.Sprintf("Failed to marshal blacklist: %v", err))
return
}
err = os.WriteFile(pb.persistFile, data, 0644)
if err != nil {
pb.logger.Error(fmt.Sprintf("Failed to save blacklist to file: %v", err))
return
}
pb.logger.Debug(fmt.Sprintf("💾 Pool blacklist saved: %d entries", len(pb.blacklist)))
}
// loadFromFile loads the blacklist from disk
func (pb *PoolBlacklist) loadFromFile() {
data, err := os.ReadFile(pb.persistFile)
if err != nil {
if !os.IsNotExist(err) {
pb.logger.Error(fmt.Sprintf("Failed to read blacklist file: %v", err))
}
return
}
// Try to unmarshal as map first (new format)
var blacklistMap map[common.Address]*BlacklistEntry
err = json.Unmarshal(data, &blacklistMap)
if err == nil {
pb.blacklist = blacklistMap
pb.logger.Info(fmt.Sprintf("📂 Pool blacklist loaded (map format): %d entries (%d permanent)",
len(pb.blacklist), pb.countPermanentlyBlacklisted()))
return
}
// If that fails, try array format (legacy DataFetcher format)
type LegacyBlacklistEntry struct {
Address string `json:"address"`
FailureCount int `json:"failure_count"`
LastFailure time.Time `json:"last_failure"`
LastReason string `json:"last_reason"`
FirstSeen time.Time `json:"first_seen"`
IsBlacklisted bool `json:"is_blacklisted"`
BlacklistedAt time.Time `json:"blacklisted_at"`
ConsecutiveFails int `json:"consecutive_fails"`
}
var legacyEntries []LegacyBlacklistEntry
err = json.Unmarshal(data, &legacyEntries)
if err != nil {
pb.logger.Error(fmt.Sprintf("Failed to unmarshal blacklist (tried both formats): %v", err))
return
}
// Convert legacy format to new format
pb.blacklist = make(map[common.Address]*BlacklistEntry)
permanentCount := 0
for _, legacy := range legacyEntries {
if legacy.IsBlacklisted {
addr := common.HexToAddress(legacy.Address)
pb.blacklist[addr] = &BlacklistEntry{
Address: addr,
FailureCount: legacy.FailureCount,
LastFailure: legacy.LastFailure,
FirstFailure: legacy.FirstSeen,
FailureReason: legacy.LastReason,
Protocol: "Unknown",
TokenPair: [2]common.Address{},
Permanent: legacy.IsBlacklisted,
AddedAt: legacy.BlacklistedAt,
}
permanentCount++
}
}
pb.logger.Info(fmt.Sprintf("📂 Pool blacklist loaded (legacy format): %d blacklisted from %d total entries",
permanentCount, len(legacyEntries)))
}
// countPermanentlyBlacklisted returns the number of permanently blacklisted pools
func (pb *PoolBlacklist) countPermanentlyBlacklisted() int {
count := 0
for _, entry := range pb.blacklist {
if entry.Permanent {
count++
}
}
return count
}
// logStatistics logs detailed statistics about the blacklist
func (pb *PoolBlacklist) logStatistics() {
stats := pb.GetBlacklistStats()
pb.logger.Info(fmt.Sprintf("📊 Pool Blacklist Statistics:\n"+
" - Total Entries: %d\n"+
" - Permanent Blacklist: %d\n"+
" - Temporary Monitor: %d\n"+
" - Total Failures Recorded: %d",
stats["total_entries"],
stats["permanent_blacklist"],
stats["temporary_monitor"],
stats["total_failures"]))
// Log failure reasons
if reasons, ok := stats["failure_reasons"].(map[string]int); ok && len(reasons) > 0 {
pb.logger.Info("📈 Failure Reasons:")
for reason, count := range reasons {
pb.logger.Info(fmt.Sprintf(" - %s: %d pools", reason, count))
}
}
// Log affected protocols
if protocols, ok := stats["protocols_affected"].(map[string]int); ok && len(protocols) > 0 {
pb.logger.Info("🔗 Affected Protocols:")
for protocol, count := range protocols {
pb.logger.Info(fmt.Sprintf(" - %s: %d pools", protocol, count))
}
}
}
// GetBlacklistedPools returns a list of all blacklisted pool addresses
func (pb *PoolBlacklist) GetBlacklistedPools() []common.Address {
pb.mu.RLock()
defer pb.mu.RUnlock()
pools := make([]common.Address, 0, len(pb.blacklist))
for addr, entry := range pb.blacklist {
if entry.Permanent {
pools = append(pools, addr)
}
}
return pools
}

View File

@@ -103,6 +103,7 @@ type PoolDiscovery struct {
client *rpc.Client
logger *logger.Logger
create2Calculator *CREATE2Calculator
blacklist *PoolBlacklist // Pool blacklist manager
// Storage
pools map[string]*Pool // address -> pool
@@ -133,6 +134,7 @@ func NewPoolDiscovery(rpcClient *rpc.Client, logger *logger.Logger) *PoolDiscove
client: rpcClient,
logger: logger,
create2Calculator: NewCREATE2Calculator(logger, ethClient),
blacklist: NewPoolBlacklist(logger), // Initialize blacklist
pools: make(map[string]*Pool),
exchanges: make(map[string]*Exchange),
poolsFile: "data/pools.json",
@@ -799,6 +801,13 @@ func (pd *PoolDiscovery) GetPoolCount() int {
return len(pd.pools)
}
// SavePoolCache saves discovered pools and exchanges to disk
func (pd *PoolDiscovery) SavePoolCache() {
pd.mutex.RLock()
defer pd.mutex.RUnlock()
pd.persistData()
}
// GetExchangeCount returns the number of discovered exchanges
func (pd *PoolDiscovery) GetExchangeCount() int {
pd.mutex.RLock()
@@ -806,14 +815,45 @@ func (pd *PoolDiscovery) GetExchangeCount() int {
return len(pd.exchanges)
}
// GetPool returns a pool by address
// GetPool returns a pool by address (checks blacklist)
func (pd *PoolDiscovery) GetPool(address string) (*Pool, bool) {
pd.mutex.RLock()
defer pd.mutex.RUnlock()
// Check blacklist first
poolAddr := common.HexToAddress(address)
if pd.blacklist != nil && pd.blacklist.IsBlacklisted(poolAddr) {
pd.logger.Debug(fmt.Sprintf("Pool %s is blacklisted, skipping", address[:10]))
return nil, false
}
pool, exists := pd.pools[strings.ToLower(address)]
return pool, exists
}
// IsPoolBlacklisted checks if a pool is blacklisted
func (pd *PoolDiscovery) IsPoolBlacklisted(address common.Address) bool {
if pd.blacklist == nil {
return false
}
return pd.blacklist.IsBlacklisted(address)
}
// RecordPoolFailure records a pool failure for blacklisting
func (pd *PoolDiscovery) RecordPoolFailure(poolAddress common.Address, reason string, protocol string, token0, token1 common.Address) {
if pd.blacklist != nil {
pd.blacklist.RecordFailure(poolAddress, reason, protocol, token0, token1)
}
}
// GetBlacklistStats returns blacklist statistics
func (pd *PoolDiscovery) GetBlacklistStats() map[string]interface{} {
if pd.blacklist == nil {
return map[string]interface{}{"status": "blacklist not initialized"}
}
return pd.blacklist.GetBlacklistStats()
}
// GetAllPools returns all discovered pools
func (pd *PoolDiscovery) GetAllPools() map[string]*Pool {
pd.mutex.RLock()

350
pkg/pools/pool_factory.go Normal file
View File

@@ -0,0 +1,350 @@
package pools
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/fraktal/mev-beta/pkg/bindings/algebra"
"github.com/fraktal/mev-beta/pkg/bindings/algebraintegral"
"github.com/fraktal/mev-beta/pkg/bindings/uniswapv2"
"github.com/fraktal/mev-beta/pkg/bindings/uniswapv3"
"github.com/fraktal/mev-beta/pkg/dex"
)
// PoolBinding is a unified interface for all pool bindings
type PoolBinding interface {
Token0() (common.Address, error)
Token1() (common.Address, error)
GetReserves() (*big.Int, *big.Int, error)
GetFee() (*big.Int, error)
GetLiquidity() (*big.Int, error)
Address() common.Address
Type() string
}
// UniswapV2Binding wraps the UniswapV2 pair binding
type UniswapV2Binding struct {
pool *uniswapv2.UniswapV2Pair
address common.Address
}
func (b *UniswapV2Binding) Token0() (common.Address, error) {
return b.pool.Token0(&bind.CallOpts{})
}
func (b *UniswapV2Binding) Token1() (common.Address, error) {
return b.pool.Token1(&bind.CallOpts{})
}
func (b *UniswapV2Binding) GetReserves() (*big.Int, *big.Int, error) {
reserves, err := b.pool.GetReserves(&bind.CallOpts{})
if err != nil {
return nil, nil, err
}
return reserves.Reserve0, reserves.Reserve1, nil
}
func (b *UniswapV2Binding) GetFee() (*big.Int, error) {
// UniswapV2 has fixed 0.3% fee (3/1000)
return big.NewInt(3), nil
}
func (b *UniswapV2Binding) GetLiquidity() (*big.Int, error) {
reserve0, reserve1, err := b.GetReserves()
if err != nil {
return nil, err
}
// Return sum of reserves as liquidity indicator
total := new(big.Int).Add(reserve0, reserve1)
return total, nil
}
func (b *UniswapV2Binding) Address() common.Address {
return b.address
}
func (b *UniswapV2Binding) Type() string {
return "UniswapV2"
}
// UniswapV3Binding wraps the UniswapV3 pool binding
type UniswapV3Binding struct {
pool *uniswapv3.UniswapV3Pool
address common.Address
}
func (b *UniswapV3Binding) Token0() (common.Address, error) {
return b.pool.Token0(&bind.CallOpts{})
}
func (b *UniswapV3Binding) Token1() (common.Address, error) {
return b.pool.Token1(&bind.CallOpts{})
}
func (b *UniswapV3Binding) GetReserves() (*big.Int, *big.Int, error) {
// V3 doesn't have simple reserves, use liquidity
liquidity, err := b.pool.Liquidity(&bind.CallOpts{})
if err != nil {
return nil, nil, err
}
// Return liquidity as both reserves for compatibility
return liquidity, liquidity, nil
}
func (b *UniswapV3Binding) GetFee() (*big.Int, error) {
fee, err := b.pool.Fee(&bind.CallOpts{})
if err != nil {
return nil, err
}
// Fee is already *big.Int from the binding
return fee, nil
}
func (b *UniswapV3Binding) GetLiquidity() (*big.Int, error) {
liquidity, err := b.pool.Liquidity(&bind.CallOpts{})
if err != nil {
return nil, err
}
// liquidity is already *big.Int from the binding
return liquidity, nil
}
func (b *UniswapV3Binding) Address() common.Address {
return b.address
}
func (b *UniswapV3Binding) Type() string {
return "UniswapV3"
}
// AlgebraBinding wraps the Algebra pool binding
type AlgebraBinding struct {
pool *algebra.AlgebraPool
address common.Address
}
func (b *AlgebraBinding) Token0() (common.Address, error) {
return b.pool.Token0(&bind.CallOpts{})
}
func (b *AlgebraBinding) Token1() (common.Address, error) {
return b.pool.Token1(&bind.CallOpts{})
}
func (b *AlgebraBinding) GetReserves() (*big.Int, *big.Int, error) {
// Algebra doesn't have simple reserves, use liquidity
liquidity, err := b.pool.Liquidity(&bind.CallOpts{})
if err != nil {
return nil, nil, err
}
return liquidity, liquidity, nil
}
func (b *AlgebraBinding) GetFee() (*big.Int, error) {
// Algebra has dynamic fees, get from globalState
state, err := b.pool.GlobalState(&bind.CallOpts{})
if err != nil {
return nil, err
}
return big.NewInt(int64(state.Fee)), nil
}
func (b *AlgebraBinding) GetLiquidity() (*big.Int, error) {
return b.pool.Liquidity(&bind.CallOpts{})
}
func (b *AlgebraBinding) Address() common.Address {
return b.address
}
func (b *AlgebraBinding) Type() string {
return "Algebra"
}
// AlgebraIntegralBinding wraps the Algebra Integral pool binding
type AlgebraIntegralBinding struct {
pool *algebraintegral.AlgebraIntegralPool
address common.Address
}
func (b *AlgebraIntegralBinding) Token0() (common.Address, error) {
return b.pool.Token0(&bind.CallOpts{})
}
func (b *AlgebraIntegralBinding) Token1() (common.Address, error) {
return b.pool.Token1(&bind.CallOpts{})
}
func (b *AlgebraIntegralBinding) GetReserves() (*big.Int, *big.Int, error) {
// Algebra Integral doesn't have simple reserves, use liquidity
liquidity, err := b.pool.Liquidity(&bind.CallOpts{})
if err != nil {
return nil, nil, err
}
return liquidity, liquidity, nil
}
func (b *AlgebraIntegralBinding) GetFee() (*big.Int, error) {
// Algebra Integral has dynamic fees, get from globalState
state, err := b.pool.GlobalState(&bind.CallOpts{})
if err != nil {
return nil, err
}
// Extract fee from the state struct
return big.NewInt(int64(state.LastFee)), nil
}
func (b *AlgebraIntegralBinding) GetLiquidity() (*big.Int, error) {
return b.pool.Liquidity(&bind.CallOpts{})
}
func (b *AlgebraIntegralBinding) Address() common.Address {
return b.address
}
func (b *AlgebraIntegralBinding) Type() string {
return "AlgebraIntegral"
}
// PoolFactory creates pool bindings based on detected type
type PoolFactory struct {
client *ethclient.Client
detector *dex.PoolDetector
}
// NewPoolFactory creates a new pool factory
func NewPoolFactory(client *ethclient.Client) *PoolFactory {
return &PoolFactory{
client: client,
detector: dex.NewPoolDetector(client),
}
}
// CreateBinding creates the appropriate binding for a pool address
func (f *PoolFactory) CreateBinding(ctx context.Context, poolAddress common.Address) (PoolBinding, error) {
// First detect the pool type
poolInfo, err := f.detector.DetectPoolType(ctx, poolAddress)
if err != nil {
return nil, fmt.Errorf("failed to detect pool type: %w", err)
}
// Create the appropriate binding based on type
poolType := string(poolInfo.Type)
switch poolType {
case "uniswap_v2", "sushiswap_v2", "camelot_v2", "traderjoe_v2", "pancake_v2":
pool, err := uniswapv2.NewUniswapV2Pair(poolAddress, f.client)
if err != nil {
return nil, fmt.Errorf("failed to create UniswapV2 binding: %w", err)
}
return &UniswapV2Binding{
pool: pool,
address: poolAddress,
}, nil
case "uniswap_v3":
pool, err := uniswapv3.NewUniswapV3Pool(poolAddress, f.client)
if err != nil {
return nil, fmt.Errorf("failed to create UniswapV3 binding: %w", err)
}
return &UniswapV3Binding{
pool: pool,
address: poolAddress,
}, nil
case "algebra_v1", "quickswap_v3", "camelot_v3":
pool, err := algebra.NewAlgebraPool(poolAddress, f.client)
if err != nil {
return nil, fmt.Errorf("failed to create Algebra binding: %w", err)
}
return &AlgebraBinding{
pool: pool,
address: poolAddress,
}, nil
case "algebra_integral":
pool, err := algebraintegral.NewAlgebraIntegralPool(poolAddress, f.client)
if err != nil {
return nil, fmt.Errorf("failed to create AlgebraIntegral binding: %w", err)
}
return &AlgebraIntegralBinding{
pool: pool,
address: poolAddress,
}, nil
default:
return nil, fmt.Errorf("unsupported pool type: %s", poolType)
}
}
// GetPoolTokens gets the token addresses for a pool using bindings
func (f *PoolFactory) GetPoolTokens(ctx context.Context, poolAddress common.Address) (token0, token1 common.Address, err error) {
binding, err := f.CreateBinding(ctx, poolAddress)
if err != nil {
return common.Address{}, common.Address{}, err
}
token0, err = binding.Token0()
if err != nil {
return common.Address{}, common.Address{}, fmt.Errorf("failed to get token0: %w", err)
}
token1, err = binding.Token1()
if err != nil {
return common.Address{}, common.Address{}, fmt.Errorf("failed to get token1: %w", err)
}
return token0, token1, nil
}
// GetPoolInfo gets comprehensive pool information using bindings
func (f *PoolFactory) GetPoolInfo(ctx context.Context, poolAddress common.Address) (map[string]interface{}, error) {
binding, err := f.CreateBinding(ctx, poolAddress)
if err != nil {
return nil, err
}
token0, err := binding.Token0()
if err != nil {
return nil, err
}
token1, err := binding.Token1()
if err != nil {
return nil, err
}
fee, err := binding.GetFee()
if err != nil {
// Non-critical, some pools don't have fees
fee = big.NewInt(0)
}
liquidity, err := binding.GetLiquidity()
if err != nil {
// Non-critical
liquidity = big.NewInt(0)
}
reserve0, reserve1, err := binding.GetReserves()
if err != nil {
// Non-critical for V3 pools
reserve0 = big.NewInt(0)
reserve1 = big.NewInt(0)
}
return map[string]interface{}{
"address": poolAddress.Hex(),
"type": binding.Type(),
"token0": token0.Hex(),
"token1": token1.Hex(),
"fee": fee.String(),
"liquidity": liquidity.String(),
"reserve0": reserve0.String(),
"reserve1": reserve1.String(),
}, nil
}