nautechsystems/nautilus_trader · error · anyhow::Error

Finalized transaction {} emitted {} Swap logs; expected exac

Error message

Finalized transaction {} emitted {} Swap logs; expected exactly one

What it means

Thrown by emit_finalized_swap_fill (crates/adapters/blockchain/src/execution/client.rs:2206) when validating the receipt of a finalized swap transaction. The client filters the receipt logs for the Uniswap V3 Swap event (first topic == keccak256("Swap(address,address,int256,int256,uint160,uint128,int24)")) and requires exactly one match, so a fill maps one-to-one onto a single pool swap. Any other count (zero or multiple) aborts fill emission; the execution transaction record stays owned so reconciliation can retry.

Source

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

        .mark_execution_event_emitted(intent_id, "fill")
        .await
}

async fn emit_finalized_swap_fill(
    plan: &SwapPlan,
    included: &IncludedTransaction,
    executor: &TransactionExecutor,
    emitter: &ExecutionEventEmitter,
) -> anyhow::Result<()> {
    let signature =
        keccak256("Swap(address,address,int256,int256,uint160,uint128,int24)").to_string();
    let swap_logs = included
        .receipt
        .logs
        .iter()
        .filter(|log| log.topics.first().is_some_and(|topic| topic == &signature))
        .collect::<Vec<_>>();
    anyhow::ensure!(
        swap_logs.len() == 1,
        "Finalized transaction {} emitted {} Swap logs; expected exactly one",
        included.tx_hash,
        swap_logs.len()
    );
    let log = swap_logs[0];
    let address = Address::from_str(&log.address)
        .with_context(|| format!("Invalid finalized Swap log address {}", log.address))?;
    anyhow::ensure!(
        address == plan.pool_address,
        "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 {}:{}",

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Look up the transaction hash on a block explorer and count logs whose topic0 is the V3 Swap signature; this tells you whether the count is 0 or >1.
  2. If >1: change the order routing so the executor submits a single-pool exact-input swap with no intermediate hops.
  3. If 0: verify the pool really is a Uniswap V3-compatible pool and that the pool's dex/chain registration points at the V3 Swap decoder.
  4. Confirm no unrelated swap shares the transaction (batching, aggregator settlement in the same tx).
  5. Leave the record for reconciliation after fixing routing; do not hand-edit the transaction record.

Example fix

// before: route through router with multi-hop path
let path = [token_in, weth, token_out]; // emits 2 Swap logs

// after: exact single-pool swap the executor validates
let pool = plan.pool_address; // one V3 pool, exactly one Swap log
Defensive patterns

Strategy: validation

Validate before calling

fn count_v3_swap_logs(receipt: &RpcTransactionReceipt) -> usize {
    let sig = keccak256("Swap(address,address,int256,int256,uint160,uint128,int24)").to_string();
    receipt.logs.iter().filter(|l| l.topics.first().is_some_and(|t| t == &sig)).count()
}
// before triggering the finalized-fill path:
if count_v3_swap_logs(&receipt) != 1 {
    log::warn!("tx {} has {} V3 Swap logs; routing must be single-hop", tx_hash, n);
}

Type guard

fn is_single_v3_swap(receipt: &RpcTransactionReceipt) -> bool {
    count_v3_swap_logs(receipt) == 1
}

Try / catch

Match the anyhow error on substring "expected exactly one"; keep the execution transaction record owned, alert on routing misconfiguration, and let reconciliation retry after the routing is fixed.

Prevention

When it happens

Trigger: The submitted swap routed through multiple V3 pools (multi-hop router path), the same transaction contained another V3 swap (aggregator/batched path), or the swap happened on a different protocol version whose event signature does not match (Uniswap V2 Pair Swap, V4 PoolManager Swap) yielding zero matches. Also produced by tests replaying synthetic receipts (see finalized_swap_without_log_rejects_and_releases_ownership).

Common situations: Pool configured with a dex name that emits a non-V3 Swap signature; strategy submitting a route with intermediate hops instead of an exact single-pool swap; MEV bundles or manual pre-signing that adds extra swaps into the same transaction.

Related errors


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