nautechsystems/nautilus_trader · error
Signed transaction calldata does not match persisted calldat
Error message
Signed transaction calldata does not match persisted calldata
What it means
`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.
Source
Thrown at crates/adapters/blockchain/src/execution/transaction.rs:302
"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"
);
anyhow::ensure!(
tx.gas_limit <= intent.gas_limit,
"Signed transaction gas limit {} exceeds configured ceiling {}",
tx.gas_limit,
intent.gas_limit
);
anyhow::ensure!(
tx.max_fee_per_gas <= u128::from(intent.max_fee_per_gas),
"Signed transaction max fee per gas {} wei exceeds configured ceiling {} wei",
tx.max_fee_per_gas,
intent.max_fee_per_gas
);
anyhow::ensure!(
tx.max_priority_fee_per_gas <= tx.max_fee_per_gas,
"Signed transaction priority fee per gas {} wei exceeds max fee per gas {} wei",
tx.max_priority_fee_per_gas,
tx.max_fee_per_gas
);
View on GitHub (pinned to 18893faf8b)
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.
Example fix
// before let input = router.encode_deploy(order_recomputed_at_sign_time); // re-encoded let signed = sign(tx.set_data(input), &wallet); // after let input = intent.input.clone(); // persisted, approved calldata let signed = sign(tx.set_data(input), &wallet); validate_signed_transaction(&signed, &intent)?;
Defensive patterns
Strategy: validation
Validate before calling
fn calldata_matches(raw: &[u8], intent: &SignedTransactionIntent) -> bool {
decode_signed_transaction(raw).map(|tx| tx.input == intent.input).unwrap_or(false)
} Type guard
fn has_expected_calldata(d: &DecodedSignedTransaction, i: &SignedTransactionIntent) -> bool {
d.input == i.input
} Try / catch
if let Err(e) = validate_signed_transaction(&raw, &intent) {
if e.to_string().contains("calldata") {
tracing::error!("signed calldata diverges from approved intent; refusing broadcast");
return Err(Error::CalldataMismatch(e));
}
return Err(e.into());
} Prevention
- 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.
When it happens
Trigger: `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.
Common situations: 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.
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 nonce {} does not match persisted nonce {
- Signed transaction destination {} does not match persisted d
- Signed transaction value does not match persisted value
- Signed transaction creates a contract instead of calling a d
- Finalized checkpoint ledger conflicts with the trusted chain
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/8b945c76ab13e1f4.
Report an issue: GitHub.