{"record":{"id":"633b97051efd283a","repo":"nautechsystems/nautilus_trader","slug":"finalized-swap-has-no-calculated-trade-information","errorCode":null,"errorMessage":"Finalized Swap has no calculated trade information","messagePattern":"Finalized Swap has no calculated trade information","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":4823,"sourceCode":"        \"Verified inclusion header {} does not match receipt hash {}\",\n        included.block_number,\n        included.receipt.block_hash\n    );\n    let timestamp_ns = block\n        .timestamp\n        .checked_mul(NANOSECONDS_IN_SECOND)\n        .ok_or_else(|| anyhow::anyhow!(\"Finalized block timestamp overflows nanoseconds\"))?;\n    let mut swap = event.to_pool_swap(\n        plan.pool.chain.clone(),\n        plan.instrument_id,\n        plan.pool.pool_identifier,\n        UnixNanos::from(timestamp_ns),\n    );\n    swap.calculate_trade_info(&plan.pool.token0, &plan.pool.token1, None)?;\n    let trade = swap\n        .trade_info\n        .as_ref()\n        .ok_or_else(|| anyhow::anyhow!(\"Finalized Swap has no calculated trade information\"))?;\n    anyhow::ensure!(\n        trade.order_side == plan.order.order_side(),\n        \"Finalized Swap side {} does not match {} order\",\n        trade.order_side,\n        plan.order.order_side()\n    );\n    let gas_cost = included\n        .receipt\n        .effective_gas_price\n        .checked_mul(U256::from(included.receipt.gas_used))\n        .ok_or_else(|| anyhow::anyhow!(\"Finalized transaction gas commission overflow\"))?;\n    let commission = Money::from_u256(gas_cost, plan.pool.chain.native_currency())?;\n    let trade_digest = keccak256(format!(\"{}:{}\", included.tx_hash, swap.log_index));\n    let trade_digest = trade_digest.to_string();\n    let trade_id = TradeId::new_checked(&trade_digest[2..38])?;\n\n    if plan\n        .order","sourceCodeStart":4805,"sourceCodeEnd":4841,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/client.rs#L4805-L4841","documentation":"After a finalized on-chain swap is built, the adapter calls `swap.calculate_trade_info` to derive trade details (price, side, quantity) from the pool tokens. This error is thrown when `calculate_trade_info` succeeded but the swap's `trade_info` field is still `None`, meaning the finalized Swap object carries no calculated trade information. It is an internal invariant check: a finalized swap should always yield trade info once calculated.","triggerScenarios":"Executing a finalized DEX swap in `BlockchainExecutionClient` where `calculate_trade_info` leaves `swap.trade_info` unset (e.g. a swap variant that cannot compute trade info for the given token pair, missing decimals, or a malformed swap amount) despite `calculate_trade_info` returning `Ok`.","commonSituations":"Swapping through a pool whose token configuration or swap direction prevents trade-info computation; adapter bugs where a new swap type does not populate `trade_info`; token decimals or pool data fetched on-chain being inconsistent so calculation silently yields nothing.","solutions":["Verify the pool token pair (token0/token1) and decimals are correctly registered in the deployment manifest for this pool","Check that the swap direction (token0->token1 or reverse) is one supported by `calculate_trade_info`","Inspect the `Swap` construction code to confirm amounts/limits are set before finalization","Upgrade or patch the blockchain adapter if `calculate_trade_info` has a known gap leaving `trade_info` as None"],"exampleFix":"// before\nswap.calculate_trade_info(&plan.pool.token0, &plan.pool.token1, None)?;\n// after: validate the swap can produce trade info first\nif swap.amount_in.is_zero() || swap.amount_out.is_zero() {\n    anyhow::bail!(\"Swap amounts are zero; trade info cannot be calculated\");\n}\nswap.calculate_trade_info(&plan.pool.token0, &plan.pool.token1, None)?;","handlingStrategy":"try-catch","validationCode":"// before execution: ensure the swap can yield trade info\nif swap.amount_in.is_zero() || swap.amount_out.is_zero() {\n    return Err(anyhow::anyhow!(\"swap amounts are zero; trade info will not be calculated\"));\n}","typeGuard":"fn has_trade_info(swap: &Swap) -> bool {\n    swap.trade_info.is_some()\n}","tryCatchPattern":"match swap.calculate_trade_info(&plan.pool.token0, &plan.pool.token1, None) {\n    Ok(_) if swap.trade_info.is_some() => { /* proceed */ }\n    Ok(_) => log::error!(\"trade info not populated; check pool token config\"),\n    Err(e) => log::error!(\"trade info calculation failed: {e}\"),\n}","preventionTips":["Validate pool token addresses and decimals against the deployment manifest before building swaps","Unit-test calculate_trade_info for both swap directions on every supported pool type","Log the swap contents when trade_info is None to catch adapter gaps early"],"tags":["rust","blockchain","swap","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}