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(&current.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

  1. Query the account's current nonce from the RPC and reconcile the local canonical nonce ledger to it before re-running migration.
  2. Ensure no other wallet/process signs with the same key; move to a dedicated signer key for this client.
  3. For intents with nonces above the account nonce, cancel or re-prepare them (drop/recover) so their stale nonces no longer count as active.
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/519b0d965f9f9e15. Report an issue: GitHub.