nautechsystems/nautilus_trader · error · anyhow::Error
No liquidity
Error message
No liquidity
What it means
update_flash_state advances fee-growth accounting during a flash loan/flash swap and requires in-range liquidity L > 0, because fee growth-per-liquidity values are divided by L. If the pool has no active liquidity the update is impossible, so it bails with "No liquidity". process_flash and execute_flash surface this to the caller.
Source
Thrown at crates/model/src/defi/pool_analysis/profiler.rs:1411
amount1,
paid0,
paid1,
);
Ok(flash_event)
}
/// Core flash loan state update logic.
///
/// # Errors
///
/// Returns error if:
/// - No active liquidity in pool
/// - Fee growth arithmetic overflows
fn update_flash_state(&mut self, paid0: U256, paid1: U256) -> anyhow::Result<()> {
let liquidity = self.tick_map.liquidity;
if liquidity == 0 {
anyhow::bail!("No liquidity")
}
let fee_protocol_0 = self.state.uniswap_v3_fee_protocol(true);
let fee_protocol_1 = self.state.uniswap_v3_fee_protocol(false);
let fee_protocol0_basis_points = self.state.fee_protocol_basis_points(true);
let fee_protocol1_basis_points = self.state.fee_protocol_basis_points(false);
// Process token0 fees
if paid0 > U256::ZERO {
let protocol_fee_0 =
Self::protocol_fee_delta(paid0, fee_protocol_0, fee_protocol0_basis_points)?;
if protocol_fee_0 > U256::ZERO {
self.state.protocol_fees_token0 += protocol_fee_0;
}
let lp_fee_0 = paid0 - protocol_fee_0;
let delta = FullMath::mul_div(lp_fee_0, Q128, U256::from(liquidity))?;View on GitHub (pinned to 18893faf8b)
Solutions
- Check the pool has active liquidity (tick_map.liquidity > 0) before running flash simulations and skip pools that are empty
- Initialize the profiler from a point where liquidity was already minted (skip pre-mint blocks)
- Treat the error as 'pool not tradeable' in portfolio logic and exclude the pool from analysis
- Verify the tick map/liquidity snapshot was loaded correctly if the pool is known to have liquidity
Example fix
// before
profiler.process_flash(&flash_event)?;
// after
if !profiler.has_active_liquidity() {
return Ok(None); // nothing to accrue fees against
}
profiler.process_flash(&flash_event)?; Defensive patterns
Strategy: validation
Validate before calling
if profiler.tick_map_liquidity() == 0 {
return Ok(None); // pool empty at current tick
} Try / catch
match profiler.process_flash(&event) {
Err(e) if e.to_string() == "No liquidity" => { /* skip empty pool */ }
other => other?,
} Prevention
- Skip flash simulations on pools with zero in-range liquidity
- Start replay after the pool's first mint
- Maintain a pool-liquidity index to filter empty pools early
- Re-verify snapshots if a known-liquid pool reports zero liquidity
When it happens
Trigger: Running a flash simulation (process_flash/execute_flash) against a pool whose tick_map.liquidity is 0 — e.g. an empty range at the current tick, a pool with only out-of-range positions, or an uninitialized/empty pool snapshot.
Common situations: Profiling illiquid or new pools with no positions spanning the current tick; replaying a block range before the first mint; pools whose liquidity was all burned before the flash event.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Position liquidity {} is less than the requested burn amount
- Liquidity addition overflow: x={current}, y={y}, delta={delt
- Liquidity subtraction underflow: x={current}, y={y}, delta={
- Cannot quote swap with zero amount
- Price limit must be less than current price for zero_for_one
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/14cfcb7de96ea568.
Report an issue: GitHub.