From 8661681110933a333bbf20a830763d5b1d291592 Mon Sep 17 00:00:00 2001 From: malik1004x Date: Sat, 3 Oct 2026 14:39:38 +0200 Subject: [PATCH] cw-1683-prepare-zano-removal (#3668) * wip zano removal * add zano removal popup * constantly show popup if user keeps using deprecated wallet * add confirmation before viewing seed * fix viewed check * prevent wallet staying open after seed backup * Update lib/entities/default_settings_migration.dart Co-authored-by: Omar Hatem * add passphrase * make popup scrollable * delete deprecated seeds when wallet is deleted * encrypt seeds * also deprecate decred * remove decred from available types * remove backup logic * showArrow: false, * Remove deprecated wallet seeds deletion Removed deprecated wallet seeds deletion from delete method. --------- Co-authored-by: Omar Hatem --- cw_core/lib/db/sqlite.dart | 19 +++- cw_core/lib/wallet_info.dart | 3 +- cw_zano/lib/zano_wallet.dart | 5 + cw_zano/lib/zano_wallet_api.dart | 7 ++ cw_zano/lib/zano_wallet_service.dart | 1 + lib/entities/default_settings_migration.dart | 10 +- lib/entities/preferences_key.dart | 4 + lib/new-ui/new_dashboard.dart | 13 +++ lib/new-ui/widgets/wallet_removal_popup.dart | 102 ++++++++++++++++++ .../dashboard/dashboard_view_model.dart | 21 ++++ res/values/strings_en.arb | 4 + tool/configure.dart | 12 +-- 12 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 lib/new-ui/widgets/wallet_removal_popup.dart diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart index 77c38b599..f1ba1d103 100644 --- a/cw_core/lib/db/sqlite.dart +++ b/cw_core/lib/db/sqlite.dart @@ -66,7 +66,7 @@ Future _initDb({String? pathOverride}) async { await db?.close(); db = await openDatabase( dbFile.path, - version: 14, + version: 15, onUpgrade: (db, oldVersion, newVersion) async { printV("migrating: $oldVersion, $newVersion"); if (oldVersion <= 1) { @@ -193,6 +193,9 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings ( await _migrateBitcoinCardStylesForAccounts(db); } + if(oldVersion <= 14) { + await _createDeprecatedWalletSeedTable(db); + } }, onCreate: (Database db, int version) async { await db.execute(''' @@ -299,6 +302,8 @@ CREATE TABLE BalanceCardStyleSettings ( await _createTronTokenTable(db); await _createImportedNFTTable(db); await _createWalletInfoAccountTable(db); + await _createDeprecatedWalletSeedTable(db); + }, ); } @@ -589,3 +594,15 @@ isDefault BOOLEAN DEFAULT FALSE ); """); } + + +Future _createDeprecatedWalletSeedTable(Database db) async { + await db.execute(""" +CREATE TABLE DeprecatedWalletSeeds ( +walletInfoId INTEGER PRIMARY KEY, +seed TEXT NOT NULL, +passphrase TEXT NOT NULL, +FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) +); +"""); +} diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index 6cb883f77..3cfc38cf7 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -793,7 +793,8 @@ class WalletInfo { } static Future delete(WalletInfo walletInfo) async { - return await db!.delete(tableName, where: 'id = ?', whereArgs: [walletInfo.id]); + final deleted = await db!.delete(tableName, where: 'id = ?', whereArgs: [walletInfo.id]); + return deleted; } static Future> selectList(String where, List whereArgs, diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index df5ceff70..20ad63f5f 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -122,6 +122,7 @@ abstract class ZanoWalletBase bool _isTransactionUpdating; bool _hasSyncAfterStartup; Timer? _autoSaveTimer; + bool _isClosed = false; /// number of transactions in each request static final int _txChunkSize = (pow(2, 32) - 1).toInt(); @@ -422,6 +423,7 @@ abstract class ZanoWalletBase @override Future close({bool shouldCleanup = true}) async { + _isClosed = true; closeWallet(null); _updateSyncInfoTimer?.cancel(); _autoSaveTimer?.cancel(); @@ -534,6 +536,9 @@ abstract class ZanoWalletBase await walletAddresses.init(); await walletAddresses.updateAddress(address); await updateTransactions(); + if (_isClosed) { + return; + } _autoSaveTimer = Timer.periodic(Duration(seconds: _autoSaveIntervalSeconds), (_) async { await save(); }); diff --git a/cw_zano/lib/zano_wallet_api.dart b/cw_zano/lib/zano_wallet_api.dart index 7196c1d94..7ee5d1898 100644 --- a/cw_zano/lib/zano_wallet_api.dart +++ b/cw_zano/lib/zano_wallet_api.dart @@ -458,6 +458,13 @@ mixin ZanoWalletApi { static Map openWalletCache = {}; + static Future closeCachedWallet(String path) async { + final cached = openWalletCache.remove(path); + if (cached != null) { + await _closeWallet(cached.walletId); + } + } + Future transfer( List destinations, BigInt fee, String comment) async { final params = TransferParams( diff --git a/cw_zano/lib/zano_wallet_service.dart b/cw_zano/lib/zano_wallet_service.dart index cf50509f5..2fa3c6012 100644 --- a/cw_zano/lib/zano_wallet_service.dart +++ b/cw_zano/lib/zano_wallet_service.dart @@ -112,6 +112,7 @@ class ZanoWalletService extends WalletService< @override Future remove(String wallet) async { + await ZanoWalletApi.closeCachedWallet(await pathForWallet(name: wallet, type: getType())); final path = await pathForWalletDir(name: wallet, type: getType()); final file = Directory(path); final isExist = file.existsSync(); diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 5b919ccd5..ce43f9362 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -2,7 +2,10 @@ import 'dart:convert'; import 'dart:io' show Directory, File, Platform; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import "package:cake_wallet/core/key_service.dart"; import 'package:cake_wallet/core/secure_storage.dart'; +import "package:cake_wallet/decred/decred.dart"; +import "package:cake_wallet/di.dart"; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; @@ -14,6 +17,9 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/new-ui/model/charts/charts_asset.dart'; +import "package:cake_wallet/store/settings_store.dart"; +import "package:cake_wallet/zano/zano.dart"; +import "package:cw_core/cake_hive.dart"; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -23,8 +29,10 @@ import 'package:cw_core/node_list.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/root_dir.dart'; import 'package:cw_core/spl_token.dart'; +import "package:cw_core/unspent_coins_info.dart"; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; +import "package:cw_core/wallet_service.dart"; import 'package:cw_core/wallet_type.dart'; import 'package:encrypt/encrypt.dart' as encrypt; import 'package:hive/hive.dart'; @@ -1377,4 +1385,4 @@ Future _addTbbTokenToExistingSolanaWallets() async { } catch (e) { printV("Error in TBB migration: $e"); } -} +} \ No newline at end of file diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index bacd03e0b..de1ea9c76 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -1,3 +1,5 @@ +import "package:cw_core/wallet_type.dart"; + class PreferencesKey { static const currentWalletType = 'current_wallet_type'; static const currentWalletName = 'current_wallet_name'; @@ -147,4 +149,6 @@ class PreferencesKey { static const balanceHideCounter = "balance_hide_counter"; static const zcashMigrationModalViewed = "zcash_migration_modal_viewed"; static const showCiBuildOverlay = "show_ci_build_overlay"; + static String deprecationPopupViewed(WalletType type) => + "deprecation_popup_viewed_${walletTypeToString(type)}"; } diff --git a/lib/new-ui/new_dashboard.dart b/lib/new-ui/new_dashboard.dart index eadf0d222..6f1869c9c 100644 --- a/lib/new-ui/new_dashboard.dart +++ b/lib/new-ui/new_dashboard.dart @@ -7,6 +7,7 @@ import "package:cake_wallet/new-ui/page_open_listener.dart"; import 'package:cake_wallet/new-ui/pages/charts_page.dart'; import 'package:cake_wallet/new-ui/pages/home_page.dart'; import 'package:cake_wallet/new-ui/widgets/changelog_modal.dart'; +import "package:cake_wallet/new-ui/widgets/wallet_removal_popup.dart"; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/new_main_navbar_widget.dart'; @@ -55,6 +56,7 @@ class _NewDashboardState extends State { setState(() { _selectedPage = 0; }); + _showWalletRemovalPopup(context); }); Future.delayed(Duration(milliseconds: 300)).then((_) { @@ -63,6 +65,7 @@ class _NewDashboardState extends State { }); }); _showVulnerableSeedsPopup(context); + _showWalletRemovalPopup(context); } @override @@ -182,4 +185,14 @@ class _NewDashboardState extends State { ); } } + + Future _showWalletRemovalPopup(BuildContext context) async { + if(await widget.dashboardViewModel.shouldShowRemovalPopup()) { + await Future.delayed(const Duration(seconds: 1)); + final toBeRemoved = await widget.dashboardViewModel.walletsToBeRemoved; + if(context.mounted) { + await showPopUp(context: context, builder: (context)=>WalletRemovalPopup(affectedWallets: toBeRemoved,)); + } + } + } } diff --git a/lib/new-ui/widgets/wallet_removal_popup.dart b/lib/new-ui/widgets/wallet_removal_popup.dart new file mode 100644 index 000000000..d1dd4a853 --- /dev/null +++ b/lib/new-ui/widgets/wallet_removal_popup.dart @@ -0,0 +1,102 @@ +import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/new-ui/widgets/new_primary_button.dart"; +import "package:cake_wallet/src/widgets/cake_image_widget.dart"; +import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart"; +import "package:cake_wallet/themes/core/theme_extension.dart"; +import "package:cw_core/currency_for_wallet_type.dart"; +import "package:cw_core/wallet_info.dart"; +import "package:cw_core/wallet_type.dart"; +import "package:flutter/material.dart"; + +class WalletRemovalPopup extends StatelessWidget { + const WalletRemovalPopup({required this.affectedWallets, super.key}); + + final List affectedWallets; + + @override + Widget build(BuildContext context) { + final types = affectedWallets.map((item) => item.type).toSet().toList(); + final iconPaths = types.map((item) => walletTypeToCryptoCurrency(item).iconPath ?? "").toList(); + final typeNames = types.map(walletTypeToDisplayName).toList(); + + return Center( + child: Material( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(18), + ), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 18, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8, + children: iconPaths + .map( + (item) => CakeImageWidget( + imageUrl: item, + width: 64, + height: 64, + ), + ) + .toList(), + ), + Text( + textAlign: TextAlign.center, + S.of(context).support_will_soon_be_removed(typeNames.join(", ")), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), + ), + Text( + textAlign: TextAlign.center, + types.map(deprecationReasonForType).whereType().join("\n\n"), + ), + Text( + textAlign: TextAlign.center, + S.of(context).make_sure_you_migrated(typeNames.join("/")), + style: TextStyle( + fontWeight: FontWeight.w500, + color: context.currentTheme.customColors.warningOutlineColor, + ), + ), + NewListSections( + sections: { + "": affectedWallets + .map( + (item) => ListItemRegularRow( + iconPath: walletTypeToCryptoCurrency(item.type).iconPath ?? "", + keyValue: item.name, + label: item.name, + showArrow: false, + ), + ) + .toList(), + }, + ), + NewPrimaryButton( + onPressed: Navigator.of(context).pop, + text: S.of(context).close, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + String? deprecationReasonForType(WalletType type) => + switch (type) { WalletType.zano => S.current.zano_removal_reason, _ => null }; +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 96c1adf42..2cc5ce1d0 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1697,4 +1697,25 @@ abstract class DashboardViewModelBase with Store { Future refreshDashboard() async { reconnect(); } + + + static const walletTypesToBeRemoved = [ + WalletType.zano, + WalletType.decred, + ]; + + Future shouldShowRemovalPopup() async { + final show = (await WalletInfo.getAll()).any((item) => + walletTypesToBeRemoved.contains(item.type) && + !(sharedPreferences.getBool(PreferencesKey.deprecationPopupViewed(item.type)) ?? false),); + for (final type in walletTypesToBeRemoved) { + await sharedPreferences.setBool(PreferencesKey.deprecationPopupViewed(type), true); + } + // if user is actively using a wallet type that's about to be removed, keep nagging them + return show || walletTypesToBeRemoved.contains(wallet.type); + } + + Future> get walletsToBeRemoved async => (await WalletInfo.getAll()) + .where((item) => walletTypesToBeRemoved.contains(item.type)) + .toList(); } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 50e1ec7ba..c9ec1b7df 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -668,6 +668,7 @@ "low_fee_alert": "You currently are using a low network fee priority. This could cause long waits, different rates, or canceled trades. We recommend setting a higher fee for a better experience.", "low_kyc": "Low KYC", "made_easy": "made easy", + "make_sure_you_migrated": "Please be sure you've backed up your seed phrase from Recovery & Keys in the meantime, and consider migrating to another ${walletTypes} wallet.", "manage_nodes": "Manage nodes", "manage_pow_nodes": "Manage PoW nodes", "manage_providers": "Manage providers", @@ -1180,6 +1181,7 @@ "show_recovery_phrase": "Show me my Recovery Phrase", "show_recovery_phrase_and_keys": "Show my Recovery Phrase & Keys", "show_seed": "Show seed", + "show_seed_confirmation": "View seed of this wallet?", "show_zcash_card": "Show zcash missing funds card", "sign_all": "Sign All", "sign_message": "Sign Message", @@ -1239,6 +1241,7 @@ "support_title_guides": "Cake Wallet docs", "support_title_live_chat": "Live support", "support_title_other_links": "Other support links", + "support_will_soon_be_removed": "Support for ${walletTypes} will soon be removed", "supported": "Supported", "swap": "Swap", "swap_from_network": "Swap from ${network}", @@ -1578,6 +1581,7 @@ "youCanGoBackToYourDapp": "You can go back to your dApp now", "your": "Your", "yy": "YY", + "zano_removal_reason": "Zano has undergone multiple hard-forks to resolve a critical network issue, including a rollback of the chain. Due to serious issues with the forks and the maintenance burden it brings to us, we will be removing Zano from Cake Wallet in a future release.", "zcash_card_description": "If you don't see your full balance Cake Wallet can attempt to sweep funds from internal change addresses used by some wallets.", "zcash_card_dismiss": "Dismiss", "zcash_card_enable_later": "You can always enable this card later in settings", diff --git a/tool/configure.dart b/tool/configure.dart index 398e9a92e..7c7bc8e98 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -2077,13 +2077,13 @@ Future generateWalletTypes({ outputContent += '\tWalletType.nano,\n'; } - if (hasDecred) { - outputContent += '\tWalletType.decred,\n'; - } + // if (hasDecred) { + // outputContent += '\tWalletType.decred,\n'; + // } - if (hasZano) { - outputContent += '\tWalletType.zano,\n'; - } + // if (hasZano) { + // outputContent += '\tWalletType.zano,\n'; + // } if (hasBanano) { outputContent += '\tWalletType.banano,\n';