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