nautechsystems/nautilus_trader · error
Quote spend limit pair {token_in} -> {token_out} is not in t
Error message
Quote spend limit pair {token_in} -> {token_out} is not in the `allowed_token_pairs` allowlist What it means
Each quote_spend_limits entry's (token_in, token_out) pair must appear in the allowed_token_pairs allowlist parsed from config. The library throws this when a spend limit references a pair that is not whitelisted, because spend limits only constrain pairs the client is allowed to trade.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:297
};
let mut parsed_pairs = HashSet::with_capacity(allowed_token_pairs.len());
for (token_in, token_out) in allowed_token_pairs {
parsed_pairs.insert((
validate_address(token_in.as_str())?,
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
);
}View on GitHub (pinned to 18893faf8b)
Solutions
- Add the limit's (token_in, token_out) pair to config.allowed_token_pairs
- Check the pair ordering matches between allowed_token_pairs and quote_spend_limits
- Verify the addresses in both lists refer to the same contracts on the same chain
Example fix
// before
allowed_token_pairs = []
quote_spend_limits = [{ token_in = "0xA", token_out = "0xB", spend_token = "0xA", max_amount = "1000" }]
// after
allowed_token_pairs = [["0xA", "0xB"]]
quote_spend_limits = [{ token_in = "0xA", token_out = "0xB", spend_token = "0xA", max_amount = "1000" }] Defensive patterns
Strategy: validation
Validate before calling
let allowed: HashSet<(String, String)> = cfg.allowed_token_pairs.iter().cloned().collect();
for l in &cfg.quote_spend_limits {
if !allowed.contains(&(l.token_in.clone(), l.token_out.clone())) {
return Err(format!("spend limit pair {} -> {} not in allowed_token_pairs", l.token_in, l.token_out));
}
} Prevention
- Generate quote_spend_limits from allowed_token_pairs so they cannot diverge
- Normalize (checksum) all addresses once at config load
- Keep one source of truth for tradable pairs
When it happens
Trigger: Calling new/transaction_limits with config.quote_spend_limits containing a limit whose (token_in, token_out) after address validation does not match any entry in config.allowed_token_pairs (case/representation differences in raw strings are normalized by validate_address, so the mismatch must be a genuinely different pair).
Common situations: Operator adds a spend limit but forgets to add the pair to allowed_token_pairs; copy-paste of token addresses between chains; swapped token_in/token_out order in one of the two lists.
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
- request_rate_per_second must be greater than zero
- order_request_rate_per_second must be greater than zero
- heartbeat_secs must be positive when set
- heartbeat_timeout_secs must cover at least two server heartb
- `router_addresses` must contain at least one router address
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/0a38098ce2df3dbf.
Report an issue: GitHub.