nautechsystems/nautilus_trader · error

Verified action nonce does not match the active intent

Error message

Verified action nonce does not match the active intent

What it means

record_execution_verification_batch appends verified-action evidence for an active execution intent. It locks the intent row and compares the batch nonce with the nonce stored on the intent; if they differ, the evidence batch is not authorized for that intent and this ensure! aborts the transaction.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:6101

            manifest_version == batch.manifest_version && manifest_digest == batch.manifest_digest,
            "Verified action manifest identity changed"
        );
        let intent_nonce = sqlx::query_scalar::<_, Option<i64>>(
            "
            SELECT nonce
            FROM execution_intent
            WHERE id = $1 AND chain_id = $2 AND wallet_address = $3 AND active
            FOR UPDATE
            ",
        )
        .bind(batch.intent_id)
        .bind(chain_id)
        .bind(batch.wallet_address)
        .fetch_optional(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to lock intent for verified action: {e}"))?
        .ok_or_else(|| anyhow::anyhow!("Active verified-action intent was not found"))?;
        anyhow::ensure!(
            intent_nonce == Some(nonce),
            "Verified action nonce does not match the active intent"
        );
        let attempt = sqlx::query_scalar::<_, i64>(
            "
            SELECT COUNT(*)
            FROM execution_verification_decision
            WHERE intent_id = $1 AND decision_class = $2
            ",
        )
        .bind(batch.intent_id)
        .bind(batch.decision_class)
        .fetch_one(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to number verified action evidence: {e}"))?;

        for (index, decision) in batch.decisions.iter().enumerate() {
            let height_start = decision

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Reload the active intent for intent_id and use its nonce when constructing the ExecutionVerificationBatch
  2. Verify the batch is being applied to the intent created for this nonce; if the intent was superseded, address the replacement scan flow instead
  3. Check that no concurrent writer advanced or reset the intent nonce between batch creation and submission
  4. Inspect execution_intent.nonce in the database and reconcile the caller's nonce ledger

Example fix

// before
let batch = ExecutionVerificationBatch { intent_id, nonce: last_used_nonce, .. };
db.record_execution_verification_batch(&batch).await?;
// after
let intent_nonce: Option<i64> = sqlx::query_scalar(
    "SELECT nonce FROM execution_intent WHERE id = $1 AND active",
).bind(intent_id).fetch_one(&db.pool).await?;
let batch = ExecutionVerificationBatch { intent_id, nonce: intent_nonce.expect("active intent nonce") as u64, .. };
db.record_execution_verification_batch(&batch).await?;
Defensive patterns

Strategy: validation

Validate before calling

let intent_nonce: Option<i64> = sqlx::query_scalar(
    "SELECT nonce FROM execution_intent WHERE id = $1 AND chain_id = $2 AND wallet_address = $3 AND active",
).bind(batch.intent_id).bind(chain_id).bind(batch.wallet_address)
.fetch_one(&pool).await?;
if intent_nonce != Some(batch.nonce as i64) {
    return Err(anyhow::anyhow!("skip batch: nonce {batch:?} does not match intent {intent_nonce:?}"));
}

Type guard

fn nonce_matches(intent_nonce: Option<i64>, batch_nonce: u64) -> bool {
    i64::try_from(batch_nonce).ok() == intent_nonce
}

Try / catch

match db.record_execution_verification_batch(&batch).await {
    Err(e) if e.to_string().contains("nonce does not match") => {
        // reload intent, refresh nonce, rebuild batch
    }
    Err(e) => return Err(e),
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Calling record_execution_verification_batch with an ExecutionVerificationBatch whose `nonce` does not equal the nonce stored in execution_intent for `batch.intent_id` (including when the intent row has a NULL nonce).

Common situations: Replaying or reordering batches after a process restart; building the batch from a stale intent record; the intent was replaced/cancelled and re-created with a new nonce; off-by-one or u64/i64 nonce conversion mistakes in the caller.

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