fix: resolve all compilation issues across transport and lifecycle packages
- Fixed duplicate type declarations in transport package - Removed unused variables in lifecycle and dependency injection - Fixed big.Int arithmetic operations in uniswap contracts - Added missing methods to MetricsCollector (IncrementCounter, RecordLatency, etc.) - Fixed jitter calculation in TCP transport retry logic - Updated ComponentHealth field access to use transport type - Ensured all core packages build successfully All major compilation errors resolved: ✅ Transport package builds clean ✅ Lifecycle package builds clean ✅ Main MEV bot application builds clean ✅ Fixed method signature mismatches ✅ Resolved type conflicts and duplications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
542
pkg/arbitrum/token_metadata.go
Normal file
542
pkg/arbitrum/token_metadata.go
Normal file
@@ -0,0 +1,542 @@
|
||||
package arbitrum
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
)
|
||||
|
||||
// TokenMetadata contains comprehensive token information
|
||||
type TokenMetadata struct {
|
||||
Address common.Address `json:"address"`
|
||||
Symbol string `json:"symbol"`
|
||||
Name string `json:"name"`
|
||||
Decimals uint8 `json:"decimals"`
|
||||
TotalSupply *big.Int `json:"totalSupply"`
|
||||
IsStablecoin bool `json:"isStablecoin"`
|
||||
IsWrapped bool `json:"isWrapped"`
|
||||
Category string `json:"category"` // "blue-chip", "defi", "meme", "unknown"
|
||||
|
||||
// Price information
|
||||
PriceUSD float64 `json:"priceUSD"`
|
||||
PriceETH float64 `json:"priceETH"`
|
||||
LastUpdated time.Time `json:"lastUpdated"`
|
||||
|
||||
// Liquidity information
|
||||
TotalLiquidityUSD float64 `json:"totalLiquidityUSD"`
|
||||
MainPool common.Address `json:"mainPool"`
|
||||
|
||||
// Risk assessment
|
||||
RiskScore float64 `json:"riskScore"` // 0.0 (safe) to 1.0 (high risk)
|
||||
IsVerified bool `json:"isVerified"`
|
||||
|
||||
// Technical details
|
||||
ContractVerified bool `json:"contractVerified"`
|
||||
Implementation common.Address `json:"implementation"` // For proxy contracts
|
||||
}
|
||||
|
||||
// TokenMetadataService manages token metadata extraction and caching
|
||||
type TokenMetadataService struct {
|
||||
client *ethclient.Client
|
||||
logger *logger.Logger
|
||||
|
||||
// Caching
|
||||
cache map[common.Address]*TokenMetadata
|
||||
cacheMu sync.RWMutex
|
||||
cacheTTL time.Duration
|
||||
|
||||
// Known tokens registry
|
||||
knownTokens map[common.Address]*TokenMetadata
|
||||
|
||||
// Contract ABIs
|
||||
erc20ABI string
|
||||
proxyABI string
|
||||
}
|
||||
|
||||
// NewTokenMetadataService creates a new token metadata service
|
||||
func NewTokenMetadataService(client *ethclient.Client, logger *logger.Logger) *TokenMetadataService {
|
||||
service := &TokenMetadataService{
|
||||
client: client,
|
||||
logger: logger,
|
||||
cache: make(map[common.Address]*TokenMetadata),
|
||||
cacheTTL: 1 * time.Hour,
|
||||
knownTokens: getKnownArbitrumTokens(),
|
||||
erc20ABI: getERC20ABI(),
|
||||
proxyABI: getProxyABI(),
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
// GetTokenMetadata retrieves comprehensive metadata for a token
|
||||
func (s *TokenMetadataService) GetTokenMetadata(ctx context.Context, tokenAddr common.Address) (*TokenMetadata, error) {
|
||||
// Check cache first
|
||||
if cached := s.getCachedMetadata(tokenAddr); cached != nil {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
// Check known tokens registry
|
||||
if known, exists := s.knownTokens[tokenAddr]; exists {
|
||||
s.cacheMetadata(tokenAddr, known)
|
||||
return known, nil
|
||||
}
|
||||
|
||||
// Extract metadata from contract
|
||||
metadata, err := s.extractMetadataFromContract(ctx, tokenAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract token metadata: %w", err)
|
||||
}
|
||||
|
||||
// Enhance with additional data
|
||||
if err := s.enhanceMetadata(ctx, metadata); err != nil {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to enhance metadata for %s: %v", tokenAddr.Hex(), err))
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
s.cacheMetadata(tokenAddr, metadata)
|
||||
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// extractMetadataFromContract extracts basic ERC20 metadata from the contract
|
||||
func (s *TokenMetadataService) extractMetadataFromContract(ctx context.Context, tokenAddr common.Address) (*TokenMetadata, error) {
|
||||
contractABI, err := abi.JSON(strings.NewReader(s.erc20ABI))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse ERC20 ABI: %w", err)
|
||||
}
|
||||
|
||||
metadata := &TokenMetadata{
|
||||
Address: tokenAddr,
|
||||
LastUpdated: time.Now(),
|
||||
}
|
||||
|
||||
// Get symbol
|
||||
if symbol, err := s.callStringMethod(ctx, tokenAddr, contractABI, "symbol"); err == nil {
|
||||
metadata.Symbol = symbol
|
||||
} else {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to get symbol for %s: %v", tokenAddr.Hex(), err))
|
||||
metadata.Symbol = "UNKNOWN"
|
||||
}
|
||||
|
||||
// Get name
|
||||
if name, err := s.callStringMethod(ctx, tokenAddr, contractABI, "name"); err == nil {
|
||||
metadata.Name = name
|
||||
} else {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to get name for %s: %v", tokenAddr.Hex(), err))
|
||||
metadata.Name = "Unknown Token"
|
||||
}
|
||||
|
||||
// Get decimals
|
||||
if decimals, err := s.callUint8Method(ctx, tokenAddr, contractABI, "decimals"); err == nil {
|
||||
metadata.Decimals = decimals
|
||||
} else {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to get decimals for %s: %v", tokenAddr.Hex(), err))
|
||||
metadata.Decimals = 18 // Default to 18 decimals
|
||||
}
|
||||
|
||||
// Get total supply
|
||||
if totalSupply, err := s.callBigIntMethod(ctx, tokenAddr, contractABI, "totalSupply"); err == nil {
|
||||
metadata.TotalSupply = totalSupply
|
||||
} else {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to get total supply for %s: %v", tokenAddr.Hex(), err))
|
||||
metadata.TotalSupply = big.NewInt(0)
|
||||
}
|
||||
|
||||
// Check if contract is verified
|
||||
metadata.ContractVerified = s.isContractVerified(ctx, tokenAddr)
|
||||
|
||||
// Categorize token
|
||||
metadata.Category = s.categorizeToken(metadata)
|
||||
|
||||
// Assess risk
|
||||
metadata.RiskScore = s.assessRisk(metadata)
|
||||
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// enhanceMetadata adds additional information to token metadata
|
||||
func (s *TokenMetadataService) enhanceMetadata(ctx context.Context, metadata *TokenMetadata) error {
|
||||
// Check if it's a stablecoin
|
||||
metadata.IsStablecoin = s.isStablecoin(metadata.Symbol, metadata.Name)
|
||||
|
||||
// Check if it's a wrapped token
|
||||
metadata.IsWrapped = s.isWrappedToken(metadata.Symbol, metadata.Name)
|
||||
|
||||
// Mark as verified if it's a known token
|
||||
metadata.IsVerified = s.isVerifiedToken(metadata.Address)
|
||||
|
||||
// Check for proxy contract
|
||||
if impl, err := s.getProxyImplementation(ctx, metadata.Address); err == nil && impl != (common.Address{}) {
|
||||
metadata.Implementation = impl
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// callStringMethod calls a contract method that returns a string
|
||||
func (s *TokenMetadataService) callStringMethod(ctx context.Context, contractAddr common.Address, contractABI abi.ABI, method string) (string, error) {
|
||||
callData, err := contractABI.Pack(method)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to pack %s call: %w", method, err)
|
||||
}
|
||||
|
||||
result, err := s.client.CallContract(ctx, ethereum.CallMsg{
|
||||
To: &contractAddr,
|
||||
Data: callData,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s call failed: %w", method, err)
|
||||
}
|
||||
|
||||
unpacked, err := contractABI.Unpack(method, result)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to unpack %s result: %w", method, err)
|
||||
}
|
||||
|
||||
if len(unpacked) == 0 {
|
||||
return "", fmt.Errorf("empty %s result", method)
|
||||
}
|
||||
|
||||
if str, ok := unpacked[0].(string); ok {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("invalid %s result type: %T", method, unpacked[0])
|
||||
}
|
||||
|
||||
// callUint8Method calls a contract method that returns a uint8
|
||||
func (s *TokenMetadataService) callUint8Method(ctx context.Context, contractAddr common.Address, contractABI abi.ABI, method string) (uint8, error) {
|
||||
callData, err := contractABI.Pack(method)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to pack %s call: %w", method, err)
|
||||
}
|
||||
|
||||
result, err := s.client.CallContract(ctx, ethereum.CallMsg{
|
||||
To: &contractAddr,
|
||||
Data: callData,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s call failed: %w", method, err)
|
||||
}
|
||||
|
||||
unpacked, err := contractABI.Unpack(method, result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to unpack %s result: %w", method, err)
|
||||
}
|
||||
|
||||
if len(unpacked) == 0 {
|
||||
return 0, fmt.Errorf("empty %s result", method)
|
||||
}
|
||||
|
||||
// Handle different possible return types
|
||||
switch v := unpacked[0].(type) {
|
||||
case uint8:
|
||||
return v, nil
|
||||
case *big.Int:
|
||||
return uint8(v.Uint64()), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid %s result type: %T", method, unpacked[0])
|
||||
}
|
||||
}
|
||||
|
||||
// callBigIntMethod calls a contract method that returns a *big.Int
|
||||
func (s *TokenMetadataService) callBigIntMethod(ctx context.Context, contractAddr common.Address, contractABI abi.ABI, method string) (*big.Int, error) {
|
||||
callData, err := contractABI.Pack(method)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pack %s call: %w", method, err)
|
||||
}
|
||||
|
||||
result, err := s.client.CallContract(ctx, ethereum.CallMsg{
|
||||
To: &contractAddr,
|
||||
Data: callData,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s call failed: %w", method, err)
|
||||
}
|
||||
|
||||
unpacked, err := contractABI.Unpack(method, result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unpack %s result: %w", method, err)
|
||||
}
|
||||
|
||||
if len(unpacked) == 0 {
|
||||
return nil, fmt.Errorf("empty %s result", method)
|
||||
}
|
||||
|
||||
if bigInt, ok := unpacked[0].(*big.Int); ok {
|
||||
return bigInt, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid %s result type: %T", method, unpacked[0])
|
||||
}
|
||||
|
||||
// categorizeToken determines the category of a token
|
||||
func (s *TokenMetadataService) categorizeToken(metadata *TokenMetadata) string {
|
||||
symbol := strings.ToUpper(metadata.Symbol)
|
||||
name := strings.ToUpper(metadata.Name)
|
||||
|
||||
// Blue-chip tokens
|
||||
blueChip := []string{"WETH", "WBTC", "USDC", "USDT", "DAI", "ARB", "GMX", "GRT"}
|
||||
for _, token := range blueChip {
|
||||
if symbol == token {
|
||||
return "blue-chip"
|
||||
}
|
||||
}
|
||||
|
||||
// DeFi tokens
|
||||
if strings.Contains(name, "DAO") || strings.Contains(name, "FINANCE") ||
|
||||
strings.Contains(name, "PROTOCOL") || strings.Contains(symbol, "LP") {
|
||||
return "defi"
|
||||
}
|
||||
|
||||
// Meme tokens (simple heuristics)
|
||||
memeKeywords := []string{"MEME", "DOGE", "SHIB", "PEPE", "FLOKI"}
|
||||
for _, keyword := range memeKeywords {
|
||||
if strings.Contains(symbol, keyword) || strings.Contains(name, keyword) {
|
||||
return "meme"
|
||||
}
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// assessRisk calculates a risk score for the token
|
||||
func (s *TokenMetadataService) assessRisk(metadata *TokenMetadata) float64 {
|
||||
risk := 0.5 // Base risk
|
||||
|
||||
// Reduce risk for verified tokens
|
||||
if metadata.ContractVerified {
|
||||
risk -= 0.2
|
||||
}
|
||||
|
||||
// Reduce risk for blue-chip tokens
|
||||
if metadata.Category == "blue-chip" {
|
||||
risk -= 0.3
|
||||
}
|
||||
|
||||
// Increase risk for meme tokens
|
||||
if metadata.Category == "meme" {
|
||||
risk += 0.3
|
||||
}
|
||||
|
||||
// Reduce risk for stablecoins
|
||||
if metadata.IsStablecoin {
|
||||
risk -= 0.4
|
||||
}
|
||||
|
||||
// Increase risk for tokens with low total supply
|
||||
if metadata.TotalSupply != nil && metadata.TotalSupply.Cmp(big.NewInt(1e15)) < 0 {
|
||||
risk += 0.2
|
||||
}
|
||||
|
||||
// Ensure risk is between 0 and 1
|
||||
if risk < 0 {
|
||||
risk = 0
|
||||
}
|
||||
if risk > 1 {
|
||||
risk = 1
|
||||
}
|
||||
|
||||
return risk
|
||||
}
|
||||
|
||||
// isStablecoin checks if a token is a stablecoin
|
||||
func (s *TokenMetadataService) isStablecoin(symbol, name string) bool {
|
||||
stablecoins := []string{"USDC", "USDT", "DAI", "FRAX", "LUSD", "MIM", "UST", "BUSD"}
|
||||
symbol = strings.ToUpper(symbol)
|
||||
name = strings.ToUpper(name)
|
||||
|
||||
for _, stable := range stablecoins {
|
||||
if symbol == stable || strings.Contains(name, stable) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Contains(name, "USD") || strings.Contains(name, "DOLLAR")
|
||||
}
|
||||
|
||||
// isWrappedToken checks if a token is a wrapped version
|
||||
func (s *TokenMetadataService) isWrappedToken(symbol, name string) bool {
|
||||
return strings.HasPrefix(strings.ToUpper(symbol), "W") || strings.Contains(strings.ToUpper(name), "WRAPPED")
|
||||
}
|
||||
|
||||
// isVerifiedToken checks if a token is in the verified list
|
||||
func (s *TokenMetadataService) isVerifiedToken(addr common.Address) bool {
|
||||
_, exists := s.knownTokens[addr]
|
||||
return exists
|
||||
}
|
||||
|
||||
// isContractVerified checks if the contract source code is verified
|
||||
func (s *TokenMetadataService) isContractVerified(ctx context.Context, addr common.Address) bool {
|
||||
// Check if contract has code
|
||||
code, err := s.client.CodeAt(ctx, addr, nil)
|
||||
if err != nil || len(code) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// In a real implementation, you would check with a verification service like Etherscan
|
||||
// For now, we'll assume contracts with code are verified
|
||||
return len(code) > 0
|
||||
}
|
||||
|
||||
// getProxyImplementation gets the implementation address for proxy contracts
|
||||
func (s *TokenMetadataService) getProxyImplementation(ctx context.Context, proxyAddr common.Address) (common.Address, error) {
|
||||
// Try EIP-1967 standard storage slot
|
||||
slot := common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
|
||||
|
||||
storage, err := s.client.StorageAt(ctx, proxyAddr, slot, nil)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
||||
if len(storage) >= 20 {
|
||||
return common.BytesToAddress(storage[12:32]), nil
|
||||
}
|
||||
|
||||
return common.Address{}, fmt.Errorf("no implementation found")
|
||||
}
|
||||
|
||||
// getCachedMetadata retrieves cached metadata if available and not expired
|
||||
func (s *TokenMetadataService) getCachedMetadata(addr common.Address) *TokenMetadata {
|
||||
s.cacheMu.RLock()
|
||||
defer s.cacheMu.RUnlock()
|
||||
|
||||
cached, exists := s.cache[addr]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if cache is expired
|
||||
if time.Since(cached.LastUpdated) > s.cacheTTL {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cached
|
||||
}
|
||||
|
||||
// cacheMetadata stores metadata in the cache
|
||||
func (s *TokenMetadataService) cacheMetadata(addr common.Address, metadata *TokenMetadata) {
|
||||
s.cacheMu.Lock()
|
||||
defer s.cacheMu.Unlock()
|
||||
|
||||
// Create a copy to avoid race conditions
|
||||
cached := *metadata
|
||||
cached.LastUpdated = time.Now()
|
||||
s.cache[addr] = &cached
|
||||
}
|
||||
|
||||
// getKnownArbitrumTokens returns a registry of known tokens on Arbitrum
|
||||
func getKnownArbitrumTokens() map[common.Address]*TokenMetadata {
|
||||
return map[common.Address]*TokenMetadata{
|
||||
// WETH
|
||||
common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"): {
|
||||
Address: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
||||
Symbol: "WETH",
|
||||
Name: "Wrapped Ether",
|
||||
Decimals: 18,
|
||||
IsWrapped: true,
|
||||
Category: "blue-chip",
|
||||
IsVerified: true,
|
||||
RiskScore: 0.1,
|
||||
IsStablecoin: false,
|
||||
},
|
||||
// USDC
|
||||
common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"): {
|
||||
Address: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"),
|
||||
Symbol: "USDC",
|
||||
Name: "USD Coin",
|
||||
Decimals: 6,
|
||||
Category: "blue-chip",
|
||||
IsVerified: true,
|
||||
RiskScore: 0.05,
|
||||
IsStablecoin: true,
|
||||
},
|
||||
// ARB
|
||||
common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"): {
|
||||
Address: common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"),
|
||||
Symbol: "ARB",
|
||||
Name: "Arbitrum",
|
||||
Decimals: 18,
|
||||
Category: "blue-chip",
|
||||
IsVerified: true,
|
||||
RiskScore: 0.2,
|
||||
},
|
||||
// USDT
|
||||
common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"): {
|
||||
Address: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
|
||||
Symbol: "USDT",
|
||||
Name: "Tether USD",
|
||||
Decimals: 6,
|
||||
Category: "blue-chip",
|
||||
IsVerified: true,
|
||||
RiskScore: 0.1,
|
||||
IsStablecoin: true,
|
||||
},
|
||||
// GMX
|
||||
common.HexToAddress("0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a"): {
|
||||
Address: common.HexToAddress("0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a"),
|
||||
Symbol: "GMX",
|
||||
Name: "GMX",
|
||||
Decimals: 18,
|
||||
Category: "defi",
|
||||
IsVerified: true,
|
||||
RiskScore: 0.3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getERC20ABI returns the standard ERC20 ABI
|
||||
func getERC20ABI() string {
|
||||
return `[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [{"name": "", "type": "string"}],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [{"name": "", "type": "string"}],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [{"name": "", "type": "uint8"}],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [{"name": "", "type": "uint256"}],
|
||||
"type": "function"
|
||||
}
|
||||
]`
|
||||
}
|
||||
|
||||
// getProxyABI returns a simple proxy ABI for implementation detection
|
||||
func getProxyABI() string {
|
||||
return `[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "implementation",
|
||||
"outputs": [{"name": "", "type": "address"}],
|
||||
"type": "function"
|
||||
}
|
||||
]`
|
||||
}
|
||||
Reference in New Issue
Block a user