nautechsystems/nautilus_trader · error

Verified swap decision header changed before signing

Error message

Verified swap decision header changed before signing

What it means

This client re-verifies the block header anchoring a swap quote immediately before signing an EIP-1559 transaction. When swap anchors are supplied, the freshly verified decision header must be byte-identical to `anchors.state`; if the chain has produced a new block (or a reorg occurred) since the quote was anchored, the header hash/content differs and the client refuses to sign so the swap executes against the exact state the quote was priced for.

Source

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

            )?
        } else {
            match decision_header {
                Some(verified) => verified,
                None => {
                    let now_unix_secs = current_unix_secs()?;
                    required_verification(
                        self.verification
                            .verify_decision_header(now_unix_secs)
                            .await,
                        "pre-sign decision header",
                    )?
                }
            }
        };
        let decision_header = decision_header_verification.value;

        if let Some(anchors) = swap_anchors {
            anyhow::ensure!(
                decision_header == anchors.state,
                "Verified swap decision header changed before signing"
            );
        }
        let decision_ancestry = self.verify_decision_ancestry(decision_header).await?;
        validate_transaction_authorization(authorization, to, value, &input)?;
        let deployment_verification = required_verification(
            self.verification
                .verify_deployment_manifest(&self.deployment_manifest, decision_header.number)
                .await,
            "pre-sign deployment manifest",
        )?;
        let authorization_decisions = match authorization {
            Some(authorization) => {
                self.verify_transaction_authorization(authorization, decision_header.number)
                    .await?
            }
            None => Vec::new(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-fetch the swap quote to obtain fresh anchors for the current chain head, then retry signing immediately.
  2. Reduce the delay between quote anchoring and signing (move signing closer to quote time, avoid long queuing).
  3. Only act on quotes anchored to blocks at or beyond the chain's safe/finalized height to avoid reorg-induced mismatches.
  4. Confirm the wallet is pointed at the intended chain/RPC endpoint; a load-balancer behind on blocks can produce mismatched headers.

Example fix

// before: reusing a long-lived anchor
let prepared = client.prepare_and_sign_with_anchors(intent_id, created, to, value, input, Some(&old_anchors), None, None).await?;
// after: refresh anchors right before signing
let fresh_quote = quoter.refresh(&old_anchors).await?;
let prepared = client.prepare_and_sign_with_anchors(intent_id, created, to, value, input, Some(&fresh_quote.anchors), None, None).await?;
Defensive patterns

Strategy: validation

Validate before calling

pub fn anchors_fresh(anchors: &SwapQuoteAnchors, current_head: u64, max_lag_blocks: u64) -> bool {
    current_head.saturating_sub(anchors.state.number) <= max_lag_blocks
}

Try / catch

// Rust: treat as retryable-stale
match client.prepare_and_sign_with_anchors(...).await {
    Err(e) if e.to_string().contains("changed before signing") => re_quote_and_retry().await?,
    other => other?,
}

Prevention

When it happens

Trigger: Calling the transaction preparation path (prepare_and_sign_with_anchors) with a `SwapQuoteAnchors` whose `state` header no longer matches the re-verified header at `anchors.state.number` — i.e. a new block was mined at that height's successor, the anchor went stale during quote-to-sign latency, or a chain reorg replaced the anchored block.

Common situations: Slow pipeline between fetching a swap quote and signing (quote anchored many seconds earlier on a fast blocktime chain); operator pauses or retries that outlive the anchor block; reorgs on a chain with weak finality; anchoring against a testnet/fork that advances quickly.

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