nautechsystems/nautilus_trader · critical
A recoverable execution for wallet {} retains signed transac
Error message
A recoverable execution for wallet {} retains signed transaction bytes; refusing to reuse its nonce without explicit recovery What it means
During startup reconciliation, the client refuses to acquire a nonce when the database still holds a recoverable execution containing signed transaction bytes for this wallet. Reusing the nonce could broadcast a second transaction with the same nonce, so recovery must be explicit. This is a safety interlock against double-spend / nonce-collision from an interrupted broadcast.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:1134
amount_in,
min_amount_out: U256::ZERO,
slippage_bps: 0,
quote_spend_ceiling: None,
profiler_position: None,
})
}
async fn reconcile_unresolved_execution(&self) -> anyhow::Result<()> {
let database = self.cache.database.clone().ok_or_else(|| {
anyhow::anyhow!("No durable store configured for execution reconciliation")
})?;
let _payload_lease = database
.acquire_execution_payload_lease(self.payload_keys.as_deref().ok_or_else(|| {
anyhow::anyhow!("Protected payload keys are required for execution recovery")
})?)
.await?;
let wallet_address = self.wallet_address.to_string();
anyhow::ensure!(
!database
.has_recoverable_signed_execution(self.chain.chain_id, &wallet_address)
.await?,
"A recoverable execution for wallet {} retains signed transaction bytes; refusing to reuse its nonce without explicit recovery",
self.wallet_address
);
let Some(intent) = database
.get_active_execution_intent(self.chain.chain_id, &wallet_address)
.await?
else {
return Ok(());
};
anyhow::ensure!(
intent.schema_version == crate::execution::transaction::EXECUTION_SCHEMA_VERSION,
"Execution intent {} uses unsupported schema version {}",
intent.id,
intent.schema_version
);View on GitHub (pinned to 18893faf8b)
Solutions
- Run the explicit recovery flow (recovery path of reconcile_unresolved_execution / recovery tooling) to replay or discard the stored signed execution before reconnecting
- Clear the recoverable signed execution rows for this wallet in the execution database once the transaction's on-chain status is confirmed
- Ensure only one client instance uses this wallet/keystore at a time and that previous runs shut down cleanly
- Restore the payload keys (payload_keys config) so recovery can proceed instead of failing earlier
Defensive patterns
Strategy: validation
Validate before calling
// before connect(), check recovery state
let has_recoverable = database.has_recoverable_signed_execution(chain.chain_id, &wallet_address).await?;
if has_recoverable {
// run explicit recovery or clean the rows first
} Prevention
- Run the recovery flow promptly after any crash instead of restarting blind
- Ensure only one process owns a wallet's nonce at a time
- Persist signed bytes and clear them atomically with broadcast confirmation
When it happens
Trigger: connect() calls reconcile_unresolved_execution(); database.has_recoverable_signed_execution(chain_id, wallet) returns true because a previous run persisted signed tx bytes (raw/sealed transaction) for a wallet before crashing or being killed mid-broadcast.
Common situations: Process killed after signing but before confirming broadcast; crash during deployment; running a second instance against the same wallet/database; stale recovery rows left after an aborted run.
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
- Execution intent {} has a signed transaction {} that was not
- Active execution intent {} has no nonce
- Canonical head changed during signer-nonce replacement scan
- Verified finalized transaction count advanced without an act
- Canonical nonce advanced without an authenticated signer tra
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/80a4c2c621cc63fd.
Report an issue: GitHub.