dev
dart 175 lines 5.82 KB
Raw
1 import 'dart:math';
2
3 import 'package:cake_wallet/entities/hash_wallet_identifier.dart';
4 import 'package:cake_wallet/entities/wallet_group.dart';
5 import 'package:cw_core/wallet_base.dart';
6 import 'package:cw_core/wallet_info.dart';
7 import 'package:shared_preferences/shared_preferences.dart';
8
9 class WalletManager {
10 WalletManager(this._sharedPreferences);
11
12 final SharedPreferences _sharedPreferences;
13
14 final List<WalletGroup> walletGroups = [];
15
16 Future<void> updateWalletGroups() async {
17 walletGroups.clear();
18
19 for (final walletInfo in await WalletInfo.getAll()) {
20 final groupKey = _resolveGroupKey(walletInfo);
21 final group = _getOrCreateGroup(groupKey);
22 group.wallets.add(walletInfo);
23 }
24
25 walletGroups.removeWhere((g) => g.wallets.isEmpty);
26 _loadCustomGroupNames();
27 }
28
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 final address = walletInfo.parentAddress ?? walletInfo.address;
37 if (address.isEmpty) {
38 return Random().nextInt(100000).toString();
39 }
40 return address;
41 }
42
43 WalletGroup _getOrCreateGroup(String groupKey) {
44 return walletGroups.firstWhere(
45 (g) => g.groupKey == groupKey,
46 orElse: () {
47 final newGroup = WalletGroup(groupKey);
48 walletGroups.add(newGroup);
49 return newGroup;
50 },
51 );
52 }
53
54 void addWallet(WalletInfo walletInfo) {
55 final groupKey = _resolveGroupKey(walletInfo);
56 final group = _getOrCreateGroup(groupKey);
57 group.wallets.add(walletInfo);
58 }
59
60 void removeWallet(WalletInfo walletInfo) {
61 final groupKey = _resolveGroupKey(walletInfo);
62 final group = _getOrCreateGroup(groupKey);
63 group.wallets.remove(walletInfo);
64
65 if (group.wallets.isEmpty) {
66 walletGroups.remove(group);
67 }
68 }
69
70 List<WalletInfo> getWalletsInGroup(String groupKey) {
71 return walletGroups
72 .firstWhere(
73 (g) => g.groupKey == groupKey,
74 orElse: () => WalletGroup(groupKey),
75 )
76 .wallets;
77 }
78
79 void _loadCustomGroupNames() {
80 for (var group in walletGroups) {
81 final key = 'wallet_group_name_${group.groupKey}';
82 final groupName = _sharedPreferences.getString(key);
83 if (groupName != null && group.wallets.length > 1) {
84 group.groupName = groupName;
85 }
86 }
87 }
88
89 void _saveCustomGroupName(String groupKey, String name) {
90 _sharedPreferences.setString('wallet_group_name_$groupKey', name);
91 }
92
93 void setGroupName(String groupKey, String name) {
94 if (groupKey.isEmpty || name.isEmpty) return;
95
96 final group = walletGroups.firstWhere((g) => g.groupKey == groupKey);
97 group.setCustomName(name);
98 _saveCustomGroupName(groupKey, name);
99 }
100
101 // ---------------------------------------------------------------------------
102 // This performs a Group-Based Lazy Migration:
103 // If the user opens a wallet in an old group,
104 // we migrate ALL wallets that share its old group key to a new hash.
105 // ---------------------------------------------------------------------------
106
107 /// When a user opens a wallet, check if it has a real hash.
108 /// If not, migrate the ENTIRE old group so they keep the same group name
109 /// and end up with the same new hash (preserving grouping).
110 Future<void> ensureGroupHasHashedIdentifier(WalletBase openedWallet) async {
111 WalletInfo walletInfo = openedWallet.walletInfo;
112
113 // If the openedWallet already has an hash, then there is nothing to do
114 if (walletInfo.hashedWalletIdentifier != null &&
115 walletInfo.hashedWalletIdentifier!.isNotEmpty) {
116 await updateWalletGroups(); // Still skeptical of calling this here. Looking for a better spot.
117 return;
118 }
119
120 // Identify the old group key for this wallet
121 final oldGroupKey = _resolveGroupKey(walletInfo); // parentAddress fallback
122
123 // Find all wallets that share this old group key (i.e the old group)
124 final oldGroupWallets = (await WalletInfo.getAll()).where((w) {
125 final key = w.hashedWalletIdentifier != null && w.hashedWalletIdentifier!.isNotEmpty
126 ? w.hashedWalletIdentifier
127 : (w.parentAddress ?? w.address);
128 return key == oldGroupKey;
129 }).toList();
130
131 if (oldGroupWallets.isEmpty) {
132 // This shouldn't happen, but just in case it does, we return.
133 return;
134 }
135
136 // Next, we determine the new group hash for these wallets
137 // Since they share the same seed, we can assign that group hash
138 // to all the wallets to preserve grouping.
139 final newGroupHash = createHashedWalletIdentifier(openedWallet);
140
141 // Migrate the old group name from oldGroupKey(i.e parentAddress) to newGroupHash
142 await _migrateGroupName(oldGroupKey, newGroupHash);
143
144 // Then we assign this new hash to each wallet in that old group and save them
145 for (final wallet in oldGroupWallets) {
146 wallet.hashedWalletIdentifier = newGroupHash;
147 await wallet.save();
148 }
149
150 // Finally, we rebuild the groups so that these wallets are now in the new group
151 await updateWalletGroups();
152 }
153
154 /// Copy an old group name to the new group key, then remove the old key.
155 Future<void> _migrateGroupName(String oldGroupKey, String newGroupKey) async {
156 final oldNameKey = 'wallet_group_name_$oldGroupKey';
157 final newNameKey = 'wallet_group_name_$newGroupKey';
158
159 final oldGroupName = _sharedPreferences.getString(oldNameKey);
160 if (oldGroupName != null) {
161 await _sharedPreferences.setString(newNameKey, oldGroupName);
162 await _sharedPreferences.remove(oldNameKey);
163 }
164 }
165
166 String? getGroupName(WalletInfo walletInfo) {
167 try {
168 final groupKey = _resolveGroupKey(walletInfo);
169 final group = walletGroups.firstWhere((g) => g.groupKey == groupKey);
170 return group.groupName;
171 } catch (_) {
172 return null;
173 }
174 }
175 }