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:
366
pkg/arbitrum/event_enrichment.go
Normal file
366
pkg/arbitrum/event_enrichment.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package arbitrum
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
"github.com/fraktal/mev-beta/pkg/oracle"
|
||||
)
|
||||
|
||||
// EventEnrichmentService provides comprehensive event data enrichment
|
||||
type EventEnrichmentService struct {
|
||||
priceOracle *oracle.PriceOracle
|
||||
tokenMetadata *TokenMetadataService
|
||||
logger *logger.Logger
|
||||
|
||||
// USD conversion constants
|
||||
usdcAddr common.Address
|
||||
wethAddr common.Address
|
||||
}
|
||||
|
||||
// NewEventEnrichmentService creates a new event enrichment service
|
||||
func NewEventEnrichmentService(
|
||||
priceOracle *oracle.PriceOracle,
|
||||
tokenMetadata *TokenMetadataService,
|
||||
logger *logger.Logger,
|
||||
) *EventEnrichmentService {
|
||||
return &EventEnrichmentService{
|
||||
priceOracle: priceOracle,
|
||||
tokenMetadata: tokenMetadata,
|
||||
logger: logger,
|
||||
usdcAddr: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC on Arbitrum
|
||||
wethAddr: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH on Arbitrum
|
||||
}
|
||||
}
|
||||
|
||||
// EnrichEvent adds comprehensive metadata and USD values to a DEX event
|
||||
func (s *EventEnrichmentService) EnrichEvent(ctx context.Context, event *EnhancedDEXEvent) error {
|
||||
// Add token metadata
|
||||
if err := s.addTokenMetadata(ctx, event); err != nil {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to add token metadata: %v", err))
|
||||
}
|
||||
|
||||
// Calculate USD values
|
||||
if err := s.calculateUSDValues(ctx, event); err != nil {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to calculate USD values: %v", err))
|
||||
}
|
||||
|
||||
// Add factory and router information
|
||||
s.addContractMetadata(event)
|
||||
|
||||
// Calculate price impact and slippage
|
||||
if err := s.calculatePriceMetrics(ctx, event); err != nil {
|
||||
s.logger.Debug(fmt.Sprintf("Failed to calculate price metrics: %v", err))
|
||||
}
|
||||
|
||||
// Assess MEV potential
|
||||
s.assessMEVPotential(event)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addTokenMetadata enriches the event with token metadata
|
||||
func (s *EventEnrichmentService) addTokenMetadata(ctx context.Context, event *EnhancedDEXEvent) error {
|
||||
// Get metadata for token in
|
||||
if event.TokenIn != (common.Address{}) {
|
||||
if metadata, err := s.tokenMetadata.GetTokenMetadata(ctx, event.TokenIn); err == nil {
|
||||
event.TokenInSymbol = metadata.Symbol
|
||||
event.TokenInName = metadata.Name
|
||||
event.TokenInDecimals = metadata.Decimals
|
||||
event.TokenInRiskScore = metadata.RiskScore
|
||||
}
|
||||
}
|
||||
|
||||
// Get metadata for token out
|
||||
if event.TokenOut != (common.Address{}) {
|
||||
if metadata, err := s.tokenMetadata.GetTokenMetadata(ctx, event.TokenOut); err == nil {
|
||||
event.TokenOutSymbol = metadata.Symbol
|
||||
event.TokenOutName = metadata.Name
|
||||
event.TokenOutDecimals = metadata.Decimals
|
||||
event.TokenOutRiskScore = metadata.RiskScore
|
||||
}
|
||||
}
|
||||
|
||||
// Get metadata for token0 and token1 if available
|
||||
if event.Token0 != (common.Address{}) {
|
||||
if metadata, err := s.tokenMetadata.GetTokenMetadata(ctx, event.Token0); err == nil {
|
||||
event.Token0Symbol = metadata.Symbol
|
||||
event.Token0Decimals = metadata.Decimals
|
||||
}
|
||||
}
|
||||
|
||||
if event.Token1 != (common.Address{}) {
|
||||
if metadata, err := s.tokenMetadata.GetTokenMetadata(ctx, event.Token1); err == nil {
|
||||
event.Token1Symbol = metadata.Symbol
|
||||
event.Token1Decimals = metadata.Decimals
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateUSDValues calculates USD values for all amounts in the event
|
||||
func (s *EventEnrichmentService) calculateUSDValues(ctx context.Context, event *EnhancedDEXEvent) error {
|
||||
// Calculate AmountInUSD
|
||||
if event.AmountIn != nil && event.TokenIn != (common.Address{}) {
|
||||
if usdValue, err := s.getTokenValueInUSD(ctx, event.TokenIn, event.AmountIn); err == nil {
|
||||
event.AmountInUSD = usdValue
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate AmountOutUSD
|
||||
if event.AmountOut != nil && event.TokenOut != (common.Address{}) {
|
||||
if usdValue, err := s.getTokenValueInUSD(ctx, event.TokenOut, event.AmountOut); err == nil {
|
||||
event.AmountOutUSD = usdValue
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate Amount0USD and Amount1USD for V3 events
|
||||
if event.Amount0 != nil && event.Token0 != (common.Address{}) {
|
||||
if usdValue, err := s.getTokenValueInUSD(ctx, event.Token0, new(big.Int).Abs(event.Amount0)); err == nil {
|
||||
event.Amount0USD = usdValue
|
||||
}
|
||||
}
|
||||
|
||||
if event.Amount1 != nil && event.Token1 != (common.Address{}) {
|
||||
if usdValue, err := s.getTokenValueInUSD(ctx, event.Token1, new(big.Int).Abs(event.Amount1)); err == nil {
|
||||
event.Amount1USD = usdValue
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate fee in USD
|
||||
if event.AmountInUSD > 0 && event.FeeBps > 0 {
|
||||
event.FeeUSD = event.AmountInUSD * float64(event.FeeBps) / 10000.0
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTokenValueInUSD converts a token amount to USD value
|
||||
func (s *EventEnrichmentService) getTokenValueInUSD(ctx context.Context, tokenAddr common.Address, amount *big.Int) (float64, error) {
|
||||
if amount == nil || amount.Sign() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Direct USDC conversion
|
||||
if tokenAddr == s.usdcAddr {
|
||||
// USDC has 6 decimals
|
||||
amountFloat := new(big.Float).SetInt(amount)
|
||||
amountFloat.Quo(amountFloat, big.NewFloat(1e6))
|
||||
result, _ := amountFloat.Float64()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Get price from oracle
|
||||
priceReq := &oracle.PriceRequest{
|
||||
TokenIn: tokenAddr,
|
||||
TokenOut: s.usdcAddr, // Convert to USDC first
|
||||
AmountIn: amount,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
priceResp, err := s.priceOracle.GetPrice(ctx, priceReq)
|
||||
if err != nil {
|
||||
// Fallback: try converting through WETH if direct conversion fails
|
||||
if tokenAddr != s.wethAddr {
|
||||
return s.getUSDValueThroughWETH(ctx, tokenAddr, amount)
|
||||
}
|
||||
return 0, fmt.Errorf("failed to get price: %w", err)
|
||||
}
|
||||
|
||||
if !priceResp.Valid || priceResp.AmountOut == nil {
|
||||
return 0, fmt.Errorf("invalid price response")
|
||||
}
|
||||
|
||||
// Convert USDC amount to USD (USDC has 6 decimals)
|
||||
usdcAmount := new(big.Float).SetInt(priceResp.AmountOut)
|
||||
usdcAmount.Quo(usdcAmount, big.NewFloat(1e6))
|
||||
result, _ := usdcAmount.Float64()
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getUSDValueThroughWETH converts token value to USD through WETH
|
||||
func (s *EventEnrichmentService) getUSDValueThroughWETH(ctx context.Context, tokenAddr common.Address, amount *big.Int) (float64, error) {
|
||||
// First convert token to WETH
|
||||
wethReq := &oracle.PriceRequest{
|
||||
TokenIn: tokenAddr,
|
||||
TokenOut: s.wethAddr,
|
||||
AmountIn: amount,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
wethResp, err := s.priceOracle.GetPrice(ctx, wethReq)
|
||||
if err != nil || !wethResp.Valid {
|
||||
return 0, fmt.Errorf("failed to convert to WETH: %w", err)
|
||||
}
|
||||
|
||||
// Then convert WETH to USD
|
||||
return s.getTokenValueInUSD(ctx, s.wethAddr, wethResp.AmountOut)
|
||||
}
|
||||
|
||||
// addContractMetadata adds factory and router contract information
|
||||
func (s *EventEnrichmentService) addContractMetadata(event *EnhancedDEXEvent) {
|
||||
// Set factory addresses based on protocol
|
||||
switch event.Protocol {
|
||||
case ProtocolUniswapV2:
|
||||
event.Factory = common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9")
|
||||
event.Router = common.HexToAddress("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24")
|
||||
case ProtocolUniswapV3:
|
||||
event.Factory = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
||||
event.Router = common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564")
|
||||
case ProtocolSushiSwapV2:
|
||||
event.Factory = common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4")
|
||||
event.Router = common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506")
|
||||
case ProtocolSushiSwapV3:
|
||||
event.Factory = common.HexToAddress("0x7770978eED668a3ba661d51a773d3a992Fc9DDCB")
|
||||
event.Router = common.HexToAddress("0x34af5256F1FC2e9F5b5c0f3d8ED82D5a15B69C88")
|
||||
case ProtocolCamelotV2:
|
||||
event.Factory = common.HexToAddress("0x6EcCab422D763aC031210895C81787E87B91B678")
|
||||
event.Router = common.HexToAddress("0xc873fEcbd354f5A56E00E710B90EF4201db2448d")
|
||||
case ProtocolCamelotV3:
|
||||
event.Factory = common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B")
|
||||
event.Router = common.HexToAddress("0x1F721E2E82F6676FCE4eA07A5958cF098D339e18")
|
||||
}
|
||||
}
|
||||
|
||||
// calculatePriceMetrics calculates price impact and slippage
|
||||
func (s *EventEnrichmentService) calculatePriceMetrics(ctx context.Context, event *EnhancedDEXEvent) error {
|
||||
// Skip if we don't have enough data
|
||||
if event.AmountIn == nil || event.AmountOut == nil ||
|
||||
event.TokenIn == (common.Address{}) || event.TokenOut == (common.Address{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get current market price
|
||||
marketReq := &oracle.PriceRequest{
|
||||
TokenIn: event.TokenIn,
|
||||
TokenOut: event.TokenOut,
|
||||
AmountIn: big.NewInt(1e18), // 1 token for reference price
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
marketResp, err := s.priceOracle.GetPrice(ctx, marketReq)
|
||||
if err != nil || !marketResp.Valid {
|
||||
return fmt.Errorf("failed to get market price: %w", err)
|
||||
}
|
||||
|
||||
// Calculate effective price from the trade
|
||||
effectivePrice := new(big.Float).Quo(
|
||||
new(big.Float).SetInt(event.AmountOut),
|
||||
new(big.Float).SetInt(event.AmountIn),
|
||||
)
|
||||
|
||||
// Calculate market price
|
||||
marketPrice := new(big.Float).Quo(
|
||||
new(big.Float).SetInt(marketResp.AmountOut),
|
||||
new(big.Float).SetInt(marketReq.AmountIn),
|
||||
)
|
||||
|
||||
// Calculate price impact: (marketPrice - effectivePrice) / marketPrice
|
||||
priceDiff := new(big.Float).Sub(marketPrice, effectivePrice)
|
||||
priceImpact := new(big.Float).Quo(priceDiff, marketPrice)
|
||||
|
||||
impact, _ := priceImpact.Float64()
|
||||
event.PriceImpact = impact
|
||||
|
||||
// Convert to basis points for slippage
|
||||
event.SlippageBps = uint64(impact * 10000)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// assessMEVPotential determines if the event has MEV potential
|
||||
func (s *EventEnrichmentService) assessMEVPotential(event *EnhancedDEXEvent) {
|
||||
// Initialize MEV assessment
|
||||
event.IsMEV = false
|
||||
event.MEVType = ""
|
||||
event.ProfitUSD = 0.0
|
||||
|
||||
// High-value transactions are more likely to be MEV
|
||||
if event.AmountInUSD > 50000 { // $50k threshold
|
||||
event.IsMEV = true
|
||||
event.MEVType = "high_value"
|
||||
event.ProfitUSD = event.AmountInUSD * 0.001 // Estimate 0.1% profit
|
||||
}
|
||||
|
||||
// High price impact suggests potential sandwich opportunity
|
||||
if event.PriceImpact > 0.02 { // 2% price impact
|
||||
event.IsMEV = true
|
||||
event.MEVType = "sandwich_opportunity"
|
||||
event.ProfitUSD = event.AmountInUSD * event.PriceImpact * 0.5 // Estimate half the impact as profit
|
||||
}
|
||||
|
||||
// High slippage tolerance indicates MEV potential
|
||||
if event.SlippageBps > 500 { // 5% slippage tolerance
|
||||
event.IsMEV = true
|
||||
if event.MEVType == "" {
|
||||
event.MEVType = "arbitrage"
|
||||
}
|
||||
event.ProfitUSD = event.AmountInUSD * 0.002 // Estimate 0.2% profit
|
||||
}
|
||||
|
||||
// Transactions involving risky tokens
|
||||
if event.TokenInRiskScore > 0.7 || event.TokenOutRiskScore > 0.7 {
|
||||
event.IsMEV = true
|
||||
if event.MEVType == "" {
|
||||
event.MEVType = "risky_arbitrage"
|
||||
}
|
||||
event.ProfitUSD = event.AmountInUSD * 0.005 // Higher profit for risky trades
|
||||
}
|
||||
|
||||
// Flash loan indicators (large amounts with no sender balance check)
|
||||
if event.AmountInUSD > 100000 && event.MEVType == "" {
|
||||
event.IsMEV = true
|
||||
event.MEVType = "flash_loan_arbitrage"
|
||||
event.ProfitUSD = event.AmountInUSD * 0.003 // Estimate 0.3% profit
|
||||
}
|
||||
}
|
||||
|
||||
// CalculatePoolTVL calculates the total value locked in a pool
|
||||
func (s *EventEnrichmentService) CalculatePoolTVL(ctx context.Context, poolAddr common.Address, token0, token1 common.Address, reserve0, reserve1 *big.Int) (float64, error) {
|
||||
if reserve0 == nil || reserve1 == nil {
|
||||
return 0, fmt.Errorf("invalid reserves")
|
||||
}
|
||||
|
||||
// Get USD value of both reserves
|
||||
value0, err := s.getTokenValueInUSD(ctx, token0, reserve0)
|
||||
if err != nil {
|
||||
value0 = 0 // Continue with just one side if the other fails
|
||||
}
|
||||
|
||||
value1, err := s.getTokenValueInUSD(ctx, token1, reserve1)
|
||||
if err != nil {
|
||||
value1 = 0
|
||||
}
|
||||
|
||||
// TVL is the sum of both reserves in USD
|
||||
tvl := value0 + value1
|
||||
|
||||
return tvl, nil
|
||||
}
|
||||
|
||||
// EnhancedDEXEvent extensions for enriched data
|
||||
type EnhancedDEXEventExtended struct {
|
||||
*EnhancedDEXEvent
|
||||
|
||||
// Additional enriched fields
|
||||
TokenInRiskScore float64 `json:"tokenInRiskScore"`
|
||||
TokenOutRiskScore float64 `json:"tokenOutRiskScore"`
|
||||
|
||||
// Pool information
|
||||
PoolTVL float64 `json:"poolTVL"`
|
||||
PoolUtilization float64 `json:"poolUtilization"` // How much of the pool was used
|
||||
|
||||
// MEV analysis
|
||||
SandwichRisk float64 `json:"sandwichRisk"` // 0.0 to 1.0
|
||||
ArbitrageProfit float64 `json:"arbitrageProfit"` // Estimated profit in USD
|
||||
|
||||
// Market context
|
||||
VolumeRank24h int `json:"volumeRank24h"` // Rank by 24h volume
|
||||
PriceChange24h float64 `json:"priceChange24h"` // Price change in last 24h
|
||||
}
|
||||
Reference in New Issue
Block a user