{"record":{"id":"1bdfd3f1412ec4c4","repo":"nautechsystems/nautilus_trader","slug":"finalized-swap-input-base-amount-does-not-match","errorCode":null,"errorMessage":"Finalized Swap input {base_amount} does not match the persisted amount {}","messagePattern":"Finalized Swap input (.+?) does not match the persisted amount (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":2249,"sourceCode":"        \"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 {}:{}\",\n                plan.pool.chain.name,\n                plan.pool.dex.name\n            )\n        })?;\n    let event = dex.parse_swap_event_rpc(log)?;\n    let base_amount = if plan.pool.get_base_token().address == plan.pool.token0.address {\n        event.amount0\n    } else {\n        event.amount1\n    };\n    anyhow::ensure!(\n        base_amount.is_positive() && base_amount.unsigned_abs() == plan.amount_in,\n        \"Finalized Swap input {base_amount} does not match the persisted amount {}\",\n        plan.amount_in\n    );\n\n    let block = executor\n        .http_rpc_client\n        .block_by_number(included.block_number, false)\n        .await?;\n    anyhow::ensure!(\n        block.hash == included.receipt.block_hash,\n        \"Finalized block {} changed from {} to {} before fill emission\",\n        included.block_number,\n        included.receipt.block_hash,\n        block.hash\n    );\n    let timestamp_ns = block\n        .timestamp","sourceCodeStart":2231,"sourceCodeEnd":2267,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/execution/client.rs#L2231-L2267","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","solutions":["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.","If the plan is stale (replacement/resubmission), reconcile the intent record so amount_in matches the landed transaction, then let the fill retry.","Verify the pool's token0/token1 order and get_base_token() against the on-chain pool contract.","Check that the decoder applies the correct decimals for the base token."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"let event = dex.parse_swap_event_rpc(&swap_log)?;\nlet base_in = if pool.get_base_token().address == pool.token0.address { event.amount0 } else { event.amount1 };\nif !(base_in.is_positive() && base_in.unsigned_abs() == plan.amount_in) {\n    log::error!(\"intent {} amount {} != on-chain input {base_in}; investigate before retry\", intent_id, plan.amount_in);\n}","typeGuard":null,"tryCatchPattern":"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.","preventionTips":["Persist the exact submitted raw amount at signing time so plan.amount_in can never drift from the broadcast transaction.","Block replacement/resubmission of an intent with a different amount_in; force a new intent instead.","Verify pool token0/token1 and base-token mapping at startup against the on-chain pool."],"tags":["blockchain","swap-fill","reconciliation","amount-mismatch"],"backgroundTag":"state-reconciliation-mismatch","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}