cake_wallet/lib/entities/wallet_manager.dart
David Adegoke 09f20b2a7b
CW-843: Enhance Wallet Groups Implementation ()
* 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
2025-03-06 02:25:38 +02:00

161 lines
5.5 KiB
Dart

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);
final Box<WalletInfo> _walletInfoSource;
final SharedPreferences _sharedPreferences;
final List<WalletGroup> walletGroups = [];
void updateWalletGroups() {
walletGroups.clear();
for (final walletInfo in _walletInfoSource.values) {
final groupKey = _resolveGroupKey(walletInfo);
final group = _getOrCreateGroup(groupKey);
group.wallets.add(walletInfo);
}
walletGroups.removeWhere((g) => g.wallets.isEmpty);
_loadCustomGroupNames();
}
String _resolveGroupKey(WalletInfo walletInfo) {
if (walletInfo.hashedWalletIdentifier != null &&
walletInfo.hashedWalletIdentifier!.isNotEmpty) {
return walletInfo.hashedWalletIdentifier!;
}
// Fallback to old logic
return walletInfo.parentAddress ?? walletInfo.address;
}
WalletGroup _getOrCreateGroup(String groupKey) {
return walletGroups.firstWhere(
(g) => g.groupKey == groupKey,
orElse: () {
final newGroup = WalletGroup(groupKey);
walletGroups.add(newGroup);
return newGroup;
},
);
}
void addWallet(WalletInfo walletInfo) {
final groupKey = _resolveGroupKey(walletInfo);
final group = _getOrCreateGroup(groupKey);
group.wallets.add(walletInfo);
}
void removeWallet(WalletInfo walletInfo) {
final groupKey = _resolveGroupKey(walletInfo);
final group = _getOrCreateGroup(groupKey);
group.wallets.remove(walletInfo);
if (group.wallets.isEmpty) {
walletGroups.remove(group);
}
}
List<WalletInfo> getWalletsInGroup(String groupKey) {
return walletGroups
.firstWhere(
(g) => g.groupKey == groupKey,
orElse: () => WalletGroup(groupKey),
)
.wallets;
}
void _loadCustomGroupNames() {
for (var group in walletGroups) {
final key = 'wallet_group_name_${group.groupKey}';
final groupName = _sharedPreferences.getString(key);
if (groupName != null && group.wallets.length > 1) {
group.groupName = groupName;
}
}
}
void _saveCustomGroupName(String groupKey, String name) {
_sharedPreferences.setString('wallet_group_name_$groupKey', name);
}
void setGroupName(String groupKey, String name) {
if (groupKey.isEmpty || name.isEmpty) return;
final group = walletGroups.firstWhere((g) => g.groupKey == groupKey);
group.setCustomName(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<void> 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<void> _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);
}
}
}