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 {spend_token}; `spend_token` must match `token_in`

What it means

Quote spend limits must be denominated in the input token: spend_token must equal token_in. The library throws this when a limit's spend_token address differs from its token_in, since the client caps spend of the token being sold into the swap.

Source

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

                validate_address(token_out.as_str())?,
            ));
        }

        let quote_spend_limits = config.quote_spend_limits.as_deref().unwrap_or_default();
        let mut parsed_quote_spend_limits = HashMap::with_capacity(quote_spend_limits.len());
        for limit in quote_spend_limits {
            let token_in = validate_address(limit.token_in.as_str())?;
            let token_out = validate_address(limit.token_out.as_str())?;
            let spend_token = validate_address(limit.spend_token.as_str())?;

            if !parsed_pairs.contains(&(token_in, token_out)) {
                anyhow::bail!(
                    "Quote spend limit pair {token_in} -> {token_out} is not in the `allowed_token_pairs` allowlist"
                );
            }

            if spend_token != token_in {
                anyhow::bail!(
                    "Quote spend limit for {token_in} -> {token_out} is denominated in {spend_token}; `spend_token` must match `token_in`"
                );
            }

            if limit.max_amount.is_empty()
                || !limit.max_amount.bytes().all(|byte| byte.is_ascii_digit())
            {
                anyhow::bail!(
                    "Quote spend limit `max_amount` '{}' must be a base-10 unsigned integer string",
                    limit.max_amount
                );
            }
            let max_amount = U256::from_str(&limit.max_amount).map_err(|_| {
                anyhow::anyhow!(
                    "Quote spend limit `max_amount` '{}' exceeds the U256 range",
                    limit.max_amount
                )
            })?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set spend_token in the limit entry to the same address as token_in
  2. Re-check the limit's semantics: it caps the amount of token_in spent per quote
  3. Fix any address copy-paste mix-ups between fields

Example fix

// before
{ token_in = "0xWETH", token_out = "0xUSDC", spend_token = "0xUSDC", max_amount = "1000000000" }
// after
{ token_in = "0xWETH", token_out = "0xUSDC", spend_token = "0xWETH", max_amount = "1000000000000000000" }
Defensive patterns

Strategy: validation

Validate before calling

for l in &cfg.quote_spend_limits {
    if !l.spend_token.eq_ignore_ascii_case(&l.token_in) {
        return Err(format!("spend_token {} must equal token_in {}", l.spend_token, l.token_in));
    }
}

Prevention

When it happens

Trigger: Calling new/transaction_limits with a config.quote_spend_limits entry where validate_address(limit.spend_token) != validate_address(limit.token_in).

Common situations: Setting spend_token to the output token (USDC) instead of the input token (WETH); copy-paste of addresses in the wrong field; assuming the limit applies to proceeds rather than spend.

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