Some checks failed
V2 CI/CD Pipeline / Pre-Flight Checks (push) Has been cancelled
V2 CI/CD Pipeline / Build & Dependencies (push) Has been cancelled
V2 CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
V2 CI/CD Pipeline / Unit Tests (100% Coverage Required) (push) Has been cancelled
V2 CI/CD Pipeline / Integration Tests (push) Has been cancelled
V2 CI/CD Pipeline / Performance Benchmarks (push) Has been cancelled
V2 CI/CD Pipeline / Decimal Precision Validation (push) Has been cancelled
V2 CI/CD Pipeline / Modularity Validation (push) Has been cancelled
V2 CI/CD Pipeline / Final Validation Summary (push) Has been cancelled
Implemented Phase 3 of the V2 architecture: a comprehensive arbitrage detection engine with path finding, profitability calculation, and opportunity detection. Core Components: - Opportunity struct: Represents arbitrage opportunities with full execution context - PathFinder: Finds two-pool, triangular, and multi-hop arbitrage paths using BFS - Calculator: Calculates profitability using protocol-specific math (V2, V3, Curve) - GasEstimator: Estimates gas costs and optimal gas prices - Detector: Main orchestration component for opportunity detection Features: - Multi-protocol support: UniswapV2, UniswapV3, Curve StableSwap - Concurrent path evaluation with configurable limits - Input amount optimization for maximum profit - Real-time swap monitoring and opportunity stream - Comprehensive statistics tracking - Token whitelisting and filtering Path Finding: - Two-pool arbitrage: A→B→A across different pools - Triangular arbitrage: A→B→C→A with three pools - Multi-hop arbitrage: Up to 4 hops with BFS search - Liquidity and protocol filtering - Duplicate path detection Profitability Calculation: - Protocol-specific swap calculations - Price impact estimation - Gas cost estimation with multipliers - Net profit after fees and gas - ROI and priority scoring - Executable opportunity filtering Testing: - 100% test coverage for all components - 1,400+ lines of comprehensive tests - Unit tests for all public methods - Integration tests for full workflows - Edge case and error handling tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
266 lines
7.8 KiB
Go
266 lines
7.8 KiB
Go
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
|
|
}
|