diff --git a/key-wallet/src/managed_account/managed_core_funds_account.rs b/key-wallet/src/managed_account/managed_core_funds_account.rs index 474b725aa..da4c2c13a 100644 --- a/key-wallet/src/managed_account/managed_core_funds_account.rs +++ b/key-wallet/src/managed_account/managed_core_funds_account.rs @@ -242,6 +242,18 @@ impl ManagedCoreFundsAccount { utxo.is_instantlocked = matches!(context, TransactionContext::InstantSend(_)); utxo.is_trusted = is_trusted_output; + // Reprocessing (e.g. mempool→block) rebuilds this UTXO from + // scratch, so carry forward flags that must not regress. An + // InstantSend lock is permanent for a txid (DIP-0010) and + // trust only ever settles, so both latch monotonically. A + // coin reservation is orthogonal to chain context and is kept + // as-is. `is_confirmed` stays freshly derived so a reorg can + // still downgrade it. + if let Some(prior) = self.utxos.get(&outpoint) { + utxo.is_instantlocked |= prior.is_instantlocked; + utxo.is_trusted |= prior.is_trusted; + utxo.is_locked = prior.is_locked; + } self.utxos.insert(outpoint, utxo); utxos_changed = true; } diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index 209930457..65469b561 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -958,6 +958,8 @@ mod tests { assert!(ctx.transaction(&txid).is_confirmed()); assert_eq!(ctx.transaction(&txid).height(), Some(1000)); assert!(ctx.first_utxo().is_confirmed); + // The earlier IS lock must survive the mempool→block reprocess. + assert!(ctx.first_utxo().is_instantlocked, "IS-lock flag must not be lost on confirmation"); assert_eq!(ctx.managed_wallet.balance.spendable(), 200_000); // Stage 4: chain-locked block (rescan with stronger context)