feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor: **Structure Changes:** - Moved all V1 code to orig/ folder (preserved with git mv) - Created docs/planning/ directory - Added orig/README_V1.md explaining V1 preservation **Planning Documents:** - 00_V2_MASTER_PLAN.md: Complete architecture overview - Executive summary of critical V1 issues - High-level component architecture diagrams - 5-phase implementation roadmap - Success metrics and risk mitigation - 07_TASK_BREAKDOWN.md: Atomic task breakdown - 99+ hours of detailed tasks - Every task < 2 hours (atomic) - Clear dependencies and success criteria - Organized by implementation phase **V2 Key Improvements:** - Per-exchange parsers (factory pattern) - Multi-layer strict validation - Multi-index pool cache - Background validation pipeline - Comprehensive observability **Critical Issues Addressed:** - Zero address tokens (strict validation + cache enrichment) - Parsing accuracy (protocol-specific parsers) - No audit trail (background validation channel) - Inefficient lookups (multi-index cache) - Stats disconnection (event-driven metrics) Next Steps: 1. Review planning documents 2. Begin Phase 1: Foundation (P1-001 through P1-010) 3. Implement parsers in Phase 2 4. Build cache system in Phase 3 5. Add validation pipeline in Phase 4 6. Migrate and test in Phase 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
493
orig/internal/registry/contract_registry.go
Normal file
493
orig/internal/registry/contract_registry.go
Normal file
@@ -0,0 +1,493 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/contracts"
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
)
|
||||
|
||||
// ContractInfo contains comprehensive information about a contract
|
||||
type ContractInfo struct {
|
||||
Address common.Address
|
||||
Type contracts.ContractType
|
||||
Name string
|
||||
Symbol string
|
||||
Decimals uint8
|
||||
Token0 common.Address // For pools
|
||||
Token1 common.Address // For pools
|
||||
Fee uint32 // For V3 pools (in basis points)
|
||||
Factory common.Address
|
||||
Protocol string
|
||||
IsVerified bool
|
||||
LastUpdated time.Time
|
||||
Confidence float64
|
||||
}
|
||||
|
||||
// ContractRegistry provides authoritative mapping of contract addresses to their types and metadata
|
||||
type ContractRegistry struct {
|
||||
mu sync.RWMutex
|
||||
contracts map[common.Address]*ContractInfo
|
||||
tokensBySymbol map[string]common.Address
|
||||
poolsByTokenPair map[string][]common.Address // "token0:token1" -> pool addresses
|
||||
detector *contracts.ContractDetector
|
||||
logger *logger.Logger
|
||||
updateInterval time.Duration
|
||||
lastFullUpdate time.Time
|
||||
}
|
||||
|
||||
// NewContractRegistry creates a new contract registry
|
||||
func NewContractRegistry(detector *contracts.ContractDetector, logger *logger.Logger) *ContractRegistry {
|
||||
registry := &ContractRegistry{
|
||||
contracts: make(map[common.Address]*ContractInfo),
|
||||
tokensBySymbol: make(map[string]common.Address),
|
||||
poolsByTokenPair: make(map[string][]common.Address),
|
||||
detector: detector,
|
||||
logger: logger,
|
||||
updateInterval: 24 * time.Hour, // Update every 24 hours
|
||||
lastFullUpdate: time.Time{},
|
||||
}
|
||||
|
||||
// Initialize with known Arbitrum contracts
|
||||
registry.initializeKnownContracts()
|
||||
return registry
|
||||
}
|
||||
|
||||
// initializeKnownContracts populates the registry with well-known Arbitrum contracts
|
||||
func (cr *ContractRegistry) initializeKnownContracts() {
|
||||
cr.mu.Lock()
|
||||
defer cr.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// Major ERC-20 tokens on Arbitrum
|
||||
knownTokens := map[common.Address]*ContractInfo{
|
||||
common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"): {
|
||||
Address: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
||||
Type: contracts.ContractTypeERC20Token,
|
||||
Name: "Wrapped Ether",
|
||||
Symbol: "WETH",
|
||||
Decimals: 18,
|
||||
Protocol: "Arbitrum",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"): {
|
||||
Address: common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"),
|
||||
Type: contracts.ContractTypeERC20Token,
|
||||
Name: "USD Coin",
|
||||
Symbol: "USDC",
|
||||
Decimals: 6,
|
||||
Protocol: "Arbitrum",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"): {
|
||||
Address: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
|
||||
Type: contracts.ContractTypeERC20Token,
|
||||
Name: "Tether USD",
|
||||
Symbol: "USDT",
|
||||
Decimals: 6,
|
||||
Protocol: "Arbitrum",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"): {
|
||||
Address: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"),
|
||||
Type: contracts.ContractTypeERC20Token,
|
||||
Name: "Wrapped BTC",
|
||||
Symbol: "WBTC",
|
||||
Decimals: 8,
|
||||
Protocol: "Arbitrum",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"): {
|
||||
Address: common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"),
|
||||
Type: contracts.ContractTypeERC20Token,
|
||||
Name: "Arbitrum",
|
||||
Symbol: "ARB",
|
||||
Decimals: 18,
|
||||
Protocol: "Arbitrum",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
// Major Uniswap V3 pools on Arbitrum
|
||||
knownPools := map[common.Address]*ContractInfo{
|
||||
common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"): {
|
||||
Address: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"),
|
||||
Type: contracts.ContractTypeUniswapV3Pool,
|
||||
Name: "USDC/WETH 0.05%",
|
||||
Token0: common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"), // USDC
|
||||
Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
||||
Fee: 500, // 0.05%
|
||||
Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
Protocol: "UniswapV3",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d"): {
|
||||
Address: common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d"),
|
||||
Type: contracts.ContractTypeUniswapV3Pool,
|
||||
Name: "USDC/WETH 0.3%",
|
||||
Token0: common.HexToAddress("0xA0b86a33E6D8E4BBa6Fd6bD5BA0e2FF8A1e8B8D4"), // USDC
|
||||
Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
||||
Fee: 3000, // 0.3%
|
||||
Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
Protocol: "UniswapV3",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0x2f5e87C9312fa29aed5c179E456625D79015299c"): {
|
||||
Address: common.HexToAddress("0x2f5e87C9312fa29aed5c179E456625D79015299c"),
|
||||
Type: contracts.ContractTypeUniswapV3Pool,
|
||||
Name: "WBTC/WETH 0.05%",
|
||||
Token0: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), // WBTC
|
||||
Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
||||
Fee: 500, // 0.05%
|
||||
Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
Protocol: "UniswapV3",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d"): {
|
||||
Address: common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d"),
|
||||
Type: contracts.ContractTypeUniswapV3Pool,
|
||||
Name: "USDT/WETH 0.05%",
|
||||
Token0: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), // USDT
|
||||
Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
||||
Fee: 500, // 0.05%
|
||||
Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
Protocol: "UniswapV3",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0xFe7D6a84287235C7b4b57C4fEb9a44d4C6Ed3BB8"): {
|
||||
Address: common.HexToAddress("0xFe7D6a84287235C7b4b57C4fEb9a44d4C6Ed3BB8"),
|
||||
Type: contracts.ContractTypeUniswapV3Pool,
|
||||
Name: "ARB/WETH 0.05%",
|
||||
Token0: common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"), // ARB
|
||||
Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
||||
Fee: 500, // 0.05%
|
||||
Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
Protocol: "UniswapV3",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
// Major routers on Arbitrum
|
||||
knownRouters := map[common.Address]*ContractInfo{
|
||||
common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"): {
|
||||
Address: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"),
|
||||
Type: contracts.ContractTypeUniswapV3Router,
|
||||
Name: "Uniswap V3 Router",
|
||||
Protocol: "UniswapV3",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"): {
|
||||
Address: common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"),
|
||||
Type: contracts.ContractTypeUniswapV3Router,
|
||||
Name: "Uniswap V3 Router 2",
|
||||
Protocol: "UniswapV3",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"): {
|
||||
Address: common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"),
|
||||
Type: contracts.ContractTypeUniswapV2Router,
|
||||
Name: "SushiSwap Router",
|
||||
Protocol: "SushiSwap",
|
||||
IsVerified: true,
|
||||
LastUpdated: now,
|
||||
Confidence: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
// Add all known contracts
|
||||
for addr, info := range knownTokens {
|
||||
cr.contracts[addr] = info
|
||||
cr.tokensBySymbol[info.Symbol] = addr
|
||||
}
|
||||
|
||||
for addr, info := range knownPools {
|
||||
cr.contracts[addr] = info
|
||||
// Index pools by token pair
|
||||
if info.Token0 != (common.Address{}) && info.Token1 != (common.Address{}) {
|
||||
pairKey := cr.makeTokenPairKey(info.Token0, info.Token1)
|
||||
cr.poolsByTokenPair[pairKey] = append(cr.poolsByTokenPair[pairKey], addr)
|
||||
}
|
||||
}
|
||||
|
||||
for addr, info := range knownRouters {
|
||||
cr.contracts[addr] = info
|
||||
}
|
||||
|
||||
cr.logger.Info("Contract registry initialized",
|
||||
"tokens", len(knownTokens),
|
||||
"pools", len(knownPools),
|
||||
"routers", len(knownRouters))
|
||||
}
|
||||
|
||||
// GetContractInfo retrieves contract information, using cache first then detection
|
||||
func (cr *ContractRegistry) GetContractInfo(ctx context.Context, address common.Address) (*ContractInfo, error) {
|
||||
// Try cache first
|
||||
if info := cr.getCachedInfo(address); info != nil {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Not in cache, use detector
|
||||
detection := cr.detector.DetectContractType(ctx, address)
|
||||
if detection.Error != nil {
|
||||
return nil, fmt.Errorf("contract detection failed: %w", detection.Error)
|
||||
}
|
||||
|
||||
// Create contract info from detection
|
||||
info := &ContractInfo{
|
||||
Address: address,
|
||||
Type: detection.ContractType,
|
||||
Name: fmt.Sprintf("Unknown %s", detection.ContractType.String()),
|
||||
Protocol: "Unknown",
|
||||
IsVerified: false,
|
||||
LastUpdated: time.Now(),
|
||||
Confidence: detection.Confidence,
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
cr.cacheContractInfo(info)
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// getCachedInfo safely retrieves cached contract info
|
||||
func (cr *ContractRegistry) getCachedInfo(address common.Address) *ContractInfo {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
return cr.contracts[address]
|
||||
}
|
||||
|
||||
// cacheContractInfo safely caches contract info
|
||||
func (cr *ContractRegistry) cacheContractInfo(info *ContractInfo) {
|
||||
cr.mu.Lock()
|
||||
defer cr.mu.Unlock()
|
||||
cr.contracts[info.Address] = info
|
||||
}
|
||||
|
||||
// IsKnownToken checks if an address is a known ERC-20 token
|
||||
func (cr *ContractRegistry) IsKnownToken(address common.Address) bool {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
if info, exists := cr.contracts[address]; exists {
|
||||
return info.Type == contracts.ContractTypeERC20Token
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsKnownPool checks if an address is a known pool
|
||||
func (cr *ContractRegistry) IsKnownPool(address common.Address) bool {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
if info, exists := cr.contracts[address]; exists {
|
||||
return info.Type == contracts.ContractTypeUniswapV2Pool ||
|
||||
info.Type == contracts.ContractTypeUniswapV3Pool
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsKnownRouter checks if an address is a known router
|
||||
func (cr *ContractRegistry) IsKnownRouter(address common.Address) bool {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
if info, exists := cr.contracts[address]; exists {
|
||||
return info.Type == contracts.ContractTypeUniswapV2Router ||
|
||||
info.Type == contracts.ContractTypeUniswapV3Router ||
|
||||
info.Type == contracts.ContractTypeUniversalRouter
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTokenBySymbol retrieves a token address by symbol
|
||||
func (cr *ContractRegistry) GetTokenBySymbol(symbol string) (common.Address, bool) {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
addr, exists := cr.tokensBySymbol[symbol]
|
||||
return addr, exists
|
||||
}
|
||||
|
||||
// GetPoolsForTokenPair retrieves pools for a given token pair
|
||||
func (cr *ContractRegistry) GetPoolsForTokenPair(token0, token1 common.Address) []common.Address {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
pairKey := cr.makeTokenPairKey(token0, token1)
|
||||
return cr.poolsByTokenPair[pairKey]
|
||||
}
|
||||
|
||||
// makeTokenPairKey creates a consistent key for token pairs
|
||||
func (cr *ContractRegistry) makeTokenPairKey(token0, token1 common.Address) string {
|
||||
// Ensure consistent ordering
|
||||
if token0.Big().Cmp(token1.Big()) > 0 {
|
||||
token0, token1 = token1, token0
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", token0.Hex(), token1.Hex())
|
||||
}
|
||||
|
||||
// GetKnownContracts returns all cached contracts
|
||||
func (cr *ContractRegistry) GetKnownContracts() map[common.Address]*ContractInfo {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
result := make(map[common.Address]*ContractInfo)
|
||||
for addr, info := range cr.contracts {
|
||||
result[addr] = info
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// UpdateContractInfo updates contract information (for dynamic discovery)
|
||||
func (cr *ContractRegistry) UpdateContractInfo(info *ContractInfo) {
|
||||
cr.mu.Lock()
|
||||
defer cr.mu.Unlock()
|
||||
|
||||
info.LastUpdated = time.Now()
|
||||
cr.contracts[info.Address] = info
|
||||
|
||||
// Update indexes
|
||||
if info.Type == contracts.ContractTypeERC20Token && info.Symbol != "" {
|
||||
cr.tokensBySymbol[info.Symbol] = info.Address
|
||||
}
|
||||
|
||||
if (info.Type == contracts.ContractTypeUniswapV2Pool || info.Type == contracts.ContractTypeUniswapV3Pool) &&
|
||||
info.Token0 != (common.Address{}) && info.Token1 != (common.Address{}) {
|
||||
pairKey := cr.makeTokenPairKey(info.Token0, info.Token1)
|
||||
// Check if already exists in slice
|
||||
pools := cr.poolsByTokenPair[pairKey]
|
||||
exists := false
|
||||
for _, pool := range pools {
|
||||
if pool == info.Address {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
cr.poolsByTokenPair[pairKey] = append(pools, info.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetCacheStats returns statistics about the cached contracts
|
||||
func (cr *ContractRegistry) GetCacheStats() map[string]int {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
stats := map[string]int{
|
||||
"total": len(cr.contracts),
|
||||
"tokens": 0,
|
||||
"v2_pools": 0,
|
||||
"v3_pools": 0,
|
||||
"routers": 0,
|
||||
"verified": 0,
|
||||
}
|
||||
|
||||
for _, info := range cr.contracts {
|
||||
switch info.Type {
|
||||
case contracts.ContractTypeERC20Token:
|
||||
stats["tokens"]++
|
||||
case contracts.ContractTypeUniswapV2Pool:
|
||||
stats["v2_pools"]++
|
||||
case contracts.ContractTypeUniswapV3Pool:
|
||||
stats["v3_pools"]++
|
||||
case contracts.ContractTypeUniswapV2Router, contracts.ContractTypeUniswapV3Router:
|
||||
stats["routers"]++
|
||||
}
|
||||
|
||||
if info.IsVerified {
|
||||
stats["verified"]++
|
||||
}
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// GetPoolInfo returns pool information for a given address
|
||||
func (cr *ContractRegistry) GetPoolInfo(address common.Address) *ContractInfo {
|
||||
cr.mu.RLock()
|
||||
defer cr.mu.RUnlock()
|
||||
|
||||
if info, exists := cr.contracts[address]; exists {
|
||||
if info.Type == contracts.ContractTypeUniswapV2Pool || info.Type == contracts.ContractTypeUniswapV3Pool {
|
||||
return info
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPool adds a pool to the registry
|
||||
func (cr *ContractRegistry) AddPool(poolAddress, token0, token1 common.Address, protocol string) {
|
||||
cr.mu.Lock()
|
||||
defer cr.mu.Unlock()
|
||||
|
||||
// Determine pool type based on protocol
|
||||
var poolType contracts.ContractType
|
||||
switch protocol {
|
||||
case "UniswapV2":
|
||||
poolType = contracts.ContractTypeUniswapV2Pool
|
||||
case "UniswapV3":
|
||||
poolType = contracts.ContractTypeUniswapV3Pool
|
||||
default:
|
||||
poolType = contracts.ContractTypeUniswapV2Pool // Default to V2
|
||||
}
|
||||
|
||||
// Add or update pool info
|
||||
info := &ContractInfo{
|
||||
Address: poolAddress,
|
||||
Type: poolType,
|
||||
Token0: token0,
|
||||
Token1: token1,
|
||||
Protocol: protocol,
|
||||
IsVerified: false, // Runtime discovered pools are not pre-verified
|
||||
LastUpdated: time.Now(),
|
||||
Confidence: 0.8, // High confidence for runtime discovered pools
|
||||
}
|
||||
|
||||
cr.contracts[poolAddress] = info
|
||||
|
||||
// Update token pair mapping
|
||||
pairKey := cr.makeTokenPairKey(token0, token1)
|
||||
if pools, exists := cr.poolsByTokenPair[pairKey]; exists {
|
||||
// Check if pool already exists in the list
|
||||
for _, existingPool := range pools {
|
||||
if existingPool == poolAddress {
|
||||
return // Already exists
|
||||
}
|
||||
}
|
||||
cr.poolsByTokenPair[pairKey] = append(pools, poolAddress)
|
||||
} else {
|
||||
cr.poolsByTokenPair[pairKey] = []common.Address{poolAddress}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user