nautechsystems/nautilus_trader · error

Slippage {slippage_bps} bps must be below {BPS_DENOMINATOR}

Error message

Slippage {slippage_bps} bps must be below {BPS_DENOMINATOR}

What it means

derive_min_amount_out reduces the quoted output by slippage_bps basis points to compute the minimum accepted output. BPS_DENOMINATOR is 10_000 (100%), so a slippage of 10_000 bps or more would drive the minimum to zero or negative, removing slippage protection entirely; such values are rejected before any math runs.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:2490

fn exact_output_amount(quote: &SwapQuote, zero_for_one: bool) -> anyhow::Result<U256> {
    let amount = if zero_for_one {
        quote.amount1
    } else {
        quote.amount0
    };

    if !amount.is_negative() {
        anyhow::bail!("Swap quote output amount {amount} is not a positive output");
    }
    Ok(amount.unsigned_abs())
}

/// Derives the minimum output accepted for the swap: the quoted output reduced by
/// `slippage_bps`, in exact integer arithmetic. Rejects a zero minimum, which would leave
/// the swap without slippage protection.
fn derive_min_amount_out(quoted_amount_out: U256, slippage_bps: u32) -> anyhow::Result<U256> {
    if slippage_bps >= BPS_DENOMINATOR {
        anyhow::bail!("Slippage {slippage_bps} bps must be below {BPS_DENOMINATOR}");
    }
    let min_amount_out = quoted_amount_out
        .checked_mul(U256::from(BPS_DENOMINATOR - slippage_bps))
        .and_then(|scaled| scaled.checked_div(U256::from(BPS_DENOMINATOR)))
        .ok_or_else(|| anyhow::anyhow!("Minimum output derivation overflow"))?;
    if min_amount_out.is_zero() {
        anyhow::bail!(
            "Derived minimum output is zero for quoted output {quoted_amount_out} at {slippage_bps} bps slippage"
        );
    }
    Ok(min_amount_out)
}

#[async_trait(?Send)]
impl ExecutionClient for BlockchainExecutionClient {
    fn is_connected(&self) -> bool {
        self.core.is_connected()
    }

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Pass slippage in basis points strictly below 10_000: 0.5% = 50, 1% = 100, 5% = 500
  2. Convert percent configs at the boundary: slippage_bps = (percent * 100) as u32, and assert it is < 10_000
  3. Sanity-check the value in tests/config validation so the mistake surfaces at startup, not mid-swap

Example fix

// before: percent used as bps / 100% tolerance
let slippage_bps = 50;   // user meant 50 percent -> 5000 is still fine, but 10000 is not
let slippage_bps = 10000; // Err: must be below 10000

// after: convert percent -> bps and keep it under 100%
let percent = 0.5;
let slippage_bps = (percent * 100.0) as u32;
assert!(slippage_bps < 10_000);
Defensive patterns

Strategy: validation

Validate before calling

const BPS_DENOMINATOR: u32 = 10_000;
anyhow::ensure!(slippage_bps < BPS_DENOMINATOR, "slippage {slippage_bps} bps must be < {BPS_DENOMINATOR}");

Type guard

fn valid_slippage_bps(bps: u32) -> bool { bps < 10_000 }

Try / catch

// Pure input error: correct the value at the call site; retrying with the same bps
// always fails. Fail fast at config load instead of catching here.

Prevention

When it happens

Trigger: Passing slippage_bps >= 10_000 to the swap execution path: a value of 10_000 meant as '100% tolerance', a percentage accidentally used as bps (e.g. 50 for 50%), or a fraction scaled wrongly (0.5 passed instead of 50).

Common situations: Config files that express slippage in percent while the API expects bps; strategies copying slippage from a different venue's units; defensive 'very high' slippage values like 100000 used in volatile markets.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/3c7047c5860f0de3. Report an issue: GitHub.