{"record":{"id":"9187bb937beebe2b","repo":"nautechsystems/nautilus_trader","slug":"router-allowance-after-transaction-does-not-equ","errorCode":null,"errorMessage":"Router allowance after transaction {} does not equal the requested amount {amount}: was {}","messagePattern":"Router allowance after transaction (.+?) does not equal the requested amount (.+?): was (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":4577,"sourceCode":"            .verification\n            .verify_decoded_call(\n                None,\n                token,\n                U256::ZERO,\n                &call,\n                included.block_number,\n                |result| ERC20::allowanceCall::abi_decode_returns(result).map_err(Into::into),\n            )\n            .await,\n        \"router allowance after finality\",\n    )\n    .with_context(|| {\n        format!(\n            \"failed to verify router allowance after included transaction {} at block {}\",\n            included.tx_hash, included.block_number\n        )\n    })?;\n    anyhow::ensure!(\n        allowance.value == amount,\n        \"Router allowance after transaction {} does not equal the requested amount {amount}: was {}\",\n        included.tx_hash,\n        allowance.value\n    );\n\n    Ok(vec![verification_decision(\n        &allowance,\n        Some(included.block_number),\n        Some(included.block_number),\n    )])\n}\n\nasync fn complete_finalized_swap(\n    plan: &SwapPlan,\n    intent_id: i64,\n    tx_hash: B256,\n    fill: Option<FinalizedSwapFill>,","sourceCodeStart":4559,"sourceCodeEnd":4595,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/client.rs#L4559-L4595","documentation":"After an approval (approve) transaction finalizes, the adapter reads `allowance(owner=wallet, spender=router)` at the inclusion block and requires it to equal exactly the requested amount. This ensure! fires when the on-chain allowance differs — the approval did not take the expected effect (wrong spender, partial/zero approval, or a non-standard token contract). Failing here prevents subsequent swaps from relying on an incorrect allowance.","triggerScenarios":"`allowance.value != amount` at `included.block_number` after a finalized approve transaction — e.g. the tx approved a different spender or amount, the approval was consumed/changed before verification, or the token's allowance semantics differ from standard ERC20.","commonSituations":"Router address mismatch between the tx and verification config, verifying an approve that was later partially consumed, non-standard ERC20 tokens (fee-on-transfer, allowance caps), or RPC serving state from a different fork.","solutions":["Verify the router (spender) address passed to verification matches the one in the submitted approve call","Re-read allowance at the inclusion block from an independent RPC to rule out fork/stale state","Inspect the approve tx receipt logs to confirm spender and value actually written","If the token is non-standard, account for its allowance semantics before enforcing exact equality"],"exampleFix":"// before\nverify_approve_allowance(&executor, &token, &router, amount, &included).await?;\n// after\nanyhow::ensure!(router == plan.router_address, \"router mismatch: {}\", router);\nverify_approve_allowance(&executor, &token, &router, amount, &included).await?;","handlingStrategy":"try-catch","validationCode":"fn approve_params_match(plan: &SwapPlan, router: Address, amount: U256) -> bool {\n    plan.router_address == router && plan.approval_amount == amount\n}","typeGuard":"fn allowance_is_exact(allowance: U256, requested: U256) -> bool {\n    allowance == requested\n}","tryCatchPattern":"match verify_approve_allowance(&executor, &token, &router, amount, &included).await {\n    Ok(d) => apply(d),\n    Err(e) if e.to_string().contains(\"does not equal the requested amount\") => {\n        tracing::warn!(\"allowance mismatch after approve {}\", included.tx_hash);\n        // re-read allowance from canonical RPC; if still wrong, re-issue approve\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep router/spender address in one place so tx construction and verification can't diverge","Avoid race windows: don't submit swaps that consume the allowance between mining and verification","Screen tokens for non-standard allowance behavior before routing through them","Verify approvals against an independent RPC if exactness is safety-critical"],"tags":["blockchain","verification","allowance-mismatch","erc20"],"backgroundTag":"unexpected-api-response-shape","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"}