nautechsystems/nautilus_trader · error

user_pusd_balance {user_pusd_balance} too small to cover fee

Error message

user_pusd_balance {user_pusd_balance} too small to cover fees at price {price}; fee-adjusted amount truncated to zero

What it means

After applying the fee curve, the fee-adjusted buy amount is truncated to USDC decimals; if the user's user_pusd_balance is so small that fees consume it and the truncated amount rounds to zero, the function refuses to produce a degenerate zero-size order and bails out instead.

Source

Thrown at crates/adapters/polymarket/src/execution/parse.rs:561

        .and_then(|cost| cost.checked_add(builder_fee))
        .context("market-buy total cost overflow")?;

    let raw = if user_pusd_balance <= total_cost {
        let divisor = platform_fee_rate
            .checked_div(price)
            .and_then(|rate| Decimal::ONE.checked_add(rate))
            .and_then(|rate| rate.checked_add(builder_taker_fee_rate))
            .context("market-buy fee divisor overflow")?;
        user_pusd_balance
            .checked_div(divisor)
            .context("market-buy adjustment overflow")?
    } else {
        amount
    };

    let adjusted = raw.trunc_with_scale(USDC_DECIMALS);
    if adjusted.is_zero() {
        anyhow::bail!(
            "user_pusd_balance {user_pusd_balance} too small to cover fees at price {price}; \
             fee-adjusted amount truncated to zero"
        );
    }
    Ok(adjusted)
}

/// Computes a pUSD commission using Polymarket's platform fee formula.
///
/// `fee = C * feeRate * (p * (1 - p))^exponent`, paid only by takers.
/// The fee is rounded to 5 decimal places.
///
/// The `fee_rate` here is the effective rate from `feeSchedule.rate` (e.g. 0.03 for
/// 3%), not the `fee_rate_bps` field on a V2 trade response. The response field is
/// the post-trade rate that actually applied; under V2 the fee is no longer carried
/// in the signed order, so we compute commissions from the instrument's fee schedule
/// rather than reading any cap off the order body.
///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Top up the USDC balance so the fee-adjusted amount is at least one lot size unit.
  2. Pick a lower-priced market where the same balance buys a nonzero amount after fees.
  3. Pre-check affordability in the caller: compute the fee-adjusted amount defensively and skip the order if it would be zero.
  4. Surface this to the user as 'insufficient balance after fees' rather than retrying.

Example fix

// before
let amount = adjust_market_buy_amount(balance, price, fee_rate, fee_exponent, builder_taker_fee_rate)?;
// after
if balance * price <= fee_estimate || balance.is_zero() {
    return Ok(None); // skip order, insufficient balance after fees
}
let amount = adjust_market_buy_amount(balance, price, fee_rate, fee_exponent, builder_taker_fee_rate)?;
Defensive patterns

Strategy: validation

Validate before calling

let raw_fee_adj = balance * price; // rough affordability probe
if raw_fee_adj < Decimal::new(1, USDC_DECIMALS as u32) {
    return Err("balance too small to cover fees at this price".into());
}

Type guard

fn can_cover_fees(balance: &Decimal, price: &Decimal) -> bool {
    !balance.is_zero() && balance * price > Decimal::ZERO
}

Prevention

When it happens

Trigger: Calling adjust_market_buy_amount with a user_pusd_balance whose fee-adjusted, fee-inclusive affordable amount truncates to 0 at the given price — i.e. balance covers less than one atomic USDC unit of the order after fees.

Common situations: Dust balances left in a Polymarket wallet, high fee rates at extreme prices, or testing boundary behavior where the caller assumed a tiny balance would still yield a minimum order.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/40c8cd7b16760494. Report an issue: GitHub.