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

  1. Check the pool has active liquidity (tick_map.liquidity > 0) before running flash simulations and skip pools that are empty
  2. Initialize the profiler from a point where liquidity was already minted (skip pre-mint blocks)
  3. Treat the error as 'pool not tradeable' in portfolio logic and exclude the pool from analysis
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/14cfcb7de96ea568. Report an issue: GitHub.