From 09f20b2a7bf3ef15bfcfcf4471e1f9dfad94708a Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 6 Mar 2025 01:25:38 +0100 Subject: [PATCH] CW-843: Enhance Wallet Groups Implementation (#2045) * feat: Enhance Wallet Groups Implementation by using hashedIdentifiers instead of parentAddresses * fix: Call updateWalletGroups even if group has an hash identifier * feat: Add secrets to workflow * feat: Enhance Wallet Groups Implementation by using hashedIdentifiers instead of parentAddresses * Handle wallet grouping edgecase where wallet is restored via non seed medium * fix: Valid wallet/wallet groups not showing up when choosing wallet/groups for creating new wallets --- .../workflows/automated_integration_test.yml | 4 + .github/workflows/pr_test_build_android.yml | 1 + .github/workflows/pr_test_build_linux.yml | 1 + .../bitcoin_wallet_creation_credentials.dart | 2 - ...coin_cash_wallet_creation_credentials.dart | 2 - cw_core/lib/wallet_credentials.dart | 2 - cw_core/lib/wallet_info.dart | 11 +- ...evm_chain_wallet_creation_credentials.dart | 1 - .../lib/nano_wallet_creation_credentials.dart | 2 - .../solana_wallet_creation_credentials.dart | 2 - .../lib/tron_wallet_creation_credentials.dart | 2 - ios/Podfile.lock | 2 +- lib/bitcoin/cw_bitcoin.dart | 2 - lib/bitcoin_cash/cw_bitcoin_cash.dart | 2 - lib/core/new_wallet_arguments.dart | 2 - lib/di.dart | 11 +- lib/entities/hash_wallet_identifier.dart | 21 +++ lib/entities/wallet_edit_page_arguments.dart | 4 +- lib/entities/wallet_group.dart | 11 +- lib/entities/wallet_manager.dart | 145 ++++++++++++------ lib/ethereum/cw_ethereum.dart | 2 - lib/nano/cw_nano.dart | 2 - lib/polygon/cw_polygon.dart | 2 - lib/reactions/on_current_wallet_change.dart | 4 + lib/solana/cw_solana.dart | 2 - .../new_wallet/wallet_group_display_page.dart | 1 - lib/src/screens/wallet/wallet_edit_page.dart | 4 +- .../screens/wallet_list/wallet_list_page.dart | 2 +- lib/tron/cw_tron.dart | 13 +- lib/view_model/wallet_creation_vm.dart | 6 +- .../wallet_groups_display_view_model.dart | 10 +- .../wallet_list/wallet_edit_view_model.dart | 4 +- lib/view_model/wallet_new_vm.dart | 7 - tool/configure.dart | 13 +- tool/utils/secret_key.dart | 2 + 35 files changed, 181 insertions(+), 123 deletions(-) create mode 100644 lib/entities/hash_wallet_identifier.dart diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 0869db8ea..539513111 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -223,6 +223,10 @@ jobs: echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart + # end of test secrets + echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart + echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - name: Rename app run: | diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 25fb144e3..6c7e82409 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -172,6 +172,7 @@ jobs: # end of test secrets echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart + echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - name: prepare monero_c and cache run: | diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index a9d8085b6..e307dc410 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -168,6 +168,7 @@ jobs: # end of test secrets echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart + echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - name: prepare monero_c and cache run: | diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index a1b1418b8..177d61e87 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -11,13 +11,11 @@ class BitcoinNewWalletCredentials extends WalletCredentials { String? derivationPath, String? passphrase, this.mnemonic, - String? parentAddress, }) : super( name: name, walletInfo: walletInfo, password: password, passphrase: passphrase, - parentAddress: parentAddress, ); final String? mnemonic; diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart index af93cdbf8..90004a485 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart @@ -8,13 +8,11 @@ class BitcoinCashNewWalletCredentials extends WalletCredentials { String? password, String? passphrase, this.mnemonic, - String? parentAddress, }) : super( name: name, walletInfo: walletInfo, password: password, passphrase: passphrase, - parentAddress: parentAddress ); final String? mnemonic; } diff --git a/cw_core/lib/wallet_credentials.dart b/cw_core/lib/wallet_credentials.dart index 55c24bf37..30ae2546c 100644 --- a/cw_core/lib/wallet_credentials.dart +++ b/cw_core/lib/wallet_credentials.dart @@ -10,7 +10,6 @@ abstract class WalletCredentials { this.passphrase, this.derivationInfo, this.hardwareWalletType, - this.parentAddress, }) { if (this.walletInfo != null && derivationInfo != null) { this.walletInfo!.derivationInfo = derivationInfo; @@ -19,7 +18,6 @@ abstract class WalletCredentials { final String name; final int? height; - String? parentAddress; int? seedPhraseLength; String? password; String? passphrase; diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index bd035e30a..d62e61941 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -81,6 +81,8 @@ class WalletInfo extends HiveObject { this.derivationInfo, this.hardwareWalletType, this.parentAddress, + this.hashedWalletIdentifier, + this.isNonSeedWallet, ) : _yatLastUsedAddressController = StreamController.broadcast(); factory WalletInfo.external({ @@ -99,6 +101,8 @@ class WalletInfo extends HiveObject { DerivationInfo? derivationInfo, HardwareWalletType? hardwareWalletType, String? parentAddress, + String? hashedWalletIdentifier, + bool? isNonSeedWallet, }) { return WalletInfo( id, @@ -116,6 +120,8 @@ class WalletInfo extends HiveObject { derivationInfo, hardwareWalletType, parentAddress, + hashedWalletIdentifier, + isNonSeedWallet ?? false, ); } @@ -196,8 +202,11 @@ class WalletInfo extends HiveObject { @HiveField(24) List? manualAddresses; - + @HiveField(25) + String? hashedWalletIdentifier; + @HiveField(26, defaultValue: false) + bool isNonSeedWallet; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; diff --git a/cw_evm/lib/evm_chain_wallet_creation_credentials.dart b/cw_evm/lib/evm_chain_wallet_creation_credentials.dart index 5075e6289..d9595a243 100644 --- a/cw_evm/lib/evm_chain_wallet_creation_credentials.dart +++ b/cw_evm/lib/evm_chain_wallet_creation_credentials.dart @@ -7,7 +7,6 @@ class EVMChainNewWalletCredentials extends WalletCredentials { required super.name, super.walletInfo, super.password, - super.parentAddress, this.mnemonic, super.passphrase, }); diff --git a/cw_nano/lib/nano_wallet_creation_credentials.dart b/cw_nano/lib/nano_wallet_creation_credentials.dart index 59789aec7..8eb6dc2d2 100644 --- a/cw_nano/lib/nano_wallet_creation_credentials.dart +++ b/cw_nano/lib/nano_wallet_creation_credentials.dart @@ -8,13 +8,11 @@ class NanoNewWalletCredentials extends WalletCredentials { String? password, DerivationType? derivationType, this.mnemonic, - String? parentAddress, String? passphrase, }) : super( name: name, password: password, walletInfo: walletInfo, - parentAddress: parentAddress, passphrase: passphrase, ); diff --git a/cw_solana/lib/solana_wallet_creation_credentials.dart b/cw_solana/lib/solana_wallet_creation_credentials.dart index 121ef2b44..309c41cf3 100644 --- a/cw_solana/lib/solana_wallet_creation_credentials.dart +++ b/cw_solana/lib/solana_wallet_creation_credentials.dart @@ -6,14 +6,12 @@ class SolanaNewWalletCredentials extends WalletCredentials { required String name, WalletInfo? walletInfo, String? password, - String? parentAddress, this.mnemonic, String? passphrase, }) : super( name: name, walletInfo: walletInfo, password: password, - parentAddress: parentAddress, passphrase: passphrase, ); final String? mnemonic; diff --git a/cw_tron/lib/tron_wallet_creation_credentials.dart b/cw_tron/lib/tron_wallet_creation_credentials.dart index fd9066acd..402cff2ff 100644 --- a/cw_tron/lib/tron_wallet_creation_credentials.dart +++ b/cw_tron/lib/tron_wallet_creation_credentials.dart @@ -7,13 +7,11 @@ class TronNewWalletCredentials extends WalletCredentials { WalletInfo? walletInfo, String? password, this.mnemonic, - String? parentAddress, String? passphrase, }) : super( name: name, walletInfo: walletInfo, password: password, - parentAddress: parentAddress, passphrase: passphrase, ); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9e2a8507a..d400d3f81 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -277,4 +277,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597 -COCOAPODS: 1.15.2 +COCOAPODS: 1.15.2 \ No newline at end of file diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index bf9ec0c4d..f93ba9550 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -34,7 +34,6 @@ class CWBitcoin extends Bitcoin { String? password, String? passphrase, String? mnemonic, - String? parentAddress, }) => BitcoinNewWalletCredentials( name: name, @@ -42,7 +41,6 @@ class CWBitcoin extends Bitcoin { password: password, passphrase: passphrase, mnemonic: mnemonic, - parentAddress: parentAddress, ); @override diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart index b74448703..be0323bfa 100644 --- a/lib/bitcoin_cash/cw_bitcoin_cash.dart +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -17,14 +17,12 @@ class CWBitcoinCash extends BitcoinCash { String? password, String? passphrase, String? mnemonic, - String? parentAddress, }) => BitcoinCashNewWalletCredentials( name: name, walletInfo: walletInfo, password: password, passphrase: passphrase, - parentAddress: parentAddress, mnemonic: mnemonic, ); diff --git a/lib/core/new_wallet_arguments.dart b/lib/core/new_wallet_arguments.dart index 2581c57bb..9e2ef9df4 100644 --- a/lib/core/new_wallet_arguments.dart +++ b/lib/core/new_wallet_arguments.dart @@ -3,12 +3,10 @@ import 'package:cw_core/wallet_type.dart'; class NewWalletArguments { final WalletType type; final String? mnemonic; - final String? parentAddress; final bool isChildWallet; NewWalletArguments({ required this.type, - this.parentAddress, this.mnemonic, this.isChildWallet = false, }); diff --git a/lib/di.dart b/lib/di.dart index 3e10bd7a1..174183f9a 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -392,11 +392,10 @@ Future setup({ getIt.registerFactory(() => NewWalletTypeViewModel(_walletInfoSource)); getIt.registerFactory( - () { - final instance = WalletManager(_walletInfoSource, getIt.get()); - instance.updateWalletGroups(); - return instance; - }, + () => WalletManager( + _walletInfoSource, + getIt.get(), + ), ); getIt.registerFactoryParam( @@ -812,7 +811,7 @@ Future setup({ editingWallet: arguments.editingWallet, isWalletGroup: arguments.isWalletGroup, groupName: arguments.groupName, - parentAddress: arguments.parentAddress, + walletGroupKey: arguments.walletGroupKey, ), ); }); diff --git a/lib/entities/hash_wallet_identifier.dart b/lib/entities/hash_wallet_identifier.dart new file mode 100644 index 000000000..8e593ec79 --- /dev/null +++ b/lib/entities/hash_wallet_identifier.dart @@ -0,0 +1,21 @@ +import 'dart:convert'; + +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cw_core/wallet_base.dart'; +import 'package:hashlib/hashlib.dart'; + +String createHashedWalletIdentifier(WalletBase wallet) { + if (wallet.seed == null) return ''; + + final salt = secrets.walletGroupSalt; + final combined = '$salt.${wallet.seed}'; + + // Convert to UTF-8 bytes. + final bytes = utf8.encode(combined); + + // Perform SHA-256 hash. + final digest = sha256.convert(bytes); + + // Return the hex string representation of the hash. + return digest.toString(); +} diff --git a/lib/entities/wallet_edit_page_arguments.dart b/lib/entities/wallet_edit_page_arguments.dart index 260471f7e..6217195dc 100644 --- a/lib/entities/wallet_edit_page_arguments.dart +++ b/lib/entities/wallet_edit_page_arguments.dart @@ -10,7 +10,7 @@ class WalletEditPageArguments { this.isWalletGroup = false, this.walletListViewModel, this.groupName = '', - this.parentAddress = '', + this.walletGroupKey = '', this.walletEditViewModel, this.walletNewVM, this.authService, @@ -19,7 +19,7 @@ class WalletEditPageArguments { final WalletListItem editingWallet; final bool isWalletGroup; final String groupName; - final String parentAddress; + final String walletGroupKey; final WalletListViewModel? walletListViewModel; final WalletEditViewModel? walletEditViewModel; diff --git a/lib/entities/wallet_group.dart b/lib/entities/wallet_group.dart index 9845aea65..ab94f3eb3 100644 --- a/lib/entities/wallet_group.dart +++ b/lib/entities/wallet_group.dart @@ -1,13 +1,14 @@ import 'package:cw_core/wallet_info.dart'; class WalletGroup { - WalletGroup(this.parentAddress) : wallets = []; + WalletGroup(this.groupKey) : wallets = []; - /// Main identifier for each group, compulsory. - final String parentAddress; + /// Primary identifier for the group. Previously was `parentAddress`. + /// Now we store either the wallet's hash OR fallback to parentAddress/address. + final String groupKey; - /// Child wallets that share the same parent address within this group - List wallets; + /// Child wallets that share the same group key + final List wallets; /// Custom name for the group, editable for multi-child wallet groups String? groupName; diff --git a/lib/entities/wallet_manager.dart b/lib/entities/wallet_manager.dart index 29c873dae..8bcabcaf2 100644 --- a/lib/entities/wallet_manager.dart +++ b/lib/entities/wallet_manager.dart @@ -1,70 +1,61 @@ +import 'package:cake_wallet/entities/hash_wallet_identifier.dart'; import 'package:cake_wallet/entities/wallet_group.dart'; +import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:hive/hive.dart'; import 'package:shared_preferences/shared_preferences.dart'; class WalletManager { - WalletManager( - this._walletInfoSource, - this._sharedPreferences, - ); + WalletManager(this._walletInfoSource, this._sharedPreferences); final Box _walletInfoSource; final SharedPreferences _sharedPreferences; final List walletGroups = []; - /// Categorize wallets into groups based on their parentAddress. - /// - /// Update the lead wallet for each group and clean up empty groups - /// i.e remove group if there's no lead wallet (i.e, no wallets left) void updateWalletGroups() { walletGroups.clear(); - for (var walletInfo in _walletInfoSource.values) { - final group = _getOrCreateGroup(_resolveParentAddress(walletInfo)); + for (final walletInfo in _walletInfoSource.values) { + final groupKey = _resolveGroupKey(walletInfo); + final group = _getOrCreateGroup(groupKey); group.wallets.add(walletInfo); } - walletGroups.removeWhere((group) => group.wallets.isEmpty); - + walletGroups.removeWhere((g) => g.wallets.isEmpty); _loadCustomGroupNames(); } - /// Function to determine the correct parentAddress for a wallet. - /// - /// If it's a parent wallet (parentAddress is null), - /// use its own address as parentAddress. - String _resolveParentAddress(WalletInfo walletInfo) { + String _resolveGroupKey(WalletInfo walletInfo) { + if (walletInfo.hashedWalletIdentifier != null && + walletInfo.hashedWalletIdentifier!.isNotEmpty) { + return walletInfo.hashedWalletIdentifier!; + } + + // Fallback to old logic return walletInfo.parentAddress ?? walletInfo.address; } - /// Check if a group with the parentAddress already exists, - /// If no group exists, create a new one. - /// - WalletGroup _getOrCreateGroup(String parentAddress) { + WalletGroup _getOrCreateGroup(String groupKey) { return walletGroups.firstWhere( - (group) => group.parentAddress == parentAddress, + (g) => g.groupKey == groupKey, orElse: () { - final newGroup = WalletGroup(parentAddress); + final newGroup = WalletGroup(groupKey); walletGroups.add(newGroup); return newGroup; }, ); } - /// Add a new wallet and update lead wallet after adding. void addWallet(WalletInfo walletInfo) { - final group = _getOrCreateGroup(_resolveParentAddress(walletInfo)); + final groupKey = _resolveGroupKey(walletInfo); + final group = _getOrCreateGroup(groupKey); group.wallets.add(walletInfo); } - /// Removes a wallet from a group i.e when it's deleted. - /// - /// Update lead wallet after removing, - /// Remove the group if it's empty (i.e., no lead wallet). void removeWallet(WalletInfo walletInfo) { - final group = _getOrCreateGroup(_resolveParentAddress(walletInfo)); + final groupKey = _resolveGroupKey(walletInfo); + final group = _getOrCreateGroup(groupKey); group.wallets.remove(walletInfo); if (group.wallets.isEmpty) { @@ -72,39 +63,99 @@ class WalletManager { } } - /// Returns all the child wallets within a group. - /// - /// If the group is not found, returns an empty group with no wallets. - List getWalletsInGroup(String parentAddress) { + List getWalletsInGroup(String groupKey) { return walletGroups .firstWhere( - (group) => group.parentAddress == parentAddress, - orElse: () => WalletGroup(parentAddress), + (g) => g.groupKey == groupKey, + orElse: () => WalletGroup(groupKey), ) .wallets; } - /// Iterate through all groups and load their custom names from storage void _loadCustomGroupNames() { for (var group in walletGroups) { - final groupName = _sharedPreferences.getString('wallet_group_name_${group.parentAddress}'); + final key = 'wallet_group_name_${group.groupKey}'; + final groupName = _sharedPreferences.getString(key); if (groupName != null && group.wallets.length > 1) { - group.groupName = groupName; // Restore custom name + group.groupName = groupName; } } } - /// Save custom name for a group - void _saveCustomGroupName(String parentAddress, String name) { - _sharedPreferences.setString('wallet_group_name_$parentAddress', name); + void _saveCustomGroupName(String groupKey, String name) { + _sharedPreferences.setString('wallet_group_name_$groupKey', name); } - // Set custom group name and persist it - void setGroupName(String parentAddress, String name) { - if (parentAddress.isEmpty || name.isEmpty) return; + void setGroupName(String groupKey, String name) { + if (groupKey.isEmpty || name.isEmpty) return; - final group = walletGroups.firstWhere((group) => group.parentAddress == parentAddress); + final group = walletGroups.firstWhere((g) => g.groupKey == groupKey); group.setCustomName(name); - _saveCustomGroupName(parentAddress, name); // Persist the custom name + _saveCustomGroupName(groupKey, name); + } + + // --------------------------------------------------------------------------- + // This performs a Group-Based Lazy Migration: + // If the user opens a wallet in an old group, + // we migrate ALL wallets that share its old group key to a new hash. + // --------------------------------------------------------------------------- + + /// When a user opens a wallet, check if it has a real hash. + /// If not, migrate the ENTIRE old group so they keep the same group name + /// and end up with the same new hash (preserving grouping). + Future ensureGroupHasHashedIdentifier(WalletBase openedWallet) async { + WalletInfo walletInfo = openedWallet.walletInfo; + + // If the openedWallet already has an hash, then there is nothing to do + if (walletInfo.hashedWalletIdentifier != null && + walletInfo.hashedWalletIdentifier!.isNotEmpty) { + updateWalletGroups(); // Still skeptical of calling this here. Looking for a better spot. + return; + } + + // Identify the old group key for this wallet + final oldGroupKey = _resolveGroupKey(walletInfo); // parentAddress fallback + + // Find all wallets that share this old group key (i.e the old group) + final oldGroupWallets = _walletInfoSource.values.where((w) { + final key = w.hashedWalletIdentifier != null && w.hashedWalletIdentifier!.isNotEmpty + ? w.hashedWalletIdentifier + : (w.parentAddress ?? w.address); + return key == oldGroupKey; + }).toList(); + + if (oldGroupWallets.isEmpty) { + // This shouldn't happen, but just in case it does, we return. + return; + } + + // Next, we determine the new group hash for these wallets + // Since they share the same seed, we can assign that group hash + // to all the wallets to preserve grouping. + final newGroupHash = createHashedWalletIdentifier(openedWallet); + + // Migrate the old group name from oldGroupKey(i.e parentAddress) to newGroupHash + await _migrateGroupName(oldGroupKey, newGroupHash); + + // Then we assign this new hash to each wallet in that old group and save them + for (final wallet in oldGroupWallets) { + wallet.hashedWalletIdentifier = newGroupHash; + await wallet.save(); + } + + // Finally, we rebuild the groups so that these wallets are now in the new group + updateWalletGroups(); + } + + /// Copy an old group name to the new group key, then remove the old key. + Future _migrateGroupName(String oldGroupKey, String newGroupKey) async { + final oldNameKey = 'wallet_group_name_$oldGroupKey'; + final newNameKey = 'wallet_group_name_$newGroupKey'; + + final oldGroupName = _sharedPreferences.getString(oldNameKey); + if (oldGroupName != null) { + await _sharedPreferences.setString(newNameKey, oldGroupName); + await _sharedPreferences.remove(oldNameKey); + } } } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index dc91e4fc2..40c7a0f77 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -11,7 +11,6 @@ class CWEthereum extends Ethereum { WalletCredentials createEthereumNewWalletCredentials({ required String name, String? mnemonic, - String? parentAddress, WalletInfo? walletInfo, String? password, String? passphrase, @@ -20,7 +19,6 @@ class CWEthereum extends Ethereum { name: name, walletInfo: walletInfo, password: password, - parentAddress: parentAddress, mnemonic: mnemonic, passphrase: passphrase, ); diff --git a/lib/nano/cw_nano.dart b/lib/nano/cw_nano.dart index 9a2243d00..463e19d65 100644 --- a/lib/nano/cw_nano.dart +++ b/lib/nano/cw_nano.dart @@ -94,14 +94,12 @@ class CWNano extends Nano { WalletInfo? walletInfo, String? password, String? mnemonic, - String? parentAddress, String? passphrase, }) => NanoNewWalletCredentials( name: name, password: password, mnemonic: mnemonic, - parentAddress: parentAddress, walletInfo: walletInfo, passphrase: passphrase, ); diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index b8f78e9e2..ec98137c5 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -11,7 +11,6 @@ class CWPolygon extends Polygon { WalletCredentials createPolygonNewWalletCredentials({ required String name, String? mnemonic, - String? parentAddress, WalletInfo? walletInfo, String? password, String? passphrase, @@ -21,7 +20,6 @@ class CWPolygon extends Polygon { walletInfo: walletInfo, password: password, mnemonic: mnemonic, - parentAddress: parentAddress, passphrase: passphrase, ); diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 3840b042e..a6475571d 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -1,6 +1,8 @@ +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/update_haven_rate.dart'; +import 'package:cake_wallet/entities/wallet_manager.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; @@ -59,6 +61,8 @@ void startCurrentWalletChangeReaction( return; } + await getIt.get().ensureGroupHasHashedIdentifier(wallet); + final node = settingsStore.getCurrentNode(wallet.type); startWalletSyncStatusChangeReaction(wallet, fiatConversionStore); diff --git a/lib/solana/cw_solana.dart b/lib/solana/cw_solana.dart index d8257396f..f0df5fba1 100644 --- a/lib/solana/cw_solana.dart +++ b/lib/solana/cw_solana.dart @@ -11,7 +11,6 @@ class CWSolana extends Solana { WalletCredentials createSolanaNewWalletCredentials({ required String name, String? mnemonic, - String? parentAddress, WalletInfo? walletInfo, String? password, String? passphrase, @@ -21,7 +20,6 @@ class CWSolana extends Solana { walletInfo: walletInfo, password: password, mnemonic: mnemonic, - parentAddress: parentAddress, passphrase: passphrase, ); diff --git a/lib/src/screens/new_wallet/wallet_group_display_page.dart b/lib/src/screens/new_wallet/wallet_group_display_page.dart index 549449a68..cd64353e2 100644 --- a/lib/src/screens/new_wallet/wallet_group_display_page.dart +++ b/lib/src/screens/new_wallet/wallet_group_display_page.dart @@ -150,7 +150,6 @@ class WalletGroupsDisplayBody extends StatelessWidget { arguments: NewWalletArguments( type: walletGroupsDisplayViewModel.type, mnemonic: mnemonic, - parentAddress: walletGroupsDisplayViewModel.parentAddress, isChildWallet: true, ), ); diff --git a/lib/src/screens/wallet/wallet_edit_page.dart b/lib/src/screens/wallet/wallet_edit_page.dart index 340091a1e..9e62284de 100644 --- a/lib/src/screens/wallet/wallet_edit_page.dart +++ b/lib/src/screens/wallet/wallet_edit_page.dart @@ -112,7 +112,7 @@ class WalletEditPage extends BasePage { pageArguments.editingWallet, password: password, isWalletGroup: pageArguments.isWalletGroup, - groupParentAddress: pageArguments.parentAddress, + walletGroupKey: pageArguments.walletGroupKey, ); }, callback: (bool isAuthenticatedSuccessfully, @@ -128,7 +128,7 @@ class WalletEditPage extends BasePage { await walletEditViewModel.changeName( pageArguments.editingWallet, isWalletGroup: pageArguments.isWalletGroup, - groupParentAddress: pageArguments.parentAddress, + walletGroupKey: pageArguments.walletGroupKey, ); confirmed = true; } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 681ca4d8a..569dce958 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -227,7 +227,7 @@ class WalletListBodyState extends State { editingWallet: wallet, isWalletGroup: true, groupName: groupName, - parentAddress: group.parentAddress, + walletGroupKey: group.groupKey, ), ); }, diff --git a/lib/tron/cw_tron.dart b/lib/tron/cw_tron.dart index bf2fac590..2726e873d 100644 --- a/lib/tron/cw_tron.dart +++ b/lib/tron/cw_tron.dart @@ -14,16 +14,15 @@ class CWTron extends Tron { WalletInfo? walletInfo, String? password, String? mnemonic, - String? parentAddress, String? passphrase, }) => TronNewWalletCredentials( - name: name, - walletInfo: walletInfo, - password: password, - mnemonic: mnemonic, - passphrase: passphrase, - parentAddress: parentAddress); + name: name, + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + passphrase: passphrase, + ); @override WalletCredentials createTronRestoreWalletFromSeedCredentials({ diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index bd6732b4a..edaff441d 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/background_tasks.dart'; import 'package:cake_wallet/entities/generate_name.dart'; +import 'package:cake_wallet/entities/hash_wallet_identifier.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -103,13 +104,16 @@ abstract class WalletCreationVMBase with Store { showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven, derivationInfo: credentials.derivationInfo ?? getDefaultCreateDerivation(), hardwareWalletType: credentials.hardwareWalletType, - parentAddress: credentials.parentAddress, ); credentials.walletInfo = walletInfo; final wallet = restoreWallet != null ? await processFromRestoredWallet(credentials, restoreWallet) : await process(credentials); + + final isNonSeedWallet = isRecovery ? wallet.seed == null : false; + walletInfo.isNonSeedWallet = isNonSeedWallet; + walletInfo.hashedWalletIdentifier = createHashedWalletIdentifier(wallet); walletInfo.address = wallet.walletAddresses.address; await _walletInfoSource.add(walletInfo); await _appStore.changeCurrentWallet(wallet); diff --git a/lib/view_model/wallet_groups_display_view_model.dart b/lib/view_model/wallet_groups_display_view_model.dart index 08515febf..09d6d656c 100644 --- a/lib/view_model/wallet_groups_display_view_model.dart +++ b/lib/view_model/wallet_groups_display_view_model.dart @@ -47,8 +47,6 @@ abstract class WalletGroupsDisplayViewModelBase with Store { @observable WalletInfo? selectedSingleWallet; - @observable - String? parentAddress; @observable bool isFetchingMnemonic; @@ -77,9 +75,6 @@ abstract class WalletGroupsDisplayViewModelBase with Store { walletToUse.name, ); - parentAddress = - isGroupSelected ? selectedWalletGroup!.parentAddress : selectedSingleWallet!.address; - return wallet.seed; } catch (e) { return null; @@ -130,11 +125,14 @@ abstract class WalletGroupsDisplayViewModelBase with Store { // Check that selected wallet type is not present already in group bool isSameTypeAsSelectedWallet = wallet.type == type; + bool isNonSeedWallet = wallet.isNonSeedWallet; + // Exclude if any of these conditions are true return isNonBIP39Wallet || isNanoDerivationType || isElectrumDerivationType || - isSameTypeAsSelectedWallet; + isSameTypeAsSelectedWallet || + isNonSeedWallet; }); if (shouldExcludeGroup) continue; diff --git a/lib/view_model/wallet_list/wallet_edit_view_model.dart b/lib/view_model/wallet_list/wallet_edit_view_model.dart index 343f160db..5379f1679 100644 --- a/lib/view_model/wallet_list/wallet_edit_view_model.dart +++ b/lib/view_model/wallet_list/wallet_edit_view_model.dart @@ -40,7 +40,7 @@ abstract class WalletEditViewModelBase with Store { Future changeName( WalletListItem walletItem, { String? password, - String? groupParentAddress, + String? walletGroupKey, bool isWalletGroup = false, }) async { state = WalletEditRenamePending(); @@ -48,7 +48,7 @@ abstract class WalletEditViewModelBase with Store { if (isWalletGroup) { _walletManager.updateWalletGroups(); - _walletManager.setGroupName(groupParentAddress!, newName); + _walletManager.setGroupName(walletGroupKey!, newName); } else { await _walletLoadingService.renameWallet( walletItem.type, diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index aa933eadc..0cd730028 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -109,7 +109,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { password: walletPassword, passphrase: passphrase, mnemonic: newWalletArguments!.mnemonic, - parentAddress: newWalletArguments!.parentAddress, ); case WalletType.haven: return haven!.createHavenNewWalletCredentials( @@ -119,7 +118,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, password: walletPassword, mnemonic: newWalletArguments!.mnemonic, - parentAddress: newWalletArguments!.parentAddress, passphrase: passphrase, ); case WalletType.bitcoinCash: @@ -128,7 +126,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { password: walletPassword, passphrase: passphrase, mnemonic: newWalletArguments!.mnemonic, - parentAddress: newWalletArguments!.parentAddress, ); case WalletType.nano: case WalletType.banano: @@ -136,7 +133,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, password: walletPassword, mnemonic: newWalletArguments!.mnemonic, - parentAddress: newWalletArguments!.parentAddress, passphrase: passphrase, ); case WalletType.polygon: @@ -144,7 +140,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, password: walletPassword, mnemonic: newWalletArguments!.mnemonic, - parentAddress: newWalletArguments!.parentAddress, passphrase: passphrase, ); case WalletType.solana: @@ -152,7 +147,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, password: walletPassword, mnemonic: newWalletArguments!.mnemonic, - parentAddress: newWalletArguments!.parentAddress, passphrase: passphrase, ); case WalletType.tron: @@ -160,7 +154,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, password: walletPassword, mnemonic: newWalletArguments!.mnemonic, - parentAddress: newWalletArguments!.parentAddress, passphrase: passphrase, ); case WalletType.wownero: diff --git a/tool/configure.dart b/tool/configure.dart index 214288078..c26e3f44b 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -160,7 +160,7 @@ abstract class Bitcoin { String? passphrase, }); WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo}); - WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic, String? parentAddress}); + WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo}); List getWordList(); Map getWalletKeys(Object wallet); @@ -882,7 +882,7 @@ import 'package:eth_sig_util/util/utils.dart'; abstract class Ethereum { List getEthereumWordList(String language); WalletService createEthereumWalletService(Box walletInfoSource, bool isDirect); - WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase}); + WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase}); WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase}); WalletCredentials createEthereumRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); WalletCredentials createEthereumHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo}); @@ -989,7 +989,7 @@ import 'package:eth_sig_util/util/utils.dart'; abstract class Polygon { List getPolygonWordList(String language); WalletService createPolygonWalletService(Box walletInfoSource, bool isDirect); - WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase}); + WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase}); WalletCredentials createPolygonRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase}); WalletCredentials createPolygonRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); WalletCredentials createPolygonHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo}); @@ -1077,7 +1077,7 @@ abstract class BitcoinCash { Box walletInfoSource, Box unspentCoinSource, bool isDirect); WalletCredentials createBitcoinCashNewWalletCredentials( - {required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic, String? parentAddress}); + {required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials( {required String name, required String mnemonic, required String password, String? passphrase}); @@ -1161,7 +1161,6 @@ abstract class Nano { required String name, String? password, String? mnemonic, - String? parentAddress, WalletInfo? walletInfo, String? passphrase, }); @@ -1281,7 +1280,7 @@ abstract class Solana { List getSolanaWordList(String language); WalletService createSolanaWalletService(Box walletInfoSource, bool isDirect); WalletCredentials createSolanaNewWalletCredentials( - {required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase}); + {required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase}); WalletCredentials createSolanaRestoreWalletFromSeedCredentials( {required String name, required String mnemonic, required String password, String? passphrase}); WalletCredentials createSolanaRestoreWalletFromPrivateKey( @@ -1369,7 +1368,7 @@ import 'package:cw_tron/default_tron_tokens.dart'; abstract class Tron { List getTronWordList(String language); WalletService createTronWalletService(Box walletInfoSource, bool isDirect); - WalletCredentials createTronNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase}); + WalletCredentials createTronNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase}); WalletCredentials createTronRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase}); WalletCredentials createTronRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); String getAddress(WalletBase wallet); diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 48614900a..0ef38e939 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -77,6 +77,7 @@ class SecretKey { SecretKey('moneroTestWalletBlockHeight', () => ''), SecretKey('chainflipApiKey', () => ''), SecretKey('chainflipAffiliateFee', () => ''), + SecretKey('walletGroupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)), ]; static final evmChainsSecrets = [ @@ -88,6 +89,7 @@ class SecretKey { static final solanaSecrets = [ SecretKey('ankrApiKey', () => ''), + SecretKey('nowNodesApiKey', () => ''), SecretKey('chainStackApiKey', () => ''), ];