nautechsystems/nautilus_trader · error

Executed amount must be positive

Error message

Executed amount must be positive

What it means

raw_amount_to_quantity converts a raw on-chain U256 executed amount into a domain Quantity. The adapter rejects a zero raw amount because an executed fill/swap of zero is meaningless and cannot be represented as a positive quantity.

Source

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

    match side {
        OrderSide::Sell => Ok((base, quote)),
        OrderSide::Buy => Ok((quote, base)),
    }
}

fn fill_price_from_quote(
    last_qty: Quantity,
    quote_amount: U256,
    quote_currency: Currency,
) -> anyhow::Result<Price> {
    let quote = Money::from_u256(quote_amount, quote_currency)?;
    Price::from_decimal_dp(quote.as_decimal() / last_qty.as_decimal(), FIXED_PRECISION)
        .map_err(anyhow::Error::from)
}

fn raw_amount_to_quantity(amount: U256, decimals: u8) -> anyhow::Result<Quantity> {
    if amount.is_zero() {
        anyhow::bail!("Executed amount must be positive");
    }
    let quantity = if decimals >= FIXED_PRECISION {
        let scale = U256::from(10u64)
            .checked_pow(U256::from(decimals - FIXED_PRECISION))
            .ok_or_else(|| anyhow::anyhow!("Executed amount scaling overflow"))?;
        Quantity::from_u256(amount / scale, FIXED_PRECISION).map_err(anyhow::Error::from)?
    } else {
        Quantity::from_u256(amount, decimals).map_err(anyhow::Error::from)?
    };

    if quantity.is_zero() {
        anyhow::bail!(
            "Executed amount {amount} is below representable quantity precision {FIXED_PRECISION}"
        );
    }
    Ok(quantity)
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the decoded executed amount is > 0 before converting; skip or drop zero-amount events
  2. Verify event parsing maps the correct log field (amount0/amount1) so real amounts aren't lost
  3. Log and skip the offending event instead of failing the whole indexing loop

Example fix

// before
let qty = raw_amount_to_quantity(U256::zero(), decimals)?;
// after
if amount.is_zero() { tracing::warn!("skipping zero-amount execution"); return Ok(None); }
let qty = raw_amount_to_quantity(amount, decimals)?;
Defensive patterns

Strategy: validation

Validate before calling

if amount.is_zero() {
    return Err(anyhow::anyhow!("refusing to convert zero executed amount"));
}
let qty = raw_amount_to_quantity(amount, decimals)?;

Type guard

fn is_positive_amount(a: alloy_primitives::U256) -> bool { !a.is_zero() }

Try / catch

match raw_amount_to_quantity(amount, decimals) {
    Ok(q) => handle(q),
    Err(e) if e.to_string().contains("must be positive") => skip_zero_fill(),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling raw_amount_to_quantity with amount = U256::zero(), typically when an on-chain event (fill or swap receipt) reports an executed amount of 0.

Common situations: Indexing a swap/fill event whose amount fields decoded to zero (e.g. dust, transfer event with 0 value, or misparsed log topics); replaying stale or synthetic events.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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