{"record":{"id":"8b945c76ab13e1f4","repo":"nautechsystems/nautilus_trader","slug":"signed-transaction-calldata-does-not-match-persist","errorCode":null,"errorMessage":"Signed transaction calldata does not match persisted calldata","messagePattern":"Signed transaction calldata does not match persisted calldata","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/transaction.rs","lineNumber":302,"sourceCode":"        \"Signed transaction nonce {} does not match persisted nonce {}\",\n        tx.nonce,\n        intent.nonce\n    );\n    anyhow::ensure!(\n        tx.to == intent.to,\n        \"Signed transaction destination {} does not match persisted destination {}\",\n        tx.to,\n        intent.to\n    );\n    anyhow::ensure!(\n        tx.value == intent.value,\n        \"Signed transaction value does not match persisted value\"\n    );\n    anyhow::ensure!(\n        tx.input == intent.input,\n        \"Signed transaction calldata does not match persisted calldata\"\n    );\n    anyhow::ensure!(\n        tx.gas_limit <= intent.gas_limit,\n        \"Signed transaction gas limit {} exceeds configured ceiling {}\",\n        tx.gas_limit,\n        intent.gas_limit\n    );\n    anyhow::ensure!(\n        tx.max_fee_per_gas <= u128::from(intent.max_fee_per_gas),\n        \"Signed transaction max fee per gas {} wei exceeds configured ceiling {} wei\",\n        tx.max_fee_per_gas,\n        intent.max_fee_per_gas\n    );\n    anyhow::ensure!(\n        tx.max_priority_fee_per_gas <= tx.max_fee_per_gas,\n        \"Signed transaction priority fee per gas {} wei exceeds max fee per gas {} wei\",\n        tx.max_priority_fee_per_gas,\n        tx.max_fee_per_gas\n    );\n","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/transaction.rs#L284-L320","documentation":"`validate_signed_transaction` requires the signed transaction's `input` (calldata) to equal the calldata persisted in the intent. The intent records exactly which contract call was approved; different calldata means the signed bytes invoke a different function/arguments than authorized, so the library refuses to broadcast them.","triggerScenarios":"`validate_signed_transaction` decodes signed bytes whose `tx.input` differs from `intent.input` — e.g. the signer re-encoded the ABI call with different arguments, a different function selector, or the persisted intent was written from an older calldata payload.","commonSituations":"ABI encoding differences between signer and intent writer (argument order, extra padding, differing encoder versions), order parameters recomputed at sign time instead of reusing the persisted ones, or replayed signed blobs from a previous order/epoch.","solutions":["Re-sign using calldata serialized once and carried verbatim from the intent (`intent.input.clone()`), then validate the fresh bytes.","Use the same ABI-encoding code path/version in both intent creation and signing; never re-encode from parsed arguments at sign time.","If the intended call legitimately changed, recreate the intent and re-sign it atomically.","Diff the calldata bytes when debugging: differing prefix usually means a different function selector; differing tail means different arguments."],"exampleFix":"// before\nlet input = router.encode_deploy(order_recomputed_at_sign_time); // re-encoded\nlet signed = sign(tx.set_data(input), &wallet);\n\n// after\nlet input = intent.input.clone(); // persisted, approved calldata\nlet signed = sign(tx.set_data(input), &wallet);\nvalidate_signed_transaction(&signed, &intent)?;","handlingStrategy":"validation","validationCode":"fn calldata_matches(raw: &[u8], intent: &SignedTransactionIntent) -> bool {\n    decode_signed_transaction(raw).map(|tx| tx.input == intent.input).unwrap_or(false)\n}","typeGuard":"fn has_expected_calldata(d: &DecodedSignedTransaction, i: &SignedTransactionIntent) -> bool {\n    d.input == i.input\n}","tryCatchPattern":"if let Err(e) = validate_signed_transaction(&raw, &intent) {\n    if e.to_string().contains(\"calldata\") {\n        tracing::error!(\"signed calldata diverges from approved intent; refusing broadcast\");\n        return Err(Error::CalldataMismatch(e));\n    }\n    return Err(e.into());\n}","preventionTips":["Encode the ABI calldata once at intent creation and pass the exact bytes to the signer.","Keep the ABI encoder version identical across services that build and sign transactions.","Never re-encode calldata from parsed arguments at sign time — reuse `intent.input` bytes directly.","When a mismatch occurs, hex-diff the two calldata blobs: prefix differs = selector change, tail differs = argument change."],"tags":["blockchain","transaction-validation","calldata","integrity"],"backgroundTag":"checksum-mismatch","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"}