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
- Wait/retry reconciliation until the chain finalizes the block containing the tx (reorg resolution)
- Verify the RPC provider reports the receipt as finalized; switch to a healthier/faster finality-tracking endpoint
- Investigate why the intent reached terminal status before finality; delay terminal marking until `receipt_is_stably_finalized` confirms
- 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
- Only mark intents terminal after confirmed finality
- Use RPC providers with reliable finality tracking
- Account for possible reorgs before trusting fast receipts
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
- Canonical nonce advanced without an authenticated signer…
- Finalized block changed from to before intent validation
- Finalized execution transaction
- Verified inclusion header
- Verified inclusion header does not match the finalized…
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)