diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 13916001ba..b2f85a2078 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -310,8 +310,8 @@ jobs: rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart - name: Test [cw_monero] - timeout-minutes: 2 - run: cd cw_monero && flutter test + timeout-minutes: 15 + run: cd cw_monero && flutter test --verbose - name: Stop screen recording, encrypt and upload if: always() run: | diff --git a/Dockerfile b/Dockerfile index 6c66620c2a..d8916f10b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,6 +59,8 @@ RUN set -o xtrace \ ffmpeg network-manager x11-utils xvfb psmisc \ # extra linux dependencies so flutter doesn't complain mesa-utils \ + # database + libsqlite3-0 libsqlite3-dev \ # aarch64-linux-gnu dependencies g++-aarch64-linux-gnu gcc-aarch64-linux-gnu \ # x86_64-linux-gnu dependencies diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index a5c209e918..4427a3675c 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -46,6 +46,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { BitcoinWalletBase({ required String password, required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required Box payjoinBox, required EncryptionFileUtils encryptionFileUtils, @@ -68,6 +69,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { xpub: xpub, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, network: networkParam == null ? BitcoinNetwork.mainnet @@ -132,7 +134,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { }) async { late Uint8List seedBytes; - switch (walletInfo.derivationInfo?.derivationType) { + final derivationInfo = await walletInfo.getDerivationInfo(); + + switch (derivationInfo.derivationType) { case DerivationType.bip39: seedBytes = await bip39.mnemonicToSeed( mnemonic, @@ -151,6 +155,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { passphrase: passphrase ?? "", password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialSilentAddresses: initialSilentAddresses, @@ -211,12 +216,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { ); } - walletInfo.derivationInfo ??= DerivationInfo(); + final derivationInfo = await walletInfo.getDerivationInfo(); // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= + derivationInfo.derivationPath ??= snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= + derivationInfo.derivationType ??= snp?.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; @@ -224,7 +229,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final passphrase = keysData.passphrase; if (mnemonic != null) { - switch (walletInfo.derivationInfo!.derivationType) { + switch (derivationInfo.derivationType) { case DerivationType.electrum: seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); @@ -245,6 +250,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { password: password, passphrase: passphrase, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp?.addresses, initialSilentAddresses: snp?.silentAddresses, @@ -264,10 +270,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { BitcoinLedgerApp? _bitcoinLedgerApp; @override - void setLedgerConnection(LedgerConnection connection) { + Future setLedgerConnection(LedgerConnection connection) async { _ledgerConnection = connection; + final derivationInfo = await walletInfo.getDerivationInfo(); _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!, - derivationPath: walletInfo.derivationInfo!.derivationPath!); + derivationPath: derivationInfo.derivationPath!); } @override @@ -491,7 +498,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { : null; final index = addressEntry?.index ?? 0; final isChange = addressEntry?.isHidden == true ? 1 : 0; - final accountPath = walletInfo.derivationInfo?.derivationPath; + final derivationInfo = await walletInfo.getDerivationInfo(); + final accountPath = derivationInfo.derivationPath; final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 3d71a0c392..65190cd03e 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -16,6 +16,10 @@ class BitcoinNewWalletCredentials extends WalletCredentials { walletInfo: walletInfo, password: password, passphrase: passphrase, + derivationInfo: DerivationInfo( + derivationType: derivationType, + derivationPath: derivationPath, + ), ); final String? mnemonic; diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 8d41298ee9..1ef9f1c89d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -22,10 +22,9 @@ class BitcoinWalletService extends WalletService< BitcoinRestoreWalletFromSeedCredentials, BitcoinWalletFromKeysCredentials, BitcoinRestoreWalletFromHardware> { - BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, + BitcoinWalletService(this.unspentCoinsInfoSource, this.payjoinSessionSource, this.isDirect); - final Box walletInfoSource; final Box unspentCoinsInfoSource; final Box payjoinSessionSource; final bool isDirect; @@ -39,7 +38,13 @@ class BitcoinWalletService extends WalletService< credentials.walletInfo?.network = network.value; final String mnemonic; - switch ( credentials.walletInfo?.derivationInfo?.derivationType) { + final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); + derivationInfo.derivationType = credentials.derivationInfo?.derivationType ?? derivationInfo.derivationType; + derivationInfo.derivationPath = credentials.derivationInfo?.derivationPath ?? derivationInfo.derivationPath; + derivationInfo.description = credentials.derivationInfo?.description ?? derivationInfo.description; + derivationInfo.scriptType = credentials.derivationInfo?.scriptType ?? derivationInfo.scriptType; + await derivationInfo.save(); + switch (derivationInfo.derivationType) { case DerivationType.bip39: final strength = credentials.seedPhraseLength == 24 ? 256 : 128; @@ -50,6 +55,7 @@ class BitcoinWalletService extends WalletService< mnemonic = await generateElectrumMnemonic(); break; } + await derivationInfo.save(); final wallet = await BitcoinWalletBase.create( mnemonic: mnemonic, @@ -74,8 +80,10 @@ class BitcoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await BitcoinWalletBase.open( password: password, @@ -106,9 +114,11 @@ class BitcoinWalletService extends WalletService< @override Future remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); @@ -122,8 +132,10 @@ class BitcoinWalletService extends WalletService< @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await BitcoinWalletBase.open( password: password, name: currentName, @@ -140,7 +152,7 @@ class BitcoinWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -148,12 +160,16 @@ class BitcoinWalletService extends WalletService< {bool? isTestnet}) async { final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; credentials.walletInfo?.network = network.value; - credentials.walletInfo?.derivationInfo?.derivationPath = + final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); + derivationInfo.derivationPath = credentials.hwAccountData.derivationPath; + await derivationInfo.save(); + credentials.walletInfo!.save(); final wallet = await BitcoinWallet( password: credentials.password!, xpub: credentials.hwAccountData.xpub, walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfoSource, networkParam: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -174,6 +190,7 @@ class BitcoinWalletService extends WalletService< password: credentials.password!, xpub: credentials.xpub, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, networkParam: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 75f59bb890..c5a21b3ed8 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -63,6 +63,7 @@ abstract class ElectrumWalletBase ElectrumWalletBase({ required String password, required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required this.network, required this.encryptionFileUtils, @@ -76,7 +77,7 @@ abstract class ElectrumWalletBase CryptoCurrency? currency, bool? alwaysScan, }) : accountHD = - getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), + getAccountHDWallet(currency, network, seedBytes, xpub, derivationInfo), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], @@ -99,9 +100,10 @@ abstract class ElectrumWalletBase this.unspentCoinsInfo = unspentCoinsInfo, this.isTestnet = !network.isMainnet, this._mnemonic = mnemonic, - super(walletInfo) { + super(walletInfo, derivationInfo) { this.electrumClient = electrumClient ?? electrum.ElectrumClient(); this.walletInfo = walletInfo; + this.derivationInfo = derivationInfo; transactionHistory = ElectrumTransactionHistory( walletInfo: walletInfo, password: password, @@ -759,8 +761,9 @@ abstract class ElectrumWalletBase pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex(); } + final derivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? electrum_path)}" + "${_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)}" "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" "/${utx.bitcoinAddressRecord.index}"; publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); @@ -969,7 +972,7 @@ abstract class ElectrumWalletBase // Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets final changeDerivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "${_hardenedDerivationPath(derivationInfo.derivationPath ?? "m/0'")}" "/${changeAddress.isHidden ? "1" : "0"}" "/${changeAddress.index}"; utxoDetails.publicKeys[address.pubKeyHash()] = @@ -1408,7 +1411,7 @@ abstract class ElectrumWalletBase } } - void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError(); + Future setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError(); Future buildHardwareWalletTransaction({ required List outputs, @@ -1435,8 +1438,8 @@ abstract class ElectrumWalletBase ? SegwitAddresType.p2wpkh.toString() : walletInfo.addressPageType.toString(), 'balance': balance[currency]?.toJSON(), - 'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index, - 'derivationPath': walletInfo.derivationInfo?.derivationPath, + 'derivationTypeIndex': derivationInfo.derivationType?.index, + 'derivationPath': derivationInfo.derivationPath, 'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(), 'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(), 'mweb_addresses': walletAddresses.mwebAddresses.map((addr) => addr.toJSON()).toList(), diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 7b32547b84..4bfa26ac98 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -60,6 +60,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { LitecoinWalletBase({ required String password, required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required EncryptionFileUtils encryptionFileUtils, Uint8List? seedBytes, @@ -80,6 +81,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { passphrase: passphrase, xpub: xpub, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, network: LitecoinNetwork.mainnet, initialAddresses: initialAddresses, @@ -166,6 +168,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { {required String mnemonic, required String password, required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required EncryptionFileUtils encryptionFileUtils, String? passphrase, @@ -177,7 +180,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { Map? initialChangeAddressIndex}) async { late Uint8List seedBytes; - switch (walletInfo.derivationInfo?.derivationType) { + switch (derivationInfo.derivationType) { case DerivationType.bip39: seedBytes = await bip39.mnemonicToSeed( mnemonic, @@ -193,6 +196,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialMwebAddresses: initialMwebAddresses, @@ -243,18 +247,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { ); } - walletInfo.derivationInfo ??= DerivationInfo(); - + final derivationInfo = await walletInfo.getDerivationInfo(); // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; + derivationInfo.derivationPath ??= snp?.derivationPath ?? electrum_path; + derivationInfo.derivationType ??= snp?.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; final mnemonic = keysData.mnemonic; final passphrase = keysData.passphrase; if (mnemonic != null) { - switch (walletInfo.derivationInfo?.derivationType) { + switch (derivationInfo.derivationType) { case DerivationType.bip39: seedBytes = await bip39.mnemonicToSeed( mnemonic, @@ -267,12 +270,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { break; } } + await derivationInfo.save(); return LitecoinWallet( mnemonic: keysData.mnemonic, xpub: keysData.xPub, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp?.addresses, initialMwebAddresses: snp?.mwebAddresses, @@ -1372,10 +1377,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { LitecoinLedgerApp? _litecoinLedgerApp; @override - void setLedgerConnection(LedgerConnection connection) { + Future setLedgerConnection(LedgerConnection connection) async { _ledgerConnection = connection; _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!, - derivationPath: walletInfo.derivationInfo!.derivationPath!); + derivationPath: derivationInfo.derivationPath!); } @override diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 84056225ee..126b6a9a3f 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -22,10 +22,8 @@ class LitecoinWalletService extends WalletService< BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials, BitcoinRestoreWalletFromHardware> { - LitecoinWalletService( - this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect); + LitecoinWalletService(this.unspentCoinsInfoSource, this.isDirect); - final Box walletInfoSource; final Box unspentCoinsInfoSource; final bool isDirect; @@ -35,7 +33,7 @@ class LitecoinWalletService extends WalletService< @override Future create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async { final String mnemonic; - switch (credentials.walletInfo?.derivationInfo?.derivationType) { + switch (credentials.derivationInfo?.derivationType) { case DerivationType.bip39: final strength = credentials.seedPhraseLength == 24 ? 256 : 128; @@ -52,6 +50,7 @@ class LitecoinWalletService extends WalletService< password: credentials.password!, passphrase: credentials.passphrase, walletInfo: credentials.walletInfo!, + derivationInfo: credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -68,8 +67,10 @@ class LitecoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await LitecoinWalletBase.open( @@ -99,12 +100,14 @@ class LitecoinWalletService extends WalletService< @override Future remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); // if there are no more litecoin wallets left, cleanup the neutrino db and other files created by mwebd: - if (walletInfoSource.values.where((info) => info.type == WalletType.litecoin).isEmpty) { + if ((await WalletInfo.selectList('type = ?', [WalletType.litecoin.index])).isEmpty) { final appDirPath = (await getApplicationSupportDirectory()).path; File neturinoDb = File('$appDirPath/neutrino.db'); File blockHeaders = File('$appDirPath/block_headers.bin'); @@ -136,8 +139,10 @@ class LitecoinWalletService extends WalletService< @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await LitecoinWalletBase.open( password: password, name: currentName, @@ -153,7 +158,7 @@ class LitecoinWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -161,13 +166,17 @@ class LitecoinWalletService extends WalletService< {bool? isTestnet}) async { final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; credentials.walletInfo?.network = network.value; - credentials.walletInfo?.derivationInfo?.derivationPath = + final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); + derivationInfo.derivationPath = credentials.hwAccountData.derivationPath; + await derivationInfo.save(); + credentials.walletInfo!.save(); final wallet = await LitecoinWallet( password: credentials.password!, xpub: credentials.hwAccountData.xpub, walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -193,6 +202,7 @@ class LitecoinWalletService extends WalletService< passphrase: credentials.passphrase, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + derivationInfo: credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 53db8c9288..2713e788ec 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -1021,6 +1021,62 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_common_ffi: + dependency: transitive + description: + name: sqflite_common_ffi + sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + url: "https://pub.dev" + source: hosted + version: "2.3.4+4" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + url: "https://pub.dev" + source: hosted + version: "2.9.0" stack_trace: dependency: transitive description: @@ -1053,6 +1109,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 527774049a..39065722bb 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -28,6 +28,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { required String mnemonic, required String password, required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required Uint8List seedBytes, required EncryptionFileUtils encryptionFileUtils, @@ -41,6 +42,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, network: BitcoinCashNetwork.mainnet, initialAddresses: initialAddresses, @@ -81,6 +83,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialBalance: initialBalance, @@ -134,6 +137,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { mnemonic: keysData.mnemonic!, password: password, walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp?.addresses.map((addr) { try { diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart index 931893ef8d..1861b21f4b 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart @@ -18,9 +18,8 @@ class BitcoinCashWalletService extends WalletService< BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, BitcoinCashNewWalletCredentials> { - BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect); + BitcoinCashWalletService(this.unspentCoinsInfoSource, this.isDirect); - final Box walletInfoSource; final Box unspentCoinsInfoSource; final bool isDirect; @@ -51,8 +50,10 @@ class BitcoinCashWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await BitcoinCashWalletBase.open( @@ -82,9 +83,11 @@ class BitcoinCashWalletService extends WalletService< @override Future remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); @@ -98,8 +101,10 @@ class BitcoinCashWalletService extends WalletService< @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await BitcoinCashWalletBase.open( password: password, name: currentName, @@ -114,7 +119,7 @@ class BitcoinCashWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart new file mode 100644 index 0000000000..e8bba66899 --- /dev/null +++ b/cw_core/lib/db/sqlite.dart @@ -0,0 +1,114 @@ + +import 'package:sqflite/sqflite.dart'; + +late Database db; + +Future initDb({String? pathOverride}) async { + db = await openDatabase( + pathOverride ?? "cake.db", + version: 1, + onCreate: (Database db, int version) async { + await db.execute( + ''' +CREATE TABLE WalletInfo ( + walletInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + id TEXT NOT NULL, + name TEXT NOT NULL, + "type" INTEGER NOT NULL, + isRecovery INTEGER DEFAULT (0) NOT NULL, + walletInfoDerivationInfoId INTEGER NOT NULL, + restoreHeight INTEGER DEFAULT (0) NOT NULL, + "timestamp" INTEGER DEFAULT (0) NOT NULL, + dirPath TEXT NOT NULL, + "path" TEXT NOT NULL, + address TEXT NOT NULL, + yatEid TEXT, + yatLastUsedAddressRaw TEXT, + showIntroCakePayCard INTEGER DEFAULT (1), + addressPageType TEXT, + network TEXT, + hardwareWalletType INTEGER, + parentAddress TEXT, + hashedWalletIdentifier TEXT, + isNonSeedWallet INTEGER DEFAULT (0) NOT NULL, + sortOrder INTEGER DEFAULT (0) NOT NULL +); +'''); + + await db.execute( + ''' +CREATE TABLE WalletInfoDerivationInfo ( + walletInfoDerivationInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + address TEXT NOT NULL, + balance TEXT NOT NULL, + transactionsCount INTEGER DEFAULT (0) NOT NULL, + derivationType INTEGER NOT NULL, + derivationPath TEXT, + scriptType TEXT, + description TEXT +); +'''); + + await db.execute( + ''' +CREATE TABLE WalletInfoAddress ( + walletInfoAddressId INTEGER PRIMARY KEY AUTOINCREMENT, + walletInfoId INTEGER, + "type" INTEGER NOT NULL, + address TEXT NOT NULL, + CONSTRAINT WalletInfoAddress_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) +); +'''); + + await db.execute( + ''' +CREATE TABLE WalletInfoAddressInfo ( + walletInfoAddressInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + walletInfoId INTEGER NOT NULL, + mapKey INTEGER NOT NULL, + mapValueAccountIndex INTEGER NOT NULL, + mapValueAddress TEXT NOT NULL, + mapValueLabel TEXT NOT NULL, + CONSTRAINT WalletInfoAddressInfo_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) +); +'''); + + await db.execute( + ''' +CREATE TABLE "WalletInfoAddressMap" ( + walletInfoAddressMapId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + walletInfoId INTEGER NOT NULL, + addressKey TEXT NOT NULL, + addressValue TEXT NOT NULL, + CONSTRAINT WalletInfoAddress_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) +); + ''' + ); + } + ); +} + +Future> dumpDb() async { + try { + return await _dumpDb(); + } catch (e) { + return { + "error": e.toString(), + "stackTrace": StackTrace.current.toString(), + }; + } +} + +Future> _getTableNames() async { + final tableNames = await db.rawQuery('SELECT name FROM sqlite_master WHERE type = "table"'); + return tableNames.map((e) => (e["name"]).toString()).toList(); +} + +Future> _dumpDb() async { + final tableNames = await _getTableNames(); + final ret = {}; + for (final tableName in tableNames) { + ret[tableName] = await db.query(tableName); + } + return ret; +} \ No newline at end of file diff --git a/cw_core/lib/wallet_addresses.dart b/cw_core/lib/wallet_addresses.dart index b0da0e7a12..b420958283 100644 --- a/cw_core/lib/wallet_addresses.dart +++ b/cw_core/lib/wallet_addresses.dart @@ -1,4 +1,3 @@ -import 'package:cw_core/address_info.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -9,8 +8,8 @@ abstract class WalletAddresses { allAddressesMap = {}, addressInfos = {}, usedAddresses = {}, - hiddenAddresses = walletInfo.hiddenAddresses?.toSet() ?? {}, - manualAddresses = walletInfo.manualAddresses?.toSet() ?? {}; + hiddenAddresses = {}, + manualAddresses = {}; final WalletInfo walletInfo; @@ -47,7 +46,7 @@ abstract class WalletAddresses { return tmp; } - Map> addressInfos; + Map> addressInfos; Set usedAddresses; @@ -62,15 +61,13 @@ abstract class WalletAddresses { Future saveAddressesInBox() async { try { walletInfo.address = address; - walletInfo.addresses = addressesMap; - walletInfo.addressInfos = addressInfos; - walletInfo.usedAddresses = usedAddresses.toList(); - walletInfo.hiddenAddresses = hiddenAddresses.toList(); - walletInfo.manualAddresses = manualAddresses.toList(); - - if (walletInfo.isInBox) { - await walletInfo.save(); - } + walletInfo.setAddresses(addressesMap); + walletInfo.setAddressInfos(addressInfos); + walletInfo.setUsedAddresses(usedAddresses.toList()); + walletInfo.setHiddenAddresses(hiddenAddresses.toList()); + walletInfo.setManualAddresses(manualAddresses.toList()); + + await walletInfo.save(); } catch (e) { printV(e.toString()); } diff --git a/cw_core/lib/wallet_addresses_with_account.dart b/cw_core/lib/wallet_addresses_with_account.dart deleted file mode 100644 index 0dcf88de05..0000000000 --- a/cw_core/lib/wallet_addresses_with_account.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:cw_core/wallet_addresses.dart'; -import 'package:cw_core/account_list.dart'; -import 'package:cw_core/wallet_info.dart'; - -abstract class WalletAddressesWithAccount extends WalletAddresses { - WalletAddressesWithAccount(WalletInfo walletInfo) : super(walletInfo); - - // T get account; - - // set account(T account); - - AccountList get accountList; -} \ No newline at end of file diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 7d0fc4f042..97785cc052 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -15,12 +15,13 @@ import 'package:cw_core/wallet_type.dart'; abstract class WalletBase { - WalletBase(this.walletInfo); + WalletBase(this.walletInfo, this.derivationInfo); static String idFor(String name, WalletType type) => walletTypeToString(type).toLowerCase() + '_' + name; WalletInfo walletInfo; + DerivationInfo derivationInfo; WalletType get type => walletInfo.type; diff --git a/cw_core/lib/wallet_credentials.dart b/cw_core/lib/wallet_credentials.dart index 30ae2546c6..b9be31d280 100644 --- a/cw_core/lib/wallet_credentials.dart +++ b/cw_core/lib/wallet_credentials.dart @@ -10,11 +10,7 @@ abstract class WalletCredentials { this.passphrase, this.derivationInfo, this.hardwareWalletType, - }) { - if (this.walletInfo != null && derivationInfo != null) { - this.walletInfo!.derivationInfo = derivationInfo; - } - } + }); final String name; final int? height; diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index d62e61941b..60551d9a86 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -1,35 +1,233 @@ import 'dart:async'; -import 'package:cw_core/address_info.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_type.dart'; -import 'package:hive/hive.dart'; - -part 'wallet_info.g.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/wallet_info_legacy.dart' as wiLegacy; + +Future 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.boxName); + await wiLegacy.WalletInfo.migrateAllToSqlite(walletInfoBox); + } catch (e) { + printV('Error performing Hive migration: $e, continuing anyway'); + } +} -@HiveType(typeId: DERIVATION_TYPE_TYPE_ID) enum DerivationType { - @HiveField(0) unknown, - @HiveField(1) def, // default is a reserved word - @HiveField(2) nano, - @HiveField(3) bip39, - @HiveField(4) electrum, } -@HiveType(typeId: HARDWARE_WALLET_TYPE_TYPE_ID) enum HardwareWalletType { - @HiveField(0) ledger, } -@HiveType(typeId: DerivationInfo.typeId) -class DerivationInfo extends HiveObject { +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> 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 deleteByWalletInfoId(int walletInfoId) async { + return await db.delete(tableName, where: 'walletInfoId = ?', whereArgs: [walletInfoId]); + } + static Future 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 toJson() { + return { + selfIdColumn: id, + "walletInfoId": walletInfoId, + "mapKey": mapKey, + "mapValueAccountIndex": accountIndex, + "mapValueAddress": address, + "mapValueLabel": label, + }; + } + + factory WalletInfoAddressInfo.fromJson(Map 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> 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 deleteByWalletInfoId(int walletInfoId) async { + return await db.delete(tableName, where: 'walletInfoId = ?', whereArgs: [walletInfoId]); + } + static Future insert(int walletInfoId, String addressKey, String addressValue) async { + return await db.insert(tableName, { + "walletInfoId": walletInfoId, + "addressKey": addressKey, + "addressValue": addressValue, + }); + } + + Map toJson() { + return { + selfIdColumn: id, + "walletInfoId": walletInfoId, + "addressKey": addressKey, + "addressValue": addressValue, + }; + } + + factory WalletInfoAddressMap.fromJson(Map 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> 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 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 deleteByType(int walletInfoId, WalletInfoAddressType type) async { + return await db.delete(tableName, where: 'walletInfoId = ? AND type = ?', whereArgs: [walletInfoId, type.index]); + } + + static Future 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 toJson() { + return { + selfIdColumn: id, + "walletInfoId": walletInfoId, + "type": type.index, + "address": address, + }; + } + + factory WalletInfoAddress.fromJson(Map 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 DerivationInfo { DerivationInfo({ + this.id = 0, this.derivationType, this.derivationPath, this.balance = "", @@ -39,33 +237,77 @@ class DerivationInfo extends HiveObject { this.description, }); - static const typeId = DERIVATION_INFO_TYPE_ID; + int id; - @HiveField(0, defaultValue: '') - String address; + static String get tableName => 'walletInfoDerivationInfo'; + static String get selfIdColumn => "${tableName}Id"; - @HiveField(1, defaultValue: '') + String address; String balance; - - @HiveField(2, defaultValue: 0) int transactionsCount; - - @HiveField(3) DerivationType? derivationType; - - @HiveField(4) String? derivationPath; + String? scriptType; + String? description; + + static Future> selectList(String where, List 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])); + } - @HiveField(5) - final String? scriptType; + Map toJson() { + return { + selfIdColumn: id, + "address": address, + "balance": balance, + "transactionsCount": transactionsCount, + "derivationType": derivationType?.index, + "derivationPath": derivationPath, + "scriptType": scriptType, + "description": description, + }; + } - @HiveField(6) - final String? description; + factory DerivationInfo.fromJson(Map 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 save() async { + final json = toJson(); + if (json[selfIdColumn] == 0) { + json[selfIdColumn] = null; + } + id = await db.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace); + return id; + } } -@HiveType(typeId: WalletInfo.typeId) -class WalletInfo extends HiveObject { +class WalletInfo { WalletInfo( + this.internalId, this.id, this.name, this.type, @@ -78,11 +320,12 @@ class WalletInfo extends HiveObject { this.yatEid, this.yatLastUsedAddressRaw, this.showIntroCakePayCard, - this.derivationInfo, + this.derivationInfoId, this.hardwareWalletType, this.parentAddress, this.hashedWalletIdentifier, this.isNonSeedWallet, + this.sortOrder, ) : _yatLastUsedAddressController = StreamController.broadcast(); factory WalletInfo.external({ @@ -98,13 +341,15 @@ class WalletInfo extends HiveObject { bool? showIntroCakePayCard, String yatEid = '', String yatLastUsedAddressRaw = '', - DerivationInfo? derivationInfo, + int? derivationInfoId, HardwareWalletType? hardwareWalletType, String? parentAddress, String? hashedWalletIdentifier, bool? isNonSeedWallet, + int? sortOrder, }) { return WalletInfo( + 0, id, name, type, @@ -117,96 +362,135 @@ class WalletInfo extends HiveObject { yatEid, yatLastUsedAddressRaw, showIntroCakePayCard, - derivationInfo, + derivationInfoId ?? -1, hardwareWalletType, parentAddress, hashedWalletIdentifier, isNonSeedWallet ?? false, + sortOrder ?? 0, ); } - static const typeId = WALLET_INFO_TYPE_ID; - static const boxName = 'WalletInfo'; + static String get tableName => 'walletInfo'; + static String get selfIdColumn => "${tableName}Id"; - @HiveField(0, defaultValue: '') - String id; + int internalId; - @HiveField(1, defaultValue: '') + String id; String name; - - @HiveField(2) WalletType type; - - @HiveField(3, defaultValue: false) bool isRecovery; - - @HiveField(4, defaultValue: 0) int restoreHeight; - - @HiveField(5, defaultValue: 0) int timestamp; - - @HiveField(6, defaultValue: '') String dirPath; - - @HiveField(7, defaultValue: '') String path; - - @HiveField(8, defaultValue: '') String address; + Future> getAddresses() async { + final list = await WalletInfoAddressMap.selectList(internalId); + return Map.fromEntries(list.map((e) => MapEntry(e.addressKey, e.addressValue))); + } - @HiveField(10) - Map? addresses; + Future setAddresses(Map addresses) async { + await WalletInfoAddressMap.deleteByWalletInfoId(internalId); + final keys = addresses.keys.toList(); + for (final address in keys) { + await WalletInfoAddressMap.insert(internalId, address, addresses[address]!); + } + } - @HiveField(11) String? yatEid; - - @HiveField(12) String? yatLastUsedAddressRaw; - - @HiveField(13) bool? showIntroCakePayCard; + Future>> getAddressInfos() async { + final list = await WalletInfoAddressInfo.selectList(internalId); + final ret = >{}; + for (final e in list) { + ret[e.mapKey] ??= []; + ret[e.mapKey]!.add(e); + } + return ret; + } - @HiveField(14) - Map>? addressInfos; + Future setAddressInfos(Map> addressInfos) async { + await WalletInfoAddressInfo.deleteByWalletInfoId(internalId); + final entries = addressInfos.entries.toList(); + for (final addressInfo in entries) { + for (final info in addressInfo.value) { + await WalletInfoAddressInfo.insert( + walletInfoId: internalId, + mapKey: addressInfo.key, + accountIndex: info.accountIndex, + address: info.address, + label: info.label, + ); + } + } + } - @HiveField(15) - List? usedAddresses; + Future> getUsedAddresses() async { + final list = await WalletInfoAddress.selectList(internalId, WalletInfoAddressType.used); + return list.map((e) => e.address).toSet(); + } + Future setUsedAddresses(List addresses) async { + await WalletInfoAddress.deleteByType(internalId, WalletInfoAddressType.used); + for (final address in addresses) { + await WalletInfoAddress.insert(internalId, WalletInfoAddressType.used, address); + } + } - @deprecated - @HiveField(16) - DerivationType? derivationType; // no longer used + Future> getHiddenAddresses() async { + final list = await WalletInfoAddress.selectList(internalId, WalletInfoAddressType.hidden); + return list.map((e) => e.address).toSet(); + } + Future setHiddenAddresses(List addresses) async { + await WalletInfoAddress.deleteByType(internalId, WalletInfoAddressType.hidden); + for (final address in addresses) { + await WalletInfoAddress.insert(internalId, WalletInfoAddressType.hidden, address); + } + } - @deprecated - @HiveField(17) - String? derivationPath; // no longer used + Future> getManualAddresses() async { + final list = await WalletInfoAddress.selectList(internalId, WalletInfoAddressType.manual); + return list.map((e) => e.address).toSet(); + } + Future setManualAddresses(List addresses) async { + await WalletInfoAddress.deleteByType(internalId, WalletInfoAddressType.manual); + for (final address in addresses) { + await WalletInfoAddress.insert(internalId, WalletInfoAddressType.manual, address); + } + } - @HiveField(18) - String? addressPageType; + Future addAddress(String address, WalletInfoAddressType type) async { + await WalletInfoAddress.insert(internalId, type, address); + } - @HiveField(19) + String? addressPageType; String? network; - - @HiveField(20) - DerivationInfo? derivationInfo; - - @HiveField(21) + int derivationInfoId; + DerivationInfo? _derivationInfo; + Future 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; - - @HiveField(22) String? parentAddress; - - @HiveField(23) - List? hiddenAddresses; - - @HiveField(24) - List? manualAddresses; - - @HiveField(25) String? hashedWalletIdentifier; - - @HiveField(26, defaultValue: false) bool isNonSeedWallet; + + int sortOrder; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; @@ -232,6 +516,90 @@ class WalletInfo extends HiveObject { StreamController _yatLastUsedAddressController; + Map 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, + }; + + factory WalletInfo.fromJson(Map json) { + return 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, + ); + } + + Future 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 delete(WalletInfo walletInfo) async { + return await db.delete(tableName, where: 'id = ?', whereArgs: [walletInfo.id]); + } + + static Future> selectList(String where, List 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> getAll() async { + return selectList('', []); + } + + static Future 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 updateRestoreHeight(int height) async { restoreHeight = height; await save(); diff --git a/cw_core/lib/wallet_info_legacy.dart b/cw_core/lib/wallet_info_legacy.dart new file mode 100644 index 0000000000..502edeb6b2 --- /dev/null +++ b/cw_core/lib/wallet_info_legacy.dart @@ -0,0 +1,308 @@ +// NOTE: This code was generated by Hive, but since Hive is on the way out, this +// code no longer uses hive generators, so when we get rid of hive we also will +// be able to get rid of + +import 'dart:async'; + +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_info.dart' as newWi; +import 'package:cw_core/address_info.dart'; +import 'package:cw_core/hive_type_ids.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:hive/hive.dart'; + +part 'wallet_info_legacy.part.dart'; + +// // @HiveType(typeId: DERIVATION_TYPE_TYPE_ID) +// enum DerivationType { +// // @HiveField(0) +// unknown, +// // @HiveField(1) +// def, // default is a reserved word +// // @HiveField(2) +// nano, +// // @HiveField(3) +// bip39, +// // @HiveField(4) +// electrum, +// } + +// // @HiveType(typeId: HARDWARE_WALLET_TYPE_TYPE_ID) +// enum HardwareWalletType { +// // @HiveField(0) +// ledger, +// } + +// @HiveType(typeId: DerivationInfo.typeId) +class DerivationInfo extends HiveObject { + DerivationInfo({ + this.derivationType, + this.derivationPath, + this.balance = "", + this.address = "", + this.transactionsCount = 0, + this.scriptType, + this.description, + }); + + static const typeId = DERIVATION_INFO_TYPE_ID; + + // @HiveField(0, defaultValue: '') + String address; + + // @HiveField(1, defaultValue: '') + String balance; + + // @HiveField(2, defaultValue: 0) + int transactionsCount; + + // @HiveField(3) + newWi.DerivationType? derivationType; + + // @HiveField(4) + String? derivationPath; + + // @HiveField(5) + final String? scriptType; + + // @HiveField(6) + final String? description; +} + +// @HiveType(typeId: WalletInfo.typeId) +class WalletInfo extends HiveObject { + WalletInfo( + 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.derivationInfo, + this.hardwareWalletType, + this.parentAddress, + this.hashedWalletIdentifier, + this.isNonSeedWallet, + ) : _yatLastUsedAddressController = StreamController.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 = '', + DerivationInfo? derivationInfo, + newWi.HardwareWalletType? hardwareWalletType, + String? parentAddress, + String? hashedWalletIdentifier, + bool? isNonSeedWallet, + }) { + return WalletInfo( + id, + name, + type, + isRecovery, + restoreHeight, + date.millisecondsSinceEpoch, + dirPath, + path, + address, + yatEid, + yatLastUsedAddressRaw, + showIntroCakePayCard, + derivationInfo, + hardwareWalletType, + parentAddress, + hashedWalletIdentifier, + isNonSeedWallet ?? false, + ); + } + + static const typeId = WALLET_INFO_TYPE_ID; + static const boxName = 'WalletInfo'; + + // @HiveField(0, defaultValue: '') + String id; + + // @HiveField(1, defaultValue: '') + String name; + + // @HiveField(2) + WalletType type; + + // @HiveField(3, defaultValue: false) + bool isRecovery; + + // @HiveField(4, defaultValue: 0) + int restoreHeight; + + // @HiveField(5, defaultValue: 0) + int timestamp; + + // @HiveField(6, defaultValue: '') + String dirPath; + + // @HiveField(7, defaultValue: '') + String path; + + // @HiveField(8, defaultValue: '') + String address; + + // @HiveField(10) + Map? addresses; + + // @HiveField(11) + String? yatEid; + + // @HiveField(12) + String? yatLastUsedAddressRaw; + + // @HiveField(13) + bool? showIntroCakePayCard; + + // @HiveField(14) + Map>? addressInfos; + + // @HiveField(15) + List? usedAddresses; + + @deprecated + // @HiveField(16) + newWi.DerivationType? derivationType; // no longer used + + @deprecated + // @HiveField(17) + String? derivationPath; // no longer used + + // @HiveField(18) + String? addressPageType; + + // @HiveField(19) + String? network; + + // @HiveField(20) + DerivationInfo? derivationInfo; + + // @HiveField(21) + newWi.HardwareWalletType? hardwareWalletType; + + // @HiveField(22) + String? parentAddress; + + // @HiveField(23) + List? hiddenAddresses; + + // @HiveField(24) + List? manualAddresses; + + // @HiveField(25) + String? hashedWalletIdentifier; + + // @HiveField(26, defaultValue: false) + bool isNonSeedWallet; + + String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; + + set yatLastUsedAddress(String address) { + yatLastUsedAddressRaw = address; + _yatLastUsedAddressController.add(address); + } + + String get yatEmojiId => yatEid ?? ''; + + bool get isShowIntroCakePayCard { + if (showIntroCakePayCard == null) { + return type != WalletType.haven; + } + return showIntroCakePayCard!; + } + + bool get isHardwareWallet => hardwareWalletType != null; + + DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp); + + Stream get yatLastUsedAddressStream => _yatLastUsedAddressController.stream; + + StreamController _yatLastUsedAddressController; + + Future updateRestoreHeight(int height) async { + restoreHeight = height; + await save(); + } + + Future migrateToSqlite() async { + final di = newWi.DerivationInfo( + id: 0, + derivationType: derivationInfo?.derivationType ?? derivationType ?? newWi.DerivationType.unknown, + derivationPath: derivationInfo?.derivationPath ?? derivationPath ?? '', + ); + final derivationInfoId = await di.save(); + final walletInfo = newWi.WalletInfo( + 0, + id, + name, + type, + isRecovery, + restoreHeight, + timestamp, + dirPath, + path, + address, + yatEid, + yatLastUsedAddressRaw, + showIntroCakePayCard, + derivationInfoId, + hardwareWalletType, + parentAddress, + hashedWalletIdentifier, + isNonSeedWallet, + 0, + ); + final wiId = await walletInfo.save(); + for (final address in usedAddresses ?? []) { + await newWi.WalletInfoAddress.insert(wiId, newWi.WalletInfoAddressType.used, address); + } + for (final address in hiddenAddresses ?? []) { + await newWi.WalletInfoAddress.insert(wiId, newWi.WalletInfoAddressType.hidden, address); + } + for (final address in manualAddresses ?? []) { + await newWi.WalletInfoAddress.insert(wiId, newWi.WalletInfoAddressType.manual, address); + } + for (int i = 0; i < (addressInfos?.length ?? 0); i++) { + for (final address in addressInfos![i] ?? []) { + await newWi.WalletInfoAddressInfo.insert( + walletInfoId: wiId, + mapKey: i, + accountIndex: address.accountIndex??0, + address: address.address, + label: address.label, + ); + } + } + await walletInfo.setAddresses(addresses ?? {}); + } + + static Future migrateAllToSqlite(final Box box) async { + printV('Migrating WalletInfo to SQLite: start'); + final sw = Stopwatch()..start(); + final list = box.values.toList(); + for (final wi in list) { + await wi.migrateToSqlite(); + await wi.delete(); + } + printV('Migrating WalletInfo to SQLite: end (${sw.elapsedMilliseconds}ms)'); + } +} diff --git a/cw_core/lib/wallet_info_legacy.part.dart b/cw_core/lib/wallet_info_legacy.part.dart new file mode 100644 index 0000000000..c91336d87c --- /dev/null +++ b/cw_core/lib/wallet_info_legacy.part.dart @@ -0,0 +1,257 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'wallet_info_legacy.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class DerivationInfoAdapter extends TypeAdapter { + @override + final int typeId = 17; + + @override + DerivationInfo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return DerivationInfo( + derivationType: fields[3] as newWi.DerivationType?, + derivationPath: fields[4] as String?, + balance: fields[1] == null ? '' : fields[1] as String, + address: fields[0] == null ? '' : fields[0] as String, + transactionsCount: fields[2] == null ? 0 : fields[2] as int, + scriptType: fields[5] as String?, + description: fields[6] as String?, + ); + } + + @override + void write(BinaryWriter writer, DerivationInfo obj) { + writer + ..writeByte(7) + ..writeByte(0) + ..write(obj.address) + ..writeByte(1) + ..write(obj.balance) + ..writeByte(2) + ..write(obj.transactionsCount) + ..writeByte(3) + ..write(obj.derivationType) + ..writeByte(4) + ..write(obj.derivationPath) + ..writeByte(5) + ..write(obj.scriptType) + ..writeByte(6) + ..write(obj.description); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DerivationInfoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +class WalletInfoAdapter extends TypeAdapter { + @override + final int typeId = 4; + + @override + WalletInfo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return WalletInfo( + fields[0] == null ? '' : fields[0] as String, + fields[1] == null ? '' : fields[1] as String, + fields[2] as WalletType, + fields[3] == null ? false : fields[3] as bool, + fields[4] == null ? 0 : fields[4] as int, + fields[5] == null ? 0 : fields[5] as int, + fields[6] == null ? '' : fields[6] as String, + fields[7] == null ? '' : fields[7] as String, + fields[8] == null ? '' : fields[8] as String, + fields[11] as String?, + fields[12] as String?, + fields[13] as bool?, + fields[20] as DerivationInfo?, + fields[21] as newWi.HardwareWalletType?, + fields[22] as String?, + fields[25] as String?, + fields[26] == null ? false : fields[26] as bool, + ) + ..addresses = (fields[10] as Map?)?.cast() + ..addressInfos = (fields[14] as Map?)?.map((dynamic k, dynamic v) => + MapEntry(k as int, (v as List).cast())) + ..usedAddresses = (fields[15] as List?)?.cast() + ..derivationType = fields[16] as newWi.DerivationType? + ..derivationPath = fields[17] as String? + ..addressPageType = fields[18] as String? + ..network = fields[19] as String? + ..hiddenAddresses = (fields[23] as List?)?.cast() + ..manualAddresses = (fields[24] as List?)?.cast(); + } + + @override + void write(BinaryWriter writer, WalletInfo obj) { + writer + ..writeByte(26) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.name) + ..writeByte(2) + ..write(obj.type) + ..writeByte(3) + ..write(obj.isRecovery) + ..writeByte(4) + ..write(obj.restoreHeight) + ..writeByte(5) + ..write(obj.timestamp) + ..writeByte(6) + ..write(obj.dirPath) + ..writeByte(7) + ..write(obj.path) + ..writeByte(8) + ..write(obj.address) + ..writeByte(10) + ..write(obj.addresses) + ..writeByte(11) + ..write(obj.yatEid) + ..writeByte(12) + ..write(obj.yatLastUsedAddressRaw) + ..writeByte(13) + ..write(obj.showIntroCakePayCard) + ..writeByte(14) + ..write(obj.addressInfos) + ..writeByte(15) + ..write(obj.usedAddresses) + ..writeByte(16) + ..write(obj.derivationType) + ..writeByte(17) + ..write(obj.derivationPath) + ..writeByte(18) + ..write(obj.addressPageType) + ..writeByte(19) + ..write(obj.network) + ..writeByte(20) + ..write(obj.derivationInfo) + ..writeByte(21) + ..write(obj.hardwareWalletType) + ..writeByte(22) + ..write(obj.parentAddress) + ..writeByte(23) + ..write(obj.hiddenAddresses) + ..writeByte(24) + ..write(obj.manualAddresses) + ..writeByte(25) + ..write(obj.hashedWalletIdentifier) + ..writeByte(26) + ..write(obj.isNonSeedWallet); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is WalletInfoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +class DerivationTypeAdapter extends TypeAdapter { + @override + final int typeId = 15; + + @override + newWi.DerivationType read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return newWi.DerivationType.unknown; + case 1: + return newWi.DerivationType.def; + case 2: + return newWi.DerivationType.nano; + case 3: + return newWi.DerivationType.bip39; + case 4: + return newWi.DerivationType.electrum; + default: + return newWi.DerivationType.unknown; + } + } + + @override + void write(BinaryWriter writer, newWi.DerivationType obj) { + switch (obj) { + case newWi.DerivationType.unknown: + writer.writeByte(0); + break; + case newWi.DerivationType.def: + writer.writeByte(1); + break; + case newWi.DerivationType.nano: + writer.writeByte(2); + break; + case newWi.DerivationType.bip39: + writer.writeByte(3); + break; + case newWi.DerivationType.electrum: + writer.writeByte(4); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DerivationTypeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +class HardwareWalletTypeAdapter extends TypeAdapter { + @override + final int typeId = 19; + + @override + newWi.HardwareWalletType read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return newWi.HardwareWalletType.ledger; + default: + return newWi.HardwareWalletType.ledger; + } + } + + @override + void write(BinaryWriter writer, newWi.HardwareWalletType obj) { + switch (obj) { + case newWi.HardwareWalletType.ledger: + writer.writeByte(0); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is HardwareWalletTypeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/wallet_service.dart b/cw_core/lib/wallet_service.dart index 85126853e0..016f3cc681 100644 --- a/cw_core/lib/wallet_service.dart +++ b/cw_core/lib/wallet_service.dart @@ -64,5 +64,5 @@ abstract class WalletService false; + Future requireHardwareWalletConnection(String name) async => false; } diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index fcf747d9bc..66464fd25e 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -674,6 +674,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_common_ffi: + dependency: "direct main" + description: + name: sqflite_common_ffi + sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + url: "https://pub.dev" + source: hosted + version: "2.3.4+4" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + url: "https://pub.dev" + source: hosted + version: "2.9.0" stack_trace: dependency: transitive description: @@ -706,6 +762,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index ed7a6e488a..d4f7694a0d 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: sdk: flutter http: ^1.1.0 file: ^7.0.0 - path_provider: ^2.0.11 + path_provider: ^2.1.5 mobx: ^2.0.7+4 flutter_mobx: ^2.0.6+1 intl: any @@ -40,6 +40,8 @@ dependencies: ref: e6232c53c1595469931ababa878759a067c02e94 torch_dart: path: ../scripts/torch_dart + sqflite: ^2.4.1 + sqflite_common_ffi: ^2.3.4+4 dev_dependencies: flutter_test: diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index fb177f4ca2..432edb4bc0 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -37,7 +37,7 @@ class DecredWallet = DecredWalletBase with _$DecredWallet; abstract class DecredWalletBase extends WalletBase with Store { - DecredWalletBase(WalletInfo walletInfo, String password, Box unspentCoinsInfo, + DecredWalletBase(WalletInfo walletInfo, DerivationInfo derivationInfo, String password, Box unspentCoinsInfo, Libwallet libwallet, Function() closeLibwallet) : _password = password, _libwallet = libwallet, @@ -45,15 +45,15 @@ abstract class DecredWalletBase this.syncStatus = NotConnectedSyncStatus(), this.unspentCoinsInfo = unspentCoinsInfo, this.watchingOnly = - walletInfo.derivationInfo?.derivationPath == DecredWalletService.pubkeyRestorePath || - walletInfo.derivationInfo?.derivationPath == + derivationInfo.derivationPath == DecredWalletService.pubkeyRestorePath || + derivationInfo.derivationPath == DecredWalletService.pubkeyRestorePathTestnet, this.balance = ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}), - this.isTestnet = walletInfo.derivationInfo?.derivationPath == + this.isTestnet = derivationInfo.derivationPath == DecredWalletService.seedRestorePathTestnet || - walletInfo.derivationInfo?.derivationPath == + derivationInfo.derivationPath == DecredWalletService.pubkeyRestorePathTestnet, - super(walletInfo) { + super(walletInfo, derivationInfo) { walletAddresses = DecredWalletAddresses(walletInfo, libwallet); transactionHistory = DecredTransactionHistory(); diff --git a/cw_decred/lib/wallet_addresses.dart b/cw_decred/lib/wallet_addresses.dart index 860a576d94..e4af108b9d 100644 --- a/cw_decred/lib/wallet_addresses.dart +++ b/cw_decred/lib/wallet_addresses.dart @@ -37,15 +37,11 @@ abstract class DecredWalletAddressesBase extends WalletAddresses with Store { @override Future init() async { - if (walletInfo.addresses != null) { - addressesMap = walletInfo.addresses!; - } - if (walletInfo.addressInfos != null) { - addressInfos = walletInfo.addressInfos!; - } - if (walletInfo.usedAddresses != null) { - usedAddresses = {...walletInfo.usedAddresses!}; - } + addressesMap = await walletInfo.getAddresses(); + addressInfos = await walletInfo.getAddressInfos(); + usedAddresses = await walletInfo.getUsedAddresses(); + manualAddresses = await walletInfo.getManualAddresses(); + hiddenAddresses = await walletInfo.getHiddenAddresses(); await updateAddressesInBox(); } @@ -61,7 +57,15 @@ abstract class DecredWalletAddressesBase extends WalletAddresses with Store { } addressesMap[addr] = ""; addressInfos[0] ??= []; - addressInfos[0]?.add(AddressInfo(address: addr, label: "", accountIndex: 0)); + addressInfos[0]?.add( + WalletInfoAddressInfo( + walletInfoId: walletInfo.internalId, + mapKey: 0, + address: addr, + label: "", + accountIndex: 0, + ), + ); }); // Add used addresses. @@ -79,11 +83,11 @@ abstract class DecredWalletAddressesBase extends WalletAddresses with Store { await saveAddressesInBox(); } - List getAddressInfos() { + List getAddressInfos() { if (addressInfos.containsKey(0)) { return addressInfos[0]!; } - return []; + return []; } Future updateAddress(String address, String label) async { @@ -128,7 +132,15 @@ abstract class DecredWalletAddressesBase extends WalletAddresses with Store { if (!addressesMap.containsKey(addr)) { addressesMap[addr] = ""; addressInfos[0] ??= []; - addressInfos[0]?.add(AddressInfo(address: addr, label: label, accountIndex: 0)); + addressInfos[0]?.add( + WalletInfoAddressInfo( + walletInfoId: walletInfo.internalId, + mapKey: 0, + address: addr, + label: label, + accountIndex: 0, + ), + ); } selectedAddr = addr; await saveAddressesInBox(); diff --git a/cw_decred/lib/wallet_service.dart b/cw_decred/lib/wallet_service.dart index 93c708886c..afcf6d5db5 100644 --- a/cw_decred/lib/wallet_service.dart +++ b/cw_decred/lib/wallet_service.dart @@ -18,9 +18,8 @@ class DecredWalletService extends WalletService< DecredRestoreWalletFromSeedCredentials, DecredRestoreWalletFromPubkeyCredentials, DecredRestoreWalletFromHardwareCredentials> { - DecredWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); + DecredWalletService(this.unspentCoinsInfoSource); - final Box walletInfoSource; final Box unspentCoinsInfoSource; final seedRestorePath = "m/44'/42'"; static final seedRestorePathTestnet = "m/44'/1'"; @@ -68,9 +67,10 @@ class DecredWalletService extends WalletService< "unsyncedaddrs": true, }; await libwallet!.createWallet(jsonEncode(config)); - final di = DerivationInfo( - derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath); - credentials.walletInfo!.derivationInfo = di; + final di = await credentials.walletInfo!.getDerivationInfo(); + di.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath; + await di.save(); + credentials.walletInfo!.save(); credentials.walletInfo!.network = network; // ios will move our wallet directory when updating. Since we must // recalculate the new path every time we open the wallet, ensure this path @@ -79,7 +79,7 @@ class DecredWalletService extends WalletService< // going forward. credentials.walletInfo!.dirPath = ""; credentials.walletInfo!.path = ""; - final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, + final wallet = DecredWallet(credentials.walletInfo!, di, credentials.password!, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await wallet.init(); return wallet; @@ -113,13 +113,17 @@ class DecredWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + final di = await walletInfo.getDerivationInfo(); if (walletInfo.network == null || walletInfo.network == "") { - walletInfo.network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet || - walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet + walletInfo.network = di.derivationPath == seedRestorePathTestnet || + di.derivationPath == pubkeyRestorePathTestnet ? testnet : mainnet; + walletInfo.save(); } await this.init(); @@ -149,7 +153,7 @@ class DecredWalletService extends WalletService< }; await libwallet!.loadWallet(jsonEncode(config)); final wallet = - DecredWallet(walletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); + DecredWallet(walletInfo, di, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await wallet.init(); return wallet; } @@ -157,25 +161,32 @@ class DecredWalletService extends WalletService< @override Future remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; - final network = currentWalletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet || - currentWalletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } + final di = await currentWalletInfo.getDerivationInfo(); + final network = di.derivationPath == seedRestorePathTestnet || + di.derivationPath == pubkeyRestorePathTestnet ? testnet : mainnet; + currentWalletInfo.network = network; + currentWalletInfo.save(); if (libwallet == null) { libwallet = await Libwallet.spawn(); libwallet!.initLibdcrwallet("", "err"); } final currentWallet = DecredWallet( - currentWalletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); + currentWalletInfo, di, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await currentWallet.renameWalletFiles(newName); @@ -185,7 +196,7 @@ class DecredWalletService extends WalletService< newWalletInfo.dirPath = ""; newWalletInfo.path = ""; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -203,13 +214,13 @@ class DecredWalletService extends WalletService< "unsyncedaddrs": true, }; await libwallet!.createWallet(jsonEncode(config)); - final di = DerivationInfo( - derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath); - credentials.walletInfo!.derivationInfo = di; + final di = await credentials.walletInfo!.getDerivationInfo(); + di.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath; + await di.save(); credentials.walletInfo!.network = network; credentials.walletInfo!.dirPath = ""; credentials.walletInfo!.path = ""; - final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, + final wallet = DecredWallet(credentials.walletInfo!, di, credentials.password!, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await wallet.init(); return wallet; @@ -231,13 +242,13 @@ class DecredWalletService extends WalletService< "unsyncedaddrs": true, }; await libwallet!.createWatchOnlyWallet(jsonEncode(config)); - final di = DerivationInfo( - derivationPath: isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath); - credentials.walletInfo!.derivationInfo = di; + final di = await credentials.walletInfo!.getDerivationInfo(); + di.derivationPath = isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath; + await di.save(); credentials.walletInfo!.network = network; credentials.walletInfo!.dirPath = ""; credentials.walletInfo!.path = ""; - final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, + final wallet = DecredWallet(credentials.walletInfo!, di, credentials.password!, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await wallet.init(); return wallet; diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock index 83fc4d2148..8558572154 100644 --- a/cw_decred/pubspec.lock +++ b/cw_decred/pubspec.lock @@ -705,6 +705,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_common_ffi: + dependency: transitive + description: + name: sqflite_common_ffi + sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + url: "https://pub.dev" + source: hosted + version: "2.3.4+4" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + url: "https://pub.dev" + source: hosted + version: "2.9.0" stack_trace: dependency: transitive description: @@ -737,6 +793,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/cw_dogecoin/lib/src/dogecoin_wallet.dart b/cw_dogecoin/lib/src/dogecoin_wallet.dart index bd7fe06997..7f96f90906 100644 --- a/cw_dogecoin/lib/src/dogecoin_wallet.dart +++ b/cw_dogecoin/lib/src/dogecoin_wallet.dart @@ -25,6 +25,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { required String mnemonic, required String password, required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required Uint8List seedBytes, required EncryptionFileUtils encryptionFileUtils, @@ -38,6 +39,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, network: DogecoinNetwork.mainnet, initialAddresses: initialAddresses, @@ -69,6 +71,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { {required String mnemonic, required String password, required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required EncryptionFileUtils encryptionFileUtils, String? passphrase, @@ -81,6 +84,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialBalance: initialBalance, @@ -134,6 +138,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { mnemonic: keysData.mnemonic!, password: password, walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp?.addresses, initialBalance: snp?.balance, diff --git a/cw_dogecoin/lib/src/dogecoin_wallet_service.dart b/cw_dogecoin/lib/src/dogecoin_wallet_service.dart index d0e3b29699..988abd2e7d 100644 --- a/cw_dogecoin/lib/src/dogecoin_wallet_service.dart +++ b/cw_dogecoin/lib/src/dogecoin_wallet_service.dart @@ -18,9 +18,8 @@ class DogeCoinWalletService extends WalletService< DogeCoinRestoreWalletFromSeedCredentials, DogeCoinRestoreWalletFromWIFCredentials, DogeCoinNewWalletCredentials> { - DogeCoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect); + DogeCoinWalletService(this.unspentCoinsInfoSource, this.isDirect); - final Box walletInfoSource; final Box unspentCoinsInfoSource; final bool isDirect; @@ -39,6 +38,7 @@ class DogeCoinWalletService extends WalletService< mnemonic: credentials.mnemonic ?? MnemonicBip39.generate(strength: strength), password: credentials.password!, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), passphrase: credentials.passphrase, @@ -51,9 +51,10 @@ class DogeCoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; - + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await DogeCoinWalletBase.open( password: password, @@ -82,9 +83,11 @@ class DogeCoinWalletService extends WalletService< @override Future remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); final unspentCoinsToDelete = unspentCoinsInfoSource.values .where((unspentCoin) => unspentCoin.walletId == walletInfo.id) @@ -99,8 +102,10 @@ class DogeCoinWalletService extends WalletService< @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await DogeCoinWalletBase.open( password: password, name: currentName, @@ -115,7 +120,7 @@ class DogeCoinWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -141,6 +146,7 @@ class DogeCoinWalletService extends WalletService< password: credentials.password!, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), passphrase: credentials.passphrase); diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index b4883570ae..2647c5fe06 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -23,6 +23,7 @@ class EthereumWallet extends EVMChainWallet { required super.client, required super.password, required super.walletInfo, + required super.derivationInfo, super.mnemonic, super.initialBalance, super.privateKey, @@ -174,6 +175,7 @@ class EthereumWallet extends EVMChainWallet { return EthereumWallet( walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), password: password, mnemonic: keysData.mnemonic, privateKey: keysData.privateKey, diff --git a/cw_ethereum/lib/ethereum_wallet_service.dart b/cw_ethereum/lib/ethereum_wallet_service.dart index f889c8d1db..b22f569cf5 100644 --- a/cw_ethereum/lib/ethereum_wallet_service.dart +++ b/cw_ethereum/lib/ethereum_wallet_service.dart @@ -10,7 +10,7 @@ import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; import 'package:cw_evm/evm_chain_wallet_service.dart'; class EthereumWalletService extends EVMChainWalletService { - EthereumWalletService(super.walletInfoSource, super.isDirect, {required this.client}); + EthereumWalletService(super.isDirect, {required this.client}); late EthereumClient client; @@ -25,6 +25,7 @@ class EthereumWalletService extends EVMChainWalletService { final wallet = EthereumWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), mnemonic: mnemonic, password: credentials.password!, passphrase: credentials.passphrase, @@ -41,8 +42,10 @@ class EthereumWalletService extends EVMChainWalletService { @override Future openWallet(String name, String password) async { - final walletInfo = - walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await EthereumWallet.open( @@ -75,8 +78,10 @@ class EthereumWalletService extends EVMChainWalletService { @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await EthereumWallet.open( password: password, name: currentName, @@ -91,21 +96,23 @@ class EthereumWalletService extends EVMChainWalletService { newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override Future restoreFromHardwareWallet( EVMChainRestoreWalletFromHardware credentials) async { - credentials.walletInfo!.derivationInfo = DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0" - ); + final di = await credentials.walletInfo!.getDerivationInfo(); + di.derivationType = DerivationType.bip39; + di.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0"; + await di.save(); credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType; credentials.walletInfo!.address = credentials.hwAccountData.address; + credentials.walletInfo!.save(); final wallet = EthereumWallet( walletInfo: credentials.walletInfo!, + derivationInfo: di, password: credentials.password!, client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -125,6 +132,7 @@ class EthereumWalletService extends EVMChainWalletService { password: credentials.password!, privateKey: credentials.privateKey, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -147,6 +155,7 @@ class EthereumWalletService extends EVMChainWalletService { password: credentials.password!, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), passphrase: credentials.passphrase, client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index f7fb244bbe..029a171958 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -64,6 +64,7 @@ abstract class EVMChainWalletBase with Store, WalletKeysFile { EVMChainWalletBase({ required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required EVMChainClient client, required CryptoCurrency nativeCurrency, String? mnemonic, @@ -85,7 +86,7 @@ abstract class EVMChainWalletBase nativeCurrency: initialBalance ?? EVMChainERC20Balance(BigInt.zero), }, ), - super(walletInfo) { + super(walletInfo, derivationInfo) { this.walletInfo = walletInfo; transactionHistory = setUpTransactionHistory(walletInfo, password, encryptionFileUtils); diff --git a/cw_evm/lib/evm_chain_wallet_service.dart b/cw_evm/lib/evm_chain_wallet_service.dart index e6bb41b86f..ea6e68a23b 100644 --- a/cw_evm/lib/evm_chain_wallet_service.dart +++ b/cw_evm/lib/evm_chain_wallet_service.dart @@ -15,9 +15,8 @@ abstract class EVMChainWalletService extends WalletSer EVMChainRestoreWalletFromSeedCredentials, EVMChainRestoreWalletFromPrivateKey, EVMChainRestoreWalletFromHardware> { - EVMChainWalletService(this.walletInfoSource, this.isDirect); + EVMChainWalletService(this.isDirect); - final Box walletInfoSource; final bool isDirect; @override @@ -48,8 +47,10 @@ abstract class EVMChainWalletService extends WalletSer @override Future remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } } diff --git a/cw_evm/lib/evm_ledger_credentials.dart b/cw_evm/lib/evm_ledger_credentials.dart index b579b91081..95caf2329c 100644 --- a/cw_evm/lib/evm_ledger_credentials.dart +++ b/cw_evm/lib/evm_ledger_credentials.dart @@ -19,8 +19,8 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress { @override EthereumAddress get address => EthereumAddress.fromHex(_address); - void setLedgerConnection(LedgerConnection connection, - [String? derivationPath]) { + Future setLedgerConnection(LedgerConnection connection, + [String? derivationPath]) async { ethereumLedgerApp = EthereumLedgerApp(connection, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0"); } diff --git a/cw_monero/lib/ledger.dart b/cw_monero/lib/ledger.dart index 7a3c95a274..7707acd480 100644 --- a/cw_monero/lib/ledger.dart +++ b/cw_monero/lib/ledger.dart @@ -15,7 +15,7 @@ String? latestLedgerCommand; typedef LedgerCallback = Void Function(Pointer, UnsignedInt); NativeCallable? callable; -void enableLedgerExchange(LedgerConnection connection) { +Future enableLedgerExchange(LedgerConnection connection) async { callable?.close(); void callback(Pointer request, int requestLength) async { diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 57cc9a3fc3..9f5986e267 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -55,6 +55,7 @@ abstract class MoneroWalletBase extends WalletBase with Store { MoneroWalletBase( {required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required String password}) : balance = ObservableMap.of({ @@ -70,7 +71,7 @@ abstract class MoneroWalletBase extends WalletBase setLedgerConnection(LedgerConnection connection) async { + await enableLedgerExchange(connection); } @override diff --git a/cw_monero/lib/monero_wallet_addresses.dart b/cw_monero/lib/monero_wallet_addresses.dart index d0cf874892..0b38ac5fd6 100644 --- a/cw_monero/lib/monero_wallet_addresses.dart +++ b/cw_monero/lib/monero_wallet_addresses.dart @@ -5,7 +5,6 @@ import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list; -import 'package:cw_monero/api/transaction_history.dart'; import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/monero_account_list.dart'; import 'package:cw_monero/monero_subaddress_list.dart'; @@ -90,8 +89,12 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { _subaddressList.subaddresses.forEach((subaddress) { addressesMap[subaddress.address] = subaddress.label; addressInfos[account.id] ??= []; - addressInfos[account.id]?.add(AddressInfo( - address: subaddress.address, label: subaddress.label, accountIndex: account.id)); + addressInfos[account.id]?.add(WalletInfoAddressInfo( + walletInfoId: walletInfo.internalId, + mapKey: account.id, + accountIndex: account.id, + address: subaddress.address, + label: subaddress.label)); }); }); diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 6fc827068f..f38341923a 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -99,9 +99,8 @@ class MoneroWalletService extends WalletService< MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials, MoneroRestoreWalletFromHardwareCredentials> { - MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); + MoneroWalletService(this.unspentCoinsInfoSource); - final Box walletInfoSource; final Box unspentCoinsInfoSource; static bool walletFilesExist(String path) => @@ -148,6 +147,7 @@ class MoneroWalletService extends WalletService< passphrase: credentials.passphrase ?? ""); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); @@ -182,10 +182,13 @@ class MoneroWalletService extends WalletService< await monero_wallet_manager .openWallet(path: path, password: password); - final walletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } final wallet = MoneroWallet( walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password); @@ -234,17 +237,22 @@ class MoneroWalletService extends WalletService< await file.delete(recursive: true); } - final walletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values.firstWhere( - (info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = MoneroWallet( walletInfo: currentWalletInfo, + derivationInfo: await currentWalletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password, ); @@ -255,7 +263,7 @@ class MoneroWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -273,6 +281,7 @@ class MoneroWalletService extends WalletService< spendKey: credentials.spendKey); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); @@ -303,6 +312,7 @@ class MoneroWalletService extends WalletService< final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); @@ -359,6 +369,7 @@ class MoneroWalletService extends WalletService< restoreHeight: credentials.height!); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); @@ -379,10 +390,11 @@ class MoneroWalletService extends WalletService< String? passphrase, int? overrideHeight, }) async { - walletInfo.derivationInfo = DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/44'/128'/0'/0/0", - ); + final derivationInfo = await walletInfo.getDerivationInfo(); + derivationInfo.derivationType = DerivationType.bip39; + derivationInfo.derivationPath = "m/44'/128'/0'/0/0"; + await derivationInfo.save(); + walletInfo.save(); final legacyMnemonic = getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? ""); @@ -409,6 +421,7 @@ class MoneroWalletService extends WalletService< final wallet = MoneroWallet( walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password, ); @@ -453,6 +466,7 @@ class MoneroWalletService extends WalletService< final wallet = MoneroWallet( walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password, ); @@ -485,6 +499,7 @@ class MoneroWalletService extends WalletService< final wallet = MoneroWallet( walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password, ); @@ -529,10 +544,13 @@ class MoneroWalletService extends WalletService< await monero_wallet_manager .openWallet(path: path, password: password); - final walletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } final wallet = MoneroWallet( walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password, ); @@ -544,12 +562,12 @@ class MoneroWalletService extends WalletService< } @override - bool requireHardwareWalletConnection(String name) { - return walletInfoSource.values - .firstWhereOrNull( - (info) => info.id == WalletBase.idFor(name, getType())) - ?.isHardwareWallet ?? - false; + Future requireHardwareWalletConnection(String name) async { + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + return walletInfo.isHardwareWallet; } } diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 914d52f6bf..6ac939a192 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -818,6 +818,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_common_ffi: + dependency: "direct dev" + description: + name: sqflite_common_ffi + sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + url: "https://pub.dev" + source: hosted + version: "2.3.4+4" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + url: "https://pub.dev" + source: hosted + version: "2.9.0" stack_trace: dependency: transitive description: @@ -850,6 +906,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 2da3023e96..3c821b612f 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -39,6 +39,7 @@ dev_dependencies: mobx_codegen: ^2.0.7 mockito: ^5.4.5 hive_generator: ^2.0.1 + sqflite_common_ffi: ^2.3.4+4 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_monero/test/monero_wallet_service_test.dart b/cw_monero/test/monero_wallet_service_test.dart index a809dbc230..3b24c205b6 100644 --- a/cw_monero/test/monero_wallet_service_test.dart +++ b/cw_monero/test/monero_wallet_service_test.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:cw_core/db/sqlite.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; @@ -7,26 +8,28 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cw_monero/monero_wallet_service.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; +import 'package:sqflite/sqflite.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'mock/path_provider.dart'; import 'utils/setup_monero_c.dart'; Future main() async { group("MoneroWalletService Tests", () { - Hive.init('./test/data/db'); late MoneroWalletService walletService; late File moneroCBinary; setUpAll(() async { + databaseFactory = databaseFactoryFfi; + await initDb(pathOverride: './test/data/db'); + Hive.init('./test/data/db'); PathProviderPlatform.instance = MockPathProviderPlatform(); - final Box walletInfoSource = - await Hive.openBox('testWalletInfo'); final Box unspentCoinsInfoSource = await Hive.openBox('testUnspentCoinsInfo'); - walletService = MoneroWalletService(walletInfoSource, unspentCoinsInfoSource); + walletService = MoneroWalletService(unspentCoinsInfoSource); moneroCBinary = getMoneroCBinary().copySync(moneroCBinaryName); }); diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index a076125446..05ff1b82c0 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -39,6 +39,7 @@ abstract class NanoWalletBase with Store, WalletKeysFile { NanoWalletBase({ required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required String mnemonic, required String password, NanoBalance? initialBalance, @@ -47,7 +48,7 @@ abstract class NanoWalletBase }) : syncStatus = NotConnectedSyncStatus(), _password = password, _mnemonic = mnemonic, - _derivationType = walletInfo.derivationInfo!.derivationType!, + _derivationType = derivationInfo.derivationType!, _isTransactionUpdating = false, _encryptionFileUtils = encryptionFileUtils, _client = NanoClient(), @@ -56,7 +57,7 @@ abstract class NanoWalletBase CryptoCurrency.nano: initialBalance ?? NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero) }), - super(walletInfo) { + super(walletInfo, derivationInfo) { this.walletInfo = walletInfo; transactionHistory = NanoTransactionHistory( walletInfo: walletInfo, @@ -431,11 +432,13 @@ abstract class NanoWalletBase derivationType = DerivationType.bip39; } - walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType); - walletInfo.derivationInfo!.derivationType ??= derivationType; + final derivationInfo = await walletInfo.getDerivationInfo(); + derivationInfo.derivationType = derivationType; + derivationInfo.save(); return NanoWallet( walletInfo: walletInfo, + derivationInfo: derivationInfo, password: password, mnemonic: keysData.mnemonic!, initialBalance: balance, diff --git a/cw_nano/lib/nano_wallet_creation_credentials.dart b/cw_nano/lib/nano_wallet_creation_credentials.dart index 8eb6dc2d21..d2a8db70bc 100644 --- a/cw_nano/lib/nano_wallet_creation_credentials.dart +++ b/cw_nano/lib/nano_wallet_creation_credentials.dart @@ -14,6 +14,7 @@ class NanoNewWalletCredentials extends WalletCredentials { password: password, walletInfo: walletInfo, passphrase: passphrase, + derivationInfo: DerivationInfo(derivationType: derivationType), ); final String? mnemonic; diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart index 5c073c9c09..0dd57928fb 100644 --- a/cw_nano/lib/nano_wallet_service.dart +++ b/cw_nano/lib/nano_wallet_service.dart @@ -19,9 +19,8 @@ class NanoWalletService extends WalletService< NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials, NanoNewWalletCredentials> { - NanoWalletService(this.walletInfoSource, this.isDirect); + NanoWalletService(this.isDirect); - final Box walletInfoSource; final bool isDirect; static bool walletFilesExist(String path) => @@ -33,7 +32,8 @@ class NanoWalletService extends WalletService< @override Future create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async { final String mnemonic; - switch (credentials.walletInfo?.derivationInfo?.derivationType) { + final derivationInfo = credentials.derivationInfo ?? await credentials.walletInfo!.getDerivationInfo(); + switch (derivationInfo.derivationType) { case DerivationType.nano: String seedKey = NanoSeeds.generateSeed(); mnemonic = credentials.mnemonic ?? NanoDerivations.standardSeedToMnemonic(seedKey); @@ -47,6 +47,7 @@ class NanoWalletService extends WalletService< final wallet = NanoWallet( walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, mnemonic: mnemonic, password: credentials.password!, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -65,20 +66,25 @@ class NanoWalletService extends WalletService< await file.delete(recursive: true); } - final walletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } String randomWords = (List.from(nm.NanoMnemomics.WORDLIST)..shuffle()).take(24).join(' '); final currentWallet = NanoWallet( walletInfo: currentWalletInfo, + derivationInfo: await currentWalletInfo.getDerivationInfo(), password: password, mnemonic: randomWords, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -91,7 +97,7 @@ class NanoWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -115,18 +121,17 @@ class NanoWalletService extends WalletService< throw Exception("Wasn't a valid nano style seed!"); } } - + final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); // should never happen but just in case: - if (credentials.walletInfo!.derivationInfo == null) { - credentials.walletInfo!.derivationInfo = DerivationInfo(derivationType: DerivationType.nano); - } else if (credentials.walletInfo!.derivationInfo!.derivationType == null) { - credentials.walletInfo!.derivationInfo!.derivationType = DerivationType.nano; + if (derivationInfo.derivationType == null) { + derivationInfo.derivationType = DerivationType.nano; } final wallet = await NanoWallet( password: credentials.password!, mnemonic: mnemonic ?? credentials.seedKey, walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); await wallet.init(); @@ -157,15 +162,17 @@ class NanoWalletService extends WalletService< } } - DerivationType derivationType = - credentials.walletInfo?.derivationInfo?.derivationType ?? DerivationType.nano; + final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); + DerivationType derivationType = derivationInfo.derivationType ?? DerivationType.nano; - credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: derivationType); + derivationInfo.derivationType = derivationType; + derivationInfo.save(); final wallet = await NanoWallet( password: credentials.password!, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -180,8 +187,10 @@ class NanoWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = - walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await NanoWalletBase.open( diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index b14c7bda88..c009b65024 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -823,6 +823,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_common_ffi: + dependency: transitive + description: + name: sqflite_common_ffi + sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + url: "https://pub.dev" + source: hosted + version: "2.3.4+4" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + url: "https://pub.dev" + source: hosted + version: "2.9.0" stack_trace: dependency: transitive description: @@ -855,6 +911,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index c0055c789e..facce73229 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -21,6 +21,7 @@ import 'package:cw_polygon/polygon_transaction_info.dart'; class PolygonWallet extends EVMChainWallet { PolygonWallet({ required super.walletInfo, + required super.derivationInfo, required super.password, super.mnemonic, super.initialBalance, @@ -155,8 +156,11 @@ class PolygonWallet extends EVMChainWallet { ); } + final derivationInfo = await walletInfo.getDerivationInfo(); + return PolygonWallet( walletInfo: walletInfo, + derivationInfo: derivationInfo, password: password, mnemonic: keysData.mnemonic, privateKey: keysData.privateKey, diff --git a/cw_polygon/lib/polygon_wallet_service.dart b/cw_polygon/lib/polygon_wallet_service.dart index 05af311e72..ff8938048d 100644 --- a/cw_polygon/lib/polygon_wallet_service.dart +++ b/cw_polygon/lib/polygon_wallet_service.dart @@ -10,8 +10,7 @@ import 'package:cw_polygon/polygon_mnemonics_exception.dart'; import 'package:cw_polygon/polygon_wallet.dart'; class PolygonWalletService extends EVMChainWalletService { - PolygonWalletService( - super.walletInfoSource, super.isDirect, { + PolygonWalletService(super.isDirect, { required this.client, }); @@ -28,6 +27,7 @@ class PolygonWalletService extends EVMChainWalletService { final wallet = PolygonWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), mnemonic: mnemonic, password: credentials.password!, passphrase: credentials.passphrase, @@ -43,8 +43,10 @@ class PolygonWalletService extends EVMChainWalletService { @override Future openWallet(String name, String password) async { - final walletInfo = - walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await PolygonWallet.open( @@ -82,6 +84,7 @@ class PolygonWalletService extends EVMChainWalletService { password: credentials.password!, privateKey: credentials.privateKey, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -95,15 +98,16 @@ class PolygonWalletService extends EVMChainWalletService { @override Future restoreFromHardwareWallet( EVMChainRestoreWalletFromHardware credentials) async { - credentials.walletInfo!.derivationInfo = DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0" - ); + final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); + derivationInfo.derivationType = DerivationType.bip39; + derivationInfo.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0"; + derivationInfo.save(); credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType; credentials.walletInfo!.address = credentials.hwAccountData.address; final wallet = PolygonWallet( walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, password: credentials.password!, client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -127,6 +131,7 @@ class PolygonWalletService extends EVMChainWalletService { password: credentials.password!, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), passphrase: credentials.passphrase, client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -141,8 +146,10 @@ class PolygonWalletService extends EVMChainWalletService { @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await PolygonWallet.open( password: password, name: currentName, @@ -157,6 +164,6 @@ class PolygonWalletService extends EVMChainWalletService { newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } } diff --git a/cw_shared_external/lib/cw_shared_external.dart b/cw_shared_external/lib/cw_shared_external.dart index 37d0350ddb..1d9df69a29 100644 --- a/cw_shared_external/lib/cw_shared_external.dart +++ b/cw_shared_external/lib/cw_shared_external.dart @@ -8,7 +8,7 @@ class CwSharedExternal { const MethodChannel('cw_shared_external'); static Future get platformVersion async { - final String version = await _channel.invokeMethod('getPlatformVersion'); + final String version = (await _channel.invokeMethod('getPlatformVersion')).toString(); return version; } } diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 39f4f6b13c..59553f0418 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -43,6 +43,7 @@ abstract class SolanaWalletBase with Store, WalletKeysFile { SolanaWalletBase({ required WalletInfo walletInfo, + required DerivationInfo derivationInfo, String? mnemonic, String? privateKey, required String password, @@ -57,7 +58,7 @@ abstract class SolanaWalletBase walletAddresses = SolanaWalletAddresses(walletInfo), balance = ObservableMap.of( {CryptoCurrency.sol: initialBalance ?? SolanaBalance(BigInt.zero.toDouble())}), - super(walletInfo) { + super(walletInfo, derivationInfo) { this.walletInfo = walletInfo; transactionHistory = SolanaTransactionHistory( walletInfo: walletInfo, @@ -441,8 +442,11 @@ abstract class SolanaWalletBase ); } + final derivationInfo = await walletInfo.getDerivationInfo(); + return SolanaWallet( walletInfo: walletInfo, + derivationInfo: derivationInfo, password: password, passphrase: keysData.passphrase, mnemonic: keysData.mnemonic, diff --git a/cw_solana/lib/solana_wallet_service.dart b/cw_solana/lib/solana_wallet_service.dart index a33cebb3a2..4e5a2aeec3 100644 --- a/cw_solana/lib/solana_wallet_service.dart +++ b/cw_solana/lib/solana_wallet_service.dart @@ -18,9 +18,8 @@ import 'package:hive/hive.dart'; class SolanaWalletService extends WalletService { - SolanaWalletService(this.walletInfoSource, this.isDirect); + SolanaWalletService(this.isDirect); - final Box walletInfoSource; final bool isDirect; @override @@ -31,6 +30,7 @@ class SolanaWalletService extends WalletService openWallet(String name, String password) async { - final walletInfo = - walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await SolanaWalletBase.open( @@ -86,9 +88,11 @@ class SolanaWalletService extends WalletService remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override @@ -98,6 +102,7 @@ class SolanaWalletService extends WalletService rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await SolanaWalletBase.open( password: password, name: currentName, @@ -148,7 +156,7 @@ class SolanaWalletService extends WalletService.of( {CryptoCurrency.trx: initialBalance ?? TronBalance(BigInt.zero)}, ), - super(walletInfo) { + super(walletInfo, derivationInfo) { this.walletInfo = walletInfo; transactionHistory = TronTransactionHistory( walletInfo: walletInfo, password: password, encryptionFileUtils: encryptionFileUtils); @@ -163,8 +164,11 @@ abstract class TronWalletBase ); } + final derivationInfo = await walletInfo.getDerivationInfo(); + return TronWallet( walletInfo: walletInfo, + derivationInfo: derivationInfo, password: password, mnemonic: keysData.mnemonic, privateKey: keysData.privateKey, diff --git a/cw_tron/lib/tron_wallet_service.dart b/cw_tron/lib/tron_wallet_service.dart index 0c1ac60029..cf83b9158c 100644 --- a/cw_tron/lib/tron_wallet_service.dart +++ b/cw_tron/lib/tron_wallet_service.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:bip39/bip39.dart' as bip39; -import 'package:collection/collection.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/pathForWallet.dart'; @@ -22,11 +21,10 @@ class TronWalletService extends WalletService< TronRestoreWalletFromSeedCredentials, TronRestoreWalletFromPrivateKey, TronNewWalletCredentials> { - TronWalletService(this.walletInfoSource, {required this.client, required this.isDirect}); + TronWalletService({required this.client, required this.isDirect}); late TronClient client; - final Box walletInfoSource; final bool isDirect; @override @@ -40,6 +38,7 @@ class TronWalletService extends WalletService< final wallet = TronWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), mnemonic: mnemonic, password: credentials.password!, passphrase: credentials.passphrase, @@ -55,8 +54,10 @@ class TronWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = - walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await TronWalletBase.open( @@ -97,6 +98,7 @@ class TronWalletService extends WalletService< password: credentials.password!, privateKey: credentials.privateKey, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -120,6 +122,7 @@ class TronWalletService extends WalletService< password: credentials.password!, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), passphrase: credentials.passphrase, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -133,8 +136,10 @@ class TronWalletService extends WalletService< @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } final currentWallet = await TronWalletBase.open( password: password, name: currentName, @@ -149,7 +154,7 @@ class TronWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -159,9 +164,11 @@ class TronWalletService extends WalletService< @override Future remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override diff --git a/cw_wownero/lib/api/wallet.dart b/cw_wownero/lib/api/wallet.dart index a4ba8cbb2e..68d0b654bb 100644 --- a/cw_wownero/lib/api/wallet.dart +++ b/cw_wownero/lib/api/wallet.dart @@ -102,16 +102,22 @@ String getAddress({int accountIndex = 0, int addressIndex = 1}) { return addressCache[wptr!.address]![accountIndex]![addressIndex]!; } -int getFullBalance({int accountIndex = 0}) => - wownero.Wallet_balance(wptr!, accountIndex: accountIndex); - -int getUnlockedBalance({int accountIndex = 0}) => - wownero.Wallet_unlockedBalance(wptr!, accountIndex: accountIndex); - -int getCurrentHeight() => wownero.Wallet_blockChainHeight(wptr!); +int getFullBalance({int accountIndex = 0}) { + if (wptr == null) return 0; + return wownero.Wallet_balance(wptr!, accountIndex: accountIndex); +} +int getUnlockedBalance({int accountIndex = 0}) { + if (wptr == null) return 0; + return wownero.Wallet_unlockedBalance(wptr!, accountIndex: accountIndex); +} +int getCurrentHeight() { + if (wptr == null) return 0; + return wownero.Wallet_blockChainHeight(wptr!); +} int cachedNodeHeight = 0; int getNodeHeightSync() { + if (wptr == null) return 0; (() async { final wptrAddress = wptr!.address; cachedNodeHeight = await Isolate.run(() async { @@ -121,7 +127,10 @@ int getNodeHeightSync() { return cachedNodeHeight; } -bool isConnectedSync() => wownero.Wallet_connected(wptr!) != 0; +bool isConnectedSync() { + if (wptr == null) return false; + return wownero.Wallet_connected(wptr!) != 0; +} Future setupNodeSync( {required String address, diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index f068ec4920..08b7e65416 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -53,7 +53,7 @@ abstract class WowneroWalletBase extends WalletBase with Store { WowneroWalletBase( - {required WalletInfo walletInfo, required Box unspentCoinsInfo, required String password}) + {required WalletInfo walletInfo, required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required String password}) : balance = ObservableMap.of({ CryptoCurrency.wow: WowneroBalance( fullBalance: wownero_wallet.getFullBalance(accountIndex: 0), @@ -66,7 +66,7 @@ abstract class WowneroWalletBase syncStatus = NotConnectedSyncStatus(), unspentCoins = [], this.unspentCoinsInfo = unspentCoinsInfo, - super(walletInfo) { + super(walletInfo, derivationInfo) { transactionHistory = WowneroTransactionHistory(); walletAddresses = WowneroWalletAddresses(walletInfo, transactionHistory); diff --git a/cw_wownero/lib/wownero_wallet_addresses.dart b/cw_wownero/lib/wownero_wallet_addresses.dart index 0b2ade0733..936c187247 100644 --- a/cw_wownero/lib/wownero_wallet_addresses.dart +++ b/cw_wownero/lib/wownero_wallet_addresses.dart @@ -88,8 +88,12 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store { _subaddressList.subaddresses.forEach((subaddress) { addressesMap[subaddress.address] = subaddress.label; addressInfos[account.id] ??= []; - addressInfos[account.id]?.add(AddressInfo( - address: subaddress.address, label: subaddress.label, accountIndex: account.id)); + addressInfos[account.id]?.add(WalletInfoAddressInfo( + walletInfoId: walletInfo.internalId, + mapKey: account.id, + accountIndex: account.id, + address: subaddress.address, + label: subaddress.label)); }); }); diff --git a/cw_wownero/lib/wownero_wallet_service.dart b/cw_wownero/lib/wownero_wallet_service.dart index 72eefabd24..b8063a315b 100644 --- a/cw_wownero/lib/wownero_wallet_service.dart +++ b/cw_wownero/lib/wownero_wallet_service.dart @@ -66,9 +66,8 @@ class WowneroWalletService extends WalletService< WowneroRestoreWalletFromSeedCredentials, WowneroRestoreWalletFromKeysCredentials, WowneroNewWalletCredentials> { - WowneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); + WowneroWalletService(this.unspentCoinsInfoSource); - final Box walletInfoSource; final Box unspentCoinsInfoSource; static bool walletFilesExist(String path) => @@ -99,7 +98,7 @@ class WowneroWalletService extends WalletService< await wownero_wallet_manager.createWallet( path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??''); final wallet = WowneroWallet( - walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); + walletInfo: credentials.walletInfo!, derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); return wallet; @@ -133,9 +132,11 @@ class WowneroWalletService extends WalletService< } await wownero_wallet_manager.openWalletAsync({'path': path, 'password': password}); - final walletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); - wallet = WowneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + wallet = WowneroWallet(walletInfo: walletInfo, derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password); final isValid = wallet.walletAddresses.validate(); if (!isValid) { @@ -206,17 +207,20 @@ class WowneroWalletService extends WalletService< await file.delete(recursive: true); } - final walletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); - final currentWallet = - WowneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } + final currentWallet = WowneroWallet(walletInfo: currentWalletInfo, derivationInfo: await currentWalletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password); await currentWallet.renameWalletFiles(newName); @@ -224,7 +228,7 @@ class WowneroWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -241,7 +245,7 @@ class WowneroWalletService extends WalletService< viewKey: credentials.viewKey, spendKey: credentials.spendKey); final wallet = WowneroWallet( - walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); + walletInfo: credentials.walletInfo!, derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); return wallet; @@ -275,7 +279,7 @@ class WowneroWalletService extends WalletService< seed: credentials.mnemonic, restoreHeight: credentials.height!); final wallet = WowneroWallet( - walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); + walletInfo: credentials.walletInfo!, derivationInfo: await credentials.walletInfo!.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: credentials.password!); await wallet.init(); return wallet; @@ -321,6 +325,7 @@ class WowneroWalletService extends WalletService< final wallet = WowneroWallet( walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password, ); @@ -350,7 +355,7 @@ class WowneroWalletService extends WalletService< wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase??''); - final wallet = WowneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); + final wallet = WowneroWallet(walletInfo: walletInfo, derivationInfo: await walletInfo.getDerivationInfo(), unspentCoinsInfo: unspentCoinsInfoSource, password: password); await wallet.init(); return wallet; diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index fbebeab578..959a222e4d 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -722,6 +722,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_common_ffi: + dependency: transitive + description: + name: sqflite_common_ffi + sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + url: "https://pub.dev" + source: hosted + version: "2.3.4+4" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + url: "https://pub.dev" + source: hosted + version: "2.9.0" stack_trace: dependency: transitive description: @@ -754,6 +810,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index da253e3ae7..913bc083ad 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -105,14 +105,14 @@ abstract class ZanoWalletBase /// number of transactions in each request static final int _txChunkSize = (pow(2, 32) - 1).toInt(); - ZanoWalletBase(WalletInfo walletInfo, String password) + ZanoWalletBase(WalletInfo walletInfo, DerivationInfo derivationInfo, String password) : balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance.empty()}), _isTransactionUpdating = false, _hasSyncAfterStartup = false, walletAddresses = ZanoWalletAddresses(walletInfo), syncStatus = NotConnectedSyncStatus(), _password = password, - super(walletInfo) { + super(walletInfo, derivationInfo) { transactionHistory = ZanoTransactionHistory(); if (!CakeHive.isAdapterRegistered(ZanoAsset.typeId)) { CakeHive.registerAdapter(ZanoAssetAdapter()); @@ -129,7 +129,7 @@ abstract class ZanoWalletBase } static Future create({required WalletCredentials credentials}) async { - final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!); + final wallet = ZanoWallet(credentials.walletInfo!, await credentials.walletInfo!.getDerivationInfo(), credentials.password!); await wallet.initWallet(); final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); final createWalletResult = await wallet.createWallet(path, credentials.password!); @@ -146,7 +146,7 @@ abstract class ZanoWalletBase static Future restore( {required ZanoRestoreWalletFromSeedCredentials credentials}) async { - final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!); + final wallet = ZanoWallet(credentials.walletInfo!, await credentials.walletInfo!.getDerivationInfo(), credentials.password!); await wallet.initWallet(); final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); final createWalletResult = await wallet.restoreWalletFromSeed( @@ -166,13 +166,13 @@ abstract class ZanoWalletBase {required String name, required String password, required WalletInfo walletInfo}) async { final path = await pathForWallet(name: name, type: walletInfo.type); if (ZanoWalletApi.openWalletCache[path] != null) { - final wallet = ZanoWallet(walletInfo, password); + final wallet = ZanoWallet(walletInfo, await walletInfo.getDerivationInfo(), password); await wallet.parseCreateWalletResult(ZanoWalletApi.openWalletCache[path]!).then((_) { unawaited(wallet.init(ZanoWalletApi.openWalletCache[path]!.wi.address)); }); return wallet; } else { - final wallet = ZanoWallet(walletInfo, password); + final wallet = ZanoWallet(walletInfo, await walletInfo.getDerivationInfo(), password); await wallet.initWallet(); final createWalletResult = await wallet.loadWallet(path, password); await wallet.parseCreateWalletResult(createWalletResult).then((_) { diff --git a/cw_zano/lib/zano_wallet_service.dart b/cw_zano/lib/zano_wallet_service.dart index 879402cfff..98d350a40f 100644 --- a/cw_zano/lib/zano_wallet_service.dart +++ b/cw_zano/lib/zano_wallet_service.dart @@ -43,9 +43,7 @@ class ZanoRestoreWalletFromKeysCredentials extends WalletCredentials { class ZanoWalletService extends WalletService { - ZanoWalletService(this.walletInfoSource); - - final Box walletInfoSource; + ZanoWalletService(); static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync(); @@ -68,7 +66,10 @@ class ZanoWalletService extends WalletService openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } try { final wallet = await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo); saveBackup(name); @@ -89,14 +90,20 @@ class ZanoWalletService extends WalletService info.id == WalletBase.idFor(wallet, getType())); - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); - final currentWallet = ZanoWallet(currentWalletInfo, password); + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw Exception('Wallet not found'); + } + final currentWallet = ZanoWallet(currentWalletInfo, await currentWalletInfo.getDerivationInfo(), password); await currentWallet.renameWalletFiles(newName); @@ -104,7 +111,7 @@ class ZanoWalletService extends WalletService walletInfoSource, Box unspentCoinSource, Box payjoinSessionSource, bool isDirect) { - return BitcoinWalletService( - walletInfoSource, unspentCoinSource, payjoinSessionSource, isDirect); + return BitcoinWalletService(unspentCoinSource, payjoinSessionSource, isDirect); } - WalletService createLitecoinWalletService( - Box walletInfoSource, Box unspentCoinSource, bool isDirect) { - return LitecoinWalletService(walletInfoSource, unspentCoinSource, isDirect); + WalletService createLitecoinWalletService(Box unspentCoinSource, bool isDirect) { + return LitecoinWalletService(unspentCoinSource, isDirect); } @override @@ -531,8 +528,8 @@ class CWBitcoin extends Bitcoin { } @override - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { - (wallet as ElectrumWallet).setLedgerConnection(connection); + Future setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) async { + await (wallet as ElectrumWallet).setLedgerConnection(connection); } @override diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart index be0323bfa8..5039652e73 100644 --- a/lib/bitcoin_cash/cw_bitcoin_cash.dart +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -5,9 +5,8 @@ class CWBitcoinCash extends BitcoinCash { String getCashAddrFormat(String address) => AddressUtils.getCashAddrFormat(address); @override - WalletService createBitcoinCashWalletService( - Box walletInfoSource, Box unspentCoinSource, bool isDirect) { - return BitcoinCashWalletService(walletInfoSource, unspentCoinSource, isDirect); + WalletService createBitcoinCashWalletService(Box unspentCoinSource, bool isDirect) { + return BitcoinCashWalletService(unspentCoinSource, isDirect); } @override diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 3ab5d4273a..e3daa62d97 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -340,8 +340,8 @@ class DFXBuyProvider extends BuyProvider { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: wallet.walletInfo.type, - onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) { - ledgerVM.setLedger(wallet); + onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) async { + await ledgerVM.setLedger(wallet); Navigator.of(context).pop(); })); } else { diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 87bb71ce92..70ba45751b 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -24,7 +24,7 @@ import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cake_backup/backup.dart' as cake_backup; class $BackupService { - $BackupService(this._secureStorage, this.walletInfoSource, this.transactionDescriptionBox, + $BackupService(this._secureStorage, this.transactionDescriptionBox, this.keyService, this.sharedPreferences) : cipher = Cryptography.instance.chacha20Poly1305Aead(), correctWallets = []; @@ -37,7 +37,6 @@ class $BackupService { final Cipher cipher; final SecureStorage _secureStorage; final SharedPreferences sharedPreferences; - final Box walletInfoSource; final Box transactionDescriptionBox; final KeyService keyService; List correctWallets; @@ -110,27 +109,14 @@ class $BackupService { } Future verifyWallets() async { - final walletInfoSource = await reloadHiveWalletInfoBox(); - correctWallets = - walletInfoSource.values.where((info) => availableWalletTypes.contains(info.type)).toList(); + await performHiveMigration(); // for backups made before sqlite migration + correctWallets = (await WalletInfo.getAll()).where((info) => availableWalletTypes.contains(info.type)).toList(); if (correctWallets.isEmpty) { - throw Exception('Correct wallets not detected'); + printV('Correct wallets not detected'); } } - Future> reloadHiveWalletInfoBox() async { - final appDir = await getAppDir(); - await CakeHive.close(); - CakeHive.init(appDir.path); - - if (!CakeHive.isAdapterRegistered(WalletInfo.typeId)) { - CakeHive.registerAdapter(WalletInfoAdapter()); - } - - return await CakeHive.openBox(WalletInfo.boxName); - } - Future importTransactionDescriptionDump() async { final appDir = await getAppDir(); final transactionDescriptionFile = File('${appDir.path}/~_transaction_descriptions_dump'); @@ -283,7 +269,7 @@ class $BackupService { Future exportKeychainDumpV2(String password, {String keychainSalt = secrets.backupKeychainSalt}) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); - final wallets = await Future.wait(walletInfoSource.values.map((walletInfo) async { + final wallets = await Future.wait((await WalletInfo.getAll()).map((walletInfo) async { try { return { 'name': walletInfo.name, diff --git a/lib/core/backup_service_v3.dart b/lib/core/backup_service_v3.dart index a0640dfd32..ac73f9b3ad 100644 --- a/lib/core/backup_service_v3.dart +++ b/lib/core/backup_service_v3.dart @@ -11,6 +11,7 @@ import 'package:crypto/crypto.dart'; import 'package:cw_core/root_dir.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; enum BackupVersion { @@ -143,7 +144,7 @@ class BackupMetadata { } class BackupServiceV3 extends $BackupService { - BackupServiceV3(super.secureStorage, super.walletInfoSource, super.transactionDescriptionBox, super.keyService, super.sharedPreferences); + BackupServiceV3(super.secureStorage, super.transactionDescriptionBox, super.keyService, super.sharedPreferences); static BackupVersion get currentVersion => BackupVersion.v3; @@ -317,7 +318,6 @@ class BackupServiceV3 extends $BackupService { Future verifyHardwareWallets(String password, {String keychainSalt = secrets.backupKeychainSalt}) async { - final walletInfoSource = await reloadHiveWalletInfoBox(); final appDir = await getAppDir(); final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); final decryptedKeychainDumpFileData = await decryptV2( @@ -334,10 +334,7 @@ class BackupServiceV3 extends $BackupService { for (final expectedHardwareWallet in expectedHardwareWallets) { final info = expectedHardwareWallet as Map; - final actualWalletInfo = walletInfoSource.values - .where((e) => - e.name == info['name'] && e.type.toString() == info['type']) - .firstOrNull; + final actualWalletInfo = await WalletInfo.get(info['name'] as String, WalletType.values.firstWhere((e) => e.toString() == info['type'] as String)); if (actualWalletInfo != null && info["hardwareWalletType"] != actualWalletInfo.hardwareWalletType?.index) { diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 2cf7a7c634..11774a4b25 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -16,8 +16,7 @@ class WalletCreationService { {required WalletType initialType, required this.keyService, required this.sharedPreferences, - required this.settingsStore, - required this.walletInfoSource}) + required this.settingsStore}) : type = initialType { changeWalletType(type: type); } @@ -26,7 +25,6 @@ class WalletCreationService { final SharedPreferences sharedPreferences; final SettingsStore settingsStore; final KeyService keyService; - final Box walletInfoSource; WalletService? _service; static const _isNewMoneroWalletPasswordUpdated = true; @@ -36,23 +34,23 @@ class WalletCreationService { _service = getIt.get(param1: type); } - bool exists(String name) { + Future exists(String name) async { final walletName = name.toLowerCase(); - return walletInfoSource.values.any((walletInfo) => walletInfo.name.toLowerCase() == walletName); + return (await WalletInfo.getAll()).any((walletInfo) => walletInfo.name.toLowerCase() == walletName); } - bool typeExists(WalletType type) { - return walletInfoSource.values.any((walletInfo) => walletInfo.type == type); + Future typeExists(WalletType type) async { + return (await WalletInfo.getAll()).any((walletInfo) => walletInfo.type == type); } - void checkIfExists(String name) { - if (exists(name)) { + Future checkIfExists(String name) async { + if (await exists(name)) { throw Exception('Wallet with name ${name} already exists!'); } } Future create(WalletCredentials credentials, {bool? isTestnet}) async { - checkIfExists(credentials.name); + await checkIfExists(credentials.name); if (credentials.password == null) { credentials.password = generateWalletPassword(); @@ -83,12 +81,12 @@ class WalletCreationService { case WalletType.solana: case WalletType.tron: case WalletType.dogecoin: + case WalletType.nano: return true; case WalletType.monero: case WalletType.wownero: case WalletType.none: case WalletType.haven: - case WalletType.nano: case WalletType.banano: case WalletType.zano: case WalletType.decred: diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 4284671c33..e7ba355d8d 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -74,7 +74,7 @@ class WalletLoadingService { } catch (error, stack) { await ExceptionHandler.resetLastPopupDate(); final isLedgerError = await ExceptionHandler.isLedgerError(error); - if (isLedgerError || requireHardwareWalletConnection(type, name)) rethrow; + if (isLedgerError || await requireHardwareWalletConnection(type, name)) rethrow; await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack)); @@ -87,9 +87,8 @@ class WalletLoadingService { } // try opening another wallet that is not corrupted to give user access to the app - final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); WalletBase? wallet; - for (var walletInfo in walletInfoSource.values) { + for (var walletInfo in await WalletInfo.getAll()) { try { final walletService = walletServiceFactory.call(walletInfo.type); final walletPassword = await keyService.getWalletPassword(walletName: walletInfo.name); @@ -191,8 +190,8 @@ class WalletLoadingService { return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}"; } - bool requireHardwareWalletConnection(WalletType type, String name) { + Future requireHardwareWalletConnection(WalletType type, String name) async { final walletService = walletServiceFactory.call(type); - return walletService.requireHardwareWalletConnection(name); + return await walletService.requireHardwareWalletConnection(name); } } diff --git a/lib/decred/cw_decred.dart b/lib/decred/cw_decred.dart index 2838959364..7fe2ee5c20 100644 --- a/lib/decred/cw_decred.dart +++ b/lib/decred/cw_decred.dart @@ -19,9 +19,8 @@ class CWDecred extends Decred { DecredRestoreWalletFromPubkeyCredentials(name: name, pubkey: pubkey, password: password); @override - WalletService createDecredWalletService( - Box walletInfoSource, Box unspentCoinSource) { - return DecredWalletService(walletInfoSource, unspentCoinSource); + WalletService createDecredWalletService(Box unspentCoinSource) { + return DecredWalletService(unspentCoinSource); } @override @@ -53,7 +52,7 @@ class CWDecred extends Decred { .toList(), priority: priority as DecredTransactionPriority); - List getAddressInfos(Object wallet) { + List getAddressInfos(Object wallet) { final decredWallet = wallet as DecredWallet; return decredWallet.walletAddresses.getAddressInfos(); } diff --git a/lib/di.dart b/lib/di.dart index e00bc5c652..effb9b46e0 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -295,7 +295,6 @@ import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart'; final getIt = GetIt.instance; var _isSetupFinished = false; -late Box _walletInfoSource; late Box _nodeSource; late Box _powNodeSource; late Box _contactSource; @@ -309,7 +308,6 @@ late Box _payjoinSessionSource; late Box _anonpayInvoiceInfoSource; Future setup({ - required Box walletInfoSource, required Box nodeSource, required Box powNodeSource, required Box contactSource, @@ -324,7 +322,6 @@ Future setup({ required SecureStorage secureStorage, required GlobalKey navigatorKey, }) async { - _walletInfoSource = walletInfoSource; _nodeSource = nodeSource; _powNodeSource = powNodeSource; _contactSource = contactSource; @@ -407,7 +404,7 @@ Future setup({ keyService: getIt.get(), sharedPreferences: getIt.get(), settingsStore: getIt.get(), - walletInfoSource: _walletInfoSource)); + )); getIt.registerFactoryParam( (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get())); @@ -421,17 +418,17 @@ Future setup({ (newWalletArgs, _) => WalletNewVM( getIt.get(), getIt.get(param1:newWalletArgs.type), - _walletInfoSource, getIt.get(param1: newWalletArgs.type), getIt.get(), - newWalletArguments: newWalletArgs,)); + newWalletArguments: newWalletArgs, + )); - getIt.registerFactory(() => NewWalletTypeViewModel(_walletInfoSource)); + final walletList = await WalletInfo.getAll(); + getIt.registerFactory(() => NewWalletTypeViewModel(walletList.isNotEmpty)); getIt.registerFactory( () => WalletManager( - _walletInfoSource, getIt.get(), ), ); @@ -510,7 +507,6 @@ Future setup({ getIt.get(), getIt.get(), getIt.get(param1: type), - _walletInfoSource, getIt.get(), type: type)); @@ -535,7 +531,6 @@ Future setup({ getIt.get(), getIt.get(), getIt.get(), - _walletInfoSource, getIt.get(), ), ); @@ -614,7 +609,7 @@ Future setup({ getIt.registerFactory(instanceName: 'login', () { return AuthPage(getIt.get(), closable: false, - onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) { + onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) async { if (!isAuthenticated) { return; } @@ -655,7 +650,7 @@ Future setup({ ); } else { // wallet is already loaded: - if (appStore.wallet != null || requireHardwareWalletConnection()) { + if (appStore.wallet != null || await requireHardwareWalletConnection()) { // goes to the dashboard: authStore.allowed(); // trigger any deep links: @@ -819,8 +814,7 @@ Future setup({ getIt.get().wallet!.isHardwareWallet ? getIt.get() : null, coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb, getIt.get(param1: coinTypeToSpendFrom), - getIt.get(), - _walletInfoSource, + getIt.get() ), ); @@ -839,7 +833,6 @@ Future setup({ if (DeviceInfo.instance.isMobile) { getIt.registerFactory( () => WalletListViewModel( - _walletInfoSource, getIt.get(), getIt.get(), getIt.get(), @@ -850,7 +843,6 @@ Future setup({ // from multiple places at the same time (Wallets DropDown, Wallets List in settings) getIt.registerLazySingleton( () => WalletListViewModel( - _walletInfoSource, getIt.get(), getIt.get(), getIt.get(), @@ -862,7 +854,7 @@ Future setup({ (Function(BuildContext)? onWalletLoaded, _) => WalletListPage( walletListViewModel: getIt.get(), authService: getIt.get(), - onWalletLoaded: onWalletLoaded, + onWalletLoaded: onWalletLoaded as Future Function(BuildContext)?, )); getIt.registerFactoryParam( @@ -1006,7 +998,7 @@ Future setup({ getIt.registerFactoryParam( (CryptoCurrency? cur, _) => - ContactListViewModel(_contactSource, _walletInfoSource, cur, getIt.get())); + ContactListViewModel(_contactSource, walletList, cur, getIt.get())); getIt.registerFactoryParam((CryptoCurrency? cur, _) => ContactListPage(getIt.get(param1: cur), getIt.get())); @@ -1160,60 +1152,51 @@ Future setup({ getIt.registerFactory(() => PaymentViewModel( appStore: getIt.get(), - walletInfoSource: _walletInfoSource, )); getIt.registerFactory(() => WalletSwitcherViewModel( appStore: getIt.get(), walletLoadingService: getIt.get(), - walletInfoSource: _walletInfoSource, )); getIt.registerFactoryParam((WalletType param1, __) { switch (param1) { case WalletType.monero: - return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource); + return monero!.createMoneroWalletService(_unspentCoinsInfoSource); case WalletType.bitcoin: return bitcoin!.createBitcoinWalletService( - _walletInfoSource, _unspentCoinsInfoSource, _payjoinSessionSource, SettingsStoreBase.walletPasswordDirectInput, ); case WalletType.litecoin: return bitcoin!.createLitecoinWalletService( - _walletInfoSource, _unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput, ); case WalletType.ethereum: - return ethereum!.createEthereumWalletService( - _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return ethereum!.createEthereumWalletService(SettingsStoreBase.walletPasswordDirectInput); case WalletType.bitcoinCash: - return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, - _unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return bitcoinCash!.createBitcoinCashWalletService(_unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput); case WalletType.dogecoin: - return dogecoin!.createDogeCoinWalletService(_walletInfoSource, - _unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return dogecoin!.createDogeCoinWalletService(_unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput); case WalletType.nano: case WalletType.banano: - return nano!.createNanoWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return nano!.createNanoWalletService(SettingsStoreBase.walletPasswordDirectInput); case WalletType.polygon: - return polygon!.createPolygonWalletService( - _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return polygon!.createPolygonWalletService(SettingsStoreBase.walletPasswordDirectInput); case WalletType.solana: - return solana!.createSolanaWalletService( - _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return solana!.createSolanaWalletService(SettingsStoreBase.walletPasswordDirectInput); case WalletType.tron: - return tron!.createTronWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return tron!.createTronWalletService(SettingsStoreBase.walletPasswordDirectInput); case WalletType.wownero: - return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource); + return wownero!.createWowneroWalletService(_unspentCoinsInfoSource); case WalletType.zano: - return zano!.createZanoWalletService(_walletInfoSource); + return zano!.createZanoWalletService(); case WalletType.decred: - return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource); + return decred!.createDecredWalletService(_unspentCoinsInfoSource); case WalletType.haven: - return HavenWalletService(_walletInfoSource); + return HavenWalletService(); case WalletType.none: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } @@ -1238,7 +1221,7 @@ Future setup({ getIt.registerFactoryParam((WalletType type, restoredWallet) => WalletRestoreViewModel(getIt.get(), getIt.get(param1: type), - _walletInfoSource, getIt.get(), + getIt.get(), type: type, restoredWallet: restoredWallet)); getIt.registerFactoryParam((WalletType type, @@ -1313,7 +1296,7 @@ Future setup({ getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get())); - getIt.registerFactory(() => BackupServiceV3(getIt.get(), _walletInfoSource, + getIt.registerFactory(() => BackupServiceV3(getIt.get(), _transactionDescriptionBox, getIt.get(), getIt.get())); diff --git a/lib/dogecoin/cw_dogecoin.dart b/lib/dogecoin/cw_dogecoin.dart index e78119306e..90aaa7b51d 100644 --- a/lib/dogecoin/cw_dogecoin.dart +++ b/lib/dogecoin/cw_dogecoin.dart @@ -4,9 +4,8 @@ part of 'dogecoin.dart'; class CWDogeCoin extends DogeCoin { @override - WalletService createDogeCoinWalletService( - Box walletInfoSource, Box unspentCoinSource, bool isDirect) { - return DogeCoinWalletService(walletInfoSource, unspentCoinSource, isDirect); + WalletService createDogeCoinWalletService(Box unspentCoinSource, bool isDirect) { + return DogeCoinWalletService(unspentCoinSource, isDirect); } @override diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 6077ade30a..49eb31f3b2 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/haven_seed_store.dart'; +import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cw_core/root_dir.dart'; @@ -21,6 +22,7 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/fs_migration.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_info_legacy.dart' as wiLegacy; import 'package:cake_wallet/exchange/trade.dart'; import 'package:encrypt/encrypt.dart' as encrypt; import 'package:collection/collection.dart'; @@ -54,12 +56,11 @@ Future defaultSettingsMigration( required SecureStorage secureStorage, required Box nodes, required Box powNodes, - required Box walletInfoSource, required Box tradeSource, required Box contactSource, required Box havenSeedStore}) async { if (Platform.isIOS) { - await ios_migrate_v1(walletInfoSource, tradeSource, contactSource); + await ios_migrate_v1(tradeSource, contactSource); } // check current nodes for nullability regardless of the version @@ -68,7 +69,7 @@ Future defaultSettingsMigration( final isNewInstall = sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null; - await _validateWalletInfoBoxData(walletInfoSource); + await _validateWalletInfoBoxData(); await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall); @@ -161,7 +162,7 @@ Future defaultSettingsMigration( break; case 5: - await addAddressesForMoneroWallets(walletInfoSource); + await addAddressesForMoneroWallets(); break; case 6: @@ -322,7 +323,7 @@ Future defaultSettingsMigration( await updateNanoNodeList(nodes: nodes); break; case 32: - await updateBtcNanoWalletInfos(walletInfoSource); + await updateBtcNanoWalletInfos(); break; case 33: await addWalletNodeList(nodes: nodes, type: WalletType.tron); @@ -360,7 +361,7 @@ Future defaultSettingsMigration( // await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes); break; case 38: - await fixBtcDerivationPaths(walletInfoSource); + await fixBtcDerivationPaths(); break; case 39: _fixNodesUseSSLFlag(nodes); @@ -726,7 +727,7 @@ Future _updateMoneroPriority(SharedPreferences sharedPreferences) async { } } -Future _validateWalletInfoBoxData(Box walletInfoSource) async { +Future _validateWalletInfoBoxData() async { try { final root = await getAppDir(); @@ -768,7 +769,7 @@ Future _validateWalletInfoBoxData(Box walletInfoSource) async } final id = prefix + '_' + name; - final exist = walletInfoSource.values.any((el) => el.id == id); + final exist = (await WalletInfo.getAll()).any((el) => el.id == id); if (exist) { continue; @@ -787,7 +788,7 @@ Future _validateWalletInfoBoxData(Box walletInfoSource) async showIntroCakePayCard: false, ); - walletInfoSource.add(walletInfo); + await walletInfo.save(); } } } catch (_) {} @@ -955,8 +956,8 @@ Future updateNodeTypes({required Box nodes}) async { }); } -Future addAddressesForMoneroWallets(Box walletInfoSource) async { - final moneroWalletsInfo = walletInfoSource.values.where((info) => info.type == WalletType.monero); +Future addAddressesForMoneroWallets() async { + final moneroWalletsInfo = (await WalletInfo.getAll()).where((info) => info.type == WalletType.monero); moneroWalletsInfo.forEach((info) async { try { final walletPath = await pathForWallet(name: info.name, type: WalletType.monero); @@ -1004,32 +1005,34 @@ Future changeTransactionPriorityAndFeeRateKeys(SharedPreferences sharedPre bitcoin!.getMediumTransactionPriority().serialize()); } -Future fixBtcDerivationPaths(Box walletsInfoSource) async { - for (WalletInfo walletInfo in walletsInfoSource.values) { +Future fixBtcDerivationPaths() async { + for (WalletInfo walletInfo in await WalletInfo.getAll()) { if (walletInfo.type == WalletType.bitcoin || walletInfo.type == WalletType.bitcoinCash || walletInfo.type == WalletType.litecoin) { - if (walletInfo.derivationInfo?.derivationPath == "m/0'/0") { - walletInfo.derivationInfo!.derivationPath = "m/0'"; + final derivationInfo = await walletInfo.getDerivationInfo(); + if (derivationInfo?.derivationPath == "m/0'/0") { + derivationInfo!.derivationPath = "m/0'"; await walletInfo.save(); } } } } - -Future updateBtcNanoWalletInfos(Box walletsInfoSource) async { - for (WalletInfo walletInfo in walletsInfoSource.values) { - if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) { - walletInfo.derivationInfo = DerivationInfo( - derivationPath: walletInfo.derivationPath, - derivationType: walletInfo.derivationType, - address: walletInfo.address, - transactionsCount: walletInfo.restoreHeight, - ); - await walletInfo.save(); - } - } -} +Future updateBtcNanoWalletInfos() async {} +// Future updateBtcNanoWalletInfos() async { +// for (WalletInfo walletInfo in await WalletInfo.getAll()) { +// if (walletInfo.type == WalletType.nano || walletInfo.type == WalletType.bitcoin) { +// final derivationInfo = await walletInfo.getDerivationInfo(); +// derivationInfo = DerivationInfo( +// derivationPath: derivationInfo?.derivationPath, +// derivationType: derivationInfo?.derivationType, +// address: walletInfo.address, +// transactionsCount: walletInfo.restoreHeight, +// ); +// await walletInfo.save(); +// } +// } +// } Future checkCurrentNodes( Box nodeSource, Box powNodeSource, SharedPreferences sharedPreferences) async { diff --git a/lib/entities/fs_migration.dart b/lib/entities/fs_migration.dart index a7604f4f14..a85c579b19 100644 --- a/lib/entities/fs_migration.dart +++ b/lib/entities/fs_migration.dart @@ -29,8 +29,7 @@ Future migrate_android_v1() async { await android_migrate_wallets(appDocDir: appDocDir); } -Future ios_migrate_v1( - Box walletInfoSource, Box tradeSource, Box contactSource) async { +Future ios_migrate_v1(Box tradeSource, Box contactSource) async { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_v1_completed') ?? false) { @@ -40,7 +39,7 @@ Future ios_migrate_v1( await ios_migrate_user_defaults(); await ios_migrate_pin(); await ios_migrate_wallet_passwords(); - await ios_migrate_wallet_info(walletInfoSource); + await ios_migrate_wallet_info(); await ios_migrate_trades_list(tradeSource); await ios_migrate_address_book(contactSource); @@ -278,7 +277,7 @@ Future android_migrate_wallets({required Directory appDocDir}) async { }); } -Future ios_migrate_wallet_info(Box walletsInfoSource) async { +Future ios_migrate_wallet_info() async { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_wallet_info_completed') ?? false) { @@ -289,6 +288,7 @@ Future ios_migrate_wallet_info(Box walletsInfoSource) async { final appDocDir = await getApplicationDocumentsDirectory(); final walletsDir = Directory('${appDocDir.path}/wallets'); final moneroWalletsDir = Directory('${walletsDir.path}/monero'); + final walletsInfo = await WalletInfo.getAll(); final infoRecords = moneroWalletsDir .listSync() .map((item) { @@ -307,7 +307,7 @@ Future ios_migrate_wallet_info(Box walletsInfoSource) async { final timestamp = dateAsDouble.toInt() * 1000; final date = DateTime.fromMillisecondsSinceEpoch(timestamp); final id = walletTypeToString(WalletType.monero).toLowerCase() + '_' + name; - final exist = walletsInfoSource.values.firstWhereOrNull((el) => el.id == id) != null; + final exist = walletsInfo.firstWhereOrNull((el) => el.id == id) != null; if (exist) { return null; @@ -334,7 +334,9 @@ Future ios_migrate_wallet_info(Box walletsInfoSource) async { .where((el) => el != null) .whereType() .toList(); - await walletsInfoSource.addAll(infoRecords); + for (final info in infoRecords) { + await info.save(); + } await prefs.setBool('ios_migration_wallet_info_completed', true); } catch (e) { printV(e.toString()); diff --git a/lib/entities/hardware_wallet/require_hardware_wallet_connection.dart b/lib/entities/hardware_wallet/require_hardware_wallet_connection.dart index a64fa5873d..3da6dcb5b2 100644 --- a/lib/entities/hardware_wallet/require_hardware_wallet_connection.dart +++ b/lib/entities/hardware_wallet/require_hardware_wallet_connection.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:shared_preferences/shared_preferences.dart'; -bool requireHardwareWalletConnection() { +Future requireHardwareWalletConnection() async { final name = getIt .get() .getString(PreferencesKey.currentWalletName); @@ -21,5 +21,5 @@ bool requireHardwareWalletConnection() { final type = deserializeFromInt(typeRaw); final walletLoadingService = getIt.get(); - return walletLoadingService.requireHardwareWalletConnection(type, name); + return await walletLoadingService.requireHardwareWalletConnection(type, name); } diff --git a/lib/entities/wallet_manager.dart b/lib/entities/wallet_manager.dart index 09b7642897..dd88c93638 100644 --- a/lib/entities/wallet_manager.dart +++ b/lib/entities/wallet_manager.dart @@ -8,17 +8,16 @@ import 'package:hive/hive.dart'; import 'package:shared_preferences/shared_preferences.dart'; class WalletManager { - WalletManager(this._walletInfoSource, this._sharedPreferences); + WalletManager(this._sharedPreferences); - final Box _walletInfoSource; final SharedPreferences _sharedPreferences; final List walletGroups = []; - void updateWalletGroups() { + Future updateWalletGroups() async { walletGroups.clear(); - for (final walletInfo in _walletInfoSource.values) { + for (final walletInfo in await WalletInfo.getAll()) { final groupKey = _resolveGroupKey(walletInfo); final group = _getOrCreateGroup(groupKey); group.wallets.add(walletInfo); @@ -115,7 +114,7 @@ class WalletManager { // 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. + await updateWalletGroups(); // Still skeptical of calling this here. Looking for a better spot. return; } @@ -123,7 +122,7 @@ class WalletManager { 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 oldGroupWallets = (await WalletInfo.getAll()).where((w) { final key = w.hashedWalletIdentifier != null && w.hashedWalletIdentifier!.isNotEmpty ? w.hashedWalletIdentifier : (w.parentAddress ?? w.address); @@ -150,7 +149,7 @@ class WalletManager { } // Finally, we rebuild the groups so that these wallets are now in the new group - updateWalletGroups(); + await updateWalletGroups(); } /// Copy an old group name to the new group key, then remove the old key. diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 9f199443de..6e327e039c 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -4,8 +4,8 @@ class CWEthereum extends Ethereum { @override List getEthereumWordList(String language) => EVMChainMnemonics.englishWordlist; - WalletService createEthereumWalletService(Box walletInfoSource, bool isDirect) => - EthereumWalletService(walletInfoSource, isDirect, client: EthereumClient()); + WalletService createEthereumWalletService(bool isDirect) => + EthereumWalletService(isDirect, client: EthereumClient()); @override WalletCredentials createEthereumNewWalletCredentials({ @@ -181,9 +181,10 @@ class CWEthereum extends Ethereum { String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { + Future setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) async { + final derivationInfo = await wallet.walletInfo.getDerivationInfo(); ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) - .setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath); + .setLedgerConnection(connection, derivationInfo.derivationPath); } @override diff --git a/lib/haven/cw_haven.dart b/lib/haven/cw_haven.dart index b69e733c3a..53cadb924e 100644 --- a/lib/haven/cw_haven.dart +++ b/lib/haven/cw_haven.dart @@ -12,16 +12,15 @@ import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; class HavenWalletService extends WalletService { - final Box walletInfoSource; - HavenWalletService(this.walletInfoSource); + HavenWalletService(); @override WalletType getType() => WalletType.haven; @override Future remove(String wallet) async { - final path = await pathForWalletDir(name: wallet, type: WalletType.haven); + final path = await pathForWalletDir(name: wallet, type: getType()); final file = Directory(path); final isExist = file.existsSync(); @@ -30,9 +29,11 @@ class HavenWalletService extends WalletService { await file.delete(recursive: true); } - final walletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); - await walletInfoSource.delete(walletInfo.key); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw Exception('Wallet not found'); + } + await WalletInfo.delete(walletInfo); } @override diff --git a/lib/locales/yoruba_intl.dart b/lib/locales/yoruba_intl.dart index 66afd8f058..684c9420e6 100644 --- a/lib/locales/yoruba_intl.dart +++ b/lib/locales/yoruba_intl.dart @@ -1032,7 +1032,7 @@ class YoCupertinoLocalizations extends GlobalCupertinoLocalizations { @override // TODO: implement backButtonLabel String get backButtonLabel => "backButtonLabel"; - + @override // TODO: implement cancelButtonLabel String get cancelButtonLabel => "cancelButtonLabel"; diff --git a/lib/main.dart b/lib/main.dart index 2433f269f6..63b547cb27 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,8 +47,9 @@ import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_logger/memory_proxy_logger.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:cw_core/utils/tor/abstract.dart'; +import 'package:cw_core/db/sqlite.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/utils/tor/abstract.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -85,12 +86,16 @@ Future runAppWithZone({Key? topLevelKey}) async { return true; }; + await FlutterDaemon().unmarkBackgroundSync(); + await initDb(); + try { CakeTor.instance = await CakeTorInstance.getInstance(); } catch (e) { printV("Failed to initialize tor: $e"); } + await initializeAppAtRoot(); if (kDebugMode) { @@ -163,22 +168,6 @@ Future initializeAppConfigs({bool loadWallet = true}) async { CakeHive.registerAdapter(AddressInfoAdapter()); } - if (!CakeHive.isAdapterRegistered(WalletInfo.typeId)) { - CakeHive.registerAdapter(WalletInfoAdapter()); - } - - if (!CakeHive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) { - CakeHive.registerAdapter(DerivationTypeAdapter()); - } - - if (!CakeHive.isAdapterRegistered(DERIVATION_INFO_TYPE_ID)) { - CakeHive.registerAdapter(DerivationInfoAdapter()); - } - - if (!CakeHive.isAdapterRegistered(HARDWARE_WALLET_TYPE_TYPE_ID)) { - CakeHive.registerAdapter(HardwareWalletTypeAdapter()); - } - if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) { CakeHive.registerAdapter(WalletTypeAdapter()); } @@ -226,6 +215,7 @@ Future initializeAppConfigs({bool loadWallet = true}) async { if (!CakeHive.isAdapterRegistered(TronToken.typeId)) { CakeHive.registerAdapter(TronTokenAdapter()); } + await performHiveMigration(); final secureStorage = secureStorageShared; final transactionDescriptionsBoxKey = @@ -241,7 +231,6 @@ Future initializeAppConfigs({bool loadWallet = true}) async { encryptionKey: transactionDescriptionsBoxKey); final trades = await CakeHive.openBox(Trade.boxName, encryptionKey: tradesBoxKey); final orders = await CakeHive.openBox(Order.boxName, encryptionKey: ordersBoxKey); - final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); final templates = await CakeHive.openBox