This document explains the key design decisions in the Solana Execution Engine.
Use traits (Strategy, DexClient, WalletSelector, Notifier) instead of concrete implementations throughout the engine.
- Testability: Mock implementations can be injected for unit testing without RPC calls
- Extensibility: New DEXes (Orca, Raydium) or strategies (TWAP, Grid) can be added without modifying the engine
- Compile-time safety: Generic bounds ensure all required capabilities are implemented
- Slightly more boilerplate than direct implementation
- Requires understanding of Rust's trait system
Use an enum for ExecutionState with explicit transition validation rather than boolean flags.
- Impossible states are unrepresentable: Can't be both "paused" and "stopped"
- Self-documenting: The enum variants clearly show all possible states
- Exhaustive matching: Compiler ensures all states are handled
pub enum ExecutionState {
Idle,
Initializing,
Running,
Paused,
Stopping,
Stopped,
Error,
}- More explicit than checking
if pausedorif running - Requires calling
transition()method instead of direct assignment
Use tokio::select! with broadcast channels for shutdown instead of Arc<AtomicBool>.
- Cancellation safety: Pending operations are properly dropped on shutdown
- No polling: Shutdown signal is event-driven, not checked in a loop
- Multiple subscribers: All components can listen to the same shutdown broadcast
tokio::select! {
_ = shutdown_rx.recv() => break,
result = execute_next() => handle(result),
}- Requires understanding of async cancellation semantics
- Slightly more complex than simple boolean flags
Wrap DexClient in a CircuitBreaker middleware rather than handling failures inline.
- Separation of concerns: Error handling logic is decoupled from business logic
- Configurable policies: Different clients can have different thresholds
- Observability: Metrics track failure rates, rejections, recovery
- Additional indirection layer
- Requires understanding of the Circuit Breaker pattern
Use thiserror to define a custom error enum rather than anyhow or string errors.
- Programmatic handling: Callers can
matchon specific error variants - Context preservation: Each variant carries relevant context (signature, pubkey, etc.)
- Zero overhead: No heap allocation for simple errors
pub enum Error {
Config { message: String, source: Option<Box<dyn Error>> },
Dex { message: String, source: Option<Box<dyn Error>> },
RateLimit { message: String, retry_after_ms: Option<u64> },
// ...
}- More boilerplate than
anyhow - Requires defining variants upfront
Use a Cargo workspace with separate engine (library) and cli (binary) crates.
- Separation of concerns: Library code is independent of CLI
- Faster compilation: CLI only recompiles when it changes
- Reusable: Engine can be embedded in other projects (TUI, web service)
- More complex directory structure
- Two
Cargo.tomlfiles to maintain
- Backtesting: Add
BacktestDexClientthat reads historical data - Metrics: Integrate with Prometheus/OpenMetrics
- Persistence: Add SQLite backend for order history