{"record":{"id":"05ee3598db70e8ed","repo":"nautechsystems/nautilus_trader","slug":"signed-transaction-nonce-does-not-match-persist","errorCode":null,"errorMessage":"Signed transaction nonce {} does not match persisted nonce {}","messagePattern":"Signed transaction nonce (.+?) does not match persisted nonce (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/transaction.rs","lineNumber":282,"sourceCode":"        intent.intent_chain_id == intent.chain_id,\n        \"Persisted intent chain ID {} does not match configured chain ID {}\",\n        intent.intent_chain_id,\n        intent.chain_id\n    );\n    anyhow::ensure!(\n        intent.row_chain_id == intent.chain_id,\n        \"Persisted transaction row chain ID {} does not match configured chain ID {}\",\n        intent.row_chain_id,\n        intent.chain_id\n    );\n\n    anyhow::ensure!(\n        tx.chain_id == u64::from(intent.chain_id),\n        \"Signed transaction chain ID {} does not match configured chain ID {}\",\n        tx.chain_id,\n        intent.chain_id\n    );\n    anyhow::ensure!(\n        tx.nonce == intent.nonce,\n        \"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\"","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/transaction.rs#L264-L300","documentation":"This error is thrown by `validate_signed_transaction` when the nonce recovered from the decoded signed EIP-1559 transaction differs from the nonce persisted in the `SignedTransactionIntent`. The library treats the persisted intent as the sole authoritative record of what was approved to sign, so any divergence between the signed bytes and that record is rejected as a tamper or replay attempt before the transaction is broadcast.","triggerScenarios":"Calling `validate_signed_transaction` (directly or via `authenticate_payload_identity_with_signer`) with raw signed bytes whose recovered nonce does not equal `intent.nonce` — e.g. bytes from a re-signed replacement transaction, a stale signed blob, or bytes whose persisted intent row was regenerated with a different nonce.","commonSituations":"The signing service re-signed the payload after a nonce was bumped (failed/stuck tx replaced), the intent row was re-created or migrated while an old signed blob remained, two components read different nonce sources (local counter vs node `eth_getTransactionCount`), or a test fixture reuses a signed blob across intents.","solutions":["Re-sign the transaction so it uses exactly `intent.nonce`, then re-run validation with the new signed bytes and the matching intent.","Verify the persisted intent row is current: if the nonce was legitimately bumped, regenerate/refresh the intent rather than forcing the old signature through.","Check that only one component mutates the nonce; align on a single source (persisted intent) for both signing and validation.","If this is a test failure, regenerate the fixture signature instead of comparing an old signed blob against a new intent."],"exampleFix":"// before\nlet signed = sign(tx_with_stale_nonce, &wallet);\nvalidate_signed_transaction(&signed, &intent)?; // nonce mismatch\n\n// after\nlet mut tx = build_tx_from_intent(&intent); // tx.nonce = intent.nonce\nlet signed = sign(&tx, &wallet);\nvalidate_signed_transaction(&signed, &intent)?;","handlingStrategy":"validation","validationCode":"fn nonce_matches(raw: &[u8], intent: &SignedTransactionIntent) -> anyhow::Result<bool> {\n    let tx = decode_signed_transaction(raw)?;\n    Ok(tx.nonce == intent.nonce)\n}","typeGuard":"fn is_expected_nonce(decoded: &DecodedSignedTransaction, intent: &SignedTransactionIntent) -> bool {\n    decoded.nonce == intent.nonce\n}","tryCatchPattern":"match validate_signed_transaction(&raw, &intent) {\n    Ok(()) => submit(raw),\n    Err(e) if e.to_string().contains(\"nonce\") => {\n        tracing::warn!(\"stale signature: nonce drift; re-signing from intent\");\n        re_sign_and_validate(&intent)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always take the nonce for signing from the persisted intent object, never from an independent node query.","Serialize intent creation and signing so no nonce mutation can happen between the two steps.","Invalidate and regenerate signed blobs whenever an intent row is recreated or the nonce advances.","Add an assertion in tests that signed fixture bytes decode to the same nonce as the paired intent."],"tags":["blockchain","transaction-validation","nonce","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-14T05:17:10.506Z"}