mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 11:18:24 +00:00
Deleting an app on iOS removes its container but NOT its keychain items, so a reinstall comes back with the seed intact and every derivation counter gone with the database. Onboarding treated that as a happy path — keys found, reuse them, carry on — which resumes a used seed at counter 0 and re-derives blinded secrets the mint has already signed. That is the duplicate _B that SeedRecoveryScreen advances the counter to avoid; the mint then either rejects the outputs or returns proofs that are already spent. Not a regression: counters died with an iOS reinstall when they lived in MMKV too. But a native release is exactly what prompts people to delete and reinstall, so it is worth closing first. Only ONE of the onboarding cases is unsafe, and the discriminator is the schema, not the keys. Keys with an intact database mean a replayed onboarding or terms re-agreement — the wallet and its counters are fine and the user should not be interrupted. Keys with NO database mean the container was wiped and the keychain survived it. Android reaches that state with no keys at all (the keystore dies with the uid, allowBackup="false" stops a Backup restore) and needs no handling, as the tests pin. The fact is knowable exactly once, inside _createOrUpdateSchema, and is gone immediately after: by the time any screen renders, setupRootStore has long since called getInstance(). So instance.ts records it and setupRootStore snapshots the pair at startup. The snapshot is the subtle part. A live "keys exist AND schema is new" check triggers on ITSELF: on a fresh install the schema IS new, so the moment onboarding saves its keys the condition turns true, and anyone who backs out and returns is offered the chance to reset a seed thirty seconds old. The question is not "are there keys now" but "were there keys before we ran". What the user is offered, per their own seed: - RECOVER — keep it. Recovery walks the derivation space, moves each counter past what the mint has seen, restores the ecash, and recovers the profile from the seedHash so the minibits.cash address survives. - START FRESH — discard it. Safe by construction, but it abandons whatever the old seed holds AND changes their identity: the Nostr keypair is NIP-06-derived from the mnemonic and walletId is regenerated, so the address changes and contacts can no longer reach them. Hence the mnemonic is shown and copyable before this is offered, and it takes a confirmation. SeedRecoveryScreen now sets isOnboarded, because it has become an exit from onboarding and was previously only ever reached from a wallet that was already past it. A no-op for that caller. Also drops torService from the services barrel: the file is zero bytes, so it is not a module, and the export was a standing tsc error. Nothing imports it. Net tsc errors 91 -> 90; no new ones. NOT device-tested — the iOS reinstall path needs a real device. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>