The Solver service (also called “solver engine”) is the mathematical brain of the trading system. It receives auctions with on-chain liquidity and computes optimal trading routes to settle orders with the best possible prices.
This documentation covers the baseline solver implementation. External solvers may use different algorithms and strategies.
Purpose and Responsibilities
The Solver focuses purely on route-finding optimization:
Route Discovery : Finds trading paths through DEX liquidity
Order Matching : Identifies Coincidence of Wants (CoWs) between orders
Solution Scoring : Calculates objective value for each proposed solution
Partial Fills : Handles partially-fillable limit orders
Multi-hop Routing : Explores paths through intermediate tokens
What the Solver does NOT do:
❌ Fetch liquidity (handled by Driver)
❌ Encode settlements (handled by Driver)
❌ Submit transactions (handled by Driver)
❌ Manage gas prices (handled by Driver)
Baseline Solver Strategies
The baseline solver implements several strategies:
1. Direct Matching (CoW)
Matches orders directly when they overlap:
Order A: Sell 100 USDC for ≥0.9 WETH
Order B: Sell 1 WETH for ≥105 USDC
CoW Match: Trade 100 USDC ↔ 0.95 WETH
(Both orders get better than limit price)
2. Single-Hop Routing
Routes through one liquidity source:
Order: Sell 1000 DAI for WETH
↓
Uniswap V2: DAI → WETH
3. Multi-Hop Routing
Finds paths through intermediate tokens:
Order: Sell USDC for RARE_TOKEN
↓
Path: USDC → WETH → RARE_TOKEN
(Uniswap) (Balancer)
4. Partial Fill Optimization
For limit orders, finds optimal fill amount:
Limit Order: Sell up to 10 WETH for ≥20000 USDC
Solver tries:
- 1 WETH → Check price
- 2 WETH → Check price
- ... up to max_partial_attempts
Selects fill amount with best surplus
Route Finding Algorithms
Base Token Strategy
From crates/solvers/src/infra/config.rs:
pub struct Config {
pub weth : eth :: WethAddress ,
pub base_tokens : Vec < eth :: TokenAddress >,
pub max_hops : usize ,
pub max_partial_attempts : usize ,
pub solution_gas_offset : eth :: Gas ,
pub native_token_price_estimation_amount : eth :: U256 ,
pub uni_v3_node_url : Option < Url >,
}
Base tokens are high-liquidity intermediaries:
base-tokens = [
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" , # WETH (always included)
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" , # USDC
"0x6B175474E89094C44Da98b954EedeAC495271d0F" , # DAI
]
Path Exploration
For token pair (A, B), solver explores:
Direct routes : A → B on each liquidity source
1-hop routes : A → base_token → B
2-hop routes : A → base_token_1 → base_token_2 → B
… up to max_hops
Increasing max_hops improves price discovery but exponentially increases computation time.
Gas Accounting
Solver accounts for execution costs:
/// Units of gas added to the trade route estimate
/// to arrive at a full settlement gas estimate.
solution_gas_offset : i64 ,
Default is ~106,391 gas (settlement overhead).
External Solver Integration
The Driver can integrate external solvers via HTTP API:
Solver Endpoint Configuration
# In driver config
[[ solver ]]
name = "external-solver"
endpoint = "http://solver.example.com:7872"
relative-slippage = "0.1"
account = "0x..."
API Interface
External solvers must implement:
POST /solve
Request:
{
"id" : "123456" ,
"orders" : [
{
"uid" : "0xabc..." ,
"sellToken" : "0x..." ,
"buyToken" : "0x..." ,
"sellAmount" : "1000000000000000000" ,
"buyAmount" : "2000000000" ,
"kind" : "sell" ,
"partiallyFillable" : false
}
],
"liquidity" : [
{
"kind" : "uniswapV2" ,
"tokens" : [ "0x..." , "0x..." ],
"reserves" : [ "1000000000" , "2000000000" ]
}
]
}
Response:
{
"solutions" : [
{
"id" : "1" ,
"trades" : [
{
"order" : "0xabc..." ,
"executedAmount" : "1000000000000000000"
}
],
"interactions" : [
{
"kind" : "uniswapV2Swap" ,
"tokenIn" : "0x..." ,
"tokenOut" : "0x..." ,
"amountIn" : "1000000000000000000"
}
]
}
]
}
Pass custom headers to solver:
[ solver . request-headers ]
authorization = "Bearer SECRET_TOKEN"
x-api-key = "YOUR_API_KEY"
Response Limits
Control response size:
response-size-limit-max-bytes = 30000000 # 30MB
Configuration Options
Command-Line Arguments
From crates/solvers/src/infra/cli.rs:
Argument Environment Variable Default Description --addrADDR127.0.0.1:7872HTTP server address --logLOGwarn,solvers=debugLog filter --use-json-logsUSE_JSON_LOGSfalseJSON log format baseline --configCONFIGRequired Path to config TOML
Configuration File
Example baseline solver config from crates/solvers/config/example.baseline.toml:
chain-id = "1" # Ethereum mainnet
# Alternatively: weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
base-tokens = []
max-hops = 0
max-partial-attempts = 5
native-token-price-estimation-amount = "100000000000000000" # 0.1 ETH
# solution-gas-offset = 106391 # Optional override
Configuration parameters:
Chain ID for WETH contract lookup (1=mainnet, 100=gnosis, etc.)
Manual WETH address override (use instead of chain-id)
Additional base tokens for routing (WETH always included)
Maximum route length. 0 = direct only, 1 = one intermediate token, etc.
Number of fill amounts to try for partial-fill orders
native-token-price-estimation-amount
Amount of native token used for price estimation
Gas overhead added to route gas estimates
Optional: Ethereum node for Uniswap V3 liquidity (RPC-based)
Running the Service
Standalone Solver
Run baseline solver independently:
Create Configuration
Create solver-config.toml: chain-id = "1"
base-tokens = [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" , # USDC
"0x6B175474E89094C44Da98b954EedeAC495271d0F" , # DAI
]
max-hops = 2
max-partial-attempts = 5
native-token-price-estimation-amount = "100000000000000000"
Start Solver
cargo run --bin solvers -- \
--addr 127.0.0.1:7872 \
baseline --config /path/to/solver-config.toml
Test
Verify it’s running: curl http://127.0.0.1:7872/metrics
With Driver
Configure driver to use the solver:
# driver-config.toml
[[ solver ]]
name = "baseline"
endpoint = "http://127.0.0.1:7872"
relative-slippage = "0.05"
account = "0x0000000000000000000000000000000000000000000000000000000000000001"
merge-solutions = true
Start both services:
# Terminal 1: Solver
cargo run --bin solvers -- baseline --config solver-config.toml
# Terminal 2: Driver
cargo run --bin driver -- --config driver-config.toml --ethrpc $RPC_URL
Docker Deployment
FROM ghcr.io/cowprotocol/services:latest
COPY solver-config.toml /config.toml
EXPOSE 7872
CMD [ "solvers" , \
"--addr" , "0.0.0.0:7872" , \
"baseline" , \
"--config" , "/config.toml" ]
Algorithm Tuning
Route Quality vs Speed
Balance solution quality against computation time:
Fast max-hops = 0
max-partial-attempts = 3
base-tokens = [] # Direct trades only
Best for high-frequency auctions
Optimal max-hops = 2
max-partial-attempts = 10
base-tokens = [ "0x..." , "0x..." ] # Multiple bases
Best for maximizing surplus
Token-Specific Optimization
For tokens with poor liquidity, increase exploration:
max-hops = 3 # More routing options
base-tokens = [
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" , # WETH
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" , # USDC
"0x6B175474E89094C44Da98b954EedeAC495271d0F" , # DAI
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" , # WBTC
]
Partial Fill Tuning
For limit-heavy markets:
max-partial-attempts = 15 # Try more fill amounts
Monitor solver latency in logs. If consistently hitting deadline, reduce max-hops or max-partial-attempts.
Monitoring and Debugging
Metrics
Prometheus metrics available at /metrics:
solver_computation_time_seconds - Time to compute solutions
solver_solutions_found - Number of solutions per auction
solver_routes_explored - Paths evaluated
solver_partial_fills_attempted - Partial fill calculations
Logging
Enable detailed solver logs:
export LOG = "warn,solvers=debug,solver=trace"
Key log events:
Route discovery progress
Solution scoring
Partial fill attempts
Performance timings
Profiling
For performance analysis:
# Enable heap profiling (jemalloc)
export MALLOC_CONF = prof : true , prof_prefix : jeprof . out
cargo run --bin solvers -- baseline --config config.toml
Advanced Features
Uniswap V3 Integration
Enable RPC-based Uniswap V3 liquidity:
uni-v3-node-url = "https://mainnet.infura.io/v3/YOUR_KEY"
Uniswap V3 integration requires direct RPC access and may slow down solving. Use for better price discovery when needed.
Solution Merging
Driver can merge multiple solutions from the same solver:
# In driver config
[[ solver ]]
merge-solutions = true # Combine non-conflicting solutions
Useful when solver generates multiple independent routes.
Custom Solver Strategies
Implement custom solving logic by:
Creating new solver binary
Implementing /solve API endpoint
Returning solutions in standard format
Configuring in driver
See solver API interface section for details.
Computation Complexity
Route exploration is exponential in max-hops:
max-hops = 0: O(n) routes (n = liquidity sources)
max-hops = 1: O(n * b) routes (b = base tokens)
max-hops = 2: O(n * b²) routes
Recommendation: Start with max-hops = 1 and increase only if needed.
Memory Usage
Partial fill optimization creates solution copies:
max-partial-attempts = 5 # 5x memory per limit order
For auctions with many limit orders, consider reducing this value.
Deadline Management
Ensure solver completes before driver deadline:
Driver deadline: 15s
Solver target: <12s (20% buffer)
Troubleshooting
No Solutions Found
Check:
Liquidity data is present in auction
Orders are within slippage bounds
Base tokens configured correctly
max-hops allows necessary routing
Solver Timeout
Reduce computation:
max-hops = 0 # Simplify routes
max-partial-attempts = 3 # Fewer iterations
Poor Quality Solutions
Increase exploration:
max-hops = 2 # More routing options
base-tokens = [ ...] # Add more intermediate tokens
High Memory Usage
Limit partial fill attempts:
max-partial-attempts = 3 # Reduce solution copies
Driver - Handles liquidity fetching and execution
Autopilot - Distributes auctions to solvers
Orderbook - Source of orders
Further Reading