{"record":{"id":"735b8a0274c4be1b","repo":"nautechsystems/nautilus_trader","slug":"finalized-swap-input-quote-amount-does-not-match","errorCode":null,"errorMessage":"Finalized Swap input {quote_amount} does not match the persisted amount {}","messagePattern":"Finalized Swap input (.+?) does not match the persisted amount (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":4785,"sourceCode":"        })?;\n    let event = dex.parse_swap_event_rpc(log)?;\n    let (base_amount, quote_amount) =\n        if plan.pool.get_base_token().address == plan.pool.token0.address {\n            (event.amount0, event.amount1)\n        } else {\n            (event.amount1, event.amount0)\n        };\n    let last_qty = match plan.order.order_side() {\n        OrderSide::Sell => {\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            plan.order.quantity()\n        }\n        OrderSide::Buy => {\n            anyhow::ensure!(\n                quote_amount.is_positive() && quote_amount.unsigned_abs() == plan.amount_in,\n                \"Finalized Swap input {quote_amount} does not match the persisted amount {}\",\n                plan.amount_in\n            );\n            anyhow::ensure!(\n                base_amount.is_negative(),\n                \"Finalized Swap base amount {base_amount} is not a BUY output\"\n            );\n            raw_amount_to_quantity(\n                base_amount.unsigned_abs(),\n                plan.pool.get_base_token().decimals,\n            )?\n        }\n    };\n\n    let block = &included.finality.inclusion_header;\n    anyhow::ensure!(\n        block.number == included.block_number","sourceCodeStart":4767,"sourceCodeEnd":4803,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/client.rs#L4767-L4803","documentation":"For BUY orders, the finalized Swap event's quote-side amount must be positive and equal in absolute value to the persisted plan.amount_in, since for a BUY the quote token is the input. A mismatch means the on-chain quote input differs from the recorded plan amount.","triggerScenarios":"Verifying a BUY swap where the selected quote amount is <= 0 or unsigned_abs() != plan.amount_in — caused by wrong base/quote amount mapping (amount0 vs amount1), a plan whose recorded input was changed, or the log belonging to a different swap in the same transaction.","commonSituations":"Token0/token1 ordering confusion between pool metadata and decoder output; amount_in persisted in different units (wei vs token decimals); executing via an aggregator that split the input across pools so this pool's input is smaller than the plan total.","solutions":["Check that the base/quote selection branch matches the pool's token ordering","Persist amount_in with the same decimals/units the RPC decoder emits","If the route splits input across multiple pools, sum per-pool inputs rather than comparing plan.amount_in to one leg","Verify the log corresponds to this plan's swap (tx hash, log index) before comparing amounts"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if order_side == OrderSide::Buy && !(quote_amount.is_positive() && quote_amount.unsigned_abs() == plan.amount_in) {\n    return Err(anyhow!(\"BUY input mismatch: event={} plan={}\", quote_amount, plan.amount_in));\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = verify_finalized_swap(...).await {\n    if e.to_string().contains(\"does not match the persisted amount\") {\n        reconcile_with_route_splits(tx_hash).await?;\n    } else { return Err(e); }\n}","preventionTips":["Persist amount_in in the same units the RPC decoder emits","Map amount0/amount1 to base/quote once, centrally, from token ordering","Handle aggregator routes that split input across pools by summing legs","Confirm the log belongs to this plan's swap before comparing amounts"],"tags":["blockchain","swap","amount-mismatch","buy"],"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-14T05:17:10.506Z"}