package arbitrage import ( "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/your-org/mev-bot/pkg/types" ) // OpportunityType represents the type of arbitrage opportunity type OpportunityType string const ( // OpportunityTypeTwoPool is a simple two-pool arbitrage OpportunityTypeTwoPool OpportunityType = "two_pool" // OpportunityTypeMultiHop is a multi-hop arbitrage (3+ pools) OpportunityTypeMultiHop OpportunityType = "multi_hop" // OpportunityTypeSandwich is a sandwich attack opportunity OpportunityTypeSandwich OpportunityType = "sandwich" // OpportunityTypeTriangular is a triangular arbitrage (A→B→C→A) OpportunityTypeTriangular OpportunityType = "triangular" ) // Opportunity represents an arbitrage opportunity type Opportunity struct { // Identification ID string `json:"id"` Type OpportunityType `json:"type"` DetectedAt time.Time `json:"detected_at"` BlockNumber uint64 `json:"block_number"` // Path Path []*PathStep `json:"path"` // Economics InputToken common.Address `json:"input_token"` OutputToken common.Address `json:"output_token"` InputAmount *big.Int `json:"input_amount"` OutputAmount *big.Int `json:"output_amount"` GrossProfit *big.Int `json:"gross_profit"` // Before gas GasCost *big.Int `json:"gas_cost"` // Estimated gas cost in wei NetProfit *big.Int `json:"net_profit"` // After gas ROI float64 `json:"roi"` // Return on investment (%) PriceImpact float64 `json:"price_impact"` // Price impact (%) // Execution Priority int `json:"priority"` // Higher = more urgent ExecuteAfter time.Time `json:"execute_after"` // Earliest execution time ExpiresAt time.Time `json:"expires_at"` // Opportunity expiration Executable bool `json:"executable"` // Can be executed now? // Context (for sandwich attacks) VictimTx *common.Hash `json:"victim_tx,omitempty"` // Victim transaction FrontRunTx *common.Hash `json:"front_run_tx,omitempty"` // Front-run transaction BackRunTx *common.Hash `json:"back_run_tx,omitempty"` // Back-run transaction VictimSlippage *big.Int `json:"victim_slippage,omitempty"` // Slippage imposed on victim } // PathStep represents one step in an arbitrage path type PathStep struct { // Pool information PoolAddress common.Address `json:"pool_address"` Protocol types.ProtocolType `json:"protocol"` // Token swap TokenIn common.Address `json:"token_in"` TokenOut common.Address `json:"token_out"` AmountIn *big.Int `json:"amount_in"` AmountOut *big.Int `json:"amount_out"` // Pool state (for V3) SqrtPriceX96Before *big.Int `json:"sqrt_price_x96_before,omitempty"` SqrtPriceX96After *big.Int `json:"sqrt_price_x96_after,omitempty"` LiquidityBefore *big.Int `json:"liquidity_before,omitempty"` LiquidityAfter *big.Int `json:"liquidity_after,omitempty"` // Fee Fee uint32 `json:"fee"` // Fee in basis points or pips FeeAmount *big.Int `json:"fee_amount"` // Fee paid in output token } // IsProfit returns true if the opportunity is profitable after gas func (o *Opportunity) IsProfitable() bool { return o.NetProfit != nil && o.NetProfit.Sign() > 0 } // MeetsThreshold returns true if net profit meets the minimum threshold func (o *Opportunity) MeetsThreshold(minProfit *big.Int) bool { if o.NetProfit == nil || minProfit == nil { return false } return o.NetProfit.Cmp(minProfit) >= 0 } // IsExpired returns true if the opportunity has expired func (o *Opportunity) IsExpired() bool { return time.Now().After(o.ExpiresAt) } // CanExecute returns true if the opportunity can be executed now func (o *Opportunity) CanExecute() bool { now := time.Now() return o.Executable && !o.IsExpired() && now.After(o.ExecuteAfter) && o.IsProfitable() } // GetTotalFees returns the sum of all fees in the path func (o *Opportunity) GetTotalFees() *big.Int { totalFees := big.NewInt(0) for _, step := range o.Path { if step.FeeAmount != nil { totalFees.Add(totalFees, step.FeeAmount) } } return totalFees } // GetPriceImpactPercentage returns price impact as a percentage func (o *Opportunity) GetPriceImpactPercentage() float64 { return o.PriceImpact * 100 } // GetROIPercentage returns ROI as a percentage func (o *Opportunity) GetROIPercentage() float64 { return o.ROI * 100 } // GetPathDescription returns a human-readable path description func (o *Opportunity) GetPathDescription() string { if len(o.Path) == 0 { return "empty path" } // Build path string: Token0 → Token1 → Token2 → Token0 path := "" for i, step := range o.Path { if i == 0 { path += step.TokenIn.Hex()[:10] + " → " } path += step.TokenOut.Hex()[:10] if i < len(o.Path)-1 { path += " → " } } return path } // GetProtocolPath returns a string of protocols in the path func (o *Opportunity) GetProtocolPath() string { if len(o.Path) == 0 { return "empty" } path := "" for i, step := range o.Path { path += string(step.Protocol) if i < len(o.Path)-1 { path += " → " } } return path } // OpportunityFilter represents filters for searching opportunities type OpportunityFilter struct { MinProfit *big.Int // Minimum net profit MaxGasCost *big.Int // Maximum acceptable gas cost MinROI float64 // Minimum ROI percentage Type *OpportunityType // Filter by opportunity type InputToken *common.Address // Filter by input token OutputToken *common.Address // Filter by output token Protocols []types.ProtocolType // Filter by protocols in path MaxPathLength int // Maximum path length (number of hops) OnlyExecutable bool // Only return executable opportunities } // Matches returns true if the opportunity matches the filter func (f *OpportunityFilter) Matches(opp *Opportunity) bool { // Check minimum profit if f.MinProfit != nil && (opp.NetProfit == nil || opp.NetProfit.Cmp(f.MinProfit) < 0) { return false } // Check maximum gas cost if f.MaxGasCost != nil && (opp.GasCost == nil || opp.GasCost.Cmp(f.MaxGasCost) > 0) { return false } // Check minimum ROI if f.MinROI > 0 && opp.ROI < f.MinROI { return false } // Check opportunity type if f.Type != nil && opp.Type != *f.Type { return false } // Check input token if f.InputToken != nil && opp.InputToken != *f.InputToken { return false } // Check output token if f.OutputToken != nil && opp.OutputToken != *f.OutputToken { return false } // Check protocols if len(f.Protocols) > 0 { hasMatch := false for _, step := range opp.Path { for _, protocol := range f.Protocols { if step.Protocol == protocol { hasMatch = true break } } if hasMatch { break } } if !hasMatch { return false } } // Check path length if f.MaxPathLength > 0 && len(opp.Path) > f.MaxPathLength { return false } // Check executability if f.OnlyExecutable && !opp.CanExecute() { return false } return true } // OpportunityStats contains statistics about detected opportunities type OpportunityStats struct { TotalDetected int `json:"total_detected"` TotalProfitable int `json:"total_profitable"` TotalExecutable int `json:"total_executable"` TotalExecuted int `json:"total_executed"` TotalExpired int `json:"total_expired"` AverageProfit *big.Int `json:"average_profit"` MedianProfit *big.Int `json:"median_profit"` MaxProfit *big.Int `json:"max_profit"` TotalProfit *big.Int `json:"total_profit"` AverageROI float64 `json:"average_roi"` SuccessRate float64 `json:"success_rate"` // Executed / Detected LastDetected time.Time `json:"last_detected"` DetectionRate float64 `json:"detection_rate"` // Opportunities per minute }