nautechsystems/nautilus_trader · error

Quote spend limit for token {} uses {} decimals, expected po

Error message

Quote spend limit for token {} uses {} decimals, expected pool quote-token decimals {}

What it means

The quote-spend ceiling's decimal precision must match the pool quote token's decimals so the cap amount is interpreted in the same units as the spend. A mismatch would silently under/over-cap, so it is rejected.

Source

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

        }

        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
            .core
            .cache()
            .pool_profiler(&instrument_id)
            .cloned()
            .ok_or_else(|| {
                anyhow::anyhow!(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set `spend_token_decimals` to the pool quote token's actual decimals in the spend-limit config
  2. Re-derive decimals from the token contract/metadata instead of hardcoding
  3. If the address matched but decimals differ, suspect a proxy/scam token — verify the token before trading

Example fix

// before
spend_token_decimals: 18 // USDC
// after
spend_token_decimals: 6
Defensive patterns

Strategy: validation

Validate before calling

let ceiling = limits.get(&(token_in, token_out)).unwrap();
if ceiling.spend_token_decimals != quote_token.decimals {
    return Err(format!("decimals {} != {}", ceiling.spend_token_decimals, quote_token.decimals));
}

Type guard

fn decimals_match(ceiling: &Ceiling, quote: &Token) -> bool {
    ceiling.spend_token_decimals == quote.decimals
}

Try / catch

match client.submit_order(buy_order).await {
    Err(e) if e.to_string().contains("decimals") => {
        // refetch token metadata and regenerate the ceiling, then retry
        regenerate_spend_limit(&quote_token)?;
        client.submit_order(buy_order).await
    }
    r => r,
}

Prevention

When it happens

Trigger: `prepare_swap()` on a BUY order where `ceiling.spend_token_decimals != quote_token.decimals` (token matched by address but decimals configured wrong, or ceiling reused across wrapped/underlying variants).

Common situations: Configured 18 decimals for USDC/USDT (6); ceiling entry copied from a wrapped token (WETH 18) to base token; upstream token contract changed decimals metadata.

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/93ac09ee2ea7e9c9. Report an issue: GitHub.