Mainnet migration notice for wallets coming from testnet - #649
Conversation
A wallet that already exists when the mainnet build first opens sees a one-time two-page notice: a branded intro while the app checks the wallet's testnet history, then a page for what it finds. Miners are told to keep the wallet for their rewards and see their block count; users with a balance may keep or replace the wallet; wallets with nothing on testnet may create a new one or migrate, which keeps everything as is. Balances come from the old Planck RPC and mined blocks from the old indexer, both constants inside the migration service and used only there. If testnet cannot be reached the holder page is shown with a note. New and imported wallets mark the notice done so only upgrades see it. debugMainnetMigration forces any outcome in debug builds.
A silent refresh fired before the controller's initial load won the in-flight dedup and never cleared isLoading. Accounts with rows were rescued by the non-empty check; empty accounts stayed on the skeleton until a manual refresh. Drop the combined all-accounts provider nothing consumed.
One lead line, then a mined and a not-mined card built from the Wallet Created screen's row card, now shared as InfoCard.
The debug constant still forces the notice; the intro page now shows a chip per outcome so every page can be checked without a hot restart. Not-mined card reads: keep your secret phrase or create a new one.
The muted footnote was easy to miss. The holder page now opens with a warning row, built from InfoCard with a new trailing slot, and a Retry that runs the check once more. QuantusButton hugs its content when no width is given so the underline variant works inline.
n13
left a comment
There was a problem hiding this comment.
Verdict: changes requested.
-
[P2] Await the migration completion write before leaving the flow. SettingsService.setMainnetMigrationDone drops the Future returned by SharedPreferences.setBool, while MainnetMigrationScreen._finish immediately renders Home and the create/import paths immediately navigate onward. If the platform write fails, the app still treats the notice as retired and the failure is not surfaced; on the next launch the notice appears again, including for a newly created wallet. Make the setter asynchronous, treat a false result as a failure, await it through MainnetMigrationService.markDone and all call sites, and only transition after success. Please add a regression test for a failed completion write.
-
[P3] mobile-app/lib/v2/screens/welcome/mainnet_migration_screen.dart:288 hard-codes BorderRadius.circular(16). The mobile v2 rules require radii from context.radiusV3; use the matching theme radius token here.
Validation: the three focused migration/pagination test files pass (18 tests), GitHub Analyze and dependency checks pass, and the live testnet GraphQL response matches the id/total_mined_blocks assumptions.
setMainnetMigrationDone now awaits SharedPreferences and throws when the write is refused, so a wallet is never treated as migrated on a failed write. The notice stays open and shows the error; create and import surface it through their existing error paths. Block count card uses the md radius token. Regression tests cover a failed write on the notice and on wallet creation; the settings mock is regenerated.
n13
left a comment
There was a problem hiding this comment.
Verdict: approved.
The previous findings are resolved:
- The completion flag is now awaited, a refused SharedPreferences write throws, the migration screen stays open and reports the failure, and create/import paths await persistence.
- The block-count card now uses the v2 theme radius token.
No new findings on the latest head ca73686.
Validation: 22 focused migration, pagination, and wallet-creation tests passed locally; the final mock-formatting commit was rechecked with its dependent test; git diff --check is clean; and the full GitHub workflow passed formatting, analysis, mobile-app tests, SDK tests, and cold-wallet tests.
When the mainnet build opens on a wallet that already exists, the user sees a one-time two-page notice instead of a silent zero balance.
Flow
Pages live in a PageView with the eased swipe animation; swiping back to the intro works. "Create New Wallet" goes through the existing reset confirmation (backup checkbox and device auth) and lands on the welcome screen. Finishing records the outcome as a telemetry event.
Check
MainnetMigrationServiceholds the old Planck RPC and indexer URLs as constants and is their only user. It gathers every stored account plus each software wallet's wormhole address (where mining rewards were paid), sumsaccount_stats.total_mined_blocksfrom the old indexer, and sums free balances from the old RPC. The whole check has a 20 second timeout and is not retried on its own, so the fallback page never flips back to a spinner; Retry on that page runs it again.Gating
mainnet_migration_doneis unset; finishing sets it.AppConstants.debugMainnetMigrationset to any ofminer,holder,newcomer, orerrorforces the notice in debug builds. The intro page then shows a chip per outcome, so every page can be checked by tapping, without a hot restart.Shared card
The holder cards are the Wallet Created screen's account row, extracted to
InfoCardand used in both places.Home activity for empty accounts (pre-existing bug)
The activity list stayed on skeletons for accounts with no history until a manual refresh. The history poller reads the pagination controller and calls
silentRefreshin the same tick, before the controller's own_initreaches its fetch; the initial load then joins the silent fetch through the in-flight dedup, and the silent path never clearsisLoading. Accounts with rows were rescued by the non-empty check, empty ones were not.silentRefreshnow runs the initial load whileisLoadingis set, with a regression test that fails on the old code. The unused combined all-accounts provider is removed.SDK
SubstrateService.queryBalanceOn(rpc, address)extracted fromqueryBalanceso the check can read balances from another chain.SettingsServicegains the done flag.Tests
Service: which addresses are checked (including the wormhole address, and none for hardware-only wallets), classification for each outcome, summing blocks across addresses, indexer failure surfacing, and the pending flag. Screen: each outcome's copy and buttons, Done and Keep and Migrate retiring the notice, Create New Wallet reaching the reset confirmation, the unreachable fallback and its Retry, a failed completion write keeping the notice open, and the loader while the check runs. Wallet creation: a failed completion write surfaces instead of adding the account. Full mobile, SDK, and cold wallet suites pass.
Simulator
Walked every page on the iPhone 17 Pro simulator with each forced outcome: intro, miner (Done to home), holder (Keep Wallet to home), newcomer (Migrate Old Wallet to home), testnet unreachable warning row with Retry, and Create New Wallet through the reset confirmation, device auth, and back to the welcome screen. The home screen shows "No Transactions Yet" right after launch for a fresh wallet.