Files
hash-wallet/lib/entities/wallet_manager.dart
T
cyanandGitHub 76876181e5 feat: initial hive -> SQLite (#2502)
* feat: initial hive -> SQLite

Includes migration script and initial work done to
remove dependency on hive generators.

Also contains some refactoring to make sync code
async.

Features new dev screen that lists all SQLite tables

Aspects that need testing:
- backups (from this version, and from previous versions as well)
- wallet loading / wallet list
- almost everything else
- renaming
- deletion
- sorting in wallet list
- wallet groups
- wallet seed/keys
- wallet switching
- wallet creation / restore - all methods
- wallet address page
- hardware wallet connection (and prompts from mall screens)
- contacts
- advanced settings in wallet creation
- derivation paths
- some minor wownero fixes
- base, hardware wallets

* fix: build errors

* fix: Bad state: No element in backup restore

* address comments from review

* fix: HardwareWalletType
2025-10-31 22:37:15 +01:00

167 lines
5.6 KiB
Dart

import 'dart:math';
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._sharedPreferences);
final SharedPreferences _sharedPreferences;
final List<WalletGroup> walletGroups = [];
Future<void> updateWalletGroups() async {
walletGroups.clear();
for (final walletInfo in await WalletInfo.getAll()) {
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
final address = walletInfo.parentAddress ?? walletInfo.address;
if (address.isEmpty) {
return Random().nextInt(100000).toString();
}
return 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) {
await 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 = (await WalletInfo.getAll()).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
await 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);
}
}
}