nautechsystems/nautilus_trader · error · anyhow::Error
Finalized Swap side {} does not match Sell order
Error message
Finalized Swap side {} does not match Sell order What it means
Side check in emit_finalized_swap_fill: the executor only sells the base token for quote, so the order is a Sell and the calculated trade.side derived from the Swap event must equal OrderSide::Sell. The mismatch means the decoded swap moved the tokens in the opposite direction to what the order implies.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:2281
included.receipt.block_hash,
block.hash
);
let timestamp_ns = block
.timestamp
.checked_mul(NANOSECONDS_IN_SECOND)
.ok_or_else(|| anyhow::anyhow!("Finalized block timestamp overflows nanoseconds"))?;
let mut swap = event.to_pool_swap(
plan.pool.chain.clone(),
plan.instrument_id,
plan.pool.pool_identifier,
UnixNanos::from(timestamp_ns),
);
swap.calculate_trade_info(&plan.pool.token0, &plan.pool.token1, None)?;
let trade = swap
.trade_info
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Finalized Swap has no calculated trade information"))?;
anyhow::ensure!(
trade.order_side == OrderSide::Sell,
"Finalized Swap side {} does not match Sell order",
trade.order_side
);
let gas_cost = included
.receipt
.effective_gas_price
.checked_mul(U256::from(included.receipt.gas_used))
.ok_or_else(|| anyhow::anyhow!("Finalized transaction gas commission overflow"))?;
let commission = Money::from_u256(gas_cost, plan.pool.chain.native_currency())?;
let trade_digest = keccak256(format!("{}:{}", included.tx_hash, swap.log_index));
let trade_digest = trade_digest.to_string();
let trade_id = TradeId::new_checked(&trade_digest[2..38])?;
if plan
.order
.trade_ids()
.iter()View on GitHub (pinned to d1527c24af)
Solutions
- Compare the pool's token0/token1 registration against the on-chain pool contract (slot0/token0/token1 getters).
- Confirm the order submitted is a Sell of the base instrument, matching the executor's supported direction.
- Re-derive zero_for_one from the current pool ordering before signing.
- Re-register the pool with corrected token ordering and let reconciliation reprocess.
Defensive patterns
Strategy: validation
Validate before calling
// before submitting:
let expected_side = OrderSide::Sell; // this executor only sells base
if order.side() != expected_side {
anyhow::bail!("only Sell orders supported by the swap executor");
}
// verify token ordering against chain
assert_eq!(pool.token0.address, chain_pool.token0().await?); Type guard
fn order_supported_by_swap_executor(order: &Order) -> bool {
order.side() == OrderSide::Sell
} Try / catch
Catch the side mismatch, halt fills for that pool, and re-validate pool token registration before any retry; treat as a data-integrity fault, not a transient error.
Prevention
- Validate pool token0/token1 against the on-chain contract at registration time.
- Restrict strategy submission to Sell orders on this execution route.
When it happens
Trigger: Pool registered with token0/token1 reversed relative to on-chain order, making the direction derivation flip; a swap direction (zero_for_one) inverted between quoting and signing; submitting a Buy order through this sell-only execution path.
Common situations: Pool metadata imported with token0/token1 swapped; a strategy configured to buy the base token although the adapter only supports selling base for quote; direction flags computed from a stale pool snapshot.
Related errors
- Finalized transaction {} emitted {} Swap logs; expected exac
- Unsupported order side {}; only Buy and Sell are supported
- Pool {instrument_id} has no fee tier
- Pool {instrument_id} fee {fee} exceeds uint24
- Finalized Swap side {} does not match {} order
AI-assisted analysis of nautechsystems/nautilus_trader@d1527c24af (2026-08-21).
Data as JSON: /api/errors/4319c60301f99085.
Report an issue: GitHub.