{"record":{"id":"f08a9f3a3bd34f11","repo":"nautechsystems/nautilus_trader","slug":"finalized-transaction-emitted-swap-logs-exp","errorCode":null,"errorMessage":"Finalized transaction {} emitted {} Swap logs; expected exactly one","messagePattern":"Finalized transaction (.+?) emitted (.+?) Swap logs; expected exactly one","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":2220,"sourceCode":"        .mark_execution_event_emitted(intent_id, \"fill\")\n        .await\n}\n\nasync fn emit_finalized_swap_fill(\n    plan: &SwapPlan,\n    included: &IncludedTransaction,\n    executor: &TransactionExecutor,\n    emitter: &ExecutionEventEmitter,\n) -> anyhow::Result<()> {\n    let signature =\n        keccak256(\"Swap(address,address,int256,int256,uint160,uint128,int24)\").to_string();\n    let swap_logs = included\n        .receipt\n        .logs\n        .iter()\n        .filter(|log| log.topics.first().is_some_and(|topic| topic == &signature))\n        .collect::<Vec<_>>();\n    anyhow::ensure!(\n        swap_logs.len() == 1,\n        \"Finalized transaction {} emitted {} Swap logs; expected exactly one\",\n        included.tx_hash,\n        swap_logs.len()\n    );\n    let log = swap_logs[0];\n    let address = Address::from_str(&log.address)\n        .with_context(|| format!(\"Invalid finalized Swap log address {}\", log.address))?;\n    anyhow::ensure!(\n        address == plan.pool_address,\n        \"Finalized Swap log came from pool {address}, expected {}\",\n        plan.pool_address\n    );\n\n    let dex = crate::exchanges::get_dex_extended(plan.pool.chain.name, &plan.pool.dex.name)\n        .ok_or_else(|| {\n            anyhow::anyhow!(\n                \"No RPC Swap decoder for {}:{}\",","sourceCodeStart":2202,"sourceCodeEnd":2238,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/execution/client.rs#L2202-L2238","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["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.","If >1: change the order routing so the executor submits a single-pool exact-input swap with no intermediate hops.","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.","Confirm no unrelated swap shares the transaction (batching, aggregator settlement in the same tx).","Leave the record for reconciliation after fixing routing; do not hand-edit the transaction record."],"exampleFix":"// before: route through router with multi-hop path\nlet path = [token_in, weth, token_out]; // emits 2 Swap logs\n\n// after: exact single-pool swap the executor validates\nlet pool = plan.pool_address; // one V3 pool, exactly one Swap log","handlingStrategy":"validation","validationCode":"fn count_v3_swap_logs(receipt: &RpcTransactionReceipt) -> usize {\n    let sig = keccak256(\"Swap(address,address,int256,int256,uint160,uint128,int24)\").to_string();\n    receipt.logs.iter().filter(|l| l.topics.first().is_some_and(|t| t == &sig)).count()\n}\n// before triggering the finalized-fill path:\nif count_v3_swap_logs(&receipt) != 1 {\n    log::warn!(\"tx {} has {} V3 Swap logs; routing must be single-hop\", tx_hash, n);\n}","typeGuard":"fn is_single_v3_swap(receipt: &RpcTransactionReceipt) -> bool {\n    count_v3_swap_logs(receipt) == 1\n}","tryCatchPattern":"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.","preventionTips":["Configure swaps as exact single-pool (single-hop) routes; forbid multi-hop paths in strategy config.","Register pools under the dex variant whose Swap event signature the decoder expects.","Integration-test the submit-to-finalize path against a forked chain so routing regressions surface before live capital."],"tags":["blockchain","uniswap-v3","transaction-receipt","swap-fill","reconciliation"],"backgroundTag":"transaction-receipt-log-validation","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}