counterBackups preserved a removed mint's counters in MMKV for restore on
re-add. That's now redundant: mint_counters rows are never deleted on mint
removal and addMint restores via hydrateCountersFromDatabase. Keeping both
also caused a latent double-advance on re-add (updateMintCountersFromBackup
bumped SQLite on top of the retained row).
- MintsStore: remove counterBackups field, CounterBackupModel/CounterBackup
type, addOrUpdateCounterBackup + updateMintCountersFromBackup, the call in
removeMint/addMint, and the counterBackups loop in seedCountersToDatabase.
removeMint now just detaches; addMint relies on hydrate.
- snapshot compat: MintsStore.preProcessSnapshot strips counterBackups from
old snapshots so applySnapshot tolerates the removed field.
- backup export: drop the counterBackups field (old backups that contain it
are stripped on import via the same preProcessSnapshot).
- one-time seed (rootStoreModelVersion 35->36): _runMigrations copies any
removed-mint counters from the RAW pre-upgrade snapshot's counterBackups
into SQLite, so a later re-add still restores them. Monotonic.
Full suite green (15 suites / 163 tests). counterBackups was the last
counter-adjacent field still in MMKV.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relocate per-transaction in-flight request data (params for NUT-19
idempotent retry) from the MST MintProofsCounter to a dedicated SQLite
table, so retries work with no MST loaded — completing the off-MST set
needed for background NWC.
- schema/migration v29: inflight_requests (txId PK, mintUrl, keysetId,
request JSON); added to cleanAll.
- inFlightRepo: add (INSERT OR REPLACE = set semantics) / get /
getInFlightRequestsByMint / remove / seed (ON CONFLICT DO NOTHING).
- WalletStore: write/remove via Database (receive/send/mint paths).
- inFlightOperations: enumerate via a flat Database.getInFlightRequestsByMint
query instead of the mint.proofsCountersWithInFlightRequests nested loop;
removeInFlightRequest via Database; queue guard uses the DB count.
- Mint model: remove inFlightRequests map, InFlightRequestModel, all the
in-flight actions/views (counter + mint level). The InFlightRequest TYPE
is kept (WalletStore option signatures). MintProofsCounter is now just
{keyset, unit, counter}. migrateSnapshot strips inFlightRequests AND
meltCounterValues from old snapshots.
- one-time seed (rootStoreModelVersion 34->35): _runMigrations reads the
raw pre-upgrade snapshot for any in-flight requests; idempotent.
- tests: __tests__/inFlightRequests.test.ts.
Full suite green (15 suites / 163 tests). With M1+M2, the MintProofsCounter
sub-model now carries only the counter (itself SQLite-authoritative) — a
candidate to collapse later.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relocate per-transaction melt recovery data (the serialized meltPreview)
from the MST MintProofsCounter (debounced MMKV) to a dedicated SQLite
table, so it can be written synchronously and read with no MST loaded.
Why: meltPreview is recovery-critical — it unblinds the change of a paid-
but-unconfirmed melt. It was persisted only via the batched whole-tree
MMKV snapshot, so a crash right after the payment was submitted could lose
it and the change ecash. It's also a prerequisite for off-MST background
melt (NWC pay_invoice).
- schema/migration v28: new melt_recovery table (txId PK, mintUrl,
keysetId, meltPreview JSON); added to cleanAll.
- meltRecoveryRepo: add (ON CONFLICT DO NOTHING — first preview wins,
matching the old "already tracked" guard) / get / remove / seed.
- WalletStore: write the preview synchronously via Database.addMeltRecovery
BEFORE completeMelt; remove on terminal success/failure.
- meltOperations / transferOperationApi: read/remove via Database instead
of the counter model; drop the now-needless counter fetch in those blocks.
- Mint model: remove meltCounterValues map, MeltCounterValueModel, the melt
actions/views, the dead counterAtMelt field, and serializeMeltPreview
(moved to cashuUtils). migrateSnapshot now STRIPS meltCounterValues from
old snapshots so applySnapshot tolerates the removed field.
- one-time seed (rootStoreModelVersion 33->34): _runMigrations reads the
RAW pre-upgrade snapshot (the model no longer holds it) and copies any
in-flight meltPreview into SQLite. Idempotent.
- tests: __tests__/meltRecovery.test.ts (JSON round-trip, first-wins,
remove, isolation, idempotent seed).
Full suite green (14 suites / 157 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the MMKV->SQLite counter copy out of the setupRootStore hot path
into _runMigrations (gated by version bump 32->33). The core path now only
does the every-launch hydrate, alongside loadProofsFromDatabase. Restructure
_runMigrations so each step is independent and the version is set once at
the end (a throwing step retries next launch; also fixes a latent quirk
where v29-31 users never had their version bumped).
The seed reads the LIVE MST counters, which still hold real values after
the snapshot strip (postProcessSnapshot strips counter from saves, not the
in-memory model). Hydrate stays every-launch — it is not a migration.
Safety for devices that ALREADY migrated to SQLite while still on model
v32 (SQLite populated, MMKV stripped to 0): the re-run of the migration
cannot reset their counters, guarded two ways —
1. seedCountersToDatabase only seeds counters > 0 (never writes a
stripped/zero value), and
2. the repo upsert is monotonic (MAX), so a seed can never lower an
existing SQLite counter.
Hydrate also restores the real values into the model before the seed runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>