WalletPanelViewModel
Wallet panel presenter: drives the canonical WalletConfig and translates SDK / seed state into WalletStatus for the panel UI.
The config authority. The panel is the only surface with the network / proving-mode / proof-server toggles, so it's where config changes enter the system. It passes the current WalletConfig to MidnightSdkProvider.ensureSdk, which builds (or rebuilds) the one shared SDK. Other consumers (BBoard, Kicks) follow that SDK via the provider — they never build their own. That's what makes the app sync once instead of once per consumer.
Does NOT own the SDK lifecycle. MidnightSdkProvider is a process singleton that survives activity recreation, so there's no SDK to close in onCleared. The provider also owns seed bootstrap (it delegates to WalletSeedSource) and wallet proving-key readiness; this VM just requests the SDK and reads balances off it.
Public surface: status (observe), refreshBalance / registerDust (act). Funding needs no handler — the Receive screen shows the airdrop command and the SDK's subscription picks up the credit on its own.
Constructors
Properties
Branded backup-section state for the pill — the real per-lane status from the wallet (com.midnight.kuira.sdk.MidnightWallet.backupStatus) plus the sigil-presence identity lane. Surfaces "needs consent"/syncing/up-to-date instead of the old silent "cloud sync" label.
The restore gate's consent launches (#61) — the panel collects these into the SAME launcher as consentRequests; results route back through onConsentResult, which offers them to the gate first.
Whether the user has saved their phrase — drives the backup-status chip.
True while the restore gate is offering a restore — the panel renders the blocking "Restore your wallet data?" step (connectRestore / skipRestore). Making the skip an explicit choice (with the re-sync cost stated) is the point: the naked system consent was too easy to dismiss by accident, silently costing a full genesis replay.
The revealed 24 words while the reveal screen is open; null otherwise. Cleared on exit.
The selected network — the DURABLE single source of truth (NetworkPreferenceStore), surviving process death so a session-lock kill can't revert the choice to the localnet default. The panel observes this instead of an in-memory selection.
Live sync progress for the sheet's WalletSyncIndicator — derived from the SDK's com.midnight.kuira.sdk.MidnightWallet.syncStatus, the SAME signal the background Live-Update notification uses (#259). So the in-app pill and the notification are always consistent: it shows for EVERY sync (foreground refresh, proactive background tracker, genesis), with the fraction + the phase label resolved through the one shared labelRes mapping. Non-null only while SyncStatus.Syncing; null otherwise (the indicator hides).
Functions
Accept the setup offer: run the normal enable flow (consent + bidirectional sync).
Drop the in-memory revealed words (call when the reveal screen closes).
The user chose Connect & Restore on the offer step.
The WalletConfig the user last acted on, or a network-only default if the panel never bootstrapped yet. The full-screen overlay host (WalletOverlayHost) needs this to drive Send: the proving mode / proof-server URL live in the pill's local state, not in the host, so the host reads back the config the pill already built. By the time Send is reachable the pill has called refreshBalance (which sets lastRequestedConfig), so this is non-null in practice; the fallback just keeps the contract total.
TRUE backup disable (#246): delete BOTH cloud blobs (dust + app-state), then revoke the Drive grant, and opt out locally. Order matters — delete the blobs (which need Drive access) BEFORE revoking consent. The wallet + sigil are unaffected (recovery is the passkey); this only drops the cloud copies. The host confirms first via the BackupSection dialog.
Dismiss the setup offer — never nagged again; the BackupSection toggle remains.
Enable cross-device Dust cloud sync: obtain the Drive drive.appdata grant, then run a full sync. This is bidirectional — once consent exists, wallet.refresh() first restores from the cloud checkpoint if this device has none (so a fresh device deltas instead of replaying genesis), then uploads the latest checkpoint. If consent is already granted it runs silently; otherwise it emits an IntentSenderRequest via consentRequests for the panel to launch — the result returns through onConsentResult.
Deliberate "Re-sync balance" recovery: rebuild the unshielded UTXO cache from genesis to fix a stale NIGHT count (ghost AVAILABLE coins left by a missed spent-event / cross-app spend on the shared wallet, roadmap #52). Reuses lastRequestedConfig — the config the user last acted on — so the Settings affordance can fire this without re-threading the config through the overlay. No-op if no wallet action has run yet (nothing to resync, and no config to bootstrap with).
Validate a phrase without restoring — for live input feedback in the restore screen.
Record that the user has saved their phrase (their "I've written it down" confirm).
Continue after the Drive consent activity returns.
Bootstrap (if needed) and refresh balances. Progressive: emits Ready as soon as the SDK is built so the user sees their addresses immediately, then re-emits Ready after the full resync lands so the values catch up.
Register the wallet's NIGHT key for Dust generation, then poll until the first Dust UTXO surfaces (or DUST_VISIBLE_TIMEOUT_MS elapses). Must run once after the wallet first holds NIGHT — until then the chain won't release spendable Dust and contract calls (fee-paying) fail.
Reset the restore flow to Idle (on screen close / re-edit after an error).
Reset the send flow to Idle (on Send-screen close, or to re-edit after a failure).
Restore a wallet from a 24-word phrase. Result lands in restoreState.
Reveal the recovery phrase (biometric-gated). Result lands in revealedPhrase.
Seed the first-run network from a host default; ignored once the user has ever chosen.
Persist the user's network choice durably (synchronous — survives an immediate kill).
Send unshielded NIGHT to toAddress (#240). Bootstraps/reuses the shared SDK, then calls MidnightSdk.sendNight, which validates, selects fewest coins, auto-consolidates when needed, signs, pays dust fees, and submits. Surfaces coarse progress (consolidating / submitting) and the typed outcome through sendState; the live balance observer + a post-send refresh update the pill.
Two-way dust-backup toggle from the pill's Switch.
The user explicitly chose to re-sync from scratch.