nautechsystems/nautilus_trader · error

Quote spend limit for {token_in} -> {token_out} is denominat

Error message

Quote spend limit for {token_in} -> {token_out} is denominated in {}, expected quote token {}

What it means

For BUY swaps the configured quote-spend ceiling must be denominated in the pool's actual quote token. If `ceiling.spend_token` differs from `quote_token.address`, the cap would limit the wrong asset, so the swap is rejected.

Source

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

        if slippage_bps > self.transaction_limits.max_slippage_bps {
            anyhow::bail!(
                "Slippage {slippage_bps} bps exceeds the configured `max_slippage_bps` {}",
                self.transaction_limits.max_slippage_bps
            );
        }

        let quote_spend_ceiling = if order.order_side() == OrderSide::Buy {
            let ceiling = self
                .transaction_limits
                .quote_spend_limits
                .get(&(token_in, token_out))
                .ok_or_else(|| {
                    anyhow::anyhow!(
                        "No `quote_spend_limits` entry for BUY token pair {token_in} -> {token_out}"
                    )
                })?;
            anyhow::ensure!(
                ceiling.spend_token == quote_token.address,
                "Quote spend limit for {token_in} -> {token_out} is denominated in {}, expected quote token {}",
                ceiling.spend_token,
                quote_token.address
            );
            anyhow::ensure!(
                ceiling.spend_token_decimals == quote_token.decimals,
                "Quote spend limit for token {} uses {} decimals, expected pool quote-token decimals {}",
                ceiling.spend_token,
                ceiling.spend_token_decimals,
                quote_token.decimals
            );
            Some(ceiling)
        } else {
            None
        };

        let profiler = self

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Correct the ceiling's `spend_token` to the pool's actual quote-token address
  2. Fix the (token_in, token_out) key so the ceiling matches the pair actually being swapped
  3. Recompute the spend limit if the real quote token has different decimals/value

Example fix

// before
spend_token: USDT_ADDRESS // pool quote is USDC
// after
spend_token: USDC_ADDRESS // matches quote_token.address
Defensive patterns

Strategy: validation

Validate before calling

let ceiling = limits.get(&(token_in, token_out)).ok_or("missing ceiling")?;
if ceiling.spend_token != quote_token.address {
    return Err("ceiling denominated in wrong token".into());
}

Type guard

fn ceiling_matches_quote(ceiling: &Ceiling, quote: &Token) -> bool {
    ceiling.spend_token == quote.address
}

Try / catch

match client.submit_order(buy_order).await {
    Err(e) if e.to_string().contains("denominated in") => {
        reload_spend_limits_for_pair(token_in, token_out)?;
        client.submit_order(buy_order).await
    }
    r => r,
}

Prevention

When it happens

Trigger: `prepare_swap()` on a BUY order where the `quote_spend_limits` entry's `spend_token` address does not equal the pool's quote-token address.

Common situations: Pool where the configured 'quote' differs from the actual pool quote token (e.g. USDT vs USDC pair swapped); copy-pasted limit entry between markets; token address checksum/case mismatch.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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