feat: use a dedicated HD branch for wormhole change addresses - #590
Conversation
State files written before the planck -> token rename persist pending spends under 'changeAmountPlanck'. Without a fallback, fromJson threw on the missing 'changeAmountToken' key, breaking encrypted-account load for anyone with an in-flight send at upgrade time.
n13
left a comment
There was a problem hiding this comment.
Review (two passes: full diff review, fix, re-review)
Scope reviewed: HD change-branch feature (encrypted_account_service, hd_wallet_service, wormhole_utxo_service, wormhole_coin_selection, wormhole_send_service) + the planck→token / QUAN→token rename across mobile-app, miner-app, cold-wallet-app, l10n, and all test changes.
Issue found and fixed (pushed as 753be70)
PendingSpend.fromJson crashed on legacy state files — the rename changed the persisted JSON key changeAmountPlanck → changeAmountToken, but pending-spend records are written to getApplicationSupportDirectory() (encrypted_account_w{N}.json), which survives app updates on both iOS and Android and is only wiped on logout. A user with an in-flight send at upgrade time would hit a null-cast TypeError inside _readState, breaking every encrypted-account load. Fixed with a fallback to the legacy key, plus a regression test. (The sibling nextChangeIndex field already had a correct ?? 0 migration — old change addresses stay covered on the external branch by the persisted nextIndex.)
Verified sound
- Branch separation logic: discovery scans both branches in parallel;
nextIndex/nextChangeIndexare tracked and persisted independently; change-index reservation (_reserveChangeIndex) and per-batch bump ofnextChangeIndexare correct, including the exact-max send (no change → no index consumed). - Migration path for existing wallets: old change UTXOs on the external branch are still rediscovered (they carry transfers, so gap-limit discovery finds them) and remain spendable — secrets are re-derived from the UTXO owner's
isChangeflag, andownsAddresscovers both branches. - Pending-spend pruning correctly treats change-branch scan entries as discovered-used while filtering the synthetic external index 0.
- Totals:
totalReceivedToken/changeReceivedTokencomputed frombyAddresswhich is initialized for every requested address, so the!lookups are safe;incomingTokenidentity (incoming + changeReceived − spent = balance) is test-covered. - Rename consistency: no stale
planck/QUAN/deriveWormholereferences remain; l10n placeholders ({tokenSymbol}) match betweenapp_en.arb,app_id.arb, and the generated localizations; generated localization signatures match all call sites. - Tests: the updated tests genuinely strengthen coverage (dual-branch
ownsAddress, distinct change indices for overlapping sends, change-owned input secret re-derivation, legacy state file withoutnextChangeIndex) — no weakened assertions found.
Non-blocking observation
EncryptedAccountState.incomingToken (external receipts excluding returning change) is added and tested but not yet consumed by the UI — encryptedTotalReceivedProvider (activity_screen.dart "Total received") still shows totalReceivedToken, which includes change. That's the pre-existing behavior, so not a regression, but if the intent was to report externally received funds separately, the provider should switch to s.incomingToken. Product call.
Checks run
melos run analyze(flutter analyze --fatal-infos, 4 packages): SUCCESS- quantus_sdk: encrypted_account / coin_selection / utxo / send service tests, call_decoder, payload parser, generate_keys — all pass (incl. new legacy-key test)
- mobile-app unit tests (amount input, exchange rate, pos, locale handling): all pass
- cold-wallet transaction_signing_test: all pass
Verdict
Approve (posted as comment — can't self-approve as author). The one real bug found (legacy persisted-key crash) is fixed and tested; the rest of the feature is well-designed and the rename is thorough and consistent. Good to merge once CI is green.
dewabisma
left a comment
There was a problem hiding this comment.
This is mixed with the quan to token word changes. So, noisy.
But I saw the dedicated HD branch update for change addresses. LGTM!
yeah sorry I noticed the planck thing in the middle of doing this, didn't realize its so wide-spread Should have been 2 PRs |
Change addresses derive from a dedicated HD branch; discovery scans both branches and change indices are tracked separately.