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

David Adegoke committed Mar 6, 2025 at 01:25 UTC 09f20b2a7bf3ef15bfcfcf4471e1f9dfad94708a
35 files changed +181 -123
.github/workflows/automated_integration_test.yml
+4
@@ -223,6 +223,10 @@ jobs:
223 echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
224 echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
225 echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart
226 + # end of test secrets
227 + echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
228 + echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
229 + echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart
230
231 - name: Rename app
232 run: |
.github/workflows/pr_test_build_android.yml
+1
@@ -172,6 +172,7 @@ jobs:
172 # end of test secrets
173 echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
174 echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
175 + echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart
176
177 - name: prepare monero_c and cache
178 run: |
.github/workflows/pr_test_build_linux.yml
+1
@@ -168,6 +168,7 @@ jobs:
168 # end of test secrets
169 echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
170 echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
171 + echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart
172
173 - name: prepare monero_c and cache
174 run: |
cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
-2
@@ -11,13 +11,11 @@ class BitcoinNewWalletCredentials extends WalletCredentials {
11 String? derivationPath,
12 String? passphrase,
13 this.mnemonic,
14 - String? parentAddress,
14 }) : super(
15 name: name,
16 walletInfo: walletInfo,
17 password: password,
18 passphrase: passphrase,
20 - parentAddress: parentAddress,
19 );
20
21 final String? mnemonic;
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart
-2
@@ -8,13 +8,11 @@ class BitcoinCashNewWalletCredentials extends WalletCredentials {
8 String? password,
9 String? passphrase,
10 this.mnemonic,
11 - String? parentAddress,
11 }) : super(
12 name: name,
13 walletInfo: walletInfo,
14 password: password,
15 passphrase: passphrase,
17 - parentAddress: parentAddress
16 );
17 final String? mnemonic;
18 }
cw_core/lib/wallet_credentials.dart
-2
@@ -10,7 +10,6 @@ abstract class WalletCredentials {
10 this.passphrase,
11 this.derivationInfo,
12 this.hardwareWalletType,
13 - this.parentAddress,
13 }) {
14 if (this.walletInfo != null && derivationInfo != null) {
15 this.walletInfo!.derivationInfo = derivationInfo;
@@ -19,7 +18,6 @@ abstract class WalletCredentials {
18
19 final String name;
20 final int? height;
22 - String? parentAddress;
21 int? seedPhraseLength;
22 String? password;
23 String? passphrase;
cw_core/lib/wallet_info.dart
+10 -1
@@ -81,6 +81,8 @@ class WalletInfo extends HiveObject {
81 this.derivationInfo,
82 this.hardwareWalletType,
83 this.parentAddress,
84 + this.hashedWalletIdentifier,
85 + this.isNonSeedWallet,
86 ) : _yatLastUsedAddressController = StreamController<String>.broadcast();
87
88 factory WalletInfo.external({
@@ -99,6 +101,8 @@ class WalletInfo extends HiveObject {
101 DerivationInfo? derivationInfo,
102 HardwareWalletType? hardwareWalletType,
103 String? parentAddress,
104 + String? hashedWalletIdentifier,
105 + bool? isNonSeedWallet,
106 }) {
107 return WalletInfo(
108 id,
@@ -116,6 +120,8 @@ class WalletInfo extends HiveObject {
120 derivationInfo,
121 hardwareWalletType,
122 parentAddress,
123 + hashedWalletIdentifier,
124 + isNonSeedWallet ?? false,
125 );
126 }
127
@@ -196,8 +202,11 @@ class WalletInfo extends HiveObject {
202 @HiveField(24)
203 List<String>? manualAddresses;
204
199 -
205 + @HiveField(25)
206 + String? hashedWalletIdentifier;
207
208 + @HiveField(26, defaultValue: false)
209 + bool isNonSeedWallet;
210
211 String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
212
cw_evm/lib/evm_chain_wallet_creation_credentials.dart
-1
@@ -7,7 +7,6 @@ class EVMChainNewWalletCredentials extends WalletCredentials {
7 required super.name,
8 super.walletInfo,
9 super.password,
10 - super.parentAddress,
10 this.mnemonic,
11 super.passphrase,
12 });
cw_nano/lib/nano_wallet_creation_credentials.dart
-2
@@ -8,13 +8,11 @@ class NanoNewWalletCredentials extends WalletCredentials {
8 String? password,
9 DerivationType? derivationType,
10 this.mnemonic,
11 - String? parentAddress,
11 String? passphrase,
12 }) : super(
13 name: name,
14 password: password,
15 walletInfo: walletInfo,
17 - parentAddress: parentAddress,
16 passphrase: passphrase,
17 );
18
cw_solana/lib/solana_wallet_creation_credentials.dart
-2
@@ -6,14 +6,12 @@ class SolanaNewWalletCredentials extends WalletCredentials {
6 required String name,
7 WalletInfo? walletInfo,
8 String? password,
9 - String? parentAddress,
9 this.mnemonic,
10 String? passphrase,
11 }) : super(
12 name: name,
13 walletInfo: walletInfo,
14 password: password,
16 - parentAddress: parentAddress,
15 passphrase: passphrase,
16 );
17 final String? mnemonic;
cw_tron/lib/tron_wallet_creation_credentials.dart
-2
@@ -7,13 +7,11 @@ class TronNewWalletCredentials extends WalletCredentials {
7 WalletInfo? walletInfo,
8 String? password,
9 this.mnemonic,
10 - String? parentAddress,
10 String? passphrase,
11 }) : super(
12 name: name,
13 walletInfo: walletInfo,
14 password: password,
16 - parentAddress: parentAddress,
15 passphrase: passphrase,
16 );
17
ios/Podfile.lock
+1 -1
@@ -277,4 +277,4 @@ SPEC CHECKSUMS:
277
278 PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597
279
280 -COCOAPODS: 1.15.2
280 +COCOAPODS: 1.15.2
\ No newline at end of file
lib/bitcoin/cw_bitcoin.dart
-2
@@ -34,7 +34,6 @@ class CWBitcoin extends Bitcoin {
34 String? password,
35 String? passphrase,
36 String? mnemonic,
37 - String? parentAddress,
37 }) =>
38 BitcoinNewWalletCredentials(
39 name: name,
@@ -42,7 +41,6 @@ class CWBitcoin extends Bitcoin {
41 password: password,
42 passphrase: passphrase,
43 mnemonic: mnemonic,
45 - parentAddress: parentAddress,
44 );
45
46 @override
lib/bitcoin_cash/cw_bitcoin_cash.dart
-2
@@ -17,14 +17,12 @@ class CWBitcoinCash extends BitcoinCash {
17 String? password,
18 String? passphrase,
19 String? mnemonic,
20 - String? parentAddress,
20 }) =>
21 BitcoinCashNewWalletCredentials(
22 name: name,
23 walletInfo: walletInfo,
24 password: password,
25 passphrase: passphrase,
27 - parentAddress: parentAddress,
26 mnemonic: mnemonic,
27 );
28
lib/core/new_wallet_arguments.dart
-2
@@ -3,12 +3,10 @@ import 'package:cw_core/wallet_type.dart';
3 class NewWalletArguments {
4 final WalletType type;
5 final String? mnemonic;
6 - final String? parentAddress;
6 final bool isChildWallet;
7
8 NewWalletArguments({
9 required this.type,
11 - this.parentAddress,
10 this.mnemonic,
11 this.isChildWallet = false,
12 });
lib/di.dart
+5 -6
@@ -392,11 +392,10 @@ Future<void> setup({
392 getIt.registerFactory<NewWalletTypeViewModel>(() => NewWalletTypeViewModel(_walletInfoSource));
393
394 getIt.registerFactory<WalletManager>(
395 - () {
396 - final instance = WalletManager(_walletInfoSource, getIt.get<SharedPreferences>());
397 - instance.updateWalletGroups();
398 - return instance;
399 - },
395 + () => WalletManager(
396 + _walletInfoSource,
397 + getIt.get<SharedPreferences>(),
398 + ),
399 );
400
401 getIt.registerFactoryParam<WalletGroupsDisplayViewModel, WalletType, void>(
@@ -812,7 +811,7 @@ Future<void> setup({
811 editingWallet: arguments.editingWallet,
812 isWalletGroup: arguments.isWalletGroup,
813 groupName: arguments.groupName,
815 - parentAddress: arguments.parentAddress,
814 + walletGroupKey: arguments.walletGroupKey,
815 ),
816 );
817 });
lib/entities/hash_wallet_identifier.dart new
+21
@@ -0,0 +1,21 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
4 +import 'package:cw_core/wallet_base.dart';
5 +import 'package:hashlib/hashlib.dart';
6 +
7 +String createHashedWalletIdentifier(WalletBase wallet) {
8 + if (wallet.seed == null) return '';
9 +
10 + final salt = secrets.walletGroupSalt;
11 + final combined = '$salt.${wallet.seed}';
12 +
13 + // Convert to UTF-8 bytes.
14 + final bytes = utf8.encode(combined);
15 +
16 + // Perform SHA-256 hash.
17 + final digest = sha256.convert(bytes);
18 +
19 + // Return the hex string representation of the hash.
20 + return digest.toString();
21 +}
lib/entities/wallet_edit_page_arguments.dart
+2 -2
@@ -10,7 +10,7 @@ class WalletEditPageArguments {
10 this.isWalletGroup = false,
11 this.walletListViewModel,
12 this.groupName = '',
13 - this.parentAddress = '',
13 + this.walletGroupKey = '',
14 this.walletEditViewModel,
15 this.walletNewVM,
16 this.authService,
@@ -19,7 +19,7 @@ class WalletEditPageArguments {
19 final WalletListItem editingWallet;
20 final bool isWalletGroup;
21 final String groupName;
22 - final String parentAddress;
22 + final String walletGroupKey;
23 final WalletListViewModel? walletListViewModel;
24
25 final WalletEditViewModel? walletEditViewModel;
lib/entities/wallet_group.dart
+6 -5
@@ -1,13 +1,14 @@
1 import 'package:cw_core/wallet_info.dart';
2
3 class WalletGroup {
4 - WalletGroup(this.parentAddress) : wallets = [];
4 + WalletGroup(this.groupKey) : wallets = [];
5
6 - /// Main identifier for each group, compulsory.
7 - final String parentAddress;
6 + /// Primary identifier for the group. Previously was `parentAddress`.
7 + /// Now we store either the wallet's hash OR fallback to parentAddress/address.
8 + final String groupKey;
9
9 - /// Child wallets that share the same parent address within this group
10 - List<WalletInfo> wallets;
10 + /// Child wallets that share the same group key
11 + final List<WalletInfo> wallets;
12
13 /// Custom name for the group, editable for multi-child wallet groups
14 String? groupName;
lib/entities/wallet_manager.dart
+98 -47
@@ -1,70 +1,61 @@
1 +import 'package:cake_wallet/entities/hash_wallet_identifier.dart';
2 import 'package:cake_wallet/entities/wallet_group.dart';
3 +import 'package:cw_core/wallet_base.dart';
4 import 'package:cw_core/wallet_info.dart';
5 import 'package:hive/hive.dart';
6 import 'package:shared_preferences/shared_preferences.dart';
7
8 class WalletManager {
7 - WalletManager(
8 - this._walletInfoSource,
9 - this._sharedPreferences,
10 - );
9 + WalletManager(this._walletInfoSource, this._sharedPreferences);
10
11 final Box<WalletInfo> _walletInfoSource;
12 final SharedPreferences _sharedPreferences;
13
14 final List<WalletGroup> walletGroups = [];
15
17 - /// Categorize wallets into groups based on their parentAddress.
18 - ///
19 - /// Update the lead wallet for each group and clean up empty groups
20 - /// i.e remove group if there's no lead wallet (i.e, no wallets left)
16 void updateWalletGroups() {
17 walletGroups.clear();
18
24 - for (var walletInfo in _walletInfoSource.values) {
25 - final group = _getOrCreateGroup(_resolveParentAddress(walletInfo));
19 + for (final walletInfo in _walletInfoSource.values) {
20 + final groupKey = _resolveGroupKey(walletInfo);
21 + final group = _getOrCreateGroup(groupKey);
22 group.wallets.add(walletInfo);
23 }
24
29 - walletGroups.removeWhere((group) => group.wallets.isEmpty);
30 -
25 + walletGroups.removeWhere((g) => g.wallets.isEmpty);
26 _loadCustomGroupNames();
27 }
28
34 - /// Function to determine the correct parentAddress for a wallet.
35 - ///
36 - /// If it's a parent wallet (parentAddress is null),
37 - /// use its own address as parentAddress.
38 - String _resolveParentAddress(WalletInfo walletInfo) {
29 + String _resolveGroupKey(WalletInfo walletInfo) {
30 + if (walletInfo.hashedWalletIdentifier != null &&
31 + walletInfo.hashedWalletIdentifier!.isNotEmpty) {
32 + return walletInfo.hashedWalletIdentifier!;
33 + }
34 +
35 + // Fallback to old logic
36 return walletInfo.parentAddress ?? walletInfo.address;
37 }
38
42 - /// Check if a group with the parentAddress already exists,
43 - /// If no group exists, create a new one.
44 - ///
45 - WalletGroup _getOrCreateGroup(String parentAddress) {
39 + WalletGroup _getOrCreateGroup(String groupKey) {
40 return walletGroups.firstWhere(
47 - (group) => group.parentAddress == parentAddress,
41 + (g) => g.groupKey == groupKey,
42 orElse: () {
49 - final newGroup = WalletGroup(parentAddress);
43 + final newGroup = WalletGroup(groupKey);
44 walletGroups.add(newGroup);
45 return newGroup;
46 },
47 );
48 }
49
56 - /// Add a new wallet and update lead wallet after adding.
50 void addWallet(WalletInfo walletInfo) {
58 - final group = _getOrCreateGroup(_resolveParentAddress(walletInfo));
51 + final groupKey = _resolveGroupKey(walletInfo);
52 + final group = _getOrCreateGroup(groupKey);
53 group.wallets.add(walletInfo);
54 }
55
62 - /// Removes a wallet from a group i.e when it's deleted.
63 - ///
64 - /// Update lead wallet after removing,
65 - /// Remove the group if it's empty (i.e., no lead wallet).
56 void removeWallet(WalletInfo walletInfo) {
67 - final group = _getOrCreateGroup(_resolveParentAddress(walletInfo));
57 + final groupKey = _resolveGroupKey(walletInfo);
58 + final group = _getOrCreateGroup(groupKey);
59 group.wallets.remove(walletInfo);
60
61 if (group.wallets.isEmpty) {
@@ -72,39 +63,99 @@ class WalletManager {
63 }
64 }
65
75 - /// Returns all the child wallets within a group.
76 - ///
77 - /// If the group is not found, returns an empty group with no wallets.
78 - List<WalletInfo> getWalletsInGroup(String parentAddress) {
66 + List<WalletInfo> getWalletsInGroup(String groupKey) {
67 return walletGroups
68 .firstWhere(
81 - (group) => group.parentAddress == parentAddress,
82 - orElse: () => WalletGroup(parentAddress),
69 + (g) => g.groupKey == groupKey,
70 + orElse: () => WalletGroup(groupKey),
71 )
72 .wallets;
73 }
74
87 - /// Iterate through all groups and load their custom names from storage
75 void _loadCustomGroupNames() {
76 for (var group in walletGroups) {
90 - final groupName = _sharedPreferences.getString('wallet_group_name_${group.parentAddress}');
77 + final key = 'wallet_group_name_${group.groupKey}';
78 + final groupName = _sharedPreferences.getString(key);
79 if (groupName != null && group.wallets.length > 1) {
92 - group.groupName = groupName; // Restore custom name
80 + group.groupName = groupName;
81 }
82 }
83 }
84
97 - /// Save custom name for a group
98 - void _saveCustomGroupName(String parentAddress, String name) {
99 - _sharedPreferences.setString('wallet_group_name_$parentAddress', name);
85 + void _saveCustomGroupName(String groupKey, String name) {
86 + _sharedPreferences.setString('wallet_group_name_$groupKey', name);
87 }
88
102 - // Set custom group name and persist it
103 - void setGroupName(String parentAddress, String name) {
104 - if (parentAddress.isEmpty || name.isEmpty) return;
89 + void setGroupName(String groupKey, String name) {
90 + if (groupKey.isEmpty || name.isEmpty) return;
91
106 - final group = walletGroups.firstWhere((group) => group.parentAddress == parentAddress);
92 + final group = walletGroups.firstWhere((g) => g.groupKey == groupKey);
93 group.setCustomName(name);
108 - _saveCustomGroupName(parentAddress, name); // Persist the custom name
94 + _saveCustomGroupName(groupKey, name);
95 + }
96 +
97 + // ---------------------------------------------------------------------------
98 + // This performs a Group-Based Lazy Migration:
99 + // If the user opens a wallet in an old group,
100 + // we migrate ALL wallets that share its old group key to a new hash.
101 + // ---------------------------------------------------------------------------
102 +
103 + /// When a user opens a wallet, check if it has a real hash.
104 + /// If not, migrate the ENTIRE old group so they keep the same group name
105 + /// and end up with the same new hash (preserving grouping).
106 + Future<void> ensureGroupHasHashedIdentifier(WalletBase openedWallet) async {
107 + WalletInfo walletInfo = openedWallet.walletInfo;
108 +
109 + // If the openedWallet already has an hash, then there is nothing to do
110 + if (walletInfo.hashedWalletIdentifier != null &&
111 + walletInfo.hashedWalletIdentifier!.isNotEmpty) {
112 + updateWalletGroups(); // Still skeptical of calling this here. Looking for a better spot.
113 + return;
114 + }
115 +
116 + // Identify the old group key for this wallet
117 + final oldGroupKey = _resolveGroupKey(walletInfo); // parentAddress fallback
118 +
119 + // Find all wallets that share this old group key (i.e the old group)
120 + final oldGroupWallets = _walletInfoSource.values.where((w) {
121 + final key = w.hashedWalletIdentifier != null && w.hashedWalletIdentifier!.isNotEmpty
122 + ? w.hashedWalletIdentifier
123 + : (w.parentAddress ?? w.address);
124 + return key == oldGroupKey;
125 + }).toList();
126 +
127 + if (oldGroupWallets.isEmpty) {
128 + // This shouldn't happen, but just in case it does, we return.
129 + return;
130 + }
131 +
132 + // Next, we determine the new group hash for these wallets
133 + // Since they share the same seed, we can assign that group hash
134 + // to all the wallets to preserve grouping.
135 + final newGroupHash = createHashedWalletIdentifier(openedWallet);
136 +
137 + // Migrate the old group name from oldGroupKey(i.e parentAddress) to newGroupHash
138 + await _migrateGroupName(oldGroupKey, newGroupHash);
139 +
140 + // Then we assign this new hash to each wallet in that old group and save them
141 + for (final wallet in oldGroupWallets) {
142 + wallet.hashedWalletIdentifier = newGroupHash;
143 + await wallet.save();
144 + }
145 +
146 + // Finally, we rebuild the groups so that these wallets are now in the new group
147 + updateWalletGroups();
148 + }
149 +
150 + /// Copy an old group name to the new group key, then remove the old key.
151 + Future<void> _migrateGroupName(String oldGroupKey, String newGroupKey) async {
152 + final oldNameKey = 'wallet_group_name_$oldGroupKey';
153 + final newNameKey = 'wallet_group_name_$newGroupKey';
154 +
155 + final oldGroupName = _sharedPreferences.getString(oldNameKey);
156 + if (oldGroupName != null) {
157 + await _sharedPreferences.setString(newNameKey, oldGroupName);
158 + await _sharedPreferences.remove(oldNameKey);
159 + }
160 }
161 }
lib/ethereum/cw_ethereum.dart
-2
@@ -11,7 +11,6 @@ class CWEthereum extends Ethereum {
11 WalletCredentials createEthereumNewWalletCredentials({
12 required String name,
13 String? mnemonic,
14 - String? parentAddress,
14 WalletInfo? walletInfo,
15 String? password,
16 String? passphrase,
@@ -20,7 +19,6 @@ class CWEthereum extends Ethereum {
19 name: name,
20 walletInfo: walletInfo,
21 password: password,
23 - parentAddress: parentAddress,
22 mnemonic: mnemonic,
23 passphrase: passphrase,
24 );
lib/nano/cw_nano.dart
-2
@@ -94,14 +94,12 @@ class CWNano extends Nano {
94 WalletInfo? walletInfo,
95 String? password,
96 String? mnemonic,
97 - String? parentAddress,
97 String? passphrase,
98 }) =>
99 NanoNewWalletCredentials(
100 name: name,
101 password: password,
102 mnemonic: mnemonic,
104 - parentAddress: parentAddress,
103 walletInfo: walletInfo,
104 passphrase: passphrase,
105 );
lib/polygon/cw_polygon.dart
-2
@@ -11,7 +11,6 @@ class CWPolygon extends Polygon {
11 WalletCredentials createPolygonNewWalletCredentials({
12 required String name,
13 String? mnemonic,
14 - String? parentAddress,
14 WalletInfo? walletInfo,
15 String? password,
16 String? passphrase,
@@ -21,7 +20,6 @@ class CWPolygon extends Polygon {
20 walletInfo: walletInfo,
21 password: password,
22 mnemonic: mnemonic,
24 - parentAddress: parentAddress,
23 passphrase: passphrase,
24 );
25
lib/reactions/on_current_wallet_change.dart
+4
@@ -1,6 +1,8 @@
1 +import 'package:cake_wallet/di.dart';
2 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
3 import 'package:cake_wallet/entities/fiat_api_mode.dart';
4 import 'package:cake_wallet/entities/update_haven_rate.dart';
5 +import 'package:cake_wallet/entities/wallet_manager.dart';
6 import 'package:cake_wallet/ethereum/ethereum.dart';
7 import 'package:cake_wallet/polygon/polygon.dart';
8 import 'package:cake_wallet/solana/solana.dart';
@@ -59,6 +61,8 @@ void startCurrentWalletChangeReaction(
61 return;
62 }
63
64 + await getIt.get<WalletManager>().ensureGroupHasHashedIdentifier(wallet);
65 +
66 final node = settingsStore.getCurrentNode(wallet.type);
67
68 startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
lib/solana/cw_solana.dart
-2
@@ -11,7 +11,6 @@ class CWSolana extends Solana {
11 WalletCredentials createSolanaNewWalletCredentials({
12 required String name,
13 String? mnemonic,
14 - String? parentAddress,
14 WalletInfo? walletInfo,
15 String? password,
16 String? passphrase,
@@ -21,7 +20,6 @@ class CWSolana extends Solana {
20 walletInfo: walletInfo,
21 password: password,
22 mnemonic: mnemonic,
24 - parentAddress: parentAddress,
23 passphrase: passphrase,
24 );
25
lib/src/screens/new_wallet/wallet_group_display_page.dart
-1
@@ -150,7 +150,6 @@ class WalletGroupsDisplayBody extends StatelessWidget {
150 arguments: NewWalletArguments(
151 type: walletGroupsDisplayViewModel.type,
152 mnemonic: mnemonic,
153 - parentAddress: walletGroupsDisplayViewModel.parentAddress,
153 isChildWallet: true,
154 ),
155 );
lib/src/screens/wallet/wallet_edit_page.dart
+2 -2
@@ -112,7 +112,7 @@ class WalletEditPage extends BasePage {
112 pageArguments.editingWallet,
113 password: password,
114 isWalletGroup: pageArguments.isWalletGroup,
115 - groupParentAddress: pageArguments.parentAddress,
115 + walletGroupKey: pageArguments.walletGroupKey,
116 );
117 },
118 callback: (bool isAuthenticatedSuccessfully,
@@ -128,7 +128,7 @@ class WalletEditPage extends BasePage {
128 await walletEditViewModel.changeName(
129 pageArguments.editingWallet,
130 isWalletGroup: pageArguments.isWalletGroup,
131 - groupParentAddress: pageArguments.parentAddress,
131 + walletGroupKey: pageArguments.walletGroupKey,
132 );
133 confirmed = true;
134 }
lib/src/screens/wallet_list/wallet_list_page.dart
+1 -1
@@ -227,7 +227,7 @@ class WalletListBodyState extends State<WalletListBody> {
227 editingWallet: wallet,
228 isWalletGroup: true,
229 groupName: groupName,
230 - parentAddress: group.parentAddress,
230 + walletGroupKey: group.groupKey,
231 ),
232 );
233 },
lib/tron/cw_tron.dart
+6 -7
@@ -14,16 +14,15 @@ class CWTron extends Tron {
14 WalletInfo? walletInfo,
15 String? password,
16 String? mnemonic,
17 - String? parentAddress,
17 String? passphrase,
18 }) =>
19 TronNewWalletCredentials(
21 - name: name,
22 - walletInfo: walletInfo,
23 - password: password,
24 - mnemonic: mnemonic,
25 - passphrase: passphrase,
26 - parentAddress: parentAddress);
20 + name: name,
21 + walletInfo: walletInfo,
22 + password: password,
23 + mnemonic: mnemonic,
24 + passphrase: passphrase,
25 + );
26
27 @override
28 WalletCredentials createTronRestoreWalletFromSeedCredentials({
lib/view_model/wallet_creation_vm.dart
+5 -1
@@ -4,6 +4,7 @@ import 'package:cake_wallet/core/wallet_creation_service.dart';
4 import 'package:cake_wallet/di.dart';
5 import 'package:cake_wallet/entities/background_tasks.dart';
6 import 'package:cake_wallet/entities/generate_name.dart';
7 +import 'package:cake_wallet/entities/hash_wallet_identifier.dart';
8 import 'package:cake_wallet/generated/i18n.dart';
9 import 'package:cake_wallet/nano/nano.dart';
10 import 'package:cake_wallet/store/app_store.dart';
@@ -103,13 +104,16 @@ abstract class WalletCreationVMBase with Store {
104 showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
105 derivationInfo: credentials.derivationInfo ?? getDefaultCreateDerivation(),
106 hardwareWalletType: credentials.hardwareWalletType,
106 - parentAddress: credentials.parentAddress,
107 );
108
109 credentials.walletInfo = walletInfo;
110 final wallet = restoreWallet != null
111 ? await processFromRestoredWallet(credentials, restoreWallet)
112 : await process(credentials);
113 +
114 + final isNonSeedWallet = isRecovery ? wallet.seed == null : false;
115 + walletInfo.isNonSeedWallet = isNonSeedWallet;
116 + walletInfo.hashedWalletIdentifier = createHashedWalletIdentifier(wallet);
117 walletInfo.address = wallet.walletAddresses.address;
118 await _walletInfoSource.add(walletInfo);
119 await _appStore.changeCurrentWallet(wallet);
lib/view_model/wallet_groups_display_view_model.dart
+4 -6
@@ -47,8 +47,6 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
47 @observable
48 WalletInfo? selectedSingleWallet;
49
50 - @observable
51 - String? parentAddress;
50
51 @observable
52 bool isFetchingMnemonic;
@@ -77,9 +75,6 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
75 walletToUse.name,
76 );
77
80 - parentAddress =
81 - isGroupSelected ? selectedWalletGroup!.parentAddress : selectedSingleWallet!.address;
82 -
78 return wallet.seed;
79 } catch (e) {
80 return null;
@@ -130,11 +125,14 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
125 // Check that selected wallet type is not present already in group
126 bool isSameTypeAsSelectedWallet = wallet.type == type;
127
128 + bool isNonSeedWallet = wallet.isNonSeedWallet;
129 +
130 // Exclude if any of these conditions are true
131 return isNonBIP39Wallet ||
132 isNanoDerivationType ||
133 isElectrumDerivationType ||
137 - isSameTypeAsSelectedWallet;
134 + isSameTypeAsSelectedWallet ||
135 + isNonSeedWallet;
136 });
137
138 if (shouldExcludeGroup) continue;
lib/view_model/wallet_list/wallet_edit_view_model.dart
+2 -2
@@ -40,7 +40,7 @@ abstract class WalletEditViewModelBase with Store {
40 Future<void> changeName(
41 WalletListItem walletItem, {
42 String? password,
43 - String? groupParentAddress,
43 + String? walletGroupKey,
44 bool isWalletGroup = false,
45 }) async {
46 state = WalletEditRenamePending();
@@ -48,7 +48,7 @@ abstract class WalletEditViewModelBase with Store {
48 if (isWalletGroup) {
49 _walletManager.updateWalletGroups();
50
51 - _walletManager.setGroupName(groupParentAddress!, newName);
51 + _walletManager.setGroupName(walletGroupKey!, newName);
52 } else {
53 await _walletLoadingService.renameWallet(
54 walletItem.type,
lib/view_model/wallet_new_vm.dart
-7
@@ -109,7 +109,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
109 password: walletPassword,
110 passphrase: passphrase,
111 mnemonic: newWalletArguments!.mnemonic,
112 - parentAddress: newWalletArguments!.parentAddress,
112 );
113 case WalletType.haven:
114 return haven!.createHavenNewWalletCredentials(
@@ -119,7 +118,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
118 name: name,
119 password: walletPassword,
120 mnemonic: newWalletArguments!.mnemonic,
122 - parentAddress: newWalletArguments!.parentAddress,
121 passphrase: passphrase,
122 );
123 case WalletType.bitcoinCash:
@@ -128,7 +126,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
126 password: walletPassword,
127 passphrase: passphrase,
128 mnemonic: newWalletArguments!.mnemonic,
131 - parentAddress: newWalletArguments!.parentAddress,
129 );
130 case WalletType.nano:
131 case WalletType.banano:
@@ -136,7 +133,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
133 name: name,
134 password: walletPassword,
135 mnemonic: newWalletArguments!.mnemonic,
139 - parentAddress: newWalletArguments!.parentAddress,
136 passphrase: passphrase,
137 );
138 case WalletType.polygon:
@@ -144,7 +140,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
140 name: name,
141 password: walletPassword,
142 mnemonic: newWalletArguments!.mnemonic,
147 - parentAddress: newWalletArguments!.parentAddress,
143 passphrase: passphrase,
144 );
145 case WalletType.solana:
@@ -152,7 +147,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
147 name: name,
148 password: walletPassword,
149 mnemonic: newWalletArguments!.mnemonic,
155 - parentAddress: newWalletArguments!.parentAddress,
150 passphrase: passphrase,
151 );
152 case WalletType.tron:
@@ -160,7 +154,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
154 name: name,
155 password: walletPassword,
156 mnemonic: newWalletArguments!.mnemonic,
163 - parentAddress: newWalletArguments!.parentAddress,
157 passphrase: passphrase,
158 );
159 case WalletType.wownero:
tool/configure.dart
+6 -7
@@ -160,7 +160,7 @@ abstract class Bitcoin {
160 String? passphrase,
161 });
162 WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo});
163 - WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic, String? parentAddress});
163 + WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic});
164 WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo});
165 List<String> getWordList();
166 Map<String, String> getWalletKeys(Object wallet);
@@ -882,7 +882,7 @@ import 'package:eth_sig_util/util/utils.dart';
882 abstract class Ethereum {
883 List<String> getEthereumWordList(String language);
884 WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource, bool isDirect);
885 - WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase});
885 + WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase});
886 WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase});
887 WalletCredentials createEthereumRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
888 WalletCredentials createEthereumHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
@@ -989,7 +989,7 @@ import 'package:eth_sig_util/util/utils.dart';
989 abstract class Polygon {
990 List<String> getPolygonWordList(String language);
991 WalletService createPolygonWalletService(Box<WalletInfo> walletInfoSource, bool isDirect);
992 - WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase});
992 + WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase});
993 WalletCredentials createPolygonRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase});
994 WalletCredentials createPolygonRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
995 WalletCredentials createPolygonHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
@@ -1077,7 +1077,7 @@ abstract class BitcoinCash {
1077 Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool isDirect);
1078
1079 WalletCredentials createBitcoinCashNewWalletCredentials(
1080 - {required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic, String? parentAddress});
1080 + {required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic});
1081
1082 WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
1083 {required String name, required String mnemonic, required String password, String? passphrase});
@@ -1161,7 +1161,6 @@ abstract class Nano {
1161 required String name,
1162 String? password,
1163 String? mnemonic,
1164 - String? parentAddress,
1164 WalletInfo? walletInfo,
1165 String? passphrase,
1166 });
@@ -1281,7 +1280,7 @@ abstract class Solana {
1280 List<String> getSolanaWordList(String language);
1281 WalletService createSolanaWalletService(Box<WalletInfo> walletInfoSource, bool isDirect);
1282 WalletCredentials createSolanaNewWalletCredentials(
1284 - {required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase});
1283 + {required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase});
1284 WalletCredentials createSolanaRestoreWalletFromSeedCredentials(
1285 {required String name, required String mnemonic, required String password, String? passphrase});
1286 WalletCredentials createSolanaRestoreWalletFromPrivateKey(
@@ -1369,7 +1368,7 @@ import 'package:cw_tron/default_tron_tokens.dart';
1368 abstract class Tron {
1369 List<String> getTronWordList(String language);
1370 WalletService createTronWalletService(Box<WalletInfo> walletInfoSource, bool isDirect);
1372 - WalletCredentials createTronNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? parentAddress, String? passphrase});
1371 + WalletCredentials createTronNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase});
1372 WalletCredentials createTronRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase});
1373 WalletCredentials createTronRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
1374 String getAddress(WalletBase wallet);
tool/utils/secret_key.dart
+2
@@ -77,6 +77,7 @@ class SecretKey {
77 SecretKey('moneroTestWalletBlockHeight', () => ''),
78 SecretKey('chainflipApiKey', () => ''),
79 SecretKey('chainflipAffiliateFee', () => ''),
80 + SecretKey('walletGroupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)),
81 ];
82
83 static final evmChainsSecrets = [
@@ -88,6 +89,7 @@ class SecretKey {
89
90 static final solanaSecrets = [
91 SecretKey('ankrApiKey', () => ''),
92 + SecretKey('nowNodesApiKey', () => ''),
93 SecretKey('chainStackApiKey', () => ''),
94 ];
95