feat(recovery): offer legacy-fund recovery when a degraded wallet can't shield its transparent funds
Phase 1 of automating legacy wallet.dat migration: a persistent, actionable banner that detects a wallet whose transparent funds are STUCK and offers the migrate-to-seed recovery flow (showSeedMigrationDialog). Corrected trigger (the important part): it fires ONLY when wallet_degraded_ is set — i.e. z_getnewaddress failed with "HD seed not found", so the wallet genuinely cannot create a shielded address to move its funds — AND there is a transparent balance to move. It is NOT driven by "the wallet has no mnemonic": a non-mnemonic but functional wallet (one that self-healed with a retrofit HD seed on load) has wallet_degraded_ == false, can still shield/spend, and is deliberately never flagged — matching the existing "legacy wallets are not nagged" policy. Full-node only. Implemented in the already-unit-tested node banner: new NodeBannerReason::LegacyFundsStuck + NodeBannerAction::RecoverLegacyFunds, gated on a new legacy_funds_stuck input (App computes it from wallet_degraded_ + transparentBalance). NoPeers still takes priority (can't recover before the node sees the chain); lite never shows it. Pure decision tests added: fires when stuck, never for a functional wallet, yields to NoPeers, suppressed on lite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3808,6 +3808,33 @@ void testNodeStatusBanner()
|
||||
NodeBannerInputs open; open.lite = true; open.connected = true;
|
||||
EXPECT_TRUE(!evaluateNodeStatusBanner(open).show);
|
||||
}
|
||||
// Legacy-funds-stuck: a connected+synced degraded wallet with stuck transparent funds offers recovery.
|
||||
{
|
||||
NodeBannerInputs in;
|
||||
in.connected = true; in.tip_known = true; in.blocks = 100;
|
||||
in.legacy_funds_stuck = true;
|
||||
NodeBannerState s = evaluateNodeStatusBanner(in);
|
||||
EXPECT_TRUE(s.show);
|
||||
EXPECT_TRUE(s.severity == NodeBannerSeverity::Warning);
|
||||
EXPECT_TRUE(s.reason == NodeBannerReason::LegacyFundsStuck);
|
||||
EXPECT_TRUE(s.action == NodeBannerAction::RecoverLegacyFunds);
|
||||
}
|
||||
// A functional wallet (legacy_funds_stuck false — e.g. non-mnemonic but can still shield) is NEVER flagged.
|
||||
{
|
||||
NodeBannerInputs in; in.connected = true; in.tip_known = true; in.blocks = 100;
|
||||
EXPECT_TRUE(!evaluateNodeStatusBanner(in).show);
|
||||
}
|
||||
// NoPeers takes priority: can't recover before the node can see the chain.
|
||||
{
|
||||
NodeBannerInputs in;
|
||||
in.connected = true; in.tip_known = false; in.blocks = 100; in.legacy_funds_stuck = true;
|
||||
EXPECT_TRUE(evaluateNodeStatusBanner(in).reason == NodeBannerReason::NoPeers);
|
||||
}
|
||||
// Lite never shows it (the lite branch returns first; App also gates the input to full-node).
|
||||
{
|
||||
NodeBannerInputs in; in.lite = true; in.connected = true; in.legacy_funds_stuck = true;
|
||||
EXPECT_TRUE(!evaluateNodeStatusBanner(in).show);
|
||||
}
|
||||
}
|
||||
|
||||
void testStalenessBadge()
|
||||
|
||||
Reference in New Issue
Block a user