{"record":{"id":"c64a8996eb1d6a4f","repo":"nautechsystems/nautilus_trader","slug":"verified-swap-decision-header-changed-before-signi","errorCode":null,"errorMessage":"Verified swap decision header changed before signing","messagePattern":"Verified swap decision header changed before signing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":2321,"sourceCode":"            )?\n        } else {\n            match decision_header {\n                Some(verified) => verified,\n                None => {\n                    let now_unix_secs = current_unix_secs()?;\n                    required_verification(\n                        self.verification\n                            .verify_decision_header(now_unix_secs)\n                            .await,\n                        \"pre-sign decision header\",\n                    )?\n                }\n            }\n        };\n        let decision_header = decision_header_verification.value;\n\n        if let Some(anchors) = swap_anchors {\n            anyhow::ensure!(\n                decision_header == anchors.state,\n                \"Verified swap decision header changed before signing\"\n            );\n        }\n        let decision_ancestry = self.verify_decision_ancestry(decision_header).await?;\n        validate_transaction_authorization(authorization, to, value, &input)?;\n        let deployment_verification = required_verification(\n            self.verification\n                .verify_deployment_manifest(&self.deployment_manifest, decision_header.number)\n                .await,\n            \"pre-sign deployment manifest\",\n        )?;\n        let authorization_decisions = match authorization {\n            Some(authorization) => {\n                self.verify_transaction_authorization(authorization, decision_header.number)\n                    .await?\n            }\n            None => Vec::new(),","sourceCodeStart":2303,"sourceCodeEnd":2339,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/client.rs#L2303-L2339","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-fetch the swap quote to obtain fresh anchors for the current chain head, then retry signing immediately.","Reduce the delay between quote anchoring and signing (move signing closer to quote time, avoid long queuing).","Only act on quotes anchored to blocks at or beyond the chain's safe/finalized height to avoid reorg-induced mismatches.","Confirm the wallet is pointed at the intended chain/RPC endpoint; a load-balancer behind on blocks can produce mismatched headers."],"exampleFix":"// before: reusing a long-lived anchor\nlet prepared = client.prepare_and_sign_with_anchors(intent_id, created, to, value, input, Some(&old_anchors), None, None).await?;\n// after: refresh anchors right before signing\nlet fresh_quote = quoter.refresh(&old_anchors).await?;\nlet prepared = client.prepare_and_sign_with_anchors(intent_id, created, to, value, input, Some(&fresh_quote.anchors), None, None).await?;","handlingStrategy":"validation","validationCode":"pub fn anchors_fresh(anchors: &SwapQuoteAnchors, current_head: u64, max_lag_blocks: u64) -> bool {\n    current_head.saturating_sub(anchors.state.number) <= max_lag_blocks\n}","typeGuard":null,"tryCatchPattern":"// Rust: treat as retryable-stale\nmatch client.prepare_and_sign_with_anchors(...).await {\n    Err(e) if e.to_string().contains(\"changed before signing\") => re_quote_and_retry().await?,\n    other => other?,\n}","preventionTips":["Sign as soon as possible after quoting; do not queue anchored intents for long periods.","Only accept quotes anchored to finalized blocks.","Monitor anchor age and re-quote proactively before it can go stale."],"tags":["blockchain","consistency-check","stale-anchor","pre-sign"],"backgroundTag":"invalid-state-transition","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}