nautechsystems/nautilus_trader · error
Signed transaction nonce {} does not match persisted nonce {
Error message
Signed transaction nonce {} does not match persisted nonce {} What it means
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.
Source
Thrown at crates/adapters/blockchain/src/execution/transaction.rs:282
intent.intent_chain_id == intent.chain_id,
"Persisted intent chain ID {} does not match configured chain ID {}",
intent.intent_chain_id,
intent.chain_id
);
anyhow::ensure!(
intent.row_chain_id == intent.chain_id,
"Persisted transaction row chain ID {} does not match configured chain ID {}",
intent.row_chain_id,
intent.chain_id
);
anyhow::ensure!(
tx.chain_id == u64::from(intent.chain_id),
"Signed transaction chain ID {} does not match configured chain ID {}",
tx.chain_id,
intent.chain_id
);
anyhow::ensure!(
tx.nonce == intent.nonce,
"Signed transaction nonce {} does not match persisted nonce {}",
tx.nonce,
intent.nonce
);
anyhow::ensure!(
tx.to == intent.to,
"Signed transaction destination {} does not match persisted destination {}",
tx.to,
intent.to
);
anyhow::ensure!(
tx.value == intent.value,
"Signed transaction value does not match persisted value"
);
anyhow::ensure!(
tx.input == intent.input,
"Signed transaction calldata does not match persisted calldata"View on GitHub (pinned to 18893faf8b)
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.
Example fix
// before let signed = sign(tx_with_stale_nonce, &wallet); validate_signed_transaction(&signed, &intent)?; // nonce mismatch // after let mut tx = build_tx_from_intent(&intent); // tx.nonce = intent.nonce let signed = sign(&tx, &wallet); validate_signed_transaction(&signed, &intent)?;
Defensive patterns
Strategy: validation
Validate before calling
fn nonce_matches(raw: &[u8], intent: &SignedTransactionIntent) -> anyhow::Result<bool> {
let tx = decode_signed_transaction(raw)?;
Ok(tx.nonce == intent.nonce)
} Type guard
fn is_expected_nonce(decoded: &DecodedSignedTransaction, intent: &SignedTransactionIntent) -> bool {
decoded.nonce == intent.nonce
} Try / catch
match validate_signed_transaction(&raw, &intent) {
Ok(()) => submit(raw),
Err(e) if e.to_string().contains("nonce") => {
tracing::warn!("stale signature: nonce drift; re-signing from intent");
re_sign_and_validate(&intent)?;
}
Err(e) => return Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Signed transaction destination {} does not match persisted d
- Signed transaction value does not match persisted value
- Signed transaction calldata does not match persisted calldat
- Canonical head changed during signer-nonce replacement scan
- Verified finalized transaction count advanced without an act
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/05ee3598db70e8ed.
Report an issue: GitHub.