{"record":{"id":"b4a3073a2cdfd6cf","repo":"nautechsystems/nautilus_trader","slug":"token-token-is-not-an-input-token-in-the-configu","errorCode":null,"errorMessage":"Token {token} is not an input token in the configured `allowed_token_pairs`","messagePattern":"Token (.+?) is not an input token in the configured `allowed_token_pairs`","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":743,"sourceCode":"    /// finality, leaves the in-flight slot occupied.\n    pub async fn approve(\n        &mut self,\n        token: Address,\n        amount: U256,\n        router: Address,\n    ) -> anyhow::Result<B256> {\n        if !self.router_addresses.contains(&router) {\n            anyhow::bail!(\"Router {router} is not in the configured `router_addresses` allowlist\");\n        }\n\n        if !amount.is_zero()\n            && !self\n                .transaction_limits\n                .allowed_token_pairs\n                .iter()\n                .any(|(token_in, _)| *token_in == token)\n        {\n            anyhow::bail!(\n                \"Token {token} is not an input token in the configured `allowed_token_pairs`\"\n            );\n        }\n\n        self.ensure_transaction_ready(TransactionPurpose::Approve)?;\n\n        let approval_amount = if amount.is_zero() {\n            U256::ZERO\n        } else if self.config.unlimited_approval {\n            U256::MAX\n        } else {\n            amount\n        };\n        let calldata = ERC20::approveCall {\n            spender: router,\n            amount: approval_amount,\n        }\n        .abi_encode();","sourceCodeStart":725,"sourceCodeEnd":761,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/client.rs#L725-L761","documentation":"approve() only grants an ERC-20 allowance to a router if the token appears as an input token (`token_in`) in at least one pair of the configured `allowed_token_pairs` transaction limits, and the requested amount is nonzero. This confines token approvals to tokens the strategy is actually configured to trade, so a compromised or buggy code path cannot approve arbitrary tokens. A nonzero approval for any other token bails immediately.","triggerScenarios":"Calling `client.approve(token, amount, router)` with a nonzero `amount` where no entry in `transaction_limits.allowed_token_pairs` has that token as the first element (`token_in`). Note the guard is skipped entirely when `amount` is zero (revocation approvals are always allowed).","commonSituations":"Strategy starts trading a newly added instrument (e.g. a new USDC/WETH-style pair) without extending `allowed_token_pairs`; a typo in the token address in the limits config; approving the quote token when only the base token was configured as an input; approving a settlement/intermediate token that is not directly an input.","solutions":["Add the token as the `token_in` side of an entry in the `allowed_token_pairs` section of the transaction_limits configuration, then re-run approve().","Confirm the token address matches the one used in the configured pairs (Address equality is exact; check the token's actual on-chain address for that chain, not another chain's deployment).","If you only intended to revoke an allowance, pass `U256::ZERO` as the amount; the token-pairs check is skipped for zero approvals."],"exampleFix":"// before: approving a token not listed in limits\nclient.approve(new_token, U256::from(1_000_000), router).await?;\n\n// after: add (new_token, quote_token) to allowed_token_pairs in config first,\n// then approve\ndebug_assert!(limits.allowed_token_pairs.iter().any(|(t_in, _)| *t_in == new_token));\nclient.approve(new_token, U256::from(1_000_000), router).await?;","handlingStrategy":"validation","validationCode":"if !amount.is_zero()\n    && !limits.allowed_token_pairs.iter().any(|(token_in, _)| *token_in == token)\n{\n    anyhow::bail!(\"token {token} not an allowed input; extend allowed_token_pairs\");\n}\n// safe to call client.approve(token, amount, router).await?","typeGuard":"fn is_allowed_input(token: Address, limits: &TransactionLimits) -> bool {\n    limits.allowed_token_pairs.iter().any(|(t_in, _)| *t_in == token)\n}","tryCatchPattern":"if let Err(e) = client.approve(token, amount, router).await {\n    if e.to_string().contains(\"allowed_token_pairs\") {\n        warn!(\"token missing from transaction_limits; skipping approval\");\n        return Ok(()); // or surface for config review\n    }\n    return Err(e.into());\n}","preventionTips":["Whenever a new instrument is registered, add its token pair to allowed_token_pairs in the same change.","Approve only tokens the strategy actively trades; prefer targeted (non-unlimited) approvals.","Use zero-amount approvals for revocation since they bypass the token-pairs check."],"tags":["config","security","blockchain","transaction-limits","erc20-approval"],"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-14T05:17:10.506Z"}