Files
hash-wallet/cw_core/lib/wallet_info.dart
T
jwinterm dd449f2594 Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.

Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.

Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
  our order differs from upstream's; taking theirs would reread saved
  wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
  Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
  converted to upstream's isDefault node-list key; Wownero is back in the
  node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
  prices.neroswap.com for fiat; upstream's charts tab, which reads
  prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
  upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.

Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
2026-10-24 23:49:29 -04:00

842 lines
24 KiB
Dart

import 'dart:async';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/db/sqlite.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info_legacy.dart' as wiLegacy;
import 'package:cw_core/wallet_type.dart';
import 'package:sqflite/sqflite.dart';
Future<void> performHiveMigration() async {
try {
if (!CakeHive.isAdapterRegistered(wiLegacy.WalletInfo.typeId)) {
CakeHive.registerAdapter(wiLegacy.WalletInfoAdapter());
}
if (!CakeHive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(wiLegacy.DerivationTypeAdapter());
}
if (!CakeHive.isAdapterRegistered(wiLegacy.DerivationInfo.typeId)) {
CakeHive.registerAdapter(wiLegacy.DerivationInfoAdapter());
}
if (!CakeHive.isAdapterRegistered(HARDWARE_WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(wiLegacy.HardwareWalletTypeAdapter());
}
final walletInfoBox = await CakeHive.openBox<wiLegacy.WalletInfo>(wiLegacy.WalletInfo.boxName);
await wiLegacy.WalletInfo.migrateAllToSqlite(walletInfoBox);
} catch (e) {
printV('Error performing Hive migration: $e, continuing anyway');
}
}
enum DerivationType {
unknown,
def, // default is a reserved word
nano,
bip39,
electrum,
}
enum HardwareWalletType {
ledger,
bitbox,
cupcake,
coldcard,
seedsigner,
keystone,
trezor;
}
enum WalletInfoAddressType {
used,
hidden,
manual,
}
class WalletInfoAddressInfo {
WalletInfoAddressInfo({
this.id = 0,
required this.walletInfoId,
required this.mapKey,
required this.accountIndex,
required this.address,
required this.label,
});
int id;
int walletInfoId;
int mapKey;
int accountIndex;
String address;
String label;
static String get tableName => 'walletInfoAddressInfo';
static String get selfIdColumn => "${tableName}Id";
static Future<List<WalletInfoAddressInfo>> selectList(int walletInfoId) async {
final query = await db!.query(tableName, where: 'walletInfoId = ?', whereArgs: [walletInfoId]);
return List.generate(query.length, (index) => WalletInfoAddressInfo.fromJson(query[index]));
}
static Future<int> deleteByWalletInfoId(int walletInfoId) async {
return await db!.delete(tableName, where: 'walletInfoId = ?', whereArgs: [walletInfoId]);
}
static Future<int> insert({
required int walletInfoId,
required int mapKey,
required int accountIndex,
required String address,
required String label,
}) async {
return await db!.insert(tableName, {
"walletInfoId": walletInfoId,
"mapKey": mapKey,
"mapValueAccountIndex": accountIndex,
"mapValueAddress": address,
"mapValueLabel": label,
});
}
Map<String, dynamic> toJson() {
return {
selfIdColumn: id,
"walletInfoId": walletInfoId,
"mapKey": mapKey,
"mapValueAccountIndex": accountIndex,
"mapValueAddress": address,
"mapValueLabel": label,
};
}
factory WalletInfoAddressInfo.fromJson(Map<String, dynamic> json) {
return WalletInfoAddressInfo(
id: json[selfIdColumn] as int,
walletInfoId: json['walletInfoId'] as int,
mapKey: json['mapKey'] as int,
accountIndex: json['mapValueAccountIndex'] as int,
address: json['mapValueAddress'] as String,
label: json['mapValueLabel'] as String,
);
}
}
class WalletInfoAddressMap {
WalletInfoAddressMap({
required this.id,
required this.walletInfoId,
required this.addressKey,
required this.addressValue,
});
int id;
int walletInfoId;
String addressKey;
String addressValue;
static String get tableName => 'walletInfoAddressMap';
static String get selfIdColumn => "${tableName}Id";
static Future<List<WalletInfoAddressMap>> selectList(int walletInfoId) async {
final query = await db!.query(tableName, where: 'walletInfoId = ?', whereArgs: [walletInfoId]);
return List.generate(query.length, (index) => WalletInfoAddressMap.fromJson(query[index]));
}
static Future<int> deleteByWalletInfoId(int walletInfoId) async {
return await db!.delete(tableName, where: 'walletInfoId = ?', whereArgs: [walletInfoId]);
}
static Future<int> insert(int walletInfoId, String addressKey, String addressValue) async {
return await db!.insert(tableName, {
"walletInfoId": walletInfoId,
"addressKey": addressKey,
"addressValue": addressValue,
});
}
Map<String, dynamic> toJson() {
return {
selfIdColumn: id,
"walletInfoId": walletInfoId,
"addressKey": addressKey,
"addressValue": addressValue,
};
}
factory WalletInfoAddressMap.fromJson(Map<String, dynamic> json) {
return WalletInfoAddressMap(
id: json[selfIdColumn] as int,
walletInfoId: json['walletInfoId'] as int,
addressKey: json['addressKey'] as String,
addressValue: json['addressValue'] as String,
);
}
}
class WalletInfoAddress {
WalletInfoAddress({
this.id = 0,
required this.walletInfoId,
required this.type,
required this.address,
});
int id;
int walletInfoId;
WalletInfoAddressType type;
String address;
static String get tableName => 'walletInfoAddress';
static String get selfIdColumn => "${tableName}Id";
static Future<List<WalletInfoAddress>> selectList(
int walletInfoId, WalletInfoAddressType type) async {
final query = await db!.query(tableName,
where: 'walletInfoId = ? AND type = ?', whereArgs: [walletInfoId, type.index]);
return List.generate(query.length, (index) => WalletInfoAddress.fromJson(query[index]));
}
static Future<int> deleteByAddress(
int walletInfoId, WalletInfoAddressType type, String address) async {
return await db!.delete(tableName,
where: 'walletInfoId = ? AND type = ? AND address = ?',
whereArgs: [walletInfoId, type.index, address]);
}
static Future<int> deleteByType(int walletInfoId, WalletInfoAddressType type) async {
return await db!.delete(tableName,
where: 'walletInfoId = ? AND type = ?', whereArgs: [walletInfoId, type.index]);
}
static Future<int> insert(int walletInfoId, WalletInfoAddressType type, String address) async {
final select = await db!.query(tableName,
where: 'walletInfoId = ? AND type = ? AND address = ?',
whereArgs: [walletInfoId, type.index, address]);
if (select.isNotEmpty) {
return select[0][selfIdColumn] as int;
}
return await db!.insert(tableName, {
"walletInfoId": walletInfoId,
"type": type.index,
"address": address,
});
}
Map<String, dynamic> toJson() {
return {
selfIdColumn: id,
"walletInfoId": walletInfoId,
"type": type.index,
"address": address,
};
}
factory WalletInfoAddress.fromJson(Map<String, dynamic> json) {
return WalletInfoAddress(
id: json[selfIdColumn] as int,
walletInfoId: json['walletInfoId'] as int,
type: WalletInfoAddressType.values[json['type'] as int],
address: json['address'] as String,
);
}
}
class WalletInfoAccount {
WalletInfoAccount({
required this.walletInfoId,
required this.accountIndex,
required this.label,
});
factory WalletInfoAccount.fromJson(Map<String, dynamic> json) => WalletInfoAccount(
walletInfoId: json["walletInfoId"] as int,
accountIndex: json["accountIndex"] as int,
label: json["label"] as String,
);
int walletInfoId;
int accountIndex;
String label;
static String get tableName => "walletInfoAccount";
static Future<List<WalletInfoAccount>> selectList(int walletInfoId) async {
final query = await db!.query(
tableName,
where: "walletInfoId = ?",
whereArgs: [walletInfoId],
orderBy: "accountIndex ASC",
);
return List.generate(query.length, (index) => WalletInfoAccount.fromJson(query[index]));
}
static Future<int> deleteByWalletInfoId(int walletInfoId) async =>
await db!.delete(tableName, where: "walletInfoId = ?", whereArgs: [walletInfoId]);
static Future<int> insertOrUpdate({
required int walletInfoId,
required int accountIndex,
required String label,
}) async =>
await db!.transaction((txn) async {
final updated = await txn.update(
tableName,
{"label": label},
where: "walletInfoId = ? AND accountIndex = ?",
whereArgs: [walletInfoId, accountIndex],
);
if (updated > 0) return updated;
return await txn.insert(tableName, {
"walletInfoId": walletInfoId,
"accountIndex": accountIndex,
"label": label,
});
});
Map<String, dynamic> toJson() => {
"walletInfoId": walletInfoId,
"accountIndex": accountIndex,
"label": label,
};
}
class DerivationInfo {
DerivationInfo({
this.id = 0,
this.derivationType,
this.derivationPath,
this.balance = "",
this.address = "",
this.transactionsCount = 0,
this.scriptType,
this.description,
});
int id;
static String get tableName => 'walletInfoDerivationInfo';
static String get selfIdColumn => "${tableName}Id";
String address;
String balance;
int transactionsCount;
DerivationType? derivationType;
String? derivationPath;
String? scriptType;
String? description;
static Future<List<DerivationInfo>> selectList(String where, List<dynamic> whereArgs) async {
final query = await db!.query(
tableName,
columns: [
selfIdColumn,
'address',
'balance',
'transactionsCount',
'derivationType',
'derivationPath',
'scriptType',
'description',
],
where: where.isNotEmpty ? where : "1 = 1",
whereArgs: whereArgs.isNotEmpty ? whereArgs : null,
);
return List.generate(query.length, (index) => DerivationInfo.fromJson(query[index]));
}
Map<String, dynamic> toJson() {
return {
selfIdColumn: id,
"address": address,
"balance": balance,
"transactionsCount": transactionsCount,
"derivationType": derivationType?.index,
"derivationPath": derivationPath,
"scriptType": scriptType,
"description": description,
};
}
factory DerivationInfo.fromJson(Map<String, dynamic> json) {
return DerivationInfo(
id: json[selfIdColumn] as int,
derivationType: DerivationType.values[json['derivationType'] as int? ?? 0],
derivationPath: json['derivationPath'] as String?,
balance: json['balance'] as String? ?? "",
address: json['address'] as String? ?? "",
transactionsCount: json['transactionsCount'] as int? ?? 0,
scriptType: json['scriptType'] as String?,
description: json['description'] as String?,
);
}
Future<int> save() async {
final json = toJson();
if (json[selfIdColumn] == 0) {
json[selfIdColumn] = null;
}
id = await db!.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace);
return id;
}
}
class WalletInfo {
WalletInfo(
this.internalId,
this.id,
this.name,
this.type,
this.isRecovery,
this.restoreHeight,
this.timestamp,
this.dirPath,
this.path,
this.address,
this.yatEid,
this.yatLastUsedAddressRaw,
this.showIntroCakePayCard,
this.derivationInfoId,
this.hardwareWalletType,
this.parentAddress,
this.hashedWalletIdentifier,
this.isNonSeedWallet,
this.sortOrder,
this.currentAccountIndex,
this.addressPageType,
this.receiveInfoboxDismissed,
this.showCombinedBalance,
this.favoriteTokenAddress,
this.showSeedBackupReminder)
: _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external({
required String id,
required String name,
required WalletType type,
required bool isRecovery,
required int restoreHeight,
required DateTime date,
required String dirPath,
required String path,
required String address,
bool? showIntroCakePayCard,
String yatEid = "",
String yatLastUsedAddressRaw = "",
int? derivationInfoId,
HardwareWalletType? hardwareWalletType,
String? parentAddress,
String? hashedWalletIdentifier,
bool? isNonSeedWallet,
int? sortOrder,
int currentAccountIndex = 0,
bool? receiveInfoboxDismissed,
bool? showCombinedBalance,
String? favoriteTokenAddress,
}) {
final wi = WalletInfo(
0,
id,
name,
type,
isRecovery,
restoreHeight,
date.millisecondsSinceEpoch,
dirPath,
path,
address,
yatEid.isNotEmpty ? yatEid : null,
yatLastUsedAddressRaw.isNotEmpty ? yatLastUsedAddressRaw : null,
showIntroCakePayCard ?? false,
derivationInfoId ?? -1,
hardwareWalletType,
parentAddress,
hashedWalletIdentifier,
isNonSeedWallet ?? false,
sortOrder ?? 0,
currentAccountIndex,
null,
receiveInfoboxDismissed ?? false,
showCombinedBalance ?? true,
favoriteTokenAddress?.isNotEmpty == true ? favoriteTokenAddress : null,
false,
);
if (type == WalletType.bitcoin) {
wi.isMultiAccountsEnabled = false;
} else if (type == WalletType.monero || type == WalletType.wownero) {
wi.isMultiAccountsEnabled = true;
}
return wi;
}
static String get tableName => 'walletInfo';
static String get selfIdColumn => "${tableName}Id";
int internalId;
int currentAccountIndex;
String id;
String name;
WalletType type;
bool isRecovery;
int restoreHeight;
int timestamp;
String dirPath;
String path;
String address;
bool receiveInfoboxDismissed;
bool showCombinedBalance;
String? favoriteTokenAddress;
bool showSeedBackupReminder;
Future<Map<String, String>> getAddresses() async {
final list = await WalletInfoAddressMap.selectList(internalId);
return Map.fromEntries(list.map((e) => MapEntry(e.addressKey, e.addressValue)));
}
Future<void> setAddresses(Map<String, String> addresses) async {
await WalletInfoAddressMap.deleteByWalletInfoId(internalId);
final entries = addresses.entries.toList();
for (final entry in entries) {
await WalletInfoAddressMap.insert(internalId, entry.key, entry.value);
}
}
String? yatEid;
String? yatLastUsedAddressRaw;
bool? showIntroCakePayCard;
Future<Map<int, List<WalletInfoAddressInfo>>> getAddressInfos() async {
final list = await WalletInfoAddressInfo.selectList(internalId);
final ret = <int, List<WalletInfoAddressInfo>>{};
for (final e in list) {
ret[e.mapKey] ??= [];
ret[e.mapKey]!.add(e);
}
return ret;
}
Future<void> setAddressInfos(Map<int, List<WalletInfoAddressInfo>> addressInfos) async {
await WalletInfoAddressInfo.deleteByWalletInfoId(internalId);
final entries = addressInfos.entries.toList();
for (final addressInfo in entries) {
final infoList = addressInfo.value.toList();
for (final info in infoList) {
await WalletInfoAddressInfo.insert(
walletInfoId: internalId,
mapKey: addressInfo.key,
accountIndex: info.accountIndex,
address: info.address,
label: info.label,
);
}
}
}
Future<Set<String>> getUsedAddresses() async {
final list = await WalletInfoAddress.selectList(internalId, WalletInfoAddressType.used);
return list.map((e) => e.address).toSet();
}
Future<void> setUsedAddresses(List<String> addresses) async {
await WalletInfoAddress.deleteByType(internalId, WalletInfoAddressType.used);
for (final address in addresses) {
await WalletInfoAddress.insert(internalId, WalletInfoAddressType.used, address);
}
}
Future<Set<String>> getHiddenAddresses() async {
final list = await WalletInfoAddress.selectList(internalId, WalletInfoAddressType.hidden);
return list.map((e) => e.address).toSet();
}
Future<void> setHiddenAddresses(List<String> addresses) async {
await WalletInfoAddress.deleteByType(internalId, WalletInfoAddressType.hidden);
for (final address in addresses) {
await WalletInfoAddress.insert(internalId, WalletInfoAddressType.hidden, address);
}
}
Future<Set<String>> getManualAddresses() async {
final list = await WalletInfoAddress.selectList(internalId, WalletInfoAddressType.manual);
return list.map((e) => e.address).toSet();
}
Future<void> setManualAddresses(List<String> addresses) async {
await WalletInfoAddress.deleteByType(internalId, WalletInfoAddressType.manual);
for (final address in addresses) {
await WalletInfoAddress.insert(internalId, WalletInfoAddressType.manual, address);
}
}
Future<void> addAddress(String address, WalletInfoAddressType type) async {
await WalletInfoAddress.insert(internalId, type, address);
}
Future<List<WalletInfoAccount>> getAccounts() async {
var accounts = await WalletInfoAccount.selectList(internalId);
if (accounts.isEmpty) {
await WalletInfoAccount.insertOrUpdate(
walletInfoId: internalId,
accountIndex: 0,
label: "Primary account",
);
accounts = await WalletInfoAccount.selectList(internalId);
}
if (!accounts.any((a) => a.accountIndex == currentAccountIndex)) {
currentAccountIndex = accounts.first.accountIndex;
await save();
}
return accounts;
}
Future<void> setSelectedAccount(int accountIndex) async {
currentAccountIndex = accountIndex;
await save();
}
Future<void> setAccounts(List<WalletInfoAccount> accounts) async {
await WalletInfoAccount.deleteByWalletInfoId(internalId);
for (final account in accounts) {
await WalletInfoAccount.insertOrUpdate(
walletInfoId: internalId,
accountIndex: account.accountIndex,
label: account.label,
);
}
if (accounts.isNotEmpty && !accounts.any((a) => a.accountIndex == currentAccountIndex)) {
currentAccountIndex = accounts.first.accountIndex;
await save();
}
}
Future<void> addAccount({
required int accountIndex,
required String label,
}) async {
await WalletInfoAccount.insertOrUpdate(
walletInfoId: internalId,
accountIndex: accountIndex,
label: label,
);
}
Future<void> renameAccount({
required int accountIndex,
required String label,
}) async {
await WalletInfoAccount.insertOrUpdate(
walletInfoId: internalId,
accountIndex: accountIndex,
label: label,
);
}
String? addressPageType;
String? network;
int? accountDiscoveryLimit;
bool? isMultiAccountsEnabled;
int derivationInfoId;
DerivationInfo? _derivationInfo;
Future<DerivationInfo> getDerivationInfo() async {
if (_derivationInfo != null) {
return _derivationInfo!;
}
final list =
await DerivationInfo.selectList('walletInfoDerivationInfoId = ?', [derivationInfoId]);
if (list.isEmpty) {
final di = DerivationInfo(
id: 0,
derivationType: DerivationType.unknown,
);
derivationInfoId = await di.save();
_derivationInfo = di;
return di;
}
_derivationInfo = list[0];
return _derivationInfo!;
}
HardwareWalletType? hardwareWalletType;
String? parentAddress;
String? hashedWalletIdentifier;
bool isNonSeedWallet;
int sortOrder;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) {
yatLastUsedAddressRaw = address;
_yatLastUsedAddressController.add(address);
}
String get yatEmojiId => yatEid ?? '';
bool get isShowIntroCakePayCard {
return showIntroCakePayCard ?? false;
}
bool get isHardwareWallet => [
HardwareWalletType.bitbox,
HardwareWalletType.ledger,
HardwareWalletType.trezor
].contains(hardwareWalletType);
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
StreamController<String> _yatLastUsedAddressController;
Map<String, dynamic> toJson() => {
selfIdColumn: internalId,
"id": id,
"name": name,
"type": type.index,
"isRecovery": isRecovery ? 1 : 0,
"restoreHeight": restoreHeight,
"timestamp": timestamp,
"dirPath": dirPath,
"path": path,
"address": address,
"yatEid": yatEid,
"yatLastUsedAddressRaw": yatLastUsedAddressRaw,
"showIntroCakePayCard":
showIntroCakePayCard == true ? 1 : 0, // SQL regression: null -> false
"walletInfoDerivationInfoId": derivationInfoId,
"hardwareWalletType": hardwareWalletType?.index,
"parentAddress": parentAddress,
"hashedWalletIdentifier": hashedWalletIdentifier,
"isNonSeedWallet": isNonSeedWallet ? 1 : 0,
"sortOrder": sortOrder,
"currentAccountIndex": currentAccountIndex,
"addressPageType": addressPageType,
"receiveInfoboxDismissed": receiveInfoboxDismissed ? 1 : 0,
"showCombinedBalance": showCombinedBalance ? 1 : 0,
"favoriteTokenAddress": favoriteTokenAddress,
"showSeedBackupReminder": showSeedBackupReminder ? 1 : 0,
"network": network,
"accountDiscoveryLimit": accountDiscoveryLimit,
"isMultiAccountsEnabled":
isMultiAccountsEnabled == null ? null : (isMultiAccountsEnabled! ? 1 : 0),
};
factory WalletInfo.fromJson(Map<String, dynamic> json) {
final info = WalletInfo(
json[selfIdColumn] as int,
json['id'] as String,
json['name'] as String,
WalletType.values[json['type'] as int],
(json['isRecovery'] as int) == 1,
json['restoreHeight'] as int,
json['timestamp'] as int,
json['dirPath'] as String,
json['path'] as String,
json['address'] as String,
json['yatEid'] as String?,
json['yatLastUsedAddressRaw'] as String?,
(json['showIntroCakePayCard'] as int) == 1,
json['walletInfoDerivationInfoId'] as int,
json['hardwareWalletType'] == null
? null
: HardwareWalletType.values[json['hardwareWalletType'] as int],
json['parentAddress'] as String?,
json['hashedWalletIdentifier'] as String?,
(json['isNonSeedWallet'] as int) == 1,
json['sortOrder'] as int? ?? 0,
json['currentAccountIndex'] as int? ?? 0,
json['addressPageType'] as String? ?? null,
json['receiveInfoboxDismissed'] != 0,
json["showCombinedBalance"] != 0,
json["favoriteTokenAddress"] as String? ?? null,
json["showSeedBackupReminder"] == 1);
info.network = json['network'] as String?;
info.accountDiscoveryLimit = json['accountDiscoveryLimit'] as int?;
final rawIsMultiAccountsEnabled = json['isMultiAccountsEnabled'];
info.isMultiAccountsEnabled =
rawIsMultiAccountsEnabled == null ? null : (rawIsMultiAccountsEnabled as int) == 1;
return info;
}
Future<int> save() async {
final json = toJson();
if (json[selfIdColumn] == 0) {
json[selfIdColumn] = null;
}
if (_derivationInfo != null) {
derivationInfoId = await _derivationInfo!.save();
}
internalId = await db!.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace);
return internalId;
}
static Future<int> delete(WalletInfo walletInfo) async {
final deleted = await db!.delete(tableName, where: 'id = ?', whereArgs: [walletInfo.id]);
return deleted;
}
static Future<List<WalletInfo>> selectList(String where, List<dynamic> whereArgs,
{String orderBy = 'sortOrder'}) async {
final list = await db!.query(
tableName,
where: where.isNotEmpty ? where : "1 = 1",
whereArgs: whereArgs.isNotEmpty ? whereArgs : null,
orderBy: orderBy,
);
return List.generate(list.length, (index) => WalletInfo.fromJson(list[index]));
}
static Future<List<WalletInfo>> getAll() async {
return selectList('', []);
}
static Future<WalletInfo?> get(String name, WalletType type) async {
final list = await selectList('name = ? AND type = ?', [name, type.index]);
if (list.isEmpty) {
return null;
}
return list[0];
}
Future<void> updateRestoreHeight(int height) async {
restoreHeight = height;
await save();
}
Future<void> setAccountDiscoveryLimit(int limit) async {
accountDiscoveryLimit = limit;
await save();
}
Future<void> clearSeedBackupReminder() async {
final previous = showSeedBackupReminder;
showSeedBackupReminder = false;
try {
await save();
} catch (e) {
showSeedBackupReminder = previous;
printV("Failed to save the seed backup reminder flag: $e");
}
}
}