MidnightWallet

Embedded wallet that handles transaction balancing and submission on-device.

Implements TransactionBalancer so it can be plugged into com.midnight.kuira.core.compact.MidnightConfig as a drop-in replacement for the remote DAppConnectorClient.

Uses DustSyncManager for session-scoped caching. Handles error 170 (InvalidDustSpendProof) by auto-retrying with a fresh dust sync.

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

Bech32m address this wallet receives NIGHT at. Exposed for dApps that want to display / share it.

Link copied to clipboard

Same "disable = stop uploading" gate for the app-state blob (#246). Set false by disableAppStateCloudBackup; the host persists the preference and re-applies it after each SDK (re)build — WITHOUT it a disable wouldn't stick (the next refresh would re-upload the deleted blob). Default true.

Link copied to clipboard
var appStateProvider: suspend () -> ByteArray??

Host-registered snapshot of app state to back up automatically on each refresh (≤2 KB; e.g. Kicks match witnesses). The host sets this once after obtaining the wallet — the SDK can't inject it (the provider lives above this layer). The upload is silent (seed-keyed) + digest-guarded, so it's a no-op when the snapshot is unchanged. Null → nothing to back up.

Link copied to clipboard

Observable cloud-backup status (dust + app-state lanes), updated by the backup methods so the UI can show "syncing / up to date / needs consent / failed" instead of a silent log. Identity recovery (the passkey) is sourced separately at the app layer.

Link copied to clipboard

User opt-out for dust cloud backup. When false, backupDustToCloud (and therefore refresh) skip the Drive upload entirely — that's the "disable" semantics: we simply stop uploading. The host persists the preference and re-applies it after each SDK (re)build. Default true.

Link copied to clipboard

Live NIGHT-UTXO list (as the registration-filter JSON) wired by MidnightSdk. Lets balance/balanceFlow compute dustRegistered from true per-UTXO registration state — "are all current NIGHT UTXOs generating dust?" — instead of the dust>0 heuristic (#265). Null → fall back to the heuristic (e.g. before the SDK wires it).

Link copied to clipboard

Per-transaction inbound NIGHT receipts (#284), classified by UTXO-set provenance in the indexer subscription: a genuine receipt is a transaction that created NIGHT to us without spending any of our UTXOs. Our own sends (which return change to us) are NEVER receipts, so a consumer wiring this to a notification will never tell the user they "received" their own change. Each event carries the exact amount received IN THAT transaction — not a balance delta. Cold by default (emptyFlow) when no subscription is wired (e.g. tests).

Link copied to clipboard
val syncStatus: StateFlow<SyncStatus>

Observable wallet-sync status (#235). EVERY heavy sync this wallet runs publishes here — syncDust, the proactive proactiveDustResync, the shielded + dust refresh, and the forceFullSync genesis rebuild — so a background observer (the foreground-service Live-Update notification) can render progress for whichever phase is in flight without driving the sync itself. The in-app WalletSyncIndicator can also collect this instead of the per-call lambda.

Link copied to clipboard

SUPPLIER of the wallet-level backup prefs carried INSIDE the app-state blob (see WalletBackupPrefs) — the SDK can't derive them (the committed toggle is host UI state). A supplier, not a pushed field, on purpose: it's read at UPLOAD time, so a pref change (e.g. the restore gate committing the toggle ON, or mirroring an opt-out) is always reflected in the next blob without the host re-pushing a snapshot at every mutation site — a pushed snapshot went stale and clobbered restored cloud truth. The host sets this once per build. Null supplier / null result → prefs marked absent.

Functions

Link copied to clipboard
suspend fun backupAppStateToCloud(appMetadata: ByteArray? = null)

Back up host app state (≤2 KB; e.g. Kicks match witnesses) to the cloud, encrypted under a seed-derived key — no passkey/biometric, so it's safe to call automatically. No-op when no coordinator is wired. The coordinator hash-guards redundant uploads, so this is cheap to call after every meaningful state change. Never throws to the caller.

Link copied to clipboard
suspend fun backupDustToCloud()

Snapshot the current dust checkpoint and hand it to the cloud backup coordinator (e.g. Google Drive) for cross-device recovery. No-op when no coordinator is wired or there's no checkpoint yet. The coordinator hash-guards redundant uploads, so this is cheap to call after every sync.

Link copied to clipboard
suspend fun balance(): WalletBalance

Current snapshot of every balance the SDK tracks for this wallet — unshielded NIGHT, shielded NIGHT, DUST, and the registration flag.

Link copied to clipboard
open suspend override fun balanceAndSubmit(provenTxHex: String, onProgress: suspend (BalanceProgress) -> Unit?)
Link copied to clipboard

Observable balance — emits a fresh WalletBalance whenever the wallet's unshielded OR shielded NIGHT changes. Both are driven by background indexer subscriptions, so externally-received funds (an airdrop, an incoming shielded or unshielded transfer) surface automatically without a manual refresh. Each emission also re-reads dust.

Link copied to clipboard
open suspend override fun balanceTransaction(provenTxHex: String): String
Link copied to clipboard
suspend fun close()

Release resources. Suspends because closing the dust state must be serialized against an in-flight balance: balanceAgainst reads the shared DustLocalState's nullifiers under balanceMutex, so closing it out from under a running balance throws "DustLocalState has been closed" (regression — every other dust-close path already holds this mutex; the teardown one added later did not). Under localnet the balance is instant so the window never opened; on PreProd it spans seconds of dust sync, so a teardown mid-deploy raced the close. Acquiring the mutex here makes the teardown WAIT for the balance instead of yanking its state.

Link copied to clipboard

Delete the cloud app-state blob (#246 — true backup disable). Clears the remote blob + local upload digest via the coordinator; no-op when none is wired. Recovery is unaffected (the seed is passkey-derived) — this only drops the convenience app-state copy.

Link copied to clipboard

Disable dust cloud backup and DELETE the cloud copy (#246 — true backup disable). Stops future uploads (dustBackupEnabled = false) and clears the remote blob + local upload digests via the coordinator. No-op when no coordinator is wired. The wallet itself is unaffected — recovery is the passkey; the cloud blob is only a dust-sync-speed checkpoint.

Link copied to clipboard
suspend fun fetchAppState(): ByteArray?

Fetch the previously backed-up host app state, or null if none / no coordinator / unavailable. Decryption is seed-derived (no biometric), so a freshly-signed-in device recovers its app state silently.

Link copied to clipboard

Fetch the full restored app state: wallet-level WalletBackupPrefs (when the blob carries the SDK envelope) + the host payload. fetchAppState is the host-payload shortcut; this variant exists for the bootstrap restore flow, which needs the prefs to decide restore-over-genesis. One Block Store read serves both.

Link copied to clipboard
suspend fun forceResyncUnshielded()

Force-rebuild the unshielded UTXO cache from genesis to self-heal a stale NIGHT balance.

Link copied to clipboard
suspend fun refresh()

Force a fresh resync of both shielded and dust state.

Link copied to clipboard
open suspend override fun submitTransaction(balancedTxHex: String)
Link copied to clipboard
suspend fun syncDust(onProgress: suspend (eventsProcessed: Int, totalEvents: Int) -> Unit? = null)

Sync dust state from the blockchain.

Link copied to clipboard
suspend fun unregisteredNightUtxos(nightUtxosJson: String): String

NIGHT UTXOs (from nightUtxosJson) that are NOT yet generating dust — i.e. still need a registration. Matched natively against the current synced dust state's backing-nights, so it's correct regardless of the cache-layer tokens. A null/absent dust state means nothing is registered yet, so all are returned. Drives MidnightSdk.registerForDustGeneration's per-UTXO registration loop.

Link copied to clipboard
suspend fun waitForFunding(minNight: BigInteger, pollIntervalMs: Long, timeoutMs: Long = DEFAULT_FUNDING_TIMEOUT_MS): WalletBalance

Suspend until the wallet's NIGHT balance reaches minNight or timeoutMs elapses.