frozen coins are never spendable, and a rescan already running is adopted

four wallet defects, none urgent, all cheap:

selectCoins let an explicit coin-control pick override a freeze. naming an
outpoint now overrides only the confirmed-only default; frozen is absolute.

balances counted frozen coins in onchainConfirmed, so Send showed a figure a
max-value spend could not reach. Balances gains onchainFrozen — a component of
confirmed, not a deduction — filled at the route layer, because freezing is
Officer policy in Postgres and no backend can see it. The route only reads utxos
when something is actually frozen. Send subtracts it under "Spendable";
Overview lists it beside unconfirmed.

a rescan in flight upstream was invisible after a sidecar restart, and a second
POST would have queued behind it (scantxoutset is single-threaded node-wide).
adoptRescan polls an existing NBXplorer scan instead of starting one, and the
GET route falls back to it when local state is gone.

the per-variant scan deadline counted queue time, so a variant that sat behind
another wallet's scan timed out without ever having run. the deadline now
refreshes while the status reads Queued.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 20:56:37 +00:00
co-authored by Claude Opus 5
parent e0f6a469aa
commit a61d5c3a91
14 changed files with 191 additions and 18 deletions
+8 -3
View File
@@ -268,10 +268,15 @@ export function selectCoins(params: CoinSelectionParams): CoinSelection {
const allow = params.outpoints && params.outpoints.length > 0 ? new Set(params.outpoints) : null;
const eligible = params.utxos.filter((u) => {
// An explicit coin-control pick is authoritative: it overrides both the frozen flag and the
// confirmed-only default, because the user named this exact outpoint.
if (allow) return allow.has(outpointOf(u));
// Frozen is never overridable, not even by naming the outpoint. Freezing is the owner saying "this
// coin does not exist for spending purposes" — a reservation against an open channel, a coin held
// for tax lots, dust being kept out of the way — and a rule that a sufficiently explicit request
// can bypass is not a rule. The route refuses a named frozen outpoint before ever reaching here;
// this is what makes that true for any future caller too.
if (u.frozen) return false;
// An explicit coin-control pick is otherwise authoritative: it overrides the confirmed-only
// default, because the user named this exact outpoint and can see what it is.
if (allow) return allow.has(outpointOf(u));
if (!params.spendUnconfirmed && u.confirmations < 1) return false;
return true;
});