Files
minibits_wallet/__tests__
minibits-cashandClaude Opus 4.8 a3051d327a Ask before resuming a seed that outlived its wallet
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>
2026-08-04 14:46:58 +02:00
..
2026-06-27 23:55:40 +02:00
2026-07-08 15:44:11 +02:00