{"record":{"id":"0a38098ce2df3dbf","repo":"nautechsystems/nautilus_trader","slug":"quote-spend-limit-pair-token-in-token-out-i","errorCode":null,"errorMessage":"Quote spend limit pair {token_in} -> {token_out} is not in the `allowed_token_pairs` allowlist","messagePattern":"Quote spend limit pair (.+?) -> (.+?) is not in the `allowed_token_pairs` allowlist","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":297,"sourceCode":"        };\n\n        let mut parsed_pairs = HashSet::with_capacity(allowed_token_pairs.len());\n        for (token_in, token_out) in allowed_token_pairs {\n            parsed_pairs.insert((\n                validate_address(token_in.as_str())?,\n                validate_address(token_out.as_str())?,\n            ));\n        }\n\n        let quote_spend_limits = config.quote_spend_limits.as_deref().unwrap_or_default();\n        let mut parsed_quote_spend_limits = HashMap::with_capacity(quote_spend_limits.len());\n        for limit in quote_spend_limits {\n            let token_in = validate_address(limit.token_in.as_str())?;\n            let token_out = validate_address(limit.token_out.as_str())?;\n            let spend_token = validate_address(limit.spend_token.as_str())?;\n\n            if !parsed_pairs.contains(&(token_in, token_out)) {\n                anyhow::bail!(\n                    \"Quote spend limit pair {token_in} -> {token_out} is not in the `allowed_token_pairs` allowlist\"\n                );\n            }\n\n            if spend_token != token_in {\n                anyhow::bail!(\n                    \"Quote spend limit for {token_in} -> {token_out} is denominated in {spend_token}; `spend_token` must match `token_in`\"\n                );\n            }\n\n            if limit.max_amount.is_empty()\n                || !limit.max_amount.bytes().all(|byte| byte.is_ascii_digit())\n            {\n                anyhow::bail!(\n                    \"Quote spend limit `max_amount` '{}' must be a base-10 unsigned integer string\",\n                    limit.max_amount\n                );\n            }","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/client.rs#L279-L315","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nallowed_token_pairs = []\nquote_spend_limits = [{ token_in = \"0xA\", token_out = \"0xB\", spend_token = \"0xA\", max_amount = \"1000\" }]\n// after\nallowed_token_pairs = [[\"0xA\", \"0xB\"]]\nquote_spend_limits = [{ token_in = \"0xA\", token_out = \"0xB\", spend_token = \"0xA\", max_amount = \"1000\" }]","handlingStrategy":"validation","validationCode":"let allowed: HashSet<(String, String)> = cfg.allowed_token_pairs.iter().cloned().collect();\nfor l in &cfg.quote_spend_limits {\n    if !allowed.contains(&(l.token_in.clone(), l.token_out.clone())) {\n        return Err(format!(\"spend limit pair {} -> {} not in allowed_token_pairs\", l.token_in, l.token_out));\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["config","validation","allowlist"],"backgroundTag":"invalid-config-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}