From bd5c5835057596a78ccb56cd2edbfb7bd75089de Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sun, 1 Mar 2026 14:48:35 -0600 Subject: [PATCH 1/7] fix: match showLoading push/pop navigators --- lib/utilities/show_loading.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/utilities/show_loading.dart b/lib/utilities/show_loading.dart index 39537e37d..68fab92fe 100644 --- a/lib/utilities/show_loading.dart +++ b/lib/utilities/show_loading.dart @@ -42,6 +42,7 @@ Future showLoading({ unawaited( showDialog( context: context, + useRootNavigator: rootNavigator, barrierDismissible: false, builder: (_) => WillPopScope( onWillPop: () async => false, From 9d005554a4e13afbec42db06ebdd77ac0584221a Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sun, 1 Mar 2026 14:49:06 -0600 Subject: [PATCH 2/7] fix: use dialog context for backup success dialog pop --- .../stack_backup_views/create_backup_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index ebb6b94cf..859005c8d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -121,7 +121,7 @@ class _RestoreFromFileViewState extends ConsumerState { await showDialog( context: context, barrierDismissible: false, - builder: (_) => !Util.isDesktop + builder: (dialogContext) => !Util.isDesktop ? StackOkDialog(title: "Backup saved to:", message: savedPath) : DesktopDialog( maxHeight: double.infinity, @@ -154,7 +154,9 @@ class _RestoreFromFileViewState extends ConsumerState { child: PrimaryButton( label: "Ok", buttonHeight: ButtonHeight.l, - onPressed: Navigator.of(context).pop, + onPressed: () { + Navigator.of(dialogContext).pop(); + }, ), ), ], From df4b7f9a12904d7a4e29b190617fab439cb6e475 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 3 Mar 2026 18:08:21 -0600 Subject: [PATCH 3/7] fix: prevent error dialog from popping DesktopHomeView route --- .../stack_backup_views/create_backup_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 859005c8d..2a08a49fe 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -178,6 +178,7 @@ class _RestoreFromFileViewState extends ConsumerState { builder: (_) => StackOkDialog( title: "Backup creation failed", message: ex?.toString() ?? "Unexpected error", + desktopPopRootNavigator: true, ), ); } From c90c9e90fc51c8188dd386a7a0340f1d95f6bebe Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 3 Mar 2026 18:15:26 -0600 Subject: [PATCH 4/7] fix: skip wallets with missing data during backup instead of aborting eg. view-only wallets --- .../helpers/restore_create_backup.dart | 109 ++++++++++-------- 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 5f93a9dea..63e2bdcc3 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -269,63 +269,72 @@ abstract class SWB { final List backupWallets = []; for (final wallet in _wallets.wallets) { - final Map backupWallet = {}; - backupWallet['name'] = wallet.info.name; - backupWallet['id'] = wallet.walletId; - backupWallet['isFavorite'] = wallet.info.isFavourite; - backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString; - - if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { - backupWallet['viewOnlyWalletDataKey'] = - (await wallet.getViewOnlyWalletData()).toJsonEncodedString(); - } else if (wallet is MnemonicInterface) { - backupWallet['mnemonic'] = await wallet.getMnemonic(); - backupWallet['mnemonicPassphrase'] = await wallet - .getMnemonicPassphrase(); - } else if (wallet is PrivateKeyInterface) { - backupWallet['privateKey'] = await wallet.getPrivateKey(); - } else if (wallet is BitcoinFrostWallet) { - final String? keys = await wallet.getSerializedKeys(); - final String? config = await wallet.getMultisigConfig(); - if (keys == null || config == null) { - final String err = - "${wallet.info.coin.identifier} wallet ${wallet.info.name} " - "has null keys or config"; - Logging.instance.e(err); - throw Exception(err); + try { + final Map backupWallet = {}; + backupWallet['name'] = wallet.info.name; + backupWallet['id'] = wallet.walletId; + backupWallet['isFavorite'] = wallet.info.isFavourite; + backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString; + + if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { + backupWallet['viewOnlyWalletDataKey'] = + (await wallet.getViewOnlyWalletData()).toJsonEncodedString(); + } else if (wallet is MnemonicInterface) { + backupWallet['mnemonic'] = await wallet.getMnemonic(); + backupWallet['mnemonicPassphrase'] = await wallet + .getMnemonicPassphrase(); + } else if (wallet is PrivateKeyInterface) { + backupWallet['privateKey'] = await wallet.getPrivateKey(); + } else if (wallet is BitcoinFrostWallet) { + final String? keys = await wallet.getSerializedKeys(); + final String? config = await wallet.getMultisigConfig(); + if (keys == null || config == null) { + final String err = + "${wallet.info.coin.identifier} wallet ${wallet.info.name} " + "has null keys or config"; + Logging.instance.e(err); + throw Exception(err); + } + //This case should never actually happen in practice unless the whole + // wallet is somehow corrupt + // TODO [prio=low]: solve case in which either keys or config is null. + + // Format keys & config as a JSON string and set otherDataJsonString. + final Map frostData = {}; + frostData["keys"] = keys; + frostData["config"] = config; + backupWallet['frostWalletData'] = jsonEncode(frostData); } - //This case should never actually happen in practice unless the whole - // wallet is somehow corrupt - // TODO [prio=low]: solve case in which either keys or config is null. - - // Format keys & config as a JSON string and set otherDataJsonString. - final Map frostData = {}; - frostData["keys"] = keys; - frostData["config"] = config; - backupWallet['frostWalletData'] = jsonEncode(frostData); - } - backupWallet['coinName'] = wallet.info.coin.identifier; - backupWallet['storedChainHeight'] = wallet.info.cachedChainHeight; + backupWallet['coinName'] = wallet.info.coin.identifier; + backupWallet['storedChainHeight'] = wallet.info.cachedChainHeight; - // backupWallet['txidList'] = DB.instance.get( - // boxName: wallet.walletId, key: "cachedTxids") as List?; - // the following can cause a deadlock - // (await manager.transactionData).getAllTransactions().keys.toList(); + // backupWallet['txidList'] = DB.instance.get( + // boxName: wallet.walletId, key: "cachedTxids") as List?; + // the following can cause a deadlock + // (await manager.transactionData).getAllTransactions().keys.toList(); - backupWallet['restoreHeight'] = wallet.info.restoreHeight; + backupWallet['restoreHeight'] = wallet.info.restoreHeight; - final isarNotes = await MainDB.instance.isar.transactionNotes - .where() - .walletIdEqualTo(wallet.walletId) - .findAll(); + final isarNotes = await MainDB.instance.isar.transactionNotes + .where() + .walletIdEqualTo(wallet.walletId) + .findAll(); - final notes = isarNotes.asMap().map( - (key, value) => MapEntry(value.txid, value.value), - ); + final notes = isarNotes.asMap().map( + (key, value) => MapEntry(value.txid, value.value), + ); - backupWallet['notes'] = notes; + backupWallet['notes'] = notes; - backupWallets.add(backupWallet); + backupWallets.add(backupWallet); + } catch (e, s) { + Logging.instance.w( + "SWB skipping wallet ${wallet.info.name} " + "(${wallet.info.coin.identifier})", + error: e, + stackTrace: s, + ); + } } backupJson['wallets'] = backupWallets; From 79d0db2fae61ffee35c2a7193adb66b7ecbed7b3 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 3 Mar 2026 19:18:48 -0600 Subject: [PATCH 5/7] fix: fix race condition in cryptonote coins --- lib/wallets/isar/models/wallet_info.dart | 165 ++++++++++++----------- 1 file changed, 83 insertions(+), 82 deletions(-) diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index db0a65c70..2d1d76fd2 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -187,60 +187,60 @@ class WalletInfo implements IsarId { required Balance newBalance, required Isar isar, }) async { - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - final newEncoded = newBalance.toJsonIgnoreCoin(); - - // only update if there were changes to the balance - if (thisInfo.cachedBalanceString != newEncoded) { - await isar.writeTxn(() async { + // Read inside the tx so concurrent updates don't create a race condition. + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && thisInfo.cachedBalanceString != newEncoded) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(cachedBalanceString: newEncoded), ); - }); - } + } + }); } Future updateBalanceSecondary({ required Balance newBalance, required Isar isar, }) async { - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - final newEncoded = newBalance.toJsonIgnoreCoin(); - - // only update if there were changes to the balance - if (thisInfo.cachedBalanceSecondaryString != newEncoded) { - await isar.writeTxn(() async { + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && + thisInfo.cachedBalanceSecondaryString != newEncoded) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(cachedBalanceSecondaryString: newEncoded), ); - }); - } + } + }); } Future updateBalanceTertiary({ required Balance newBalance, required Isar isar, }) async { - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - final newEncoded = newBalance.toJsonIgnoreCoin(); - - // only update if there were changes to the balance - if (thisInfo.cachedBalanceTertiaryString != newEncoded) { - await isar.writeTxn(() async { + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && + thisInfo.cachedBalanceTertiaryString != newEncoded) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(cachedBalanceTertiaryString: newEncoded), ); - }); - } + } + }); } /// copies this with a new chain height and updates the db @@ -248,17 +248,18 @@ class WalletInfo implements IsarId { required int newHeight, required Isar isar, }) async { - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - // only update if there were changes to the height - if (thisInfo.cachedChainHeight != newHeight) { - await isar.writeTxn(() async { + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && thisInfo.cachedChainHeight != newHeight) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(cachedChainHeight: newHeight), ); - }); - } + } + }); } /// update favourite wallet and its index it the ui list. @@ -283,18 +284,18 @@ class WalletInfo implements IsarId { index = -1; } - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - - // only update if there were changes to the height - if (thisInfo.favouriteOrderIndex != index) { - await isar.writeTxn(() async { + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && thisInfo.favouriteOrderIndex != index) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(favouriteOrderIndex: index), ); - }); - } + } + }); } /// copies this with a new name and updates the db @@ -303,35 +304,35 @@ class WalletInfo implements IsarId { if (newName.isEmpty) { throw Exception("Empty wallet name not allowed!"); } - - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - - // only update if there were changes to the name - if (thisInfo.name != newName) { - await isar.writeTxn(() async { + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && thisInfo.name != newName) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put(thisInfo.copyWith(name: newName)); - }); - } + } + }); } - /// copies this with a new name and updates the db + /// copies this with a new receiving address and updates the db Future updateReceivingAddress({ required String newAddress, required Isar isar, }) async { - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - // only update if there were changes to the name - if (thisInfo.cachedReceivingAddress != newAddress) { - await isar.writeTxn(() async { + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && thisInfo.cachedReceivingAddress != newAddress) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(cachedReceivingAddress: newAddress), ); - }); - } + } + }); } /// update [otherData] with the map entries in [newEntries] @@ -339,23 +340,24 @@ class WalletInfo implements IsarId { required Map newEntries, required Isar isar, }) async { - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo == null) return; - final Map newMap = {}; - newMap.addAll(thisInfo.otherData); - newMap.addAll(newEntries); - final encodedNew = jsonEncode(newMap); + final newMap = Map.from(thisInfo.otherData) + ..addAll(newEntries); + final encodedNew = jsonEncode(newMap); - // only update if there were changes - if (thisInfo.otherDataJsonString != encodedNew) { - await isar.writeTxn(() async { + if (thisInfo.otherDataJsonString != encodedNew) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(otherDataJsonString: encodedNew), ); - }); - } + } + }); } /// Can be dangerous. Don't use unless you know the consequences @@ -385,28 +387,26 @@ class WalletInfo implements IsarId { } } - /// copies this with a new name and updates the db + /// copies this with a new restore height and updates the db Future updateRestoreHeight({ required int newRestoreHeight, required Isar isar, }) async { - // don't allow empty names if (newRestoreHeight < 0) { throw Exception("Negative restore height not allowed!"); } - - // try to get latest instance of this from db - final thisInfo = await isar.walletInfo.get(id) ?? this; - - // only update if there were changes to the name - if (thisInfo.restoreHeight != newRestoreHeight) { - await isar.writeTxn(() async { + await isar.writeTxn(() async { + final thisInfo = await isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); + if (thisInfo != null && thisInfo.restoreHeight != newRestoreHeight) { await isar.walletInfo.delete(thisInfo.id); await isar.walletInfo.put( thisInfo.copyWith(restoreHeight: newRestoreHeight), ); - }); - } + } + }); } /// copies this with a new name and updates the db @@ -442,7 +442,8 @@ class WalletInfo implements IsarId { }) async { await updateOtherData( newEntries: { - WalletInfoKeys.solanaCustomTokenMintAddresses: newMintAddresses.toList(), + WalletInfoKeys.solanaCustomTokenMintAddresses: newMintAddresses + .toList(), }, isar: isar, ); From 393a1e27d208fd4a7e20577aafe866105c05d823 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 3 Mar 2026 19:19:35 -0600 Subject: [PATCH 6/7] fix: handle corrupted wallets view-only wallets could previously become broken due to race condition addressed by 79d0db2f this handles those broken wallets --- .../helpers/restore_create_backup.dart | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 63e2bdcc3..547129bce 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -276,9 +276,34 @@ abstract class SWB { backupWallet['isFavorite'] = wallet.info.isFavourite; backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString; - if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { - backupWallet['viewOnlyWalletDataKey'] = - (await wallet.getViewOnlyWalletData()).toJsonEncodedString(); + // Check secure storage for view-only data even if flag is missing. + String? rawViewOnlyData; + if (wallet is ViewOnlyOptionInterface) { + rawViewOnlyData = await _secureStore.read( + key: Wallet.getViewOnlyWalletDataSecStoreKey( + walletId: wallet.walletId, + ), + ); + } + + if (rawViewOnlyData != null) { + backupWallet['viewOnlyWalletDataKey'] = rawViewOnlyData; + // Patch missing isViewOnlyKey flag in otherDataJsonString. + if (wallet.info.otherData[WalletInfoKeys.isViewOnlyKey] != true) { + final patchedOtherData = Map.from( + wallet.info.otherData, + ); + patchedOtherData[WalletInfoKeys.isViewOnlyKey] = true; + final parsed = ViewOnlyWalletData.fromJsonEncodedString( + rawViewOnlyData, + walletId: wallet.walletId, + ); + patchedOtherData[WalletInfoKeys.viewOnlyTypeIndexKey] = + parsed.type.index; + backupWallet['otherDataJsonString'] = jsonEncode( + patchedOtherData, + ); + } } else if (wallet is MnemonicInterface) { backupWallet['mnemonic'] = await wallet.getMnemonic(); backupWallet['mnemonicPassphrase'] = await wallet @@ -387,7 +412,7 @@ abstract class SWB { String? mnemonic, mnemonicPassphrase, privateKey; ViewOnlyWalletData? viewOnlyData; - if (info.isViewOnly) { + if (info.isViewOnly || walletbackup['viewOnlyWalletDataKey'] is String) { final viewOnlyDataEncoded = walletbackup['viewOnlyWalletDataKey'] as String; @@ -784,6 +809,28 @@ abstract class SWB { ); } + // Patch missing isViewOnlyKey if backup has view-only data. + if (walletbackup['viewOnlyWalletDataKey'] is String && + otherData?[WalletInfoKeys.isViewOnlyKey] != true) { + otherData ??= {}; + otherData[WalletInfoKeys.isViewOnlyKey] = true; + if (otherData[WalletInfoKeys.viewOnlyTypeIndexKey] == null) { + try { + final parsed = ViewOnlyWalletData.fromJsonEncodedString( + walletbackup['viewOnlyWalletDataKey'] as String, + walletId: walletId, + ); + otherData[WalletInfoKeys.viewOnlyTypeIndexKey] = parsed.type.index; + } catch (e, s) { + Logging.instance.e( + "SWB restore: failed to recover viewOnlyTypeIndexKey", + error: e, + stackTrace: s, + ); + } + } + } + final info = WalletInfo( coinName: coin.identifier, walletId: walletId, From d29ac5a02d2bee6e365bc3f2c71cb633870179cd Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 3 Mar 2026 19:25:33 -0600 Subject: [PATCH 7/7] Revert "fix: skip wallets with missing data during backup instead of aborting" This reverts commit c90c9e90fc51c8188dd386a7a0340f1d95f6bebe. --- .../helpers/restore_create_backup.dart | 153 ++++++++---------- 1 file changed, 71 insertions(+), 82 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 547129bce..4b05ab122 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -269,97 +269,86 @@ abstract class SWB { final List backupWallets = []; for (final wallet in _wallets.wallets) { - try { - final Map backupWallet = {}; - backupWallet['name'] = wallet.info.name; - backupWallet['id'] = wallet.walletId; - backupWallet['isFavorite'] = wallet.info.isFavourite; - backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString; - - // Check secure storage for view-only data even if flag is missing. - String? rawViewOnlyData; - if (wallet is ViewOnlyOptionInterface) { - rawViewOnlyData = await _secureStore.read( - key: Wallet.getViewOnlyWalletDataSecStoreKey( - walletId: wallet.walletId, - ), + final Map backupWallet = {}; + backupWallet['name'] = wallet.info.name; + backupWallet['id'] = wallet.walletId; + backupWallet['isFavorite'] = wallet.info.isFavourite; + backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString; + + // Check secure storage for view-only data even if flag is missing. + String? rawViewOnlyData; + if (wallet is ViewOnlyOptionInterface) { + rawViewOnlyData = await _secureStore.read( + key: Wallet.getViewOnlyWalletDataSecStoreKey( + walletId: wallet.walletId, + ), + ); + } + + if (rawViewOnlyData != null) { + backupWallet['viewOnlyWalletDataKey'] = rawViewOnlyData; + // Patch missing isViewOnlyKey flag in otherDataJsonString. + if (wallet.info.otherData[WalletInfoKeys.isViewOnlyKey] != true) { + final patchedOtherData = Map.from( + wallet.info.otherData, + ); + patchedOtherData[WalletInfoKeys.isViewOnlyKey] = true; + final parsed = ViewOnlyWalletData.fromJsonEncodedString( + rawViewOnlyData, + walletId: wallet.walletId, ); + patchedOtherData[WalletInfoKeys.viewOnlyTypeIndexKey] = + parsed.type.index; + backupWallet['otherDataJsonString'] = jsonEncode(patchedOtherData); } - - if (rawViewOnlyData != null) { - backupWallet['viewOnlyWalletDataKey'] = rawViewOnlyData; - // Patch missing isViewOnlyKey flag in otherDataJsonString. - if (wallet.info.otherData[WalletInfoKeys.isViewOnlyKey] != true) { - final patchedOtherData = Map.from( - wallet.info.otherData, - ); - patchedOtherData[WalletInfoKeys.isViewOnlyKey] = true; - final parsed = ViewOnlyWalletData.fromJsonEncodedString( - rawViewOnlyData, - walletId: wallet.walletId, - ); - patchedOtherData[WalletInfoKeys.viewOnlyTypeIndexKey] = - parsed.type.index; - backupWallet['otherDataJsonString'] = jsonEncode( - patchedOtherData, - ); - } - } else if (wallet is MnemonicInterface) { - backupWallet['mnemonic'] = await wallet.getMnemonic(); - backupWallet['mnemonicPassphrase'] = await wallet - .getMnemonicPassphrase(); - } else if (wallet is PrivateKeyInterface) { - backupWallet['privateKey'] = await wallet.getPrivateKey(); - } else if (wallet is BitcoinFrostWallet) { - final String? keys = await wallet.getSerializedKeys(); - final String? config = await wallet.getMultisigConfig(); - if (keys == null || config == null) { - final String err = - "${wallet.info.coin.identifier} wallet ${wallet.info.name} " - "has null keys or config"; - Logging.instance.e(err); - throw Exception(err); - } - //This case should never actually happen in practice unless the whole - // wallet is somehow corrupt - // TODO [prio=low]: solve case in which either keys or config is null. - - // Format keys & config as a JSON string and set otherDataJsonString. - final Map frostData = {}; - frostData["keys"] = keys; - frostData["config"] = config; - backupWallet['frostWalletData'] = jsonEncode(frostData); + } else if (wallet is MnemonicInterface) { + backupWallet['mnemonic'] = await wallet.getMnemonic(); + backupWallet['mnemonicPassphrase'] = await wallet + .getMnemonicPassphrase(); + } else if (wallet is PrivateKeyInterface) { + backupWallet['privateKey'] = await wallet.getPrivateKey(); + } else if (wallet is BitcoinFrostWallet) { + final String? keys = await wallet.getSerializedKeys(); + final String? config = await wallet.getMultisigConfig(); + if (keys == null || config == null) { + final String err = + "${wallet.info.coin.identifier} wallet ${wallet.info.name} " + "has null keys or config"; + Logging.instance.e(err); + throw Exception(err); } - backupWallet['coinName'] = wallet.info.coin.identifier; - backupWallet['storedChainHeight'] = wallet.info.cachedChainHeight; + //This case should never actually happen in practice unless the whole + // wallet is somehow corrupt + // TODO [prio=low]: solve case in which either keys or config is null. + + // Format keys & config as a JSON string and set otherDataJsonString. + final Map frostData = {}; + frostData["keys"] = keys; + frostData["config"] = config; + backupWallet['frostWalletData'] = jsonEncode(frostData); + } + backupWallet['coinName'] = wallet.info.coin.identifier; + backupWallet['storedChainHeight'] = wallet.info.cachedChainHeight; - // backupWallet['txidList'] = DB.instance.get( - // boxName: wallet.walletId, key: "cachedTxids") as List?; - // the following can cause a deadlock - // (await manager.transactionData).getAllTransactions().keys.toList(); + // backupWallet['txidList'] = DB.instance.get( + // boxName: wallet.walletId, key: "cachedTxids") as List?; + // the following can cause a deadlock + // (await manager.transactionData).getAllTransactions().keys.toList(); - backupWallet['restoreHeight'] = wallet.info.restoreHeight; + backupWallet['restoreHeight'] = wallet.info.restoreHeight; - final isarNotes = await MainDB.instance.isar.transactionNotes - .where() - .walletIdEqualTo(wallet.walletId) - .findAll(); + final isarNotes = await MainDB.instance.isar.transactionNotes + .where() + .walletIdEqualTo(wallet.walletId) + .findAll(); - final notes = isarNotes.asMap().map( - (key, value) => MapEntry(value.txid, value.value), - ); + final notes = isarNotes.asMap().map( + (key, value) => MapEntry(value.txid, value.value), + ); - backupWallet['notes'] = notes; + backupWallet['notes'] = notes; - backupWallets.add(backupWallet); - } catch (e, s) { - Logging.instance.w( - "SWB skipping wallet ${wallet.info.name} " - "(${wallet.info.coin.identifier})", - error: e, - stackTrace: s, - ); - } + backupWallets.add(backupWallet); } backupJson['wallets'] = backupWallets;