nautechsystems/nautilus_trader · error · anyhow::Error
Ticks {tick_lower} and {tick_upper} must be multiples of the
Error message
Ticks {tick_lower} and {tick_upper} must be multiples of the tick spacing What it means
This error is thrown by `validate_ticks` in the liquidity profiler when either the lower or upper tick of a position is not an integer multiple of the pool's tick spacing. Concentrated-liquidity pools (e.g. Uniswap v3) only accept positions whose tick boundaries align to the pool's configured tick spacing, so the profiler rejects any mint/burn with misaligned ticks before analytics are updated.
Source
Thrown at crates/model/src/defi/pool_analysis/profiler.rs:1628
///
/// Ensures ticks are properly ordered, aligned to tick spacing, and within
/// valid bounds. Used by all position-related operations.
///
/// # Errors
///
/// This function returns an error if:
/// - `tick_lower >= tick_upper` (invalid range).
/// - Ticks are not multiples of pool's tick spacing.
/// - Ticks are outside `MIN_TICK/MAX_TICK` bounds.
fn validate_ticks(&self, tick_lower: i32, tick_upper: i32) -> anyhow::Result<()> {
if tick_lower >= tick_upper {
anyhow::bail!("Invalid tick range: {tick_lower} >= {tick_upper}")
}
if tick_lower % self.pool.tick_spacing.unwrap() as i32 != 0
|| tick_upper % self.pool.tick_spacing.unwrap() as i32 != 0
{
anyhow::bail!(
"Ticks {tick_lower} and {tick_upper} must be multiples of the tick spacing"
)
}
if tick_lower < PoolTick::MIN_TICK || tick_upper > PoolTick::MAX_TICK {
anyhow::bail!("Invalid tick bounds for {tick_lower} and {tick_upper}");
}
Ok(())
}
/// Updates all liquidity analytics.
fn update_liquidity_analytics(&mut self) {
self.analytics.liquidity_utilization_rate = self.liquidity_utilization_rate();
}
/// Returns the pool's active liquidity tracked by the tick map.
///
/// This represents the effective liquidity available for trading at the current price.View on GitHub (pinned to 18893faf8b)
Solutions
- Round tick_lower/tick_upper to the pool's tick spacing (floor toward MIN_TICK for lower, ceiling toward MAX_TICK for upper) before calling mint/burn handlers.
- Fetch the correct `tick_spacing` for the specific pool rather than assuming a value; ensure `pool.tick_spacing` is populated (it is unwrapped here).
- Use existing tick-math helpers (e.g. nearest usable tick functions) to derive ticks from a price range.
- Log the offending ticks and spacing to confirm alignment before retrying the event processing.
Example fix
// before let (tick_lower, tick_upper) = (lower_from_price, upper_from_price); profiler.execute_mint(owner, tick_lower, tick_upper, amount); // after let spacing = pool.tick_spacing.unwrap() as i32; let tick_lower = (lower_from_price / spacing) * spacing; let tick_upper = ((upper_from_price + spacing - 1) / spacing) * spacing; profiler.execute_mint(owner, tick_lower, tick_upper, amount);
Defensive patterns
Strategy: validation
Validate before calling
fn ticks_aligned(tick_lower: i32, tick_upper: i32, tick_spacing: i32) -> bool {
tick_spacing > 0
&& tick_lower % tick_spacing == 0
&& tick_upper % tick_spacing == 0
&& tick_lower < tick_upper
} Try / catch
match profiler.execute_mint(owner, tick_lower, tick_upper, amount) {
Ok(_) => {},
Err(e) if e.to_string().contains("must be multiples of the tick spacing") => {
// recompute ticks rounded to spacing and retry
},
Err(e) => return Err(e),
} Prevention
- Always derive ticks from prices using spacing-aware rounding helpers, never raw tick math.
- Look up the pool's actual tick_spacing per pool instead of hardcoding a value.
- Unit-test mint/burn event handlers against pools of several fee tiers/spacings.
- Ensure tick_spacing is populated before processing events (it is unwrapped in the check).
When it happens
Trigger: Calling `process_mint`, `execute_mint`, `process_burn`, or `execute_burn` with `tick_lower` or `tick_upper` where `tick % tick_spacing != 0` (tick_spacing read from `self.pool.tick_spacing`, which is also unwrapped and would panic if None). The ticks may individually be valid but still misaligned, e.g. lower=10, upper=210 with tick_spacing 60.
Common situations: Parsing on-chain Mint/Burn events from pools with non-standard tick spacing; hardcoding tick ranges copied from a pool with a different fee tier/spacing; computing ticks via arithmetic that doesn't round to spacing (e.g. price-to-tick conversion without rounding down to the nearest valid tick).
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Invalid tick range: {tick_lower} >= {tick_upper}
- Invalid tick bounds for {tick_lower} and {tick_upper}
- RPC snapshot contains duplicate ticks
- Slippage {actual_slippage} bps exceeds tolerance {max_slippa
- Insufficient liquidity: requested {amount_out_requested}, av
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/653e3f5d7ee4877e.
Report an issue: GitHub.