Files
minibits-cashandClaude Opus 4.8 8e73efe42a Carry mints into SQLite by looking, not by asking the version
A test wallet lost every mint on restart. Confirmed on device.

The mints were in neither place. postProcessSnapshot strips them from every
save, and the one-time seed that was supposed to copy them into SQLite never
ran — so the snapshot stopped carrying them while the table stayed empty, and
the next launch had nothing to load.

The seed was gated on `currentVersion < 39`, and the version lied.
rootStore.version is `types.optional(types.number, rootStoreModelVersion)`, so a
factory reset stamps a wallet as fully migrated on the spot. Restore an older
snapshot over that — install the v26 native bundle, whose rootStoreModelVersion
is 32 — and the version stays 39: `39 < 39` skips the seed forever, on a wallet
that has never seeded anything.

The circumstances were unusual; the design was wrong. Gating a DATA migration on
a version number assumes the version and the data agree, and SQLite and the MMKV
snapshot are separate engines that fail independently — the database migration
can roll back while the rootStore migration commits, or vice versa. When they
disagree, the version is bookkeeping and the data is the truth.

hydrateMintsFromDatabase now converges on the actual state, every launch:

  - table has mints          -> SQLite wins; the snapshot no longer carries them
  - table empty, store has   -> they exist only in the snapshot; write them through
  - both empty               -> a fresh wallet; nothing to do

No version consulted, so it cannot be skipped, and it is idempotent (an upsert by
mint id). The <39 block is gone, with a note on why it must not come back.

The mintId backfills in <38 stay version-gated, and correctly so: those migrate
pre-existing rows once. This one is not that — it is a reconciliation between two
stores of the same fact, and reconciliations must look.

Tests: 510 pass. Two reproduce the device's exact state (mints only in the
snapshot, no migration running) and fail against the version-gated design.

Recovery for an already-emptied wallet needs no code: re-adding the mint at the
same url reattaches its proofs (proofs.mintUrl never moved) and addMint restores
the derivation index from mint_counters, which is keyed by keysetId and so was
never touched. No blinded-secret reuse — which is what keying counters by keyset,
and giving Mint.id referential authority only, were for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:10:33 +02:00
..
2026-06-27 23:55:40 +02:00
2026-07-08 15:44:11 +02:00