stack_wallet/lib/db/migrate_wallets_to_isar.dart
2024-01-15 09:42:49 -06:00

214 lines
6.7 KiB
Dart

import 'dart:convert';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/wallets/isar/models/token_wallet_info.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info_meta.dart';
import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_extension.dart';
Future<void> migrateWalletsToIsar({
required SecureStorageInterface secureStore,
}) async {
await MainDB.instance.initMainDB();
// ensure fresh
await MainDB.instance.isar
.writeTxn(() async => await MainDB.instance.isar.transactionV2s.clear());
final allWalletsBox = await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
final names = DB.instance
.get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?;
if (names == null) {
// no wallets to migrate
return;
}
//
// Parse the old data from the Hive map into a nice list
//
final List<
({
Coin coin,
String name,
String walletId,
})> oldInfo = Map<String, dynamic>.from(names).values.map((e) {
final map = e as Map;
return (
coin: Coin.values.byName(map["coin"] as String),
walletId: map["id"] as String,
name: map["name"] as String,
);
}).toList();
//
// Get current ordered list of favourite wallet Ids
//
final List<String> favourites =
(await Hive.openBox<String>(DB.boxNameFavoriteWallets)).values.toList();
final List<(WalletInfo, WalletInfoMeta)> newInfo = [];
final List<TokenWalletInfo> tokenInfo = [];
final List<TransactionNote> migratedNotes = [];
//
// Convert each old info into the new Isar WalletInfo
//
for (final old in oldInfo) {
final walletBox = await Hive.openBox<dynamic>(old.walletId);
//
// First handle transaction notes
//
final newNoteCount = await MainDB.instance.isar.transactionNotes
.where()
.walletIdEqualTo(old.walletId)
.count();
if (newNoteCount == 0) {
final map = walletBox.get('notes') as Map?;
if (map != null) {
final notes = Map<String, String>.from(map);
for (final txid in notes.keys) {
final note = notes[txid];
if (note != null && note.isNotEmpty) {
final newNote = TransactionNote(
walletId: old.walletId,
txid: txid,
value: note,
);
migratedNotes.add(newNote);
}
}
}
}
// reset stellar + tezos address type
if (old.coin == Coin.stellar ||
old.coin == Coin.stellarTestnet ||
old.coin == Coin.tezos) {
await MainDB.instance.deleteWalletBlockchainData(old.walletId);
}
//
// Set other data values
//
Map<String, dynamic> otherData = {};
final List<String>? tokenContractAddresses = walletBox.get(
"ethTokenContracts",
) as List<String>?;
if (tokenContractAddresses?.isNotEmpty == true) {
otherData[WalletInfoKeys.tokenContractAddresses] = tokenContractAddresses;
for (final address in tokenContractAddresses!) {
final contract = await MainDB.instance.isar.ethContracts
.where()
.addressEqualTo(address)
.findFirst();
if (contract != null) {
tokenInfo.add(
TokenWalletInfo(
walletId: old.walletId,
tokenAddress: address,
tokenFractionDigits: contract.decimals,
),
);
}
}
}
// epiccash specifics
if (old.coin == Coin.epicCash) {
final epicWalletInfo = ExtraEpiccashWalletInfo.fromMap({
"receivingIndex": walletBox.get("receivingIndex") as int? ?? 0,
"changeIndex": walletBox.get("changeIndex") as int? ?? 0,
"slatesToAddresses": walletBox.get("slate_to_address") as Map? ?? {},
"slatesToCommits": walletBox.get("slatesToCommits") as Map? ?? {},
"lastScannedBlock": walletBox.get("lastScannedBlock") as int? ?? 0,
"restoreHeight": walletBox.get("restoreHeight") as int? ?? 0,
"creationHeight": walletBox.get("creationHeight") as int? ?? 0,
});
otherData[WalletInfoKeys.epiccashData] = jsonEncode(
epicWalletInfo.toMap(),
);
} else if (old.coin == Coin.firo || old.coin == Coin.firoTestNet) {
otherData[WalletInfoKeys.lelantusCoinIsarRescanRequired] = walletBox
.get(WalletInfoKeys.lelantusCoinIsarRescanRequired) as bool? ??
true;
}
//
// Clear out any keys with null values as they are not needed
//
otherData.removeWhere((key, value) => value == null);
final infoMeta = WalletInfoMeta(
walletId: old.walletId,
isMnemonicVerified: allWalletsBox
.get("${old.walletId}_mnemonicHasBeenVerified") as bool? ??
false,
);
final info = WalletInfo(
coinName: old.coin.name,
walletId: old.walletId,
name: old.name,
mainAddressType: old.coin.primaryAddressType,
favouriteOrderIndex: favourites.indexOf(old.walletId),
cachedChainHeight: walletBox.get(
DBKeys.storedChainHeight,
) as int? ??
0,
cachedBalanceString: walletBox.get(
DBKeys.cachedBalance,
) as String?,
cachedBalanceSecondaryString: walletBox.get(
DBKeys.cachedBalanceSecondary,
) as String?,
otherDataJsonString: jsonEncode(otherData),
);
newInfo.add((info, infoMeta));
}
if (migratedNotes.isNotEmpty) {
await MainDB.instance.isar.writeTxn(() async {
await MainDB.instance.isar.transactionNotes.putAll(migratedNotes);
});
}
if (newInfo.isNotEmpty) {
await MainDB.instance.isar.writeTxn(() async {
await MainDB.instance.isar.walletInfo
.putAll(newInfo.map((e) => e.$1).toList());
await MainDB.instance.isar.walletInfoMeta
.putAll(newInfo.map((e) => e.$2).toList());
if (tokenInfo.isNotEmpty) {
await MainDB.instance.isar.tokenWalletInfo.putAll(tokenInfo);
}
});
}
await _cleanupOnSuccess(
walletIds: newInfo.map((e) => e.$1.walletId).toList());
}
Future<void> _cleanupOnSuccess({required List<String> walletIds}) async {
await Hive.deleteBoxFromDisk(DB.boxNameFavoriteWallets);
await Hive.deleteBoxFromDisk(DB.boxNameAllWalletsData);
for (final walletId in walletIds) {
await Hive.deleteBoxFromDisk(walletId);
}
}