nautechsystems/nautilus_trader · critical
Retained active nonce {nonce} is above canonical nonce {next
Error message
Retained active nonce {nonce} is above canonical nonce {next_canonical_nonce} What it means
During migration, an active signed intent whose nonce is not the next canonical nonce must lie strictly below it (an in-flight older transaction). This error is thrown when a retained active intent's nonce is greater than or equal to the canonical next nonce, meaning the signer's persisted ledger thinks a transaction occupies a nonce at or beyond what the account has not yet used — a ledger/account-state divergence that would cause nonce reuse or gaps. The library fails the migration rather than broadcasting with a conflicting nonce.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:5486
!matches!(intent.status.as_str(), "finalized" | "reverted"),
"Active terminal intent conflicts with the canonical nonce ledger"
);
records.push(ExecutionVerificationMigrationRecord {
intent_id: intent.id,
nonce: Some(nonce),
transaction_hash: Some(current.transaction_hash.clone()),
terminal_status: None,
block_number: None,
block_hash: None,
receipt_success: None,
gas_used: None,
effective_gas_price: None,
recover_prepared: false,
decisions: vec![base_decision],
});
continue;
}
anyhow::ensure!(
nonce < next_canonical_nonce,
"Retained active nonce {nonce} is above canonical nonce {next_canonical_nonce}"
);
let receipt_verification = required_verification(
self.verification
.verify_receipt(&B256::from_str(¤t.transaction_hash).with_context(
|| {
format!(
"Retained transaction hash {} is invalid",
current.transaction_hash
)
},
)?)
.await,
"migration receipt",
)?;
let receipt = receipt_verification.value.clone();View on GitHub (pinned to 18893faf8b)
Solutions
- Query the account's current nonce from the RPC and reconcile the local canonical nonce ledger to it before re-running migration.
- Ensure no other wallet/process signs with the same key; move to a dedicated signer key for this client.
- For intents with nonces above the account nonce, cancel or re-prepare them (drop/recover) so their stale nonces no longer count as active.
- Confirm the configured account/address matches the one used when the state was persisted; fix the config and rebuild the ledger.
Example fix
// before (stale local canonical nonce after external transactions) let next_canonical_nonce = persisted_ledger.next_nonce; // 5 // retained active intent nonce = 7 -> error // after (resync from chain before migrating) let next_canonical_nonce = provider.get_transaction_count(address, None).await?; // 8 drop_or_recover_intents_above(next_canonical_nonce);
Defensive patterns
Strategy: validation
Validate before calling
async fn check_nonce_health(provider: &Provider, address: Address, ledger_next: u64, active_intents: &[Intent]) -> Result<(), String> {
let chain_nonce = provider.get_transaction_count(address, None).await?;
if ledger_next < chain_nonce {
return Err(format!("local canonical nonce {} below chain nonce {}", ledger_next, chain_nonce));
}
if active_intents.iter().any(|i| i.nonce.map_or(false, |n| n >= ledger_next && n != ledger_next)) {
return Err("active intent nonce above canonical nonce".into());
}
Ok(())
} Type guard
fn nonce_in_range(intent: &Intent, next_canonical: u64) -> bool {
intent.nonce.map_or(true, |n| n < next_canonical)
} Prevention
- Never sign with the same key from multiple processes or wallets.
- Resync the canonical nonce from the chain before every migration/startup.
- Confirm the configured signer address matches the account the state was built for.
- Handle reorgs by re-deriving the canonical nonce rather than decrementing persisted values.
- Cancel or drop stale intents whose nonces exceed the account nonce.
When it happens
Trigger: Raised by `anyhow::ensure!(nonce < next_canonical_nonce, ...)` when: the on-chain account nonce advanced past what the local ledger recorded (e.g. transactions signed/managed outside this client, or the node reconnected to a different signer account), or the local canonical nonce was recomputed downward after a state restore while active intents retained higher nonces.
Common situations: Using the same signing key from another process/wallet concurrently; restoring an old local state while the chain account moved on; pointing the adapter at a different RPC endpoint/account than the one whose state was persisted; missed reorg handling that rolled back the canonical nonce incorrectly.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Canonical head changed during signer-nonce replacement scan
- Verified finalized transaction count advanced without an act
- Execution intent {} has a signed transaction {} that was not
- Canonical nonce advanced without an authenticated signer tra
- Verified finalized transaction count is outside the owned re
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/519b0d965f9f9e15.
Report an issue: GitHub.