nautechsystems/nautilus_trader · error · anyhow::Error

Finalized Swap input

Error message

Finalized Swap input {base_amount} does not match the persisted amount {}

What it means

For SELL orders, the finalized Swap event's base-side amount must be positive and equal in absolute value to the persisted plan.amount_in. A mismatch means the on-chain swap input differs from the amount the execution plan recorded, indicating the persisted record no longer describes the actual fill.

Solutions

  1. Confirm base/quote side selection (amount0 vs amount1) matches the pool's token ordering used when persisting amount_in
  2. Re-persist amount_in from the exact quote used at submission time (same decimals and scaling)
  3. Verify the tx really was the plan's transaction (check tx hash and router path); a different tx's log will not match
  4. If fees-on-transfer change effective input, compare against gross amount and account for the fee explicitly
Defensive patterns

Strategy: validation

Validate before calling

if order_side == OrderSide::Sell && !(base_amount.is_positive() && base_amount.unsigned_abs() == plan.amount_in) {
    return Err(anyhow!("SELL input mismatch: event={} plan={}", base_amount, plan.amount_in));
}

Try / catch

match verify_finalized_swap(...).await {
    Err(e) if e.to_string().contains("does not match the persisted amount") => requote_and_replan(),
    other => other,
}

Prevention

When it happens

Trigger: Verifying a SELL swap where event.amount0/amount1 (selected as base per token ordering) is <= 0, or its unsigned_abs() differs from plan.amount_in — e.g. the persisted plan amount was quoted pre-slippage/fee while the chain executed a different input, or amount0/amount1 were assigned to the wrong sides.

Common situations: Plan amount stored with different decimals scaling than the decoded event; token0/token1 ordering mismatch so base/quote amounts are swapped; the tx actually executed a partial or altered input after being front-run or partially filled; plan amount updated after submission without re-verifying.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

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

    let dex = crate::exchanges::get_dex_extended(plan.pool.chain.name, &plan.pool.dex.name)
        .ok_or_else(|| {
            anyhow::anyhow!(
                "No RPC Swap decoder for {}:{}",
                plan.pool.chain.name,
                plan.pool.dex.name
            )
        })?;
    let event = dex.parse_swap_event_rpc(log)?;
    let (base_amount, quote_amount) =
        if plan.pool.get_base_token().address == plan.pool.token0.address {
            (event.amount0, event.amount1)
        } else {
            (event.amount1, event.amount0)
        };
    let last_qty = match plan.order.order_side() {
        OrderSide::Sell => {
            anyhow::ensure!(
                base_amount.is_positive() && base_amount.unsigned_abs() == plan.amount_in,
                "Finalized Swap input {base_amount} does not match the persisted amount {}",
                plan.amount_in
            );
            plan.order.quantity()
        }
        OrderSide::Buy => {
            anyhow::ensure!(
                quote_amount.is_positive() && quote_amount.unsigned_abs() == plan.amount_in,
                "Finalized Swap input {quote_amount} does not match the persisted amount {}",
                plan.amount_in
            );
            anyhow::ensure!(
                base_amount.is_negative(),
                "Finalized Swap base amount {base_amount} is not a BUY output"
            );
            raw_amount_to_quantity(
                base_amount.unsigned_abs(),

View on GitHub (pinned to 18893faf8b)