WalletPanelViewModel

class WalletPanelViewModel @Inject constructor(sdkProvider: MidnightSdkProvider, sigilStateStore: SigilStateStore, driveAuth: DriveAuthManager, sessionLock: SessionLock, networkStore: NetworkPreferenceStore, appContext: Context, appDataProvider: Optional<AppDataBackupProvider>, backupState: DustBackupStateStore, restoreGate: DustRestoreGateImpl, identityProvenance: IdentityProvenanceStore) : ViewModel

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

Link copied to clipboard
@Inject
constructor(sdkProvider: MidnightSdkProvider, sigilStateStore: SigilStateStore, driveAuth: DriveAuthManager, sessionLock: SessionLock, networkStore: NetworkPreferenceStore, appContext: Context, appDataProvider: Optional<AppDataBackupProvider>, backupState: DustBackupStateStore, restoreGate: DustRestoreGateImpl, identityProvenance: IdentityProvenanceStore)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

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.

Link copied to clipboard
val backupSetupOffer: StateFlow<Boolean>
Link copied to clipboard
Link copied to clipboard

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.

Link copied to clipboard
val recoveryError: StateFlow<String?>
Link copied to clipboard

Whether the user has saved their phrase — drives the backup-status chip.

Link copied to clipboard
val restoreOffer: StateFlow<Boolean>

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.

Link copied to clipboard
Link copied to clipboard
val retryRequests: SharedFlow<WalletConfig>
Link copied to clipboard
val revealedPhrase: StateFlow<List<String>?>

The revealed 24 words while the reveal screen is open; null otherwise. Cleared on exit.

Link copied to clipboard

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.

Link copied to clipboard
val sendState: StateFlow<SendUiState>
Link copied to clipboard
val status: StateFlow<WalletStatus>
Link copied to clipboard

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

Link copied to clipboard

Accept the setup offer: run the normal enable flow (consent + bidirectional sync).

Link copied to clipboard
open fun addCloseable(closeable: AutoCloseable)
fun addCloseable(key: String, closeable: AutoCloseable)
Link copied to clipboard

Drop the in-memory revealed words (call when the reveal screen closes).

Link copied to clipboard

The user chose Connect & Restore on the offer step.

Link copied to clipboard

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.

Link copied to clipboard

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.

Link copied to clipboard

Dismiss the setup offer — never nagged again; the BackupSection toggle remains.

Link copied to clipboard

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.

Link copied to clipboard

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).

Link copied to clipboard
Link copied to clipboard

Validate a phrase without restoring — for live input feedback in the restore screen.

Link copied to clipboard
fun lockNow()

Manually lock the session (the wallet sheet's "Lock now"). Drops the cached SDK; the balance hides and the next action re-authenticates.

Link copied to clipboard

Record that the user has saved their phrase (their "I've written it down" confirm).

Link copied to clipboard
fun onConsentResult(config: WalletConfig, activity: FragmentActivity, data: Intent?)

Continue after the Drive consent activity returns.

Link copied to clipboard
fun refreshBalance(config: WalletConfig, activity: FragmentActivity, force: Boolean = false, resyncUnshielded: Boolean = false)

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.

Link copied to clipboard

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.

Link copied to clipboard

Reset the restore flow to Idle (on screen close / re-edit after an error).

Link copied to clipboard

Reset the send flow to Idle (on Send-screen close, or to re-edit after a failure).

Link copied to clipboard

Restore a wallet from a 24-word phrase. Result lands in restoreState.

Link copied to clipboard

Reveal the recovery phrase (biometric-gated). Result lands in revealedPhrase.

Link copied to clipboard

Seed the first-run network from a host default; ignored once the user has ever chosen.

Link copied to clipboard

Persist the user's network choice durably (synchronous — survives an immediate kill).

Link copied to clipboard
fun sendNight(config: WalletConfig, activity: FragmentActivity, toAddress: String, amount: BigInteger)

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.

Link copied to clipboard
fun setDustBackup(enabled: Boolean, config: WalletConfig, activity: FragmentActivity)

Two-way dust-backup toggle from the pill's Switch.

Link copied to clipboard

The user explicitly chose to re-sync from scratch.