Update module name to github.com/fraktal/mev-beta and fix channel closing issues in pipeline stages

This commit is contained in:
Krypto Kajun
2025-09-12 19:08:38 -05:00
parent fbb85e529a
commit 1113d82499
31 changed files with 3359 additions and 210 deletions

219
pkg/events/parser.go Normal file
View File

@@ -0,0 +1,219 @@
package events
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
)
// EventType represents the type of DEX event
type EventType int
const (
Unknown EventType = iota
Swap
AddLiquidity
RemoveLiquidity
NewPool
)
// String returns a string representation of the event type
func (et EventType) String() string {
switch et {
case Unknown:
return "Unknown"
case Swap:
return "Swap"
case AddLiquidity:
return "AddLiquidity"
case RemoveLiquidity:
return "RemoveLiquidity"
case NewPool:
return "NewPool"
default:
return "Unknown"
}
}
// Event represents a parsed DEX event
type Event struct {
Type EventType
Protocol string // UniswapV2, UniswapV3, SushiSwap, etc.
PoolAddress common.Address
Token0 common.Address
Token1 common.Address
Amount0 *big.Int
Amount1 *big.Int
SqrtPriceX96 *uint256.Int
Liquidity *uint256.Int
Tick int
Timestamp uint64
TransactionHash common.Hash
BlockNumber uint64
}
// EventParser parses DEX events from Ethereum transactions
type EventParser struct {
// Known DEX contract addresses
UniswapV2Factory common.Address
UniswapV3Factory common.Address
SushiSwapFactory common.Address
// Router addresses
UniswapV2Router01 common.Address
UniswapV2Router02 common.Address
UniswapV3Router common.Address
SushiSwapRouter common.Address
// Known pool addresses (for quick lookup)
knownPools map[common.Address]string
}
// NewEventParser creates a new event parser
func NewEventParser() *EventParser {
parser := &EventParser{
UniswapV2Factory: common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"),
UniswapV3Factory: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
SushiSwapFactory: common.HexToAddress("0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac"),
UniswapV2Router01: common.HexToAddress("0xf164fC0Ec4E93095b804a4795bBe1e041497b92a"),
UniswapV2Router02: common.HexToAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"),
UniswapV3Router: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"),
SushiSwapRouter: common.HexToAddress("0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F"),
knownPools: make(map[common.Address]string),
}
// Pre-populate some known pools for demonstration
parser.knownPools[common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")] = "UniswapV3"
parser.knownPools[common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")] = "UniswapV2"
return parser
}
// ParseTransaction parses a transaction for DEX events
func (ep *EventParser) ParseTransaction(tx *types.Transaction, blockNumber uint64, timestamp uint64) ([]*Event, error) {
events := make([]*Event, 0)
// Check if this is a DEX interaction
if !ep.IsDEXInteraction(tx) {
return events, nil
}
// Determine the protocol
protocol := ep.identifyProtocol(tx)
// For now, we'll return mock data for demonstration
if tx.To() != nil {
event := &Event{
Type: Swap,
Protocol: protocol,
PoolAddress: *tx.To(),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
Amount0: big.NewInt(1000000000), // 1000 USDC
Amount1: big.NewInt(500000000000000000), // 0.5 WETH
SqrtPriceX96: uint256.NewInt(2505414483750470000),
Liquidity: uint256.NewInt(1000000000000000000),
Tick: 200000,
Timestamp: timestamp,
TransactionHash: tx.Hash(),
BlockNumber: blockNumber,
}
events = append(events, event)
}
return events, nil
}
// IsDEXInteraction checks if a transaction interacts with a known DEX contract
func (ep *EventParser) IsDEXInteraction(tx *types.Transaction) bool {
if tx.To() == nil {
return false
}
to := *tx.To()
// Check factory contracts
if to == ep.UniswapV2Factory ||
to == ep.UniswapV3Factory ||
to == ep.SushiSwapFactory {
return true
}
// Check router contracts
if to == ep.UniswapV2Router01 ||
to == ep.UniswapV2Router02 ||
to == ep.UniswapV3Router ||
to == ep.SushiSwapRouter {
return true
}
// Check known pools
if _, exists := ep.knownPools[to]; exists {
return true
}
return false
}
// identifyProtocol identifies which DEX protocol a transaction is interacting with
func (ep *EventParser) identifyProtocol(tx *types.Transaction) string {
if tx.To() == nil {
return "Unknown"
}
to := *tx.To()
// Check factory contracts
if to == ep.UniswapV2Factory {
return "UniswapV2"
}
if to == ep.UniswapV3Factory {
return "UniswapV3"
}
if to == ep.SushiSwapFactory {
return "SushiSwap"
}
// Check router contracts
if to == ep.UniswapV2Router01 || to == ep.UniswapV2Router02 {
return "UniswapV2"
}
if to == ep.UniswapV3Router {
return "UniswapV3"
}
if to == ep.SushiSwapRouter {
return "SushiSwap"
}
// Check known pools
if protocol, exists := ep.knownPools[to]; exists {
return protocol
}
// Try to identify from function signature in transaction data
if len(tx.Data()) >= 4 {
sig := common.Bytes2Hex(tx.Data()[:4])
switch sig {
case "0xac9650d8": // multicall (Uniswap V3)
return "UniswapV3"
case "0x88316456": // swap (Uniswap V2)
return "UniswapV2"
case "0x128acb08": // swap (SushiSwap)
return "SushiSwap"
}
}
return "Unknown"
}
// AddKnownPool adds a pool address to the known pools map
func (ep *EventParser) AddKnownPool(address common.Address, protocol string) {
ep.knownPools[address] = protocol
}
// GetKnownPools returns all known pools
func (ep *EventParser) GetKnownPools() map[common.Address]string {
return ep.knownPools
}

162
pkg/events/parser_test.go Normal file
View File

@@ -0,0 +1,162 @@
package events
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/assert"
)
func TestEventTypeString(t *testing.T) {
assert.Equal(t, "Unknown", Unknown.String())
assert.Equal(t, "Swap", Swap.String())
assert.Equal(t, "AddLiquidity", AddLiquidity.String())
assert.Equal(t, "RemoveLiquidity", RemoveLiquidity.String())
assert.Equal(t, "NewPool", NewPool.String())
assert.Equal(t, "Unknown", EventType(999).String()) // Test unknown value
}
func TestNewEventParser(t *testing.T) {
parser := NewEventParser()
assert.NotNil(t, parser)
assert.NotNil(t, parser.knownPools)
assert.NotEmpty(t, parser.knownPools)
}
func TestIsDEXInteraction(t *testing.T) {
parser := NewEventParser()
// Test with Uniswap V2 factory address
tx1 := types.NewTransaction(0, parser.UniswapV2Factory, big.NewInt(0), 0, big.NewInt(0), nil)
assert.True(t, parser.IsDEXInteraction(tx1))
// Test with Uniswap V3 factory address
tx2 := types.NewTransaction(0, parser.UniswapV3Factory, big.NewInt(0), 0, big.NewInt(0), nil)
assert.True(t, parser.IsDEXInteraction(tx2))
// Test with SushiSwap factory address
tx3 := types.NewTransaction(0, parser.SushiSwapFactory, big.NewInt(0), 0, big.NewInt(0), nil)
assert.True(t, parser.IsDEXInteraction(tx3))
// Test with Uniswap V2 router address
tx4 := types.NewTransaction(0, parser.UniswapV2Router02, big.NewInt(0), 0, big.NewInt(0), nil)
assert.True(t, parser.IsDEXInteraction(tx4))
// Test with a known pool address
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
parser.AddKnownPool(poolAddr, "UniswapV3")
tx5 := types.NewTransaction(0, poolAddr, big.NewInt(0), 0, big.NewInt(0), nil)
assert.True(t, parser.IsDEXInteraction(tx5))
// Test with a random address (should be false)
randomAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
tx6 := types.NewTransaction(0, randomAddr, big.NewInt(0), 0, big.NewInt(0), nil)
assert.False(t, parser.IsDEXInteraction(tx6))
// Test with contract creation transaction (nil To address)
tx7 := types.NewContractCreation(0, big.NewInt(0), 0, big.NewInt(0), nil)
assert.False(t, parser.IsDEXInteraction(tx7))
}
func TestIdentifyProtocol(t *testing.T) {
parser := NewEventParser()
// Test with Uniswap V2 factory address
tx1 := types.NewTransaction(0, parser.UniswapV2Factory, big.NewInt(0), 0, big.NewInt(0), nil)
assert.Equal(t, "UniswapV2", parser.identifyProtocol(tx1))
// Test with Uniswap V3 factory address
tx2 := types.NewTransaction(0, parser.UniswapV3Factory, big.NewInt(0), 0, big.NewInt(0), nil)
assert.Equal(t, "UniswapV3", parser.identifyProtocol(tx2))
// Test with SushiSwap factory address
tx3 := types.NewTransaction(0, parser.SushiSwapFactory, big.NewInt(0), 0, big.NewInt(0), nil)
assert.Equal(t, "SushiSwap", parser.identifyProtocol(tx3))
// Test with Uniswap V2 router address
tx4 := types.NewTransaction(0, parser.UniswapV2Router02, big.NewInt(0), 0, big.NewInt(0), nil)
assert.Equal(t, "UniswapV2", parser.identifyProtocol(tx4))
// Test with a known pool address
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
parser.AddKnownPool(poolAddr, "UniswapV3")
tx5 := types.NewTransaction(0, poolAddr, big.NewInt(0), 0, big.NewInt(0), nil)
assert.Equal(t, "UniswapV3", parser.identifyProtocol(tx5))
// Test with a random address (should be Unknown)
randomAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
tx6 := types.NewTransaction(0, randomAddr, big.NewInt(0), 0, big.NewInt(0), nil)
assert.Equal(t, "Unknown", parser.identifyProtocol(tx6))
// Test with contract creation transaction (nil To address)
tx7 := types.NewContractCreation(0, big.NewInt(0), 0, big.NewInt(0), nil)
assert.Equal(t, "Unknown", parser.identifyProtocol(tx7))
}
func TestAddKnownPoolAndGetKnownPools(t *testing.T) {
parser := NewEventParser()
initialCount := len(parser.GetKnownPools())
// Add a new pool
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
parser.AddKnownPool(addr, "TestProtocol")
// Check that the pool was added
pools := parser.GetKnownPools()
assert.Equal(t, initialCount+1, len(pools))
assert.Equal(t, "TestProtocol", pools[addr])
// Add another pool
addr2 := common.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd")
parser.AddKnownPool(addr2, "AnotherProtocol")
// Check that both pools are in the map
pools = parser.GetKnownPools()
assert.Equal(t, initialCount+2, len(pools))
assert.Equal(t, "TestProtocol", pools[addr])
assert.Equal(t, "AnotherProtocol", pools[addr2])
}
func TestParseTransaction(t *testing.T) {
parser := NewEventParser()
// Create a transaction that interacts with a DEX
tx := types.NewTransaction(0, parser.UniswapV3Factory, big.NewInt(0), 0, big.NewInt(0), nil)
blockNumber := uint64(12345)
timestamp := uint64(1620000000)
// Parse the transaction
events, err := parser.ParseTransaction(tx, blockNumber, timestamp)
assert.NoError(t, err)
assert.Len(t, events, 1)
// Check the parsed event
event := events[0]
assert.Equal(t, Swap, event.Type)
assert.Equal(t, "UniswapV3", event.Protocol)
assert.Equal(t, parser.UniswapV3Factory, event.PoolAddress)
assert.Equal(t, blockNumber, event.BlockNumber)
assert.Equal(t, timestamp, event.Timestamp)
assert.Equal(t, tx.Hash(), event.TransactionHash)
assert.NotNil(t, event.Amount0)
assert.NotNil(t, event.Amount1)
assert.NotNil(t, event.SqrtPriceX96)
assert.NotNil(t, event.Liquidity)
}
func TestParseTransactionNonDEX(t *testing.T) {
parser := NewEventParser()
// Create a transaction that doesn't interact with a DEX
randomAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
tx := types.NewTransaction(0, randomAddr, big.NewInt(0), 0, big.NewInt(0), nil)
blockNumber := uint64(12345)
timestamp := uint64(1620000000)
// Parse the transaction
events, err := parser.ParseTransaction(tx, blockNumber, timestamp)
assert.NoError(t, err)
assert.Len(t, events, 0)
}