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:
@@ -550,7 +550,8 @@ export class ClnRestBackend extends BaseBackend {
|
||||
lightningInbound += Math.floor((total - ours) / 1000);
|
||||
}
|
||||
|
||||
return { onchainConfirmed, onchainUnconfirmed, lightningBalance, lightningInbound };
|
||||
// Core Lightning reserves coins itself; Officer freezing is the route's overlay, so 0 here.
|
||||
return { onchainConfirmed, onchainUnconfirmed, onchainFrozen: 0, lightningBalance, lightningInbound };
|
||||
}
|
||||
|
||||
// ── on-chain ───────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -460,6 +460,7 @@ export class LndBackend extends BaseBackend {
|
||||
]);
|
||||
return {
|
||||
onchainConfirmed: toSats(chain.confirmed_balance),
|
||||
onchainFrozen: 0,
|
||||
onchainUnconfirmed: toSats(chain.unconfirmed_balance),
|
||||
// `balance` is the deprecated flat field; the nested Amount is authoritative on v0.11+.
|
||||
lightningBalance: toSats(channels.local_balance?.sat ?? channels.balance),
|
||||
|
||||
@@ -341,6 +341,7 @@ export class LndHubBackend extends BaseBackend {
|
||||
return {
|
||||
// A custodial account has no on-chain balance of its own; deposits land in the lightning balance.
|
||||
onchainConfirmed: 0,
|
||||
onchainFrozen: 0,
|
||||
onchainUnconfirmed: 0,
|
||||
lightningBalance: num(res?.BTC?.AvailableBalance),
|
||||
// Inbound liquidity is the operator's problem and is never reported.
|
||||
|
||||
@@ -278,6 +278,7 @@ export class NwcBackend extends BaseBackend {
|
||||
return {
|
||||
// NWC is lightning-only; there is no on-chain side to report.
|
||||
onchainConfirmed: 0,
|
||||
onchainFrozen: 0,
|
||||
onchainUnconfirmed: 0,
|
||||
// `balance` is msats (the WebLN shim Zeus uses divides by 1000 before the UI ever sees it).
|
||||
lightningBalance: Math.floor((res.balance ?? 0) / 1000),
|
||||
|
||||
@@ -339,6 +339,23 @@ export class OnchainBackend extends BaseBackend {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-attach to a rescan that outlived this process, and put the refresh back on the end of it.
|
||||
*
|
||||
* Read-only with respect to the upstream: it never starts a scan, so calling it when nothing is running
|
||||
* costs one request per account and answers null.
|
||||
*/
|
||||
async adoptRescan(): Promise<RescanState | null> {
|
||||
const adopt = this.chain.adoptRescan?.bind(this.chain);
|
||||
if (!adopt) return null;
|
||||
const handle = await adopt(this.scanContext().accounts);
|
||||
if (!handle) return null;
|
||||
// The same chaining startRescan does, and for the same reason: coins recovered by a scan are invisible
|
||||
// until the snapshot in front of them is rebuilt.
|
||||
void handle.done.then(() => this.refresh()).catch(() => {});
|
||||
return handle.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wallet's view of the chain, stale-while-revalidate.
|
||||
*
|
||||
@@ -528,6 +545,8 @@ export class OnchainBackend extends BaseBackend {
|
||||
}
|
||||
return {
|
||||
onchainConfirmed: confirmed,
|
||||
// Filled in by the route, which is the only layer that knows what the owner froze.
|
||||
onchainFrozen: 0,
|
||||
onchainUnconfirmed: unconfirmed,
|
||||
// No channels exist, and null is the contract's "this backend has no lightning" value.
|
||||
lightningBalance: null,
|
||||
|
||||
Reference in New Issue
Block a user