nautechsystems/nautilus_trader · error

Pending nonce does not match the verified canonical nonce

Error message

Pending nonce does not match the verified canonical nonce

What it means

Before signing, the client reads the account nonce twice: the canonical nonce at the verified decision block and the pending (mempool-inclusive) transaction count. Signing is only safe when they match — i.e. the wallet has no in-flight transactions. A mismatch means pending transactions exist that would collide with the nonce about to be assigned, so the client aborts.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:2428

        if let Some(anchors) = swap_anchors {
            decisions.extend(self.verify_swap_anchors_before_sign(anchors).await?);
            decisions.extend(anchors.precondition_decisions.iter().cloned());
        } else {
            decisions.extend(self.verify_pre_sign_header_fence(decision_header).await?);
        }
        let canonical_nonce_verification = required_verification(
            self.verification
                .verify_transaction_count(&self.wallet_address, decision_header.number)
                .await,
            "pre-sign canonical nonce reread",
        )?;
        let pending_nonce_verification = required_verification(
            self.verification
                .verify_pending_transaction_count(&self.wallet_address)
                .await,
            "pre-sign pending nonce reread",
        )?;
        anyhow::ensure!(
            pending_nonce_verification.value == canonical_nonce_verification.value,
            "Pending nonce does not match the verified canonical nonce"
        );
        let nonce = canonical_nonce_verification.value;
        decisions.push(verification_decision(
            &canonical_nonce_verification,
            decision_height,
            decision_height,
        ));
        decisions.push(verification_decision(
            &pending_nonce_verification,
            None,
            None,
        ));
        let tx = build_eip1559_transaction(
            expected_chain_id,
            nonce,
            gas_limit,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Wait for the pending transaction(s) to be confirmed (or explicitly replace/cancel them with a higher-priority-fee same-nonce transaction), then retry.
  2. Ensure a single signing process owns the wallet; add cross-process nonce leasing/locking.
  3. Retry after the mempool clears; the check is designed to be re-run and will pass once pending == canonical.
  4. Point the client at a consistent, in-sync RPC endpoint; avoid load-balanced providers with divergent nodes.

Example fix

// before: immediate retry after broadcast, pending tx still in flight
let prepared = client.prepare_and_sign(intent).await?; // fails: pending nonce 7 != canonical 6
// after: confirm the previous tx first
wait_for_confirmation(previous_tx_hash).await?;
let prepared = client.prepare_and_sign(intent).await?;
Defensive patterns

Strategy: retry

Validate before calling

pub async fn nonce_clear(ver: &Verification, wallet: Address) -> anyhow::Result<bool> {
    let canonical = ver.verify_transaction_count(&wallet, latest()).await?;
    let pending = ver.verify_pending_transaction_count(&wallet).await?;
    Ok(pending.value == canonical.value)
}

Try / catch

match sign_attempt().await {
    Err(e) if e.to_string().contains("Pending nonce does not match") => {
        wait_for_pending_confirmation().await?;
        sign_attempt().await?
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling the pre-sign path while the wallet has unconfirmed transactions in the mempool (pending nonce > canonical nonce), or a stale/inconsistent RPC where the pending view lags or diverges from the canonical view.

Common situations: A previous broadcast is still unconfirmed (stuck/slow transaction); another process or bot shares the same wallet key and broadcast concurrently; RPC provider returning inconsistent pending vs latest state; retry loop re-signing before confirmation of the earlier tx.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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