- Created MODULARITY_REQUIREMENTS.md with component independence rules - Created PROTOCOL_SUPPORT_REQUIREMENTS.md covering 13+ protocols - Created TESTING_REQUIREMENTS.md enforcing 100% coverage - Updated CLAUDE.md with strict feature/v2/* branch strategy Requirements documented: - Component modularity (standalone + integrated) - 100% test coverage enforcement (non-negotiable) - All DEX protocols (Uniswap V2/V3/V4, Curve, Balancer V2/V3, Kyber Classic/Elastic, Camelot V2/V3 with all Algebra variants) - Proper decimal handling (critical for calculations) - Pool caching with multi-index and O(1) mappings - Market building with essential arbitrage detection values - Price movement detection with decimal precision - Transaction building (single and batch execution) - Pool discovery and caching - Comprehensive validation at all layers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
V2 Modularity Requirements
Core Principle: Standalone Components
Every component MUST be able to run independently OR as part of the integrated system.
Component Independence Rules
1. Zero Hard Dependencies
- Each component communicates through well-defined interfaces only
- NO direct imports between sibling components
- NO shared state except through explicit interfaces
- Each component has its own configuration
2. Standalone Executability
Every component must support:
// Example: Each parser can be used standalone
parser := uniswap_v2.NewParser(logger, cache)
event, err := parser.ParseLog(log, tx)
// OR as part of factory
factory := parsers.NewFactory()
factory.Register(ProtocolUniswapV2, parser)
3. Interface-First Design
Define interfaces BEFORE implementation:
// pkg/parsers/interface.go
type Parser interface {
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
ParseReceipt(receipt *types.Receipt, tx *types.Transaction) ([]*Event, error)
SupportedProtocols() []Protocol
ValidateEvent(event *Event) error
}
// Each parser implements this independently
Component Modularity Matrix
| Component | Standalone Use Case | Integrated Use Case | Interface |
|---|---|---|---|
| UniswapV2Parser | Parse individual V2 transactions | Part of ParserFactory | Parser |
| UniswapV3Parser | Parse individual V3 transactions | Part of ParserFactory | Parser |
| PoolCache | Standalone pool lookup service | Shared cache for all parsers | PoolCache |
| EventValidator | Validate any event independently | Part of validation pipeline | Validator |
| AddressIndex | Standalone address lookup | Part of multi-index cache | Index |
| TokenPairIndex | Standalone pair lookup | Part of multi-index cache | Index |
| BackgroundValidator | Standalone validation service | Part of monitoring pipeline | BackgroundValidator |
Directory Structure for Modularity
pkg/
├── parsers/
│ ├── interface.go # Parser interface (shared)
│ ├── factory.go # Factory for integration
│ ├── uniswap_v2/ # Standalone package
│ │ ├── parser.go # Can be imported independently
│ │ ├── parser_test.go # Self-contained tests
│ │ └── README.md # Standalone usage docs
│ ├── uniswap_v3/ # Standalone package
│ │ ├── parser.go
│ │ ├── parser_test.go
│ │ └── README.md
│ └── sushiswap/ # Standalone package
│ ├── parser.go
│ ├── parser_test.go
│ └── README.md
│
├── cache/
│ ├── interface.go # Cache interfaces
│ ├── pool_cache.go # Main cache (uses indexes)
│ ├── indexes/ # Standalone index packages
│ │ ├── address/
│ │ │ ├── index.go # Standalone address index
│ │ │ └── index_test.go
│ │ ├── tokenpair/
│ │ │ ├── index.go # Standalone pair index
│ │ │ └── index_test.go
│ │ └── liquidity/
│ │ ├── index.go # Standalone liquidity index
│ │ └── index_test.go
│
├── validation/
│ ├── interface.go # Validator interface
│ ├── validator.go # Main validator
│ ├── rules/ # Standalone rule packages
│ │ ├── zero_address/
│ │ │ ├── rule.go # Standalone rule
│ │ │ └── rule_test.go
│ │ ├── zero_amount/
│ │ │ ├── rule.go
│ │ │ └── rule_test.go
│ │ └── pool_cache/
│ │ ├── rule.go
│ │ └── rule_test.go
│ └── background/
│ ├── validator.go # Standalone background validator
│ └── validator_test.go
Testing Requirements for Modularity
1. Unit Tests (Component Isolation)
Each component has 100% independent unit tests:
// pkg/parsers/uniswap_v2/parser_test.go
func TestParser_Standalone(t *testing.T) {
// NO dependencies on other parsers
// NO dependencies on factory
// Uses mocks for interfaces only
logger := NewMockLogger()
cache := NewMockCache()
parser := NewParser(logger, cache)
event, err := parser.ParseLog(mockLog, mockTx)
assert.NoError(t, err)
assert.NotNil(t, event)
}
2. Integration Tests (Component Composition)
Test components working together:
// tests/integration/parser_factory_test.go
func TestParserFactory_Integration(t *testing.T) {
// Test all parsers working through factory
factory := parsers.NewFactory()
// Each parser registered independently
factory.Register(ProtocolUniswapV2, uniswap_v2.NewParser(logger, cache))
factory.Register(ProtocolUniswapV3, uniswap_v3.NewParser(logger, cache))
// Test factory routing
parser, err := factory.GetParser(ProtocolUniswapV2)
assert.NoError(t, err)
}
3. Standalone Executability Tests
Each component has example main:
// examples/uniswap_v2_parser/main.go
func main() {
// Demonstrate standalone usage
logger := logger.New("info", "text", "")
cache := cache.NewPoolCache()
parser := uniswap_v2.NewParser(logger, cache)
// Parse single transaction
event, err := parser.ParseLog(log, tx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Parsed event: %+v\n", event)
}
Dependency Injection Pattern
All components use constructor injection:
// Bad: Hard dependency
type UniswapV2Parser struct {
cache *PoolCache // Hard dependency!
}
// Good: Interface dependency
type UniswapV2Parser struct {
cache PoolCache // Interface - can be mocked or replaced
}
func NewParser(logger Logger, cache PoolCache) *UniswapV2Parser {
return &UniswapV2Parser{
logger: logger,
cache: cache,
}
}
Configuration Independence
Each component has its own config:
// pkg/parsers/uniswap_v2/config.go
type Config struct {
EnableValidation bool
CacheTimeout time.Duration
MaxRetries int
}
// Standalone usage
config := uniswap_v2.Config{
EnableValidation: true,
CacheTimeout: 5 * time.Minute,
MaxRetries: 3,
}
parser := uniswap_v2.NewParserWithConfig(logger, cache, config)
// Integrated usage
factory.RegisterWithConfig(
ProtocolUniswapV2,
parser,
config,
)
Interface Contracts
Minimal Interface Surface
Each interface has 1-3 methods maximum:
// Good: Focused interface
type Parser interface {
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
}
// Bad: God interface
type Parser interface {
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
ParseReceipt(receipt *types.Receipt, tx *types.Transaction) ([]*Event, error)
ValidateEvent(event *Event) error
GetStats() Stats
Configure(config Config) error
// ... too many responsibilities
}
Interface Segregation
Split large interfaces:
// Split responsibilities
type LogParser interface {
ParseLog(log *types.Log, tx *types.Transaction) (*Event, error)
}
type ReceiptParser interface {
ParseReceipt(receipt *types.Receipt, tx *types.Transaction) ([]*Event, error)
}
type EventValidator interface {
ValidateEvent(event *Event) error
}
// Compose as needed
type Parser interface {
LogParser
ReceiptParser
EventValidator
}
Build Tags for Optional Components
Use build tags for optional features:
// pkg/parsers/uniswap_v2/parser.go
// +build !minimal
// Full implementation with all features
// pkg/parsers/uniswap_v2/parser_minimal.go
// +build minimal
// Minimal implementation for embedded systems
Build options:
# Full build (all components)
go build ./...
# Minimal build (core only)
go build -tags minimal ./...
# Custom build (specific parsers only)
go build -tags "uniswap_v2 uniswap_v3" ./...
Component Communication Patterns
1. Synchronous (Direct Call)
For tight coupling when needed:
event, err := parser.ParseLog(log, tx)
2. Asynchronous (Channels)
For loose coupling:
eventChan := make(chan *Event, 100)
go parser.ParseAsync(logs, eventChan)
3. Pub/Sub (Event Bus)
For many-to-many communication:
bus := eventbus.New()
parser.Subscribe(bus, "parsed")
validator.Subscribe(bus, "parsed")
bus.Publish("parsed", event)
4. Interface Composition
For static composition:
type CompositeParser struct {
v2 *uniswap_v2.Parser
v3 *uniswap_v3.Parser
}
func (c *CompositeParser) Parse(log *types.Log) (*Event, error) {
// Route to appropriate parser
}
Success Criteria for Modularity
Each component MUST:
- Compile independently (
go build ./pkg/parsers/uniswap_v2) - Test independently (
go test ./pkg/parsers/uniswap_v2) - Run standalone example (
go run examples/uniswap_v2_parser/main.go) - Have zero sibling dependencies
- Communicate only through interfaces
- Include standalone usage documentation
- Pass integration tests when composed
- Support mock implementations for testing
Anti-Patterns to Avoid
❌ Circular Dependencies
// pkg/parsers/uniswap_v2/parser.go
import "pkg/parsers/uniswap_v3" // BAD!
// pkg/parsers/uniswap_v3/parser.go
import "pkg/parsers/uniswap_v2" // BAD!
❌ Shared Mutable State
// BAD: Global shared state
var globalCache *PoolCache
func (p *Parser) Parse(log *types.Log) (*Event, error) {
pool := globalCache.Get(log.Address) // BAD!
}
❌ Hard-coded Dependencies
// BAD: Creates own dependencies
func NewParser() *Parser {
return &Parser{
cache: NewPoolCache(), // BAD! Should be injected
}
}
❌ Leaky Abstractions
// BAD: Exposes internal structure
type Parser interface {
GetInternalCache() *PoolCache // BAD! Leaks implementation
}
Migration Strategy
When moving from V1 to V2:
- Extract Interface: Define interface from V1 implementation
- Create Package: Move to standalone package
- Inject Dependencies: Replace hard dependencies with interfaces
- Add Tests: Unit tests for standalone operation
- Create Example: Standalone usage example
- Document: README with standalone and integrated usage
- Verify: Check all modularity criteria
Component Checklist
Before marking any component as "complete":
- Compiles independently
- Tests independently (>90% coverage)
- Has standalone example
- Has README with usage
- Zero sibling dependencies
- All dependencies injected through interfaces
- Can be mocked for testing
- Integrated into factory/orchestrator
- Integration tests pass
- Performance benchmarks exist
- Documentation complete
Principle: If you can't run it standalone, it's not modular enough. Guideline: If you can't mock it, it's too coupled. Rule: If it has circular dependencies, redesign it.