nautechsystems/nautilus_trader · error
Replacement transaction {transaction_hash} has no retained p
Error message
Replacement transaction {transaction_hash} has no retained payload What it means
A replacement transaction hash can only be attached if a row exists in `execution_transaction_hash` for (intent_id, chain_id, transaction_hash). This row is created when the transaction payload was first retained/authenticated. If the locked lookup returns no row, the hash being claimed as a verified replacement was never retained by the system, so attachment is refused.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6441
"Intent cannot attach a verified replacement from status {current_status}"
);
let (hash_id, payload_expected, already_current) =
sqlx::query_as::<_, (i64, bool, bool)>(
"
SELECT id, payload_expected, current
FROM execution_transaction_hash
WHERE intent_id = $1 AND chain_id = $2 AND transaction_hash = $3
FOR UPDATE
",
)
.bind(scan.intent_id)
.bind(chain_id)
.bind(transaction_hash)
.fetch_optional(&mut *transaction)
.await
.context("failed to lock authenticated replacement payload")?
.ok_or_else(|| {
anyhow::anyhow!(
"Replacement transaction {transaction_hash} has no retained payload"
)
})?;
anyhow::ensure!(
payload_expected,
"Replacement transaction {transaction_hash} is not authenticated"
);
if !already_current {
sqlx::query(
"
UPDATE execution_transaction_hash
SET current = FALSE, status = 'replaced', updated_at = NOW()
WHERE intent_id = $1 AND current
",
)
.bind(scan.intent_id)
.execute(&mut *transaction)View on GitHub (pinned to 18893faf8b)
Solutions
- Retain and authenticate the replacement transaction payload through the normal retention API first, then re-run the scan record step.
- Normalize the hash casing/encoding so it matches how it was stored (lowercase hex, same bytes).
- Verify the hash belongs to this intent_id and chain_id, not another intent's replacement.
Example fix
// before: hash straight from an explorer lookup
let scan = ExecutionReplacementScan { matched_transaction_hash: Some(explorer_tx_hash), .. };
// after: retain first
let retained = db.retain_transaction_payload(chain_id, intent_id, explorer_tx_hash, payload).await?;
let scan = ExecutionReplacementScan { matched_transaction_hash: Some(retained.hash), .. }; Defensive patterns
Strategy: validation
Validate before calling
let retained = sqlx::query_scalar::<_, i64>("SELECT id FROM execution_transaction_hash WHERE intent_id=$1 AND chain_id=$2 AND transaction_hash=$3").bind(scan.intent_id).bind(chain_id).bind(tx_hash).fetch_optional(&pool).await?;
if retained.is_none() { return Err(anyhow!("replacement tx {tx_hash} was never retained for this intent")); } Type guard
async fn hash_is_retained(pool: &PgPool, intent_id: i64, chain_id: i32, hash: &str) -> anyhow::Result<bool> {
Ok(sqlx::query_scalar::<_, i64>("SELECT id FROM execution_transaction_hash WHERE intent_id=$1 AND chain_id=$2 AND transaction_hash=$3")
.bind(intent_id).bind(chain_id).bind(hash).fetch_optional(pool).await?.is_some())
} Prevention
- Only use transaction hashes that went through the internal authenticated retention path.
- Normalize hashes (lowercase hex) before storage and lookup to avoid key mismatches.
- Confirm the hash belongs to the same intent_id/chain_id before attaching it.
When it happens
Trigger: Setting `scan.matched_transaction_hash` to a tx hash that was never persisted to `execution_transaction_hash` for this intent/chain — e.g. a hash observed off-chain (mempool, explorer) without going through the authenticated retention path, or a hash belonging to a different intent.
Common situations: Scanning an explorer API found the replacement but the internal retention step never ran or used a different intent id; a copy/paste or encoding difference (checksummed vs lowercase hex) makes the hash not match the stored key.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Execution schema version {} is newer than supported version
- Verified finalized transaction count advanced without an act
- Restored transaction purpose is inconsistent
- Canonical nonce ledger changed during verification bootstrap
- Verified finalized transaction count is outside the owned re
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/663bbaed9c382cbd.
Report an issue: GitHub.