nautechsystems/nautilus_trader · error

Input token {} balance {balance} is below the swap amount {}

Error message

Input token {} balance {balance} is below the swap amount {}

What it means

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.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:2428

    let allowance = erc20_contract
        .allowance(&plan.token_in, &executor.wallet_address, &plan.router)
        .await?;

    if allowance < plan.amount_in {
        anyhow::bail!(
            "Router allowance {allowance} is below the swap amount {} for input token {}; approve the router explicitly before submitting",
            plan.amount_in,
            plan.token_in
        );
    }

    let balance = erc20_contract
        .balance_of(&plan.token_in, &executor.wallet_address)
        .await?;

    if balance < plan.amount_in {
        anyhow::bail!(
            "Input token {} balance {balance} is below the swap amount {}",
            plan.token_in,
            plan.amount_in
        );
    }

    Ok(())
}

/// Converts a base-denominated order quantity into raw token units with exact integer
/// scaling by the base token decimals.
///
/// `Quantity::raw` is scaled to the greater of its declared precision and `FIXED_PRECISION`;
/// token amounts are scaled to the token's decimals. Quantities not exactly representable in
/// token units are rejected rather than rounded.
fn quantity_to_raw_amount(quantity: Quantity, decimals: u8) -> anyhow::Result<U256> {
    if quantity.is_zero() {
        anyhow::bail!("Order quantity must be positive");

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Transfer plan.token_in of at least plan.amount_in to the configured wallet_address on the correct chain
  2. Confirm wallet_address in the client config is the account that holds the funds and matches the signer key
  3. Retry after checking the balance in a block explorer to confirm the transfer landed (watch for pending txs)
  4. Reduce the order/swap size to the available balance

Example fix

// before: order sized 1000 USDC but wallet holds 250 USDC
let plan = build_swap_plan(amount_in: qty_to_raw(1000, 6));

// after: size the swap from the live balance
let balance = erc20.balance_of(&token_in, &wallet).await?;
let amount_in = balance.min(desired_amount_in); // never exceeds holdings
Defensive patterns

Strategy: validation

Validate before calling

// Size swaps from the live balance
let balance = erc20.balance_of(&token_in, &wallet).await?;
anyhow::ensure!(balance >= amount_in, "wallet short of {token_in}: {balance} < {amount_in}");

Type guard

fn has_sufficient_balance(balance: U256, amount_in: U256) -> bool { balance >= amount_in }

Try / catch

// Terminal precondition: do not retry after funding mid-flight; fund, wait for the
// transfer to confirm, then rebuild the plan with a fresh quote.
if let Err(e) = execute_swap(plan).await {
    if e.to_string().contains("balance") { notify_treasury(&e.to_string()); }
    return Err(e);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/49538ad31a6aa87e. Report an issue: GitHub.