From 76876181e593f8c5ae4cfa3af5215cc3fbe4028f Mon Sep 17 00:00:00 2001 From: cyan Date: Fri, 31 Oct 2025 22:37:15 +0100 Subject: [PATCH] feat: initial hive -> SQLite (#2502) * feat: initial hive -> SQLite Includes migration script and initial work done to remove dependency on hive generators. Also contains some refactoring to make sync code async. Features new dev screen that lists all SQLite tables Aspects that need testing: - backups (from this version, and from previous versions as well) - wallet loading / wallet list - almost everything else - renaming - deletion - sorting in wallet list - wallet groups - wallet seed/keys - wallet switching - wallet creation / restore - all methods - wallet address page - hardware wallet connection (and prompts from mall screens) - contacts - advanced settings in wallet creation - derivation paths - some minor wownero fixes - base, hardware wallets * fix: build errors * fix: Bad state: No element in backup restore * address comments from review * fix: HardwareWalletType --- .github/workflows/pr_test_build_linux.yml | 4 +- Dockerfile | 2 + cw_base/lib/base_wallet.dart | 2 + cw_base/lib/base_wallet_service.dart | 27 +- cw_bitcoin/lib/bitcoin_wallet.dart | 20 +- .../bitcoin_wallet_creation_credentials.dart | 4 + cw_bitcoin/lib/bitcoin_wallet_service.dart | 40 +- cw_bitcoin/lib/electrum_wallet.dart | 18 +- cw_bitcoin/lib/litecoin_wallet.dart | 17 +- cw_bitcoin/lib/litecoin_wallet_service.dart | 38 +- cw_bitcoin/pubspec.lock | 64 ++ .../lib/src/bitcoin_cash_wallet.dart | 4 + .../lib/src/bitcoin_cash_wallet_service.dart | 25 +- cw_core/lib/db/sqlite.dart | 114 ++++ cw_core/lib/wallet_addresses.dart | 25 +- .../lib/wallet_addresses_with_account.dart | 13 - cw_core/lib/wallet_base.dart | 3 +- cw_core/lib/wallet_credentials.dart | 6 +- cw_core/lib/wallet_info.dart | 550 +++++++++++++++--- cw_core/lib/wallet_info_legacy.dart | 308 ++++++++++ cw_core/lib/wallet_info_legacy.part.dart | 287 +++++++++ cw_core/lib/wallet_service.dart | 2 +- cw_core/pubspec.lock | 64 ++ cw_core/pubspec.yaml | 4 +- cw_decred/lib/wallet.dart | 12 +- cw_decred/lib/wallet_addresses.dart | 38 +- cw_decred/lib/wallet_service.dart | 67 ++- cw_decred/pubspec.lock | 64 ++ cw_dogecoin/lib/src/dogecoin_wallet.dart | 5 + .../lib/src/dogecoin_wallet_service.dart | 28 +- cw_ethereum/lib/ethereum_wallet.dart | 2 + cw_ethereum/lib/ethereum_wallet_service.dart | 29 +- cw_evm/lib/evm_chain_wallet.dart | 3 +- cw_evm/lib/evm_chain_wallet_service.dart | 11 +- .../evm_chain_ledger_credentials.dart | 4 +- cw_monero/lib/ledger.dart | 2 +- cw_monero/lib/monero_wallet.dart | 7 +- cw_monero/lib/monero_wallet_addresses.dart | 9 +- cw_monero/lib/monero_wallet_service.dart | 62 +- cw_monero/pubspec.lock | 64 ++ cw_monero/pubspec.yaml | 1 + .../test/monero_wallet_service_test.dart | 11 +- cw_nano/lib/nano_wallet.dart | 11 +- .../lib/nano_wallet_creation_credentials.dart | 1 + cw_nano/lib/nano_wallet_service.dart | 47 +- cw_nano/pubspec.lock | 64 ++ cw_polygon/lib/polygon_wallet.dart | 4 + cw_polygon/lib/polygon_wallet_service.dart | 29 +- .../lib/cw_shared_external.dart | 2 +- cw_solana/lib/solana_wallet.dart | 6 +- cw_solana/lib/solana_wallet_service.dart | 28 +- cw_tron/lib/tron_wallet.dart | 6 +- cw_tron/lib/tron_wallet_service.dart | 29 +- cw_wownero/lib/api/wallet.dart | 25 +- cw_wownero/lib/wownero_wallet.dart | 4 +- cw_wownero/lib/wownero_wallet_addresses.dart | 8 +- cw_wownero/lib/wownero_wallet_service.dart | 39 +- cw_wownero/pubspec.lock | 64 ++ cw_zano/lib/zano_wallet.dart | 12 +- cw_zano/lib/zano_wallet_service.dart | 25 +- cw_zano/pubspec.lock | 64 ++ lib/base/cw_base.dart | 11 +- lib/bitcoin/cw_bitcoin.dart | 11 +- lib/bitcoin_cash/cw_bitcoin_cash.dart | 5 +- lib/core/backup_service.dart | 36 +- lib/core/backup_service_v3.dart | 9 +- lib/core/wallet_creation_service.dart | 20 +- lib/core/wallet_loading_service.dart | 9 +- lib/decred/cw_decred.dart | 7 +- lib/di.dart | 67 +-- lib/dogecoin/cw_dogecoin.dart | 5 +- lib/entities/default_settings_migration.dart | 61 +- lib/entities/fs_migration.dart | 14 +- .../require_hardware_wallet_connection.dart | 4 +- lib/entities/wallet_manager.dart | 13 +- lib/ethereum/cw_ethereum.dart | 13 +- lib/haven/cw_haven.dart | 13 +- lib/locales/yoruba_intl.dart | 2 +- lib/main.dart | 31 +- lib/monero/cw_monero.dart | 9 +- lib/nano/cw_nano.dart | 4 +- lib/polygon/cw_polygon.dart | 12 +- .../on_authentication_state_change.dart | 4 +- lib/reactions/on_current_wallet_change.dart | 4 +- lib/solana/cw_solana.dart | 4 +- .../screens/contact/contact_list_page.dart | 5 +- .../screens/new_wallet/new_wallet_page.dart | 2 +- .../screens/restore/wallet_restore_page.dart | 2 +- .../screens/settings/other_settings_page.dart | 12 + lib/src/screens/wallet/wallet_edit_page.dart | 16 +- .../screens/wallet_list/wallet_list_page.dart | 36 +- .../wallet_switcher_bottom_sheet.dart | 139 ++--- lib/store/app_store.dart | 4 +- lib/store/yat/yat_store.dart | 4 +- lib/tron/cw_tron.dart | 4 +- lib/utils/exception_handler.dart | 2 + lib/utils/token_utilities.dart | 30 +- .../contact_list/contact_list_view_model.dart | 29 +- .../dashboard/dashboard_view_model.dart | 9 +- .../exchange/exchange_view_model.dart | 10 +- .../hardware_wallet/bitbox_view_model.dart | 8 +- .../hardware_wallet_view_model.dart | 2 +- .../hardware_wallet/ledger_view_model.dart | 10 +- .../hardware_wallet/trezor_view_model.dart | 8 +- .../new_wallet_type_view_model.dart | 9 +- .../payment/payment_view_model.dart | 8 +- .../restore_from_backup_view_model.dart | 4 +- lib/view_model/send/send_view_model.dart | 6 +- .../settings/other_settings_view_model.dart | 2 +- lib/view_model/wallet_creation_vm.dart | 26 +- .../wallet_groups_display_view_model.dart | 46 +- .../wallet_hardware_restore_view_model.dart | 7 +- .../wallet_list/wallet_edit_view_model.dart | 2 +- .../wallet_list/wallet_list_view_model.dart | 123 ++-- lib/view_model/wallet_new_vm.dart | 3 +- lib/view_model/wallet_restore_view_model.dart | 5 +- .../wallet_switcher_view_model.dart | 10 +- lib/wownero/cw_wownero.dart | 5 +- lib/zano/cw_zano.dart | 4 +- tool/configure.dart | 42 +- tool/localization/localization_constants.dart | 10 +- 121 files changed, 2623 insertions(+), 917 deletions(-) create mode 100644 cw_core/lib/db/sqlite.dart delete mode 100644 cw_core/lib/wallet_addresses_with_account.dart create mode 100644 cw_core/lib/wallet_info_legacy.dart create mode 100644 cw_core/lib/wallet_info_legacy.part.dart diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 746bdcf17..0e736ee64 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -308,8 +308,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 5daa6a3f1..a4a0c19c8 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_base/lib/base_wallet.dart b/cw_base/lib/base_wallet.dart index accb336ea..d1287da78 100644 --- a/cw_base/lib/base_wallet.dart +++ b/cw_base/lib/base_wallet.dart @@ -22,6 +22,7 @@ class BaseWallet extends EVMChainWallet { BaseWallet({ required super.walletInfo, required super.password, + required super.derivationInfo, super.mnemonic, super.initialBalance, super.privateKey, @@ -150,6 +151,7 @@ class BaseWallet extends EVMChainWallet { return BaseWallet( walletInfo: walletInfo, + derivationInfo: await walletInfo.getDerivationInfo(), password: password, mnemonic: keysData.mnemonic, privateKey: keysData.privateKey, diff --git a/cw_base/lib/base_wallet_service.dart b/cw_base/lib/base_wallet_service.dart index f7ef5e96b..7457cbd58 100644 --- a/cw_base/lib/base_wallet_service.dart +++ b/cw_base/lib/base_wallet_service.dart @@ -11,7 +11,6 @@ import 'package:cw_base/base_mnemonics_exception.dart'; class BaseWalletService extends EVMChainWalletService { BaseWalletService( - super.walletInfoSource, super.isDirect, { required this.client, }); @@ -29,6 +28,7 @@ class BaseWalletService extends EVMChainWalletService { final wallet = BaseWallet( walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), mnemonic: mnemonic, password: credentials.password!, passphrase: credentials.passphrase, @@ -44,8 +44,10 @@ class BaseWalletService 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 BaseWallet.open( @@ -84,6 +86,7 @@ class BaseWalletService extends EVMChainWalletService { password: credentials.password!, privateKey: credentials.privateKey, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -97,14 +100,17 @@ class BaseWalletService 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"; + await derivationInfo.save(); credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType; credentials.walletInfo!.address = credentials.hwAccountData.address; + await credentials.walletInfo!.save(); final wallet = BaseWallet( walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, password: credentials.password!, client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -128,6 +134,7 @@ class BaseWalletService extends EVMChainWalletService { password: credentials.password!, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), passphrase: credentials.passphrase, client: client, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -142,8 +149,10 @@ class BaseWalletService 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 BaseWallet.open( password: password, name: currentName, @@ -158,6 +167,6 @@ class BaseWalletService extends EVMChainWalletService { newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + newWalletInfo.save(); } } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 241ef344a..0a2b54913 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -47,6 +47,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, @@ -69,6 +70,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { xpub: xpub, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, network: networkParam == null ? BitcoinNetwork.mainnet @@ -133,7 +135,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, @@ -152,6 +156,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { passphrase: passphrase ?? "", password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialSilentAddresses: initialSilentAddresses, @@ -212,20 +217,21 @@ 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; + await derivationInfo.save(); Uint8List? seedBytes = null; final mnemonic = keysData.mnemonic; final passphrase = keysData.passphrase; if (mnemonic != null) { - switch (walletInfo.derivationInfo!.derivationType) { + switch (derivationInfo.derivationType) { case DerivationType.electrum: seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); @@ -246,6 +252,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { password: password, passphrase: passphrase, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp?.addresses, initialSilentAddresses: snp?.silentAddresses, @@ -484,7 +491,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 9be8df388..55d301d18 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 e5edfecc2..f742376fa 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -23,10 +23,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; @@ -40,7 +39,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; @@ -51,6 +56,7 @@ class BitcoinWalletService extends WalletService< mnemonic = await generateElectrumMnemonic(); break; } + await derivationInfo.save(); final wallet = await BitcoinWalletBase.create( mnemonic: mnemonic, @@ -75,8 +81,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, @@ -107,9 +115,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(); @@ -123,8 +133,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, @@ -141,7 +153,7 @@ class BitcoinWalletService extends WalletService< newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + await newWalletInfo.save(); } @override @@ -149,15 +161,18 @@ 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; final xpub = convertZpubToXpub(credentials.hwAccountData.xpub!); + await credentials.walletInfo!.save(); final wallet = await BitcoinWallet( password: credentials.password!, xpub: xpub, walletInfo: credentials.walletInfo!, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfoSource, networkParam: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), @@ -180,6 +195,7 @@ class BitcoinWalletService extends WalletService< password: credentials.password!, xpub: 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 cf948459c..816833cc1 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, @@ -75,9 +76,8 @@ abstract class ElectrumWalletBase ElectrumBalance? initialBalance, CryptoCurrency? currency, bool? alwaysScan, - }) - : accountHD = getAccountHDWallet(currency, network, seedBytes, xpub, - walletInfo.derivationInfo, walletInfo.hardwareWalletType), + }) : accountHD = + getAccountHDWallet(currency, network, seedBytes, xpub, derivationInfo, walletInfo.hardwareWalletType), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], @@ -100,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, @@ -771,8 +772,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); @@ -981,7 +983,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()] = @@ -1447,8 +1449,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 eef24040c..01c717117 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, @@ -168,6 +170,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, @@ -179,7 +182,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, @@ -195,6 +198,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialMwebAddresses: initialMwebAddresses, @@ -245,18 +249,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, @@ -269,12 +272,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, diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 84056225e..126b6a9a3 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 ff3133604..16ff71686 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -1028,6 +1028,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: @@ -1060,6 +1116,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 527774049..39065722b 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 931893ef8..1861b21f4 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 000000000..e8bba6689 --- /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 b0da0e7a1..4d4d2c0a5 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,12 @@ abstract class WalletAddresses { allAddressesMap = {}, addressInfos = {}, usedAddresses = {}, - hiddenAddresses = walletInfo.hiddenAddresses?.toSet() ?? {}, - manualAddresses = walletInfo.manualAddresses?.toSet() ?? {}; + hiddenAddresses = {}, + manualAddresses = {} { + walletInfo.getUsedAddresses().then((value) => usedAddresses = value); + walletInfo.getHiddenAddresses().then((value) => hiddenAddresses = value); + walletInfo.getManualAddresses().then((value) => manualAddresses = value); + } final WalletInfo walletInfo; @@ -47,7 +50,7 @@ abstract class WalletAddresses { return tmp; } - Map> addressInfos; + Map> addressInfos; Set usedAddresses; @@ -62,15 +65,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(); + walletInfo.setAddresses(addressesMap); + walletInfo.setAddressInfos(addressInfos); + walletInfo.setUsedAddresses(usedAddresses.toList()); + walletInfo.setHiddenAddresses(hiddenAddresses.toList()); + walletInfo.setManualAddresses(manualAddresses.toList()); - if (walletInfo.isInBox) { - await walletInfo.save(); - } + 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 0dcf88de0..000000000 --- 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 5ac3e9b45..dfcc2c460 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 30ae2546c..b9be31d28 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 4f849ec02..2fd18016b 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -1,47 +1,239 @@ 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'; +import 'package:sqflite/sqflite.dart'; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/wallet_info_legacy.dart' as wiLegacy; -part 'wallet_info.g.dart'; +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, - @HiveField(1) bitbox, - @HiveField(2) cupcake, - @HiveField(3) coldcard, - @HiveField(4) seedsigner, - @HiveField(5) keystone, - @HiveField(6) trezor, } -@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 = "", @@ -51,33 +243,77 @@ class DerivationInfo extends HiveObject { this.description, }); - static const typeId = DERIVATION_INFO_TYPE_ID; + int id; + + static String get tableName => 'walletInfoDerivationInfo'; + static String get selfIdColumn => "${tableName}Id"; - @HiveField(0, defaultValue: '') String address; - - @HiveField(1, defaultValue: '') String balance; - - @HiveField(2, defaultValue: 0) int transactionsCount; - - @HiveField(3) DerivationType? derivationType; - - @HiveField(4) String? derivationPath; + String? scriptType; + String? description; - @HiveField(5) - final String? scriptType; + 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(6) - final String? description; + Map toJson() { + return { + selfIdColumn: id, + "address": address, + "balance": balance, + "transactionsCount": transactionsCount, + "derivationType": derivationType?.index, + "derivationPath": derivationPath, + "scriptType": scriptType, + "description": 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, @@ -90,11 +326,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({ @@ -110,13 +347,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, @@ -129,96 +368,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"; + + int internalId; - @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; + 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); + } + } + + Future addAddress(String address, WalletInfoAddressType type) async { + await WalletInfoAddress.insert(internalId, type, address); + } - @HiveField(18) String? addressPageType; - - @HiveField(19) 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 ?? ''; @@ -248,6 +526,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 000000000..502edeb6b --- /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 000000000..d58826e09 --- /dev/null +++ b/cw_core/lib/wallet_info_legacy.part.dart @@ -0,0 +1,287 @@ +// 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; + case 1: + return newWi.HardwareWalletType.bitbox; + case 2: + return newWi.HardwareWalletType.cupcake; + case 3: + return newWi.HardwareWalletType.coldcard; + case 4: + return newWi.HardwareWalletType.seedsigner; + case 5: + return newWi.HardwareWalletType.keystone; + case 6: + return newWi.HardwareWalletType.trezor; + default: + return newWi.HardwareWalletType.ledger; + } + } + + @override + void write(BinaryWriter writer, newWi.HardwareWalletType obj) { + switch (obj) { + case newWi.HardwareWalletType.ledger: + writer.writeByte(0); + break; + case newWi.HardwareWalletType.bitbox: + writer.writeByte(1); + break; + case newWi.HardwareWalletType.cupcake: + writer.writeByte(2); + break; + case newWi.HardwareWalletType.coldcard: + writer.writeByte(3); + break; + case newWi.HardwareWalletType.seedsigner: + writer.writeByte(4); + break; + case newWi.HardwareWalletType.keystone: + writer.writeByte(5); + break; + case newWi.HardwareWalletType.trezor: + writer.writeByte(6); + 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 85126853e..016f3cc68 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 2cfa93590..918a81a0b 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 ed1f90c9f..7c963f246 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 @@ -44,6 +44,8 @@ dependencies: git: url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v2 + 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 fb177f4ca..432edb4bc 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 860a576d9..e4af108b9 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 93c708886..afcf6d5db 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 83fc4d214..855857215 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 7e0b61a90..964c34b85 100644 --- a/cw_dogecoin/lib/src/dogecoin_wallet.dart +++ b/cw_dogecoin/lib/src/dogecoin_wallet.dart @@ -26,6 +26,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, @@ -39,6 +40,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, network: DogecoinNetwork.mainnet, initialAddresses: initialAddresses, @@ -83,6 +85,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, @@ -95,6 +98,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + derivationInfo: derivationInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialBalance: initialBalance, @@ -148,6 +152,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 d0e3b2969..988abd2e7 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 48cd67806..edf101171 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, @@ -176,6 +177,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 d208fdcc6..1976c277d 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 3bf066c9d..e1058da64 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -67,6 +67,7 @@ abstract class EVMChainWalletBase with Store, WalletKeysFile { EVMChainWalletBase({ required WalletInfo walletInfo, + required DerivationInfo derivationInfo, required EVMChainClient client, required CryptoCurrency nativeCurrency, String? mnemonic, @@ -88,7 +89,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 e6bb41b86..ea6e68a23 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/hardware/evm_chain_ledger_credentials.dart b/cw_evm/lib/hardware/evm_chain_ledger_credentials.dart index 13a93cfb2..fdf7a5c14 100644 --- a/cw_evm/lib/hardware/evm_chain_ledger_credentials.dart +++ b/cw_evm/lib/hardware/evm_chain_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 7a3c95a27..7707acd48 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 57cc9a3fc..9f5986e26 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 d0cf87489..0b38ac5fd 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 6fc827068..f38341923 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 23944e496..ae521ab8d 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 1eb8155a7..3877032b8 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 a809dbc23..3b24c205b 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 cab02921b..2cf7fd30d 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, @@ -433,11 +434,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 8eb6dc2d2..d2a8db70b 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 5c073c9c0..0dd57928f 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 b14c7bda8..c009b6502 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 ed0e54eb1..5b587992f 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, @@ -152,8 +153,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 ece7a6b23..23a31ece6 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 37d0350dd..1d9df69a2 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 581ba5622..245f1e722 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, @@ -449,8 +450,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 faeb24a1b..a16f3ee6f 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( @@ -88,9 +90,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 @@ -100,6 +104,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, @@ -150,7 +158,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 0c1ac6002..cf83b9158 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 a4ba8cbb2..68d0b654b 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 f068ec492..08b7e6541 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 0b2ade073..936c18724 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 72eefabd2..b8063a315 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 8710715d5..2e1b3fb09 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 da253e3ae..913bc083a 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 879402cff..98d350a40 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 getBaseWordList(String language) => EVMChainMnemonics.englishWordlist; - WalletService createBaseWalletService(Box walletInfoSource, bool isDirect) => - BaseWalletService(walletInfoSource, isDirect, client: BaseClient()); + WalletService createBaseWalletService(bool isDirect) => + BaseWalletService(isDirect, client: BaseClient()); @override WalletCredentials createBaseNewWalletCredentials({ @@ -216,14 +216,15 @@ class CWBase extends Base { @override - void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) { + Future setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async { if (service is EVMChainLedgerService) { ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection( - service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath); + service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath); } else if (service is EVMChainBitboxService) { ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials) - .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath); + .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath); } + return Future.value(); } @override diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 452b802c0..4a9b2f1f6 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -260,17 +260,14 @@ class CWBitcoin extends Bitcoin { } WalletService createBitcoinWalletService( - Box 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 @@ -537,7 +534,7 @@ class CWBitcoin extends Bitcoin { } @override - void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) { + Future setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async { (wallet as ElectrumWallet).hardwareWalletService = service; } diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart index be0323bfa..5039652e7 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/core/backup_service.dart b/lib/core/backup_service.dart index 87bb71ce9..6a0f66879 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'); @@ -191,12 +177,16 @@ class $BackupService { String currentWalletName = data[PreferencesKey.currentWalletName] as String; int currentWalletType = data[PreferencesKey.currentWalletType] as int; - final isCorrentCurrentWallet = correctWallets + final isCorrectCurrentWallet = correctWallets .any((info) => info.name == currentWalletName && info.type.index == currentWalletType); - if (!isCorrentCurrentWallet) { - currentWalletName = correctWallets.first.name; - currentWalletType = serializeToInt(correctWallets.first.type); + try { + if (!isCorrectCurrentWallet) { + currentWalletName = correctWallets.first.name; + currentWalletType = serializeToInt(correctWallets.first.type); + } + } catch (e) { + } if (DeviceInfo.instance.isDesktop) { @@ -283,7 +273,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 a0640dfd3..ac73f9b3a 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 254f8a16f..dec1c8022 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(); @@ -84,12 +82,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 1432ce6f3..f6fb76737 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); @@ -195,8 +194,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 283895936..7fe2ee5c2 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 8f0a712af..1d4a0ac41 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -2,7 +2,6 @@ import 'dart:async' show Timer; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/anonpay/anonpay_api.dart'; -import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/anypay/anypay_api.dart'; import 'package:cake_wallet/base/base.dart'; @@ -301,7 +300,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; @@ -315,7 +313,6 @@ late Box _payjoinSessionSource; late Box _anonpayInvoiceInfoSource; Future setup({ - required Box walletInfoSource, required Box nodeSource, required Box powNodeSource, required Box contactSource, @@ -330,7 +327,6 @@ Future setup({ required SecureStorage secureStorage, required GlobalKey navigatorKey, }) async { - _walletInfoSource = walletInfoSource; _nodeSource = nodeSource; _powNodeSource = powNodeSource; _contactSource = contactSource; @@ -432,7 +428,7 @@ Future setup({ keyService: getIt.get(), sharedPreferences: getIt.get(), settingsStore: getIt.get(), - walletInfoSource: _walletInfoSource)); + )); getIt.registerFactoryParam( (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get())); @@ -446,17 +442,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(), ), ); @@ -535,7 +531,6 @@ Future setup({ hardwareWalletVM, getIt.get(), getIt.get(param1: type), - _walletInfoSource, getIt.get(), type: type)); @@ -560,7 +555,6 @@ Future setup({ getIt.get(), getIt.get(), getIt.get(), - _walletInfoSource, getIt.get(), ), ); @@ -639,7 +633,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; } @@ -680,7 +674,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: @@ -847,8 +841,7 @@ Future setup({ : null, coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb, getIt.get(param1: coinTypeToSpendFrom), - getIt.get(), - _walletInfoSource, + getIt.get() ), ); @@ -867,7 +860,6 @@ Future setup({ if (DeviceInfo.instance.isMobile) { getIt.registerFactory( () => WalletListViewModel( - _walletInfoSource, getIt.get(), getIt.get(), getIt.get(), @@ -878,7 +870,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(), @@ -890,7 +881,7 @@ Future setup({ (Function(BuildContext)? onWalletLoaded, _) => WalletListPage( walletListViewModel: getIt.get(), authService: getIt.get(), - onWalletLoaded: onWalletLoaded, + onWalletLoaded: onWalletLoaded as Future Function(BuildContext)?, )); getIt.registerFactoryParam( @@ -1034,7 +1025,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())); @@ -1190,62 +1181,53 @@ 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.base: - return base!.createBaseWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); + return base!.createBaseWalletService(SettingsStoreBase.walletPasswordDirectInput); case WalletType.haven: - return HavenWalletService(_walletInfoSource); + return HavenWalletService(); case WalletType.none: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } @@ -1275,7 +1257,6 @@ Future setup({ return WalletRestoreViewModel( getIt.get(), getIt.get(param1: type), - _walletInfoSource, getIt.get(), type: type, restoredWallet: restoredWallet, @@ -1351,7 +1332,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 e78119306..90aaa7b51 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 c25891eeb..83671474d 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'; @@ -55,12 +57,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 @@ -69,7 +70,7 @@ Future defaultSettingsMigration( final isNewInstall = sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null; - await _validateWalletInfoBoxData(walletInfoSource); + await _validateWalletInfoBoxData(); await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall); @@ -162,7 +163,7 @@ Future defaultSettingsMigration( break; case 5: - await addAddressesForMoneroWallets(walletInfoSource); + await addAddressesForMoneroWallets(); break; case 6: @@ -323,7 +324,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); @@ -361,7 +362,7 @@ Future defaultSettingsMigration( // await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes); break; case 38: - await fixBtcDerivationPaths(walletInfoSource); + await fixBtcDerivationPaths(); break; case 39: _fixNodesUseSSLFlag(nodes); @@ -738,7 +739,7 @@ Future _updateMoneroPriority(SharedPreferences sharedPreferences) async { } } -Future _validateWalletInfoBoxData(Box walletInfoSource) async { +Future _validateWalletInfoBoxData() async { try { final root = await getAppDir(); @@ -780,7 +781,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; @@ -799,7 +800,7 @@ Future _validateWalletInfoBoxData(Box walletInfoSource) async showIntroCakePayCard: false, ); - walletInfoSource.add(walletInfo); + await walletInfo.save(); } } } catch (_) {} @@ -967,8 +968,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); @@ -1016,32 +1017,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 a7604f4f1..a85c579b1 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 a64fa5873..3da6dcb5b 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 09b764289..dd88c9363 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 92d8bbec4..4433a5494 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,18 +181,19 @@ class CWEthereum extends Ethereum { String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) { + Future setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async { if (service is EVMChainLedgerService) { ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection( - service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath); + service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath); } else if (service is EVMChainBitboxService) { ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials) - .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath); + .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath); } else if (service is EVMChainTrezorService) { ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials) - .setTrezorConnect(service.connect, wallet.walletInfo.derivationInfo?.derivationPath); + .setTrezorConnect(service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath); } } + @override HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) => EVMChainLedgerService(connection); diff --git a/lib/haven/cw_haven.dart b/lib/haven/cw_haven.dart index b69e733c3..53cadb924 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 66afd8f05..684c9420e 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 beaa1524b..a5b9be867 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'; @@ -86,12 +87,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) { @@ -164,22 +169,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()); } @@ -227,6 +216,7 @@ Future initializeAppConfigs({bool loadWallet = true}) async { if (!CakeHive.isAdapterRegistered(TronToken.typeId)) { CakeHive.registerAdapter(TronTokenAdapter()); } + await performHiveMigration(); final secureStorage = secureStorageShared; final transactionDescriptionsBoxKey = @@ -242,7 +232,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