feat(wallet): derive default wallet from nsec during onboarding

The new-user onboarding flow was still generating a random BIP39 mnemonic
in startDiscovery before connecting Spark, so the wallet for a brand-new
account could not be restored by signing in with the same nsec on another
device. Switch to generateDefaultFromPrivkey so the default wallet is
deterministic from the user's key.

Also drop the NIP-78 relay backup for default wallets: the mnemonic is
recoverable from the nsec, and Breez retains the lightning address
registration server-side, so there is nothing extra to persist. Manual
backup of non-default (random) wallets is unchanged.
This commit is contained in:
Barry Deen
2026-05-15 23:25:32 -04:00
parent 1b4e250679
commit 7d2bf59b99
@@ -160,7 +160,8 @@ class OnboardingViewModel(app: Application) : AndroidViewModel(app) {
_error.value = null
// Use real keypair if available, otherwise generate a throwaway for probing
val keypair = keyRepo.getKeypair() ?: Keys.generate()
val realKeypair = keyRepo.getKeypair()
val keypair = realKeypair ?: Keys.generate()
val pubHex = keyRepo.getPubkeyHex() ?: keypair.pubkey.toHex()
keyRepo.reloadPrefs(pubHex)
blossomRepo.reload(pubHex)
@@ -168,14 +169,15 @@ class OnboardingViewModel(app: Application) : AndroidViewModel(app) {
// Reload wallet repos so mnemonic + mode are stored under the correct pubkey prefs
walletModeRepo?.reload(pubHex)
// Pre-generate mnemonic (CPU only, no network contention with relay probing)
if (sparkRepo != null) {
// Derive the default Spark wallet deterministically from the user's nsec
// so it is recoverable on any device by signing in with the same key.
// Skip if no real keypair yet (we're only probing relays with a throwaway).
if (sparkRepo != null && realKeypair != null) {
sparkRepo.reload(pubHex)
try {
val mnemonic = sparkRepo.newMnemonic()
sparkRepo.saveMnemonic(mnemonic)
sparkRepo.generateDefaultFromPrivkey(realKeypair.privkey)
} catch (e: Exception) {
Log.w(TAG, "Mnemonic generation failed: ${e.message}")
Log.w(TAG, "Default wallet derivation failed: ${e.message}")
}
}
@@ -282,8 +284,11 @@ class OnboardingViewModel(app: Application) : AndroidViewModel(app) {
}
}
// Backup mnemonic to relays via NIP-78
if (sparkRepo != null && lightningAddress != null) {
// NIP-78 relay backup is only useful for non-default wallets — the
// default wallet's mnemonic is derived from the nsec, and Breez
// remembers its lightning address registration server-side, so
// there's nothing extra to persist on relays.
if (sparkRepo != null && !sparkRepo.isDefaultWallet()) {
val mnemonic = sparkRepo.getMnemonic()
if (mnemonic != null) {
viewModelScope.launch {