nautechsystems/nautilus_trader · error · anyhow::Error

Finalized Swap input {base_amount} does not match the persis

Error message

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

What it means

Reconciliation guard in emit_finalized_swap_fill: it decodes the finalized Swap event, picks amount0 or amount1 depending on which pool token is the base token, and requires that value to be positive and equal to plan.amount_in. A mismatch means the on-chain swap input differs from the amount persisted with the execution intent, so the fill cannot be attributed to this order.

Source

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

        "Finalized Swap log came from pool {address}, expected {}",
        plan.pool_address
    );

    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 = if plan.pool.get_base_token().address == plan.pool.token0.address {
        event.amount0
    } else {
        event.amount1
    };
    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
    );

    let block = executor
        .http_rpc_client
        .block_by_number(included.block_number, false)
        .await?;
    anyhow::ensure!(
        block.hash == included.receipt.block_hash,
        "Finalized block {} changed from {} to {} before fill emission",
        included.block_number,
        included.receipt.block_hash,
        block.hash
    );
    let timestamp_ns = block
        .timestamp

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Compare the Swap event input on the block explorer with the calldata of the transaction hash: decide whether the chain or the persisted plan is wrong.
  2. If the plan is stale (replacement/resubmission), reconcile the intent record so amount_in matches the landed transaction, then let the fill retry.
  3. Verify the pool's token0/token1 order and get_base_token() against the on-chain pool contract.
  4. Check that the decoder applies the correct decimals for the base token.
Defensive patterns

Strategy: try-catch

Validate before calling

let event = dex.parse_swap_event_rpc(&swap_log)?;
let base_in = if pool.get_base_token().address == pool.token0.address { event.amount0 } else { event.amount1 };
if !(base_in.is_positive() && base_in.unsigned_abs() == plan.amount_in) {
    log::error!("intent {} amount {} != on-chain input {base_in}; investigate before retry", intent_id, plan.amount_in);
}

Try / catch

Catch the mismatch, freeze the intent (do not auto-retry with a 'corrected' amount), alert an operator, and reconcile the persisted plan against the landed transaction before reprocessing.

Prevention

When it happens

Trigger: The intent was replaced/resubmitted with a different amount_in than the transaction that finally landed; the pool's token0/token1 or base-token mapping is registered backwards so the wrong leg is compared; decimals differences between the decoded event and the persisted raw amount; a partial or modified swap produced by an external actor (e.g., MEV re-signing).

Common situations: Replacement transactions (bumped fee) landing after the plan was updated; pool metadata imported with token0/token1 swapped; test fixtures quoting one amount but signing a transaction with another (swap_rpc_state_for_mismatch).

Related errors


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