nautechsystems/nautilus_trader · error

Canonical nonce {} is outside the owned reconciliation range

Error message

Canonical nonce {} is outside the owned reconciliation range {}..={next_nonce}

What it means

During rebroadcast reconciliation, the execution client re-fetches the wallet's canonical (mined) transaction count and the pending transaction count, and ensures both fall within the nonce range owned by the prepared transaction (prepared.nonce..=prepared.nonce+1). If the chain reports a canonical nonce outside this tiny window, the wallet's on-chain state has diverged from the client's local in-flight transaction, so it refuses to authorize the rebroadcast rather than broadcast a duplicate or stale transaction.

Source

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

        let receipt_absence = required_verification(
            self.verification
                .verify_receipt_absence(&prepared.tx_hash)
                .await,
            "rebroadcast receipt absence",
        )?;
        decisions.push(verification_decision(&receipt_absence, None, None));

        let next_nonce = prepared
            .nonce
            .checked_add(1)
            .ok_or_else(|| anyhow::anyhow!("Owned signer nonce overflow"))?;
        anyhow::ensure!(
            (prepared.nonce..=next_nonce).contains(&pending_nonce.value),
            "Pending nonce {} is outside the owned reconciliation range {}..={next_nonce}",
            pending_nonce.value,
            prepared.nonce
        );
        anyhow::ensure!(
            (prepared.nonce..=next_nonce).contains(&canonical_nonce.value),
            "Canonical nonce {} is outside the owned reconciliation range {}..={next_nonce}",
            canonical_nonce.value,
            prepared.nonce
        );

        if !receipt_absence.value {
            self.persist_rebroadcast_decisions(intent.id, prepared.nonce, &decisions)
                .await?;
            return Ok(ReconciliationAuthorization::Retain);
        }

        if canonical_nonce.value == next_nonce {
            self.persist_rebroadcast_decisions(intent.id, prepared.nonce, &decisions)
                .await?;
            return Ok(ReconciliationAuthorization::ScanReplacement(
                decision_header.value,
            ));

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Stop all other senders using this wallet so this client is the sole nonce owner
  2. Check the reported canonical nonce: if it equals prepared.nonce+1 the transaction was mined — let reconciliation retain rather than rebroadcast
  3. Reset the client's local nonce from the chain's latest transaction count and re-prepare the transaction
  4. Verify the RPC endpoint targets the intended chain_id and a consistent node

Example fix

// before
let prepared = client.prepare_transaction(intent); // stale local nonce
// after
let onchain_nonce = client.transaction_count(wallet).await;
let prepared = client.prepare_transaction_with_nonce(intent, onchain_nonce);
Defensive patterns

Strategy: validation

Validate before calling

let canonical = client.transaction_count(wallet).await?;
let pending = client.pending_count(wallet).await?;
let next = prepared.nonce.checked_add(1).expect("nonce overflow");
if !(prepared.nonce..=next).contains(&canonical) || !(prepared.nonce..=next).contains(&pending) {
    return Err("nonce window mismatch; resync local nonce from chain");
}

Prevention

When it happens

Trigger: authorize_rebroadcast() was called for a PreparedTransaction whose nonce no longer matches the chain's transaction_count for the wallet: e.g. another process/wallet instance already broadcast and mined a transaction with a different nonce, the local nonce bookkeeping drifted, or a replacement transaction with an unrelated nonce was mined in between.

Common situations: Running two executors against the same wallet key; manual transactions sent from the wallet out-of-band; restarting the client after nonce state was lost; an RPC endpoint behind a load balancer returning inconsistent nonces; replaying intents prepared against a different chain state.

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/27beb18b053c473c. Report an issue: GitHub.