nautechsystems/nautilus_trader · error

Persisted terminal transaction

Error message

Persisted terminal transaction {tx_hash} is not stable at the finalized boundary

What it means

When reconciling a persisted terminal (final-status) transaction, the adapter looks up the receipt and asks the executor whether it is stably finalized. If the receipt exists but finality cannot be confirmed at the finalized boundary (None returned), reconciliation aborts rather than treating an unstable terminal tx as included.

Solutions

  1. Wait/retry reconciliation until the chain finalizes the block containing the tx (reorg resolution)
  2. Verify the RPC provider reports the receipt as finalized; switch to a healthier/faster finality-tracking endpoint
  3. Investigate why the intent reached terminal status before finality; delay terminal marking until `receipt_is_stably_finalized` confirms
  4. If the tx was genuinely reorged out, reset the intent to a non-terminal state and rebroadcast

Example fix

// before: marking intent terminal immediately after first receipt
mark_terminal(intent, receipt);
// after: only mark terminal once stable at finalized boundary
if let Some(_) = executor.receipt_is_stably_finalized(&receipt).await? {
    mark_terminal(intent, receipt);
}
Defensive patterns

Strategy: retry

Validate before calling

if let Some(receipt) = fetch_receipt(tx_hash).await? {
    if executor.receipt_is_stably_finalized(&receipt).await?.is_none() {
        // defer reconciliation until finality
    }
}

Type guard

async fn is_stably_finalized(executor: &Executor, receipt: &Receipt) -> bool {
    executor.receipt_is_stably_finalized(receipt).await.map(|o| o.is_some()).unwrap_or(false)
}

Try / catch

loop {
    match reconcile(intent).await {
        Err(e) if e.to_string().contains("not stable at the finalized boundary") => {
            tokio::time::sleep(Duration::from_secs(12)).await; // retry after a block
        }
        r => break r,
    }
}

Prevention

When it happens

Trigger: `connect()` -> `reconcile_unresolved_execution()` encounters an intent with a terminal tx_hash; `receipt_is_stably_finalized(&receipt)` returns None (e.g. reorg observed, receipt finality status below the configured boundary).

Common situations: Chain reorg moved the tx off the finalized block; RPC endpoint lagging on finality; terminal status persisted too early after a fast confirmation heuristic; misconfigured finality threshold.

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

Appendix: source

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

                        Some(InFlightSlot::AwaitingFinality(InFlightTransaction {
                            intent_id: intent.id,
                            nonce,
                            tx_hash: replacement_hash,
                            purpose,
                        }));
                }
            }
        }
        let outcome = if finality_already_committed {
            let receipt = verified_value(
                executor.verification.verify_receipt(&tx_hash).await,
                "persisted terminal receipt",
            )?;
            let finality = executor
                .receipt_is_stably_finalized(&receipt)
                .await?
                .ok_or_else(|| {
                    anyhow::anyhow!(
                        "Persisted terminal transaction {tx_hash} is not stable at the finalized boundary"
                    )
                })?;
            let included = IncludedTransaction {
                intent_id: intent.id,
                nonce,
                tx_hash,
                block_number: receipt.block_number,
                receipt,
                finality,
            };

            if intent.status == "finalized" {
                InclusionOutcome::Finalized(included)
            } else {
                InclusionOutcome::Reverted(included)
            }
        } else {

View on GitHub (pinned to 18893faf8b)