{"record":{"id":"49538ad31a6aa87e","repo":"nautechsystems/nautilus_trader","slug":"input-token-balance-balance-is-below-the-swap","errorCode":null,"errorMessage":"Input token {} balance {balance} is below the swap amount {}","messagePattern":"Input token (.+?) balance (.+?) is below the swap amount (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":2428,"sourceCode":"\n    let allowance = erc20_contract\n        .allowance(&plan.token_in, &executor.wallet_address, &plan.router)\n        .await?;\n\n    if allowance < plan.amount_in {\n        anyhow::bail!(\n            \"Router allowance {allowance} is below the swap amount {} for input token {}; approve the router explicitly before submitting\",\n            plan.amount_in,\n            plan.token_in\n        );\n    }\n\n    let balance = erc20_contract\n        .balance_of(&plan.token_in, &executor.wallet_address)\n        .await?;\n\n    if balance < plan.amount_in {\n        anyhow::bail!(\n            \"Input token {} balance {balance} is below the swap amount {}\",\n            plan.token_in,\n            plan.amount_in\n        );\n    }\n\n    Ok(())\n}\n\n/// Converts a base-denominated order quantity into raw token units with exact integer\n/// scaling by the base token decimals.\n///\n/// `Quantity::raw` is scaled to the greater of its declared precision and `FIXED_PRECISION`;\n/// token amounts are scaled to the token's decimals. Quantities not exactly representable in\n/// token units are rejected rather than rounded.\nfn quantity_to_raw_amount(quantity: Quantity, decimals: u8) -> anyhow::Result<U256> {\n    if quantity.is_zero() {\n        anyhow::bail!(\"Order quantity must be positive\");","sourceCodeStart":2410,"sourceCodeEnd":2446,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/execution/client.rs#L2410-L2446","documentation":"Pre-trade check that reads balanceOf(plan.token_in, wallet_address) via the ERC-20 contract and rejects when the wallet holds less of the input token than plan.amount_in. It runs after the allowance check so the caller learns about funding before the router could pull tokens.","triggerScenarios":"Submitting a swap with plan.amount_in greater than the wallet's token balance: unfunded or underfunded wallet, balance consumed by concurrent strategies on the same wallet, gas/fees or prior trades reducing the balance, or wallet_address configured for a different account than the one holding funds.","commonSituations":"New environment where the execution wallet was never funded; sharing one wallet across strategies so balances race; the wallet address in config drifts from the account actually holding tokens (also caught indirectly by the signer check at connect); staked/locked balances that are not transferrable.","solutions":["Transfer plan.token_in of at least plan.amount_in to the configured wallet_address on the correct chain","Confirm wallet_address in the client config is the account that holds the funds and matches the signer key","Retry after checking the balance in a block explorer to confirm the transfer landed (watch for pending txs)","Reduce the order/swap size to the available balance"],"exampleFix":"// before: order sized 1000 USDC but wallet holds 250 USDC\nlet plan = build_swap_plan(amount_in: qty_to_raw(1000, 6));\n\n// after: size the swap from the live balance\nlet balance = erc20.balance_of(&token_in, &wallet).await?;\nlet amount_in = balance.min(desired_amount_in); // never exceeds holdings","handlingStrategy":"validation","validationCode":"// Size swaps from the live balance\nlet balance = erc20.balance_of(&token_in, &wallet).await?;\nanyhow::ensure!(balance >= amount_in, \"wallet short of {token_in}: {balance} < {amount_in}\");","typeGuard":"fn has_sufficient_balance(balance: U256, amount_in: U256) -> bool { balance >= amount_in }","tryCatchPattern":"// Terminal precondition: do not retry after funding mid-flight; fund, wait for the\n// transfer to confirm, then rebuild the plan with a fresh quote.\nif let Err(e) = execute_swap(plan).await {\n    if e.to_string().contains(\"balance\") { notify_treasury(&e.to_string()); }\n    return Err(e);\n}","preventionTips":["Monitor the execution wallet's token balances and alert below a funding threshold","Give each strategy its own wallet or reserve buffer so concurrent orders do not race one balance","Re-check balance after any deposit before sizing orders"],"tags":["erc20","balance","insufficient-funds","defi","swap","preconditions"],"backgroundTag":"erc20-insufficient-balance","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}