From d5fca1969e096ac7083bb4cbb780d0df81603ead Mon Sep 17 00:00:00 2001 From: cyan <cyjan@mrcyjanek.net> Date: Thu, 25 Jul 2024 02:59:45 +0200 Subject: [PATCH 01/19] add a script to download prebuilds of monero_c (#1554) --- pubspec_base.yaml | 1 + tool/download_moneroc_prebuilds.dart | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tool/download_moneroc_prebuilds.dart diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 458523fc2..2cfeae716 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -124,6 +124,7 @@ dev_dependencies: git: url: https://github.com/cake-tech/google-translator.git version: 1.0.0 + archive: ^3.6.1 dependency_overrides: bech32: diff --git a/tool/download_moneroc_prebuilds.dart b/tool/download_moneroc_prebuilds.dart new file mode 100644 index 000000000..58e8d4b03 --- /dev/null +++ b/tool/download_moneroc_prebuilds.dart @@ -0,0 +1,50 @@ +import 'package:dio/dio.dart'; +import 'package:archive/archive_io.dart'; + +final _dio = Dio(); + +final List<String> triplets = [ + "x86_64-linux-gnu", // linux desktop - majority of users onlinux + // "i686-linux-gnu", // not supported by cake + // "i686-meego-linux-gnu", // sailfishos (emulator)- not supported by cake + // "aarch64-linux-gnu", // not (yet) supported by cake - (mostly) mobile linux + // "aarch64-meego-linux-gnu", // sailfishos - not supported by cake + "x86_64-linux-android", + // "i686-linux-android", // not supported by monero_c - mostly old android emulators + "aarch64-linux-android", + "armv7a-linux-androideabi", + // "i686-w64-mingw32", // 32bit windows - not supported by monero_c + "x86_64-w64-mingw32", + // "x86_64-apple-darwin11", // Intel macbooks (contrib) - not used by cake + // "aarch64-apple-darwin11", // apple silicon macbooks (contrib) - not used by cake + // "host-apple-darwin", // not available on CI (yet) + // "x86_64-host-apple-darwin", // not available on CI (yet) + "aarch64-host-apple-darwin", // apple silicon macbooks (local builds) + "host-apple-ios", +]; + +Future<void> main() async { + final resp = await _dio.get("https://api.github.com/repos/mrcyjanek/monero_c/releases"); + final data = resp.data[0]; + final tagName = data['tag_name']; + print("Downloading artifacts for: ${tagName}"); + final assets = data['assets'] as List<dynamic>; + for (var i = 0; i < assets.length; i++) { + for (var triplet in triplets) { + final asset = assets[i]; + final filename = asset["name"] as String; + if (!filename.contains(triplet)) continue; + final coin = filename.split("_")[0]; + String localFilename = filename.replaceAll("${coin}_${triplet}_", ""); + localFilename = "scripts/monero_c/release/${coin}/${triplet}_${localFilename}"; + final url = asset["browser_download_url"] as String; + print("- downloading $localFilename"); + await _dio.download(url, localFilename); + print(" extracting $localFilename"); + final inputStream = InputFileStream(localFilename); + final archive = XZDecoder().decodeBuffer(inputStream); + final outputStream = OutputFileStream(localFilename.replaceAll(".xz", "")); + outputStream.writeBytes(archive); + } + } +} \ No newline at end of file From eb8158e9963b6dc518fbb24ba5bd36251e5c47f7 Mon Sep 17 00:00:00 2001 From: cyan <cyjan@mrcyjanek.net> Date: Fri, 26 Jul 2024 17:36:18 +0200 Subject: [PATCH 02/19] fix invalid wallet pointer getting set even when we throw (#1556) * fix invalid wallet pointer getting set even when we throw * Hide "wallet seeds" field if there is no seeds (cases of restore from keys) --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> --- cw_monero/lib/api/wallet_manager.dart | 37 +++++++++++-------- cw_wownero/lib/api/wallet_manager.dart | 41 +++++++++++++--------- lib/view_model/wallet_keys_view_model.dart | 12 +++---- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 02ce2b7d6..50ab41e04 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -32,13 +32,14 @@ void createWalletSync( required String language, int nettype = 0}) { txhistory = null; - wptr = monero.WalletManager_createWallet(wmPtr, + final newWptr = monero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language, networkType: 0); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - throw WalletCreationException(message: monero.Wallet_errorString(wptr!)); + throw WalletCreationException(message: monero.Wallet_errorString(newWptr)); } + wptr = newWptr; monero.Wallet_store(wptr!, path: path); openedWalletsByPath[path] = wptr!; @@ -57,7 +58,7 @@ void restoreWalletFromSeedSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - wptr = monero.WalletManager_recoveryWallet( + final newWptr = monero.WalletManager_recoveryWallet( wmPtr, path: path, password: password, @@ -67,12 +68,13 @@ void restoreWalletFromSeedSync( networkType: 0, ); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - final error = monero.Wallet_errorString(wptr!); + final error = monero.Wallet_errorString(newWptr); throw WalletRestoreFromSeedException(message: error); } + wptr = newWptr; openedWalletsByPath[path] = wptr!; } @@ -87,7 +89,7 @@ void restoreWalletFromKeysSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - wptr = monero.WalletManager_createWalletFromKeys( + final newWptr = monero.WalletManager_createWalletFromKeys( wmPtr, path: path, password: password, @@ -98,12 +100,14 @@ void restoreWalletFromKeysSync( nettype: 0, ); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { throw WalletRestoreFromKeysException( - message: monero.Wallet_errorString(wptr!)); + message: monero.Wallet_errorString(newWptr)); } + wptr = newWptr; + openedWalletsByPath[path] = wptr!; } @@ -128,7 +132,7 @@ void restoreWalletFromSpendKeySync( // ); txhistory = null; - wptr = monero.WalletManager_createDeterministicWalletFromSpendKey( + final newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey( wmPtr, path: path, password: password, @@ -138,14 +142,16 @@ void restoreWalletFromSpendKeySync( restoreHeight: restoreHeight, ); - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = monero.Wallet_errorString(newWptr); print("err: $err"); throw WalletRestoreFromKeysException(message: err); } + wptr = newWptr; + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); storeSync(); @@ -203,15 +209,16 @@ void loadWallet( }); } txhistory = null; - wptr = monero.WalletManager_openWallet(wmPtr, + final newWptr = monero.WalletManager_openWallet(wmPtr, path: path, password: password); _lastOpenedWallet = path; - final status = monero.Wallet_status(wptr!); + final status = monero.Wallet_status(newWptr); if (status != 0) { - final err = monero.Wallet_errorString(wptr!); + final err = monero.Wallet_errorString(newWptr); print(err); throw WalletOpeningException(message: err); } + wptr = newWptr; openedWalletsByPath[path] = wptr!; } } diff --git a/cw_wownero/lib/api/wallet_manager.dart b/cw_wownero/lib/api/wallet_manager.dart index 68d0796f9..afcc536e7 100644 --- a/cw_wownero/lib/api/wallet_manager.dart +++ b/cw_wownero/lib/api/wallet_manager.dart @@ -32,13 +32,14 @@ void createWalletSync( required String language, int nettype = 0}) { txhistory = null; - wptr = wownero.WalletManager_createWallet(wmPtr, + final newWptr = wownero.WalletManager_createWallet(wmPtr, path: path, password: password, language: language, networkType: 0); - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - throw WalletCreationException(message: wownero.Wallet_errorString(wptr!)); + throw WalletCreationException(message: wownero.Wallet_errorString(newWptr)); } + wptr = newWptr; wownero.Wallet_store(wptr!, path: path); openedWalletsByPath[path] = wptr!; @@ -56,9 +57,10 @@ void restoreWalletFromSeedSync( required String seed, int nettype = 0, int restoreHeight = 0}) { + var newWptr; if (seed.split(" ").length == 14) { txhistory = null; - wptr = wownero.WOWNERO_deprecated_restore14WordSeed( + newWptr = wownero.WOWNERO_deprecated_restore14WordSeed( path: path, password: password, language: seed, // I KNOW - this is supposed to be called seed @@ -70,7 +72,7 @@ void restoreWalletFromSeedSync( ); } else { txhistory = null; - wptr = wownero.WalletManager_recoveryWallet( + newWptr = wownero.WalletManager_recoveryWallet( wmPtr, path: path, password: password, @@ -81,13 +83,15 @@ void restoreWalletFromSeedSync( ); } - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - final error = wownero.Wallet_errorString(wptr!); + final error = wownero.Wallet_errorString(newWptr); throw WalletRestoreFromSeedException(message: error); } + wptr = newWptr; + openedWalletsByPath[path] = wptr!; } @@ -101,7 +105,7 @@ void restoreWalletFromKeysSync( int nettype = 0, int restoreHeight = 0}) { txhistory = null; - wptr = wownero.WalletManager_createWalletFromKeys( + final newWptr = wownero.WalletManager_createWalletFromKeys( wmPtr, path: path, password: password, @@ -112,12 +116,14 @@ void restoreWalletFromKeysSync( nettype: 0, ); - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { throw WalletRestoreFromKeysException( - message: wownero.Wallet_errorString(wptr!)); + message: wownero.Wallet_errorString(newWptr)); } + wptr = newWptr; + openedWalletsByPath[path] = wptr!; } @@ -142,7 +148,7 @@ void restoreWalletFromSpendKeySync( // ); txhistory = null; - wptr = wownero.WalletManager_createDeterministicWalletFromSpendKey( + final newWptr = wownero.WalletManager_createDeterministicWalletFromSpendKey( wmPtr, path: path, password: password, @@ -152,14 +158,16 @@ void restoreWalletFromSpendKeySync( restoreHeight: restoreHeight, ); - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - final err = wownero.Wallet_errorString(wptr!); + final err = wownero.Wallet_errorString(newWptr); print("err: $err"); throw WalletRestoreFromKeysException(message: err); } + wptr = newWptr; + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); storeSync(); @@ -217,15 +225,16 @@ void loadWallet( }); } txhistory = null; - wptr = wownero.WalletManager_openWallet(wmPtr, + final newWptr = wownero.WalletManager_openWallet(wmPtr, path: path, password: password); _lastOpenedWallet = path; - final status = wownero.Wallet_status(wptr!); + final status = wownero.Wallet_status(newWptr); if (status != 0) { - final err = wownero.Wallet_errorString(wptr!); + final err = wownero.Wallet_errorString(newWptr); print(err); throw WalletOpeningException(message: err); } + wptr = newWptr; openedWalletsByPath[path] = wptr!; } } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 511822601..1d5c27fed 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -83,8 +83,8 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem( title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + if (_appStore.wallet!.seed!.isNotEmpty) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); if (_appStore.wallet?.seed != null && @@ -123,8 +123,8 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem( title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + if (_appStore.wallet!.seed!.isNotEmpty) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); } @@ -147,8 +147,8 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem( title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + if (_appStore.wallet!.seed!.isNotEmpty) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); if (_appStore.wallet?.seed != null && From a2549b42b04fbce33b77dbf3fcdd4d6e711ac192 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Wed, 31 Jul 2024 02:26:56 +0100 Subject: [PATCH 03/19] CW-680: Fix Wakelock Issue (#1557) * chore: Bump up wakelock_plus dependency version * Fix: try fixing ci failure by bumping jdk version --- .github/workflows/pr_test_build.yml | 2 +- pubspec_base.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index f37919e9d..4c46137ac 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: "11.x" + java-version: "17.x" - name: Configure placeholder git details run: | git config --global user.email "CI@cakewallet.com" diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 2cfeae716..67a162674 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -66,7 +66,7 @@ dependencies: url: https://github.com/cake-tech/device_display_brightness.git ref: master workmanager: ^0.5.1 - wakelock_plus: ^1.1.3 + wakelock_plus: ^1.2.5 flutter_mailer: ^2.0.2 device_info_plus: ^9.1.0 base32: 2.1.3 From 9da9bee384588675dda11cf28b58e74d0ac7a030 Mon Sep 17 00:00:00 2001 From: cyan <cyjan@mrcyjanek.net> Date: Tue, 6 Aug 2024 13:01:38 +0200 Subject: [PATCH 04/19] make the error more readable when node fails to respond (#1570) --- cw_monero/lib/api/transaction_history.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 5e33c6c56..c28f162be 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -110,7 +110,10 @@ Future<PendingTransactionDescription> createTransactionSync( })(); if (error != null) { - final message = error; + String message = error; + if (message.contains("RPC error")) { + message = "Invalid node response, please try again or switch node\n\ntrace: $message"; + } throw CreationTransactionException(message: message); } From 5e944a8bf7069f0b083f177c44d1b4e5eb1f265e Mon Sep 17 00:00:00 2001 From: Omar Hatem <omarh.ismail1@gmail.com> Date: Tue, 6 Aug 2024 17:59:44 +0300 Subject: [PATCH 05/19] Try to show seeds if wallet files gets corrupted (#1567) * add litecoin nodes minor ui fix * Try to open the wallet or fetch the seeds and show them to the user * make sure the seeds are only displayed after authentication --- assets/litecoin_electrum_server_list.yml | 17 ++++++- cw_core/lib/wallet_service.dart | 19 +++++++ cw_monero/lib/monero_wallet.dart | 1 - cw_monero/lib/monero_wallet_service.dart | 37 +++++++++++--- lib/core/wallet_loading_service.dart | 31 ++++++++++- lib/di.dart | 14 +++++ .../on_authentication_state_change.dart | 14 +++++ .../monero_account_edit_or_create_page.dart | 4 +- lib/utils/exception_handler.dart | 51 +++++++++++++++++++ 9 files changed, 175 insertions(+), 13 deletions(-) diff --git a/assets/litecoin_electrum_server_list.yml b/assets/litecoin_electrum_server_list.yml index 991762885..550b900e1 100644 --- a/assets/litecoin_electrum_server_list.yml +++ b/assets/litecoin_electrum_server_list.yml @@ -1,4 +1,19 @@ - uri: ltc-electrum.cakewallet.com:50002 useSSL: true - isDefault: true \ No newline at end of file + isDefault: true +- + uri: litecoin.stackwallet.com:20063 + useSSL: true +- + uri: electrum-ltc.bysh.me:50002 + useSSL: true +- + uri: lightweight.fiatfaucet.com:50002 + useSSL: true +- + uri: electrum.ltc.xurious.com:50002 + useSSL: true +- + uri: backup.electrum-ltc.org:443 + useSSL: true diff --git a/cw_core/lib/wallet_service.dart b/cw_core/lib/wallet_service.dart index fcbd59ff3..d90ae30bc 100644 --- a/cw_core/lib/wallet_service.dart +++ b/cw_core/lib/wallet_service.dart @@ -1,6 +1,8 @@ +import 'dart:convert'; import 'dart:io'; import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_type.dart'; @@ -42,4 +44,21 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred await File(walletDirPath).copy(backupWalletDirPath); } } + + Future<String> getSeeds(String name, String password, WalletType type) async { + try { + final path = await pathForWallet(name: name, type: type); + final jsonSource = await read(path: path, password: password); + try { + final data = json.decode(jsonSource) as Map; + return data['mnemonic'] as String? ?? ''; + } catch (_) { + // if not a valid json + return jsonSource.substring(0, 200); + } + } catch (_) { + // if the file couldn't be opened or read + return ''; + } + } } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 4b596648e..b8e3c2765 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -19,7 +19,6 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/coins_info.dart'; import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index ea2f3b766..3588ebb78 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -57,8 +57,11 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials { final String spendKey; } -class MoneroWalletService extends WalletService<MoneroNewWalletCredentials, - MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials, MoneroNewWalletCredentials> { +class MoneroWalletService extends WalletService< + MoneroNewWalletCredentials, + MoneroRestoreWalletFromSeedCredentials, + MoneroRestoreWalletFromKeysCredentials, + MoneroNewWalletCredentials> { MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box<WalletInfo> walletInfoSource; @@ -183,11 +186,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials, final wmaddr = wmPtr.address; final waddr = openedWalletsByPath["$path/$wallet"]!.address; // await Isolate.run(() { - monero.WalletManager_closeWallet( - Pointer.fromAddress(wmaddr), - Pointer.fromAddress(waddr), - false - ); + monero.WalletManager_closeWallet( + Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), false); // }); openedWalletsByPath.remove("$path/$wallet"); print("wallet closed"); @@ -248,7 +248,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials, @override Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) { - throw UnimplementedError("Restoring a Monero wallet from a hardware wallet is not yet supported!"); + throw UnimplementedError( + "Restoring a Monero wallet from a hardware wallet is not yet supported!"); } @override @@ -350,4 +351,24 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials, print(e.toString()); } } + + @override + Future<String> getSeeds(String name, String password, WalletType type) async { + try { + final path = await pathForWallet(name: name, type: getType()); + + if (walletFilesExist(path)) { + await repairOldAndroidWallet(name); + } + + await monero_wallet_manager.openWalletAsync({'path': path, 'password': password}); + final walletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); + return wallet.seed; + } catch (_) { + // if the file couldn't be opened or read + return ''; + } + } } diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 1f17a7a1c..ca29576e4 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:cake_wallet/core/generate_wallet_password.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/reactions/on_authentication_state_change.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/wallet_base.dart'; @@ -52,6 +55,12 @@ class WalletLoadingService { } catch (error, stack) { ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack)); + // try fetching the seeds of the corrupted wallet to show it to the user + String corruptedWalletsSeeds = "Corrupted wallets seeds (if retrievable, empty otherwise):"; + try { + corruptedWalletsSeeds += await _getCorruptedWalletSeeds(name, type); + } catch (_) {} + // try opening another wallet that is not corrupted to give user access to the app final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName); @@ -69,12 +78,23 @@ class WalletLoadingService { await sharedPreferences.setInt( PreferencesKey.currentWalletType, serializeToInt(wallet.type)); + // if found a wallet that is not corrupted, then still display the seeds of the corrupted ones + authenticatedErrorStreamController.add(corruptedWalletsSeeds); + return wallet; - } catch (_) {} + } catch (_) { + // save seeds and show corrupted wallets' seeds to the user + try { + final seeds = await _getCorruptedWalletSeeds(walletInfo.name, walletInfo.type); + if (!corruptedWalletsSeeds.contains(seeds)) { + corruptedWalletsSeeds += seeds; + } + } catch (_) {} + } } // if all user's wallets are corrupted throw exception - throw error; + throw error.toString() + "\n\n" + corruptedWalletsSeeds; } } @@ -96,4 +116,11 @@ class WalletLoadingService { isPasswordUpdated = true; await sharedPreferences.setBool(key, isPasswordUpdated); } + + Future<String> _getCorruptedWalletSeeds(String name, WalletType type) async { + final walletService = walletServiceFactory.call(type); + final password = await keyService.getWalletPassword(walletName: name); + + return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}"; + } } diff --git a/lib/di.dart b/lib/di.dart index 1462370fc..a37574f21 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,3 +1,5 @@ +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'; @@ -487,6 +489,7 @@ Future<void> setup({ if (loginError != null) { authPageState.changeProcessText('ERROR: ${loginError.toString()}'); + loginError = null; } ReactionDisposer? _reaction; @@ -498,6 +501,17 @@ Future<void> setup({ linkViewModel.handleLink(); } }); + + Timer.periodic(Duration(seconds: 1), (timer) { + if (timer.tick > 30) { + timer.cancel(); + } + + if (loginError != null) { + authPageState.changeProcessText('ERROR: ${loginError.toString()}'); + timer.cancel(); + } + }); } }); }); diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart index 5f1214b76..e4fd9b32f 100644 --- a/lib/reactions/on_authentication_state_change.dart +++ b/lib/reactions/on_authentication_state_change.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:flutter/widgets.dart'; @@ -8,9 +10,16 @@ import 'package:cake_wallet/store/authentication_store.dart'; ReactionDisposer? _onAuthenticationStateChange; dynamic loginError; +StreamController<dynamic> authenticatedErrorStreamController = StreamController<dynamic>(); void startAuthenticationStateChange( AuthenticationStore authenticationStore, GlobalKey<NavigatorState> navigatorKey) { + authenticatedErrorStreamController.stream.listen((event) { + if (authenticationStore.state == AuthenticationState.allowed) { + ExceptionHandler.showError(event.toString(), delayInSeconds: 3); + } + }); + _onAuthenticationStateChange ??= autorun((_) async { final state = authenticationStore.state; @@ -26,6 +35,11 @@ void startAuthenticationStateChange( if (state == AuthenticationState.allowed) { await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + if (!(await authenticatedErrorStreamController.stream.isEmpty)) { + ExceptionHandler.showError( + (await authenticatedErrorStreamController.stream.first).toString()); + authenticatedErrorStreamController.stream.drain(); + } return; } }); diff --git a/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart b/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart index 779628be8..2c9918d74 100644 --- a/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart +++ b/lib/src/screens/monero_accounts/monero_account_edit_or_create_page.dart @@ -51,7 +51,9 @@ class MoneroAccountEditOrCreatePage extends BasePage { await moneroAccountCreationViewModel.save(); - Navigator.of(context).pop(_textController.text); + if (context.mounted) { + Navigator.of(context).pop(_textController.text); + } }, text: moneroAccountCreationViewModel.isEdit ? S.of(context).rename diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index b19b1bb7e..6045c0004 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -4,11 +4,13 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/root_dir.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; import 'package:cake_wallet/utils/package_info.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -254,4 +256,53 @@ class ExceptionHandler { 'productName': data.productName, }; } + + static void showError(String error, {int? delayInSeconds}) async { + if (_hasError) { + return; + } + _hasError = true; + + if (delayInSeconds != null) { + Future.delayed(Duration(seconds: delayInSeconds), () => _showCopyPopup(error)); + return; + } + + WidgetsBinding.instance.addPostFrameCallback( + (_) async => _showCopyPopup(error), + ); + } + + static Future<void> _showCopyPopup(String content) async { + if (navigatorKey.currentContext != null) { + final shouldCopy = await showPopUp<bool?>( + context: navigatorKey.currentContext!, + builder: (context) { + return AlertWithTwoActions( + isDividerExist: true, + alertTitle: S.of(context).error, + alertContent: content, + rightButtonText: S.of(context).copy, + leftButtonText: S.of(context).close, + actionRightButton: () { + Navigator.of(context).pop(true); + }, + actionLeftButton: () { + Navigator.of(context).pop(); + }, + ); + }, + ); + + if (shouldCopy == true) { + await Clipboard.setData(ClipboardData(text: content)); + await showBar<void>( + navigatorKey.currentContext!, + S.of(navigatorKey.currentContext!).copied_to_clipboard, + ); + } + } + + _hasError = false; + } } From e58d87e94cc82e268fc5e5901e4bea47c7c4ce50 Mon Sep 17 00:00:00 2001 From: cyan <cyjan@mrcyjanek.net> Date: Wed, 7 Aug 2024 13:40:31 +0200 Subject: [PATCH 06/19] add card for when monero wallet is in broken state (#1578) --- .../screens/dashboard/pages/balance_page.dart | 14 ++++++++++++++ .../dashboard/dashboard_view_model.dart | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index d95c19dad..11abdeb58 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -250,6 +250,20 @@ class CryptoBalanceWidget extends StatelessWidget { Observer(builder: (context) { return Column( children: [ + if (dashboardViewModel.isMoneroWalletBrokenReasons.isNotEmpty) ...[ + SizedBox(height: 10), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DashBoardRoundedCardWidget( + customBorder: 30, + title: "Monero wallet is broken", + subTitle: "Here are the things that are broken:\n - " + +dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ") + +"\n\nPlease restart your wallet and if it doesn't help contact our support.", + onTap: () {}, + ) + ) + ], if (dashboardViewModel.showSilentPaymentsCard) ...[ SizedBox(height: 10), Padding( diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 5b5353e06..06c565035 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -335,6 +335,23 @@ abstract class DashboardViewModelBase with Store { wallet.type == WalletType.wownero || wallet.type == WalletType.haven; + @computed + List<String> get isMoneroWalletBrokenReasons { + if (wallet.type != WalletType.monero) return []; + final keys = monero!.getKeys(wallet); + List<String> errors = [ + if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0", + if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("")) "private view key is 0", + if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0", + if (keys['publicViewKey'] == List.generate(64, (index) => "0").join("")) "private view key is 0", + if (wallet.seed == null) "wallet seed is null", + if (wallet.seed == "") "wallet seed is empty", + if (monero!.getSubaddressList(wallet).getAll(wallet)[0].address == "41d7FXjswpK1111111111111111111111111111111111111111111111111111111111111111111111111111112KhNi4") + "primary address is invalid, you won't be able to receive / spend funds", + ]; + return errors; + } + @computed bool get hasSilentPayments => wallet.type == WalletType.bitcoin && !wallet.isHardwareWallet; From 96e4a4eb6c41a0a3c541a617073096ef96c1492f Mon Sep 17 00:00:00 2001 From: cyan <cyjan@mrcyjanek.net> Date: Wed, 7 Aug 2024 18:12:49 +0200 Subject: [PATCH 07/19] monero fixes (#1581) * correct comparision while syncing * fix issue from report 25916.txt * return proper address even if numSubaddresses returned 0 --- cw_monero/lib/api/subaddress_list.dart | 6 +++++- cw_monero/lib/api/wallet.dart | 2 +- cw_monero/lib/monero_wallet_addresses.dart | 2 +- cw_wownero/lib/api/subaddress_list.dart | 6 +++++- cw_wownero/lib/wownero_wallet_addresses.dart | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cw_monero/lib/api/subaddress_list.dart b/cw_monero/lib/api/subaddress_list.dart index 57edea76e..e5145692d 100644 --- a/cw_monero/lib/api/subaddress_list.dart +++ b/cw_monero/lib/api/subaddress_list.dart @@ -42,12 +42,16 @@ class Subaddress { List<Subaddress> getAllSubaddresses() { final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); - return List.generate(size, (index) { + final list = List.generate(size, (index) { return Subaddress( accountIndex: subaddress!.accountIndex, addressIndex: index, ); }).reversed.toList(); + if (list.length == 0) { + list.add(Subaddress(addressIndex: subaddress!.accountIndex, accountIndex: 0)); + } + return list; } void addSubaddressSync({required int accountIndex, required String label}) { diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 6ca9cd1bb..0f6e59c4e 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -131,7 +131,7 @@ void storeSync() async { return monero.Wallet_synchronized(Pointer.fromAddress(addr)); }); if (lastStorePointer == wptr!.address && - lastStoreHeight + 5000 < monero.Wallet_blockChainHeight(wptr!) && + lastStoreHeight + 5000 > monero.Wallet_blockChainHeight(wptr!) && !synchronized) { return; } diff --git a/cw_monero/lib/monero_wallet_addresses.dart b/cw_monero/lib/monero_wallet_addresses.dart index f74e7dd5b..d4f22e46f 100644 --- a/cw_monero/lib/monero_wallet_addresses.dart +++ b/cw_monero/lib/monero_wallet_addresses.dart @@ -109,7 +109,7 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { accountIndex: accountIndex, defaultLabel: defaultLabel, usedAddresses: usedAddresses.toList()); - subaddress = subaddressList.subaddresses.last; + subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last; address = subaddress!.address; } diff --git a/cw_wownero/lib/api/subaddress_list.dart b/cw_wownero/lib/api/subaddress_list.dart index cec7d94cb..d8c91a584 100644 --- a/cw_wownero/lib/api/subaddress_list.dart +++ b/cw_wownero/lib/api/subaddress_list.dart @@ -41,12 +41,16 @@ class Subaddress { List<Subaddress> getAllSubaddresses() { final size = wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); - return List.generate(size, (index) { + final list = List.generate(size, (index) { return Subaddress( accountIndex: subaddress!.accountIndex, addressIndex: index, ); }).reversed.toList(); + if (list.isEmpty) { + list.add(Subaddress(addressIndex: 0, accountIndex: subaddress!.accountIndex)); + } + return list; } void addSubaddressSync({required int accountIndex, required String label}) { diff --git a/cw_wownero/lib/wownero_wallet_addresses.dart b/cw_wownero/lib/wownero_wallet_addresses.dart index dc4b42840..9eeb182eb 100644 --- a/cw_wownero/lib/wownero_wallet_addresses.dart +++ b/cw_wownero/lib/wownero_wallet_addresses.dart @@ -109,7 +109,7 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store { accountIndex: accountIndex, defaultLabel: defaultLabel, usedAddresses: usedAddresses.toList()); - subaddress = subaddressList.subaddresses.last; + subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last; address = subaddress!.address; } From 15d88e0f8dc8b9b3b055d913bb64e42ed5e322e0 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich <konstantinullrich12@gmail.com> Date: Thu, 8 Aug 2024 12:17:17 +0200 Subject: [PATCH 08/19] Add Ledger Flex Support (#1576) --- cw_bitcoin/pubspec.yaml | 4 ++++ cw_evm/pubspec.yaml | 2 +- pubspec_base.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 66c5729e8..69ff3d29b 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -56,6 +56,10 @@ dev_dependencies: hive_generator: ^1.1.3 dependency_overrides: + ledger_flutter: + git: + url: https://github.com/cake-tech/ledger-flutter.git + ref: cake-v3 watcher: ^1.1.0 # For information on the generic Dart part of this file, see the diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index e4b29b676..c3f4347c2 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -35,7 +35,7 @@ dependency_overrides: ledger_flutter: git: url: https://github.com/cake-tech/ledger-flutter.git - ref: cake + ref: cake-v3 watcher: ^1.1.0 dev_dependencies: diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 67a162674..84b4631fc 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -133,7 +133,7 @@ dependency_overrides: ledger_flutter: git: url: https://github.com/cake-tech/ledger-flutter.git - ref: cake-stax + ref: cake-v3 web3dart: git: url: https://github.com/cake-tech/web3dart.git From ba433ef6f30442e2ac69724fdcb4415699f3db8d Mon Sep 17 00:00:00 2001 From: Matthew Fosse <matt@fosse.co> Date: Thu, 8 Aug 2024 03:27:04 -0700 Subject: [PATCH 09/19] Request timeout fix (#1584) * always handle RequestFailedTimeoutException * undo change that was for testing --- cw_bitcoin/lib/electrum.dart | 69 +++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index b52015794..e3925ca74 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -236,25 +236,37 @@ class ElectrumClient { return []; }); - Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async => - callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000) - .then((dynamic result) { - if (result is Map<String, dynamic>) { - return result; - } + Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async { + try { + final result = await callWithTimeout( + method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000); + if (result is Map<String, dynamic>) { + return result; + } + } on RequestFailedTimeoutException catch (_) { + return <String, dynamic>{}; + } catch (e) { + print("getTransactionRaw: ${e.toString()}"); + return <String, dynamic>{}; + } + return <String, dynamic>{}; + } - return <String, dynamic>{}; - }); - - Future<String> getTransactionHex({required String hash}) async => - callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000) - .then((dynamic result) { - if (result is String) { - return result; - } - - return ''; - }); + Future<String> getTransactionHex({required String hash}) async { + try { + final result = await callWithTimeout( + method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000); + if (result is String) { + return result; + } + } on RequestFailedTimeoutException catch (_) { + return ''; + } catch (e) { + print("getTransactionHex: ${e.toString()}"); + return ''; + } + return ''; + } Future<String> broadcastTransaction( {required String transactionRaw, @@ -353,14 +365,21 @@ class ElectrumClient { // "height": 520481, // "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" // } - Future<int?> getCurrentBlockChainTip() => - callWithTimeout(method: 'blockchain.headers.subscribe').then((result) { - if (result is Map<String, dynamic>) { - return result["height"] as int; - } - return null; - }); + Future<int?> getCurrentBlockChainTip() async { + try { + final result = await callWithTimeout(method: 'blockchain.headers.subscribe'); + if (result is Map<String, dynamic>) { + return result["height"] as int; + } + return null; + } on RequestFailedTimeoutException catch (_) { + return null; + } catch (e) { + print("getCurrentBlockChainTip: ${e.toString()}"); + return null; + } + } BehaviorSubject<Object>? chainTipSubscribe() { _id += 1; From 8e7233b5c39faf19775989437649125b7563450d Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich <konstantinullrich12@gmail.com> Date: Fri, 9 Aug 2024 21:15:54 +0200 Subject: [PATCH 10/19] Monero stability and cleanup (#1572) * migrate monero.dart from it's own repository to monero_c * show errors when invalid monero_c library is being used * Delete unused code * Delete unused code * Fix potential bug causing missing Polyseeds and tx-keys; Add Waring * Remove unused wownero-code * bump monero_c commit --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> --- cw_core/lib/monero_wallet_utils.dart | 5 +- .../connection_to_node_exception.dart | 5 - cw_monero/lib/api/structs/account_row.dart | 12 - cw_monero/lib/api/structs/coins_info_row.dart | 73 - cw_monero/lib/api/structs/subaddress_row.dart | 15 - .../lib/api/structs/transaction_info_row.dart | 41 - cw_monero/lib/api/structs/ut8_box.dart | 8 - cw_monero/lib/api/transaction_history.dart | 5 +- cw_monero/lib/api/wallet_manager.dart | 35 + cw_monero/lib/cw_monero.dart | 8 - cw_monero/lib/cw_monero_method_channel.dart | 17 - .../lib/cw_monero_platform_interface.dart | 29 - cw_monero/lib/monero_transaction_info.dart | 23 - cw_monero/lib/monero_unspent.dart | 9 - cw_monero/lib/monero_wallet.dart | 24 +- cw_monero/lib/mymonero.dart | 1689 ----------------- cw_monero/pubspec.lock | 200 +- cw_monero/pubspec.yaml | 5 +- .../connection_to_node_exception.dart | 5 - cw_wownero/lib/api/structs/account_row.dart | 12 - .../lib/api/structs/coins_info_row.dart | 73 - .../lib/api/structs/subaddress_row.dart | 15 - .../lib/api/structs/transaction_info_row.dart | 41 - cw_wownero/lib/api/structs/ut8_box.dart | 8 - cw_wownero/lib/api/transaction_history.dart | 4 +- cw_wownero/lib/api/wallet_manager.dart | 35 + cw_wownero/lib/cw_wownero.dart | 8 - cw_wownero/lib/cw_wownero_method_channel.dart | 17 - .../lib/cw_wownero_platform_interface.dart | 29 - cw_wownero/lib/mywownero.dart | 1689 ----------------- cw_wownero/lib/wownero_transaction_info.dart | 21 - cw_wownero/lib/wownero_unspent.dart | 9 - cw_wownero/lib/wownero_wallet.dart | 22 +- cw_wownero/pubspec.lock | 8 +- cw_wownero/pubspec.yaml | 5 +- lib/monero/cw_monero.dart | 5 + .../screens/dashboard/pages/balance_page.dart | 30 + .../dashboard/dashboard_view_model.dart | 22 + lib/wownero/cw_wownero.dart | 5 + scripts/prepare_moneroc.sh | 2 +- tool/configure.dart | 4 + 41 files changed, 279 insertions(+), 3993 deletions(-) delete mode 100644 cw_monero/lib/api/exceptions/connection_to_node_exception.dart delete mode 100644 cw_monero/lib/api/structs/account_row.dart delete mode 100644 cw_monero/lib/api/structs/coins_info_row.dart delete mode 100644 cw_monero/lib/api/structs/subaddress_row.dart delete mode 100644 cw_monero/lib/api/structs/transaction_info_row.dart delete mode 100644 cw_monero/lib/api/structs/ut8_box.dart delete mode 100644 cw_monero/lib/cw_monero.dart delete mode 100644 cw_monero/lib/cw_monero_method_channel.dart delete mode 100644 cw_monero/lib/cw_monero_platform_interface.dart delete mode 100644 cw_monero/lib/mymonero.dart delete mode 100644 cw_wownero/lib/api/exceptions/connection_to_node_exception.dart delete mode 100644 cw_wownero/lib/api/structs/account_row.dart delete mode 100644 cw_wownero/lib/api/structs/coins_info_row.dart delete mode 100644 cw_wownero/lib/api/structs/subaddress_row.dart delete mode 100644 cw_wownero/lib/api/structs/transaction_info_row.dart delete mode 100644 cw_wownero/lib/api/structs/ut8_box.dart delete mode 100644 cw_wownero/lib/cw_wownero.dart delete mode 100644 cw_wownero/lib/cw_wownero_method_channel.dart delete mode 100644 cw_wownero/lib/cw_wownero_platform_interface.dart delete mode 100644 cw_wownero/lib/mywownero.dart diff --git a/cw_core/lib/monero_wallet_utils.dart b/cw_core/lib/monero_wallet_utils.dart index 1b1988eb6..8a4990f78 100644 --- a/cw_core/lib/monero_wallet_utils.dart +++ b/cw_core/lib/monero_wallet_utils.dart @@ -79,6 +79,7 @@ Future<bool> backupWalletFilesExists(String name) async { backupAddressListFile.existsSync(); } +// WARNING: Transaction keys and your Polyseed CANNOT be recovered if this file is deleted Future<void> removeCache(String name) async { final path = await pathForWallet(name: name, type: WalletType.monero); final cacheFile = File(path); @@ -92,8 +93,8 @@ Future<void> restoreOrResetWalletFiles(String name) async { final backupsExists = await backupWalletFilesExists(name); if (backupsExists) { + await removeCache(name); + await restoreWalletFiles(name); } - - removeCache(name); } diff --git a/cw_monero/lib/api/exceptions/connection_to_node_exception.dart b/cw_monero/lib/api/exceptions/connection_to_node_exception.dart deleted file mode 100644 index 483b0a174..000000000 --- a/cw_monero/lib/api/exceptions/connection_to_node_exception.dart +++ /dev/null @@ -1,5 +0,0 @@ -class ConnectionToNodeException implements Exception { - ConnectionToNodeException({required this.message}); - - final String message; -} \ No newline at end of file diff --git a/cw_monero/lib/api/structs/account_row.dart b/cw_monero/lib/api/structs/account_row.dart deleted file mode 100644 index aa492ee0f..000000000 --- a/cw_monero/lib/api/structs/account_row.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class AccountRow extends Struct { - @Int64() - external int id; - - external Pointer<Utf8> label; - - String getLabel() => label.toDartString(); - int getId() => id; -} diff --git a/cw_monero/lib/api/structs/coins_info_row.dart b/cw_monero/lib/api/structs/coins_info_row.dart deleted file mode 100644 index ff6f6ce73..000000000 --- a/cw_monero/lib/api/structs/coins_info_row.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class CoinsInfoRow extends Struct { - @Int64() - external int blockHeight; - - external Pointer<Utf8> hash; - - @Uint64() - external int internalOutputIndex; - - @Uint64() - external int globalOutputIndex; - - @Int8() - external int spent; - - @Int8() - external int frozen; - - @Uint64() - external int spentHeight; - - @Uint64() - external int amount; - - @Int8() - external int rct; - - @Int8() - external int keyImageKnown; - - @Uint64() - external int pkIndex; - - @Uint32() - external int subaddrIndex; - - @Uint32() - external int subaddrAccount; - - external Pointer<Utf8> address; - - external Pointer<Utf8> addressLabel; - - external Pointer<Utf8> keyImage; - - @Uint64() - external int unlockTime; - - @Int8() - external int unlocked; - - external Pointer<Utf8> pubKey; - - @Int8() - external int coinbase; - - external Pointer<Utf8> description; - - String getHash() => hash.toDartString(); - - String getAddress() => address.toDartString(); - - String getAddressLabel() => addressLabel.toDartString(); - - String getKeyImage() => keyImage.toDartString(); - - String getPubKey() => pubKey.toDartString(); - - String getDescription() => description.toDartString(); -} diff --git a/cw_monero/lib/api/structs/subaddress_row.dart b/cw_monero/lib/api/structs/subaddress_row.dart deleted file mode 100644 index d593a793d..000000000 --- a/cw_monero/lib/api/structs/subaddress_row.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class SubaddressRow extends Struct { - @Int64() - external int id; - - external Pointer<Utf8> address; - - external Pointer<Utf8> label; - - String getLabel() => label.toDartString(); - String getAddress() => address.toDartString(); - int getId() => id; -} \ No newline at end of file diff --git a/cw_monero/lib/api/structs/transaction_info_row.dart b/cw_monero/lib/api/structs/transaction_info_row.dart deleted file mode 100644 index bdcc64d3f..000000000 --- a/cw_monero/lib/api/structs/transaction_info_row.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class TransactionInfoRow extends Struct { - @Uint64() - external int amount; - - @Uint64() - external int fee; - - @Uint64() - external int blockHeight; - - @Uint64() - external int confirmations; - - @Uint32() - external int subaddrAccount; - - @Int8() - external int direction; - - @Int8() - external int isPending; - - @Uint32() - external int subaddrIndex; - - external Pointer<Utf8> hash; - - external Pointer<Utf8> paymentId; - - @Int64() - external int datetime; - - int getDatetime() => datetime; - int getAmount() => amount >= 0 ? amount : amount * -1; - bool getIsPending() => isPending != 0; - String getHash() => hash.toDartString(); - String getPaymentId() => paymentId.toDartString(); -} diff --git a/cw_monero/lib/api/structs/ut8_box.dart b/cw_monero/lib/api/structs/ut8_box.dart deleted file mode 100644 index 53e678c88..000000000 --- a/cw_monero/lib/api/structs/ut8_box.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class Utf8Box extends Struct { - external Pointer<Utf8> value; - - String getValue() => value.toDartString(); -} diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index c28f162be..b416e1b4e 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -1,4 +1,3 @@ - import 'dart:ffi'; import 'dart:isolate'; @@ -288,7 +287,7 @@ class Transaction { }; } - // S finalubAddress? subAddress; + // final SubAddress? subAddress; // List<Transfer> transfers = []; // final int txIndex; final monero.TransactionInfo txInfo; @@ -324,4 +323,4 @@ class Transaction { required this.key, required this.txInfo }); -} \ No newline at end of file +} diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 50ab41e04..26c83b06e 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -1,4 +1,5 @@ import 'dart:ffi'; +import 'dart:io'; import 'dart:isolate'; import 'package:cw_monero/api/account_list.dart'; @@ -8,8 +9,42 @@ import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart'; import 'package:cw_monero/api/transaction_history.dart'; import 'package:cw_monero/api/wallet.dart'; +import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; +class MoneroCException implements Exception { + final String message; + + MoneroCException(this.message); + + @override + String toString() { + return message; + } +} + +void checkIfMoneroCIsFine() { + final cppCsCpp = monero.MONERO_checksum_wallet2_api_c_cpp(); + final cppCsH = monero.MONERO_checksum_wallet2_api_c_h(); + final cppCsExp = monero.MONERO_checksum_wallet2_api_c_exp(); + + final dartCsCpp = monero.wallet2_api_c_cpp_sha256; + final dartCsH = monero.wallet2_api_c_h_sha256; + final dartCsExp = monero.wallet2_api_c_exp_sha256; + + if (cppCsCpp != dartCsCpp) { + throw MoneroCException("monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'"); + } + + if (cppCsH != dartCsH) { + throw MoneroCException("monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'"); + } + + if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) { + throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'"); + } +} + monero.WalletManager? _wmPtr; final monero.WalletManager wmPtr = Pointer.fromAddress((() { try { diff --git a/cw_monero/lib/cw_monero.dart b/cw_monero/lib/cw_monero.dart deleted file mode 100644 index 7945a020e..000000000 --- a/cw_monero/lib/cw_monero.dart +++ /dev/null @@ -1,8 +0,0 @@ - -import 'cw_monero_platform_interface.dart'; - -class CwMonero { - Future<String?> getPlatformVersion() { - return CwMoneroPlatform.instance.getPlatformVersion(); - } -} diff --git a/cw_monero/lib/cw_monero_method_channel.dart b/cw_monero/lib/cw_monero_method_channel.dart deleted file mode 100644 index 1cbca9f2c..000000000 --- a/cw_monero/lib/cw_monero_method_channel.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import 'cw_monero_platform_interface.dart'; - -/// An implementation of [CwMoneroPlatform] that uses method channels. -class MethodChannelCwMonero extends CwMoneroPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel('cw_monero'); - - @override - Future<String?> getPlatformVersion() async { - final version = await methodChannel.invokeMethod<String>('getPlatformVersion'); - return version; - } -} diff --git a/cw_monero/lib/cw_monero_platform_interface.dart b/cw_monero/lib/cw_monero_platform_interface.dart deleted file mode 100644 index 6c9b20a25..000000000 --- a/cw_monero/lib/cw_monero_platform_interface.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'cw_monero_method_channel.dart'; - -abstract class CwMoneroPlatform extends PlatformInterface { - /// Constructs a CwMoneroPlatform. - CwMoneroPlatform() : super(token: _token); - - static final Object _token = Object(); - - static CwMoneroPlatform _instance = MethodChannelCwMonero(); - - /// The default instance of [CwMoneroPlatform] to use. - /// - /// Defaults to [MethodChannelCwMonero]. - static CwMoneroPlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [CwMoneroPlatform] when - /// they register themselves. - static set instance(CwMoneroPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future<String?> getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } -} diff --git a/cw_monero/lib/monero_transaction_info.dart b/cw_monero/lib/monero_transaction_info.dart index 596b26812..76064ad11 100644 --- a/cw_monero/lib/monero_transaction_info.dart +++ b/cw_monero/lib/monero_transaction_info.dart @@ -1,8 +1,5 @@ -import 'dart:math'; - import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_monero/api/structs/transaction_info_row.dart'; import 'package:cw_core/parseBoolFromString.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/format_amount.dart'; @@ -37,26 +34,6 @@ class MoneroTransactionInfo extends TransactionInfo { }; } - MoneroTransactionInfo.fromRow(TransactionInfoRow row) - : id = "${row.getHash()}_${row.getAmount()}_${row.subaddrAccount}_${row.subaddrIndex}", - txHash = row.getHash(), - height = row.blockHeight, - direction = parseTransactionDirectionFromInt(row.direction), - date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000), - isPending = row.isPending != 0, - amount = row.getAmount(), - accountIndex = row.subaddrAccount, - addressIndex = row.subaddrIndex, - confirmations = row.confirmations, - key = getTxKey(row.getHash()), - fee = row.fee { - additionalInfo = <String, dynamic>{ - 'key': key, - 'accountIndex': accountIndex, - 'addressIndex': addressIndex - }; - } - final String id; final String txHash; final int height; diff --git a/cw_monero/lib/monero_unspent.dart b/cw_monero/lib/monero_unspent.dart index 65b5c595d..87d8f0b39 100644 --- a/cw_monero/lib/monero_unspent.dart +++ b/cw_monero/lib/monero_unspent.dart @@ -1,5 +1,4 @@ import 'package:cw_core/unspent_transaction_output.dart'; -import 'package:cw_monero/api/structs/coins_info_row.dart'; class MoneroUnspent extends Unspent { MoneroUnspent( @@ -8,13 +7,5 @@ class MoneroUnspent extends Unspent { this.isFrozen = isFrozen; } - factory MoneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => MoneroUnspent( - coinsInfoRow.getAddress(), - coinsInfoRow.getHash(), - coinsInfoRow.getKeyImage(), - coinsInfoRow.amount, - coinsInfoRow.frozen == 1, - coinsInfoRow.unlocked == 1); - final bool isUnlocked; } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index b8e3c2765..9298f8a49 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -109,9 +109,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance, @override String get seed => monero_wallet.getSeed(); - String seedLegacy(String? language) { - return monero_wallet.getSeedLegacy(language); - } + String seedLegacy(String? language) => monero_wallet.getSeedLegacy(language); @override MoneroWalletKeys get keys => MoneroWalletKeys( @@ -190,12 +188,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance, @override Future<void> startSync() async { try { - _setInitialHeight(); + _assertInitialHeight(); } catch (_) { // our restore height wasn't correct, so lets see if using the backup works: try { - await resetCache(name); - _setInitialHeight(); + await resetCache(name); // Resetting the cache removes the TX Keys and Polyseed + _assertInitialHeight(); } catch (e) { // we still couldn't get a valid height from the backup?!: // try to use the date instead: @@ -635,18 +633,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance, _listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction); } - // check if the height is correct: - void _setInitialHeight() { - if (walletInfo.isRecovery) { - return; - } + /// Asserts the current height to be above [MIN_RESTORE_HEIGHT] + void _assertInitialHeight() { + if (walletInfo.isRecovery) return; final height = monero_wallet.getCurrentHeight(); - if (height > MIN_RESTORE_HEIGHT) { - // the restore height is probably correct, so we do nothing: - return; - } + // the restore height is probably correct, so we do nothing: + if (height > MIN_RESTORE_HEIGHT) return; throw Exception("height isn't > $MIN_RESTORE_HEIGHT!"); } diff --git a/cw_monero/lib/mymonero.dart b/cw_monero/lib/mymonero.dart deleted file mode 100644 index d50e48b64..000000000 --- a/cw_monero/lib/mymonero.dart +++ /dev/null @@ -1,1689 +0,0 @@ -const prefixLength = 3; - -String swapEndianBytes(String original) { - if (original.length != 8) { - return ''; - } - - return original[6] + - original[7] + - original[4] + - original[5] + - original[2] + - original[3] + - original[0] + - original[1]; -} - -List<String> tructWords(List<String> wordSet) { - final start = 0; - final end = prefixLength; - - return wordSet.map((word) => word.substring(start, end)).toList(); -} - -String mnemonicDecode(String seed) { - final n = englistWordSet.length; - var out = ''; - var wlist = seed.split(' '); - wlist.removeLast(); - - for (var i = 0; i < wlist.length; i += 3) { - final w1 = - tructWords(englistWordSet).indexOf(wlist[i].substring(0, prefixLength)); - final w2 = tructWords(englistWordSet) - .indexOf(wlist[i + 1].substring(0, prefixLength)); - final w3 = tructWords(englistWordSet) - .indexOf(wlist[i + 2].substring(0, prefixLength)); - - if (w1 == -1 || w2 == -1 || w3 == -1) { - print("invalid word in mnemonic"); - return ''; - } - - final x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n); - - if (x % n != w1) { - print("Something went wrong when decoding your private key, please try again"); - return ''; - } - - final _res = '0000000' + x.toRadixString(16); - final start = _res.length - 8; - final end = _res.length; - final res = _res.substring(start, end); - - out += swapEndianBytes(res); - } - - return out; -} - -final englistWordSet = [ - "abbey", - "abducts", - "ability", - "ablaze", - "abnormal", - "abort", - "abrasive", - "absorb", - "abyss", - "academy", - "aces", - "aching", - "acidic", - "acoustic", - "acquire", - "across", - "actress", - "acumen", - "adapt", - "addicted", - "adept", - "adhesive", - "adjust", - "adopt", - "adrenalin", - "adult", - "adventure", - "aerial", - "afar", - "affair", - "afield", - "afloat", - "afoot", - "afraid", - "after", - "against", - "agenda", - "aggravate", - "agile", - "aglow", - "agnostic", - "agony", - "agreed", - "ahead", - "aided", - "ailments", - "aimless", - "airport", - "aisle", - "ajar", - "akin", - "alarms", - "album", - "alchemy", - "alerts", - "algebra", - "alkaline", - "alley", - "almost", - "aloof", - "alpine", - "already", - "also", - "altitude", - "alumni", - "always", - "amaze", - "ambush", - "amended", - "amidst", - "ammo", - "amnesty", - "among", - "amply", - "amused", - "anchor", - "android", - "anecdote", - "angled", - "ankle", - "annoyed", - "answers", - "antics", - "anvil", - "anxiety", - "anybody", - "apart", - "apex", - "aphid", - "aplomb", - "apology", - "apply", - "apricot", - "aptitude", - "aquarium", - "arbitrary", - "archer", - "ardent", - "arena", - "argue", - "arises", - "army", - "around", - "arrow", - "arsenic", - "artistic", - "ascend", - "ashtray", - "aside", - "asked", - "asleep", - "aspire", - "assorted", - "asylum", - "athlete", - "atlas", - "atom", - "atrium", - "attire", - "auburn", - "auctions", - "audio", - "august", - "aunt", - "austere", - "autumn", - "avatar", - "avidly", - "avoid", - "awakened", - "awesome", - "awful", - "awkward", - "awning", - "awoken", - "axes", - "axis", - "axle", - "aztec", - "azure", - "baby", - "bacon", - "badge", - "baffles", - "bagpipe", - "bailed", - "bakery", - "balding", - "bamboo", - "banjo", - "baptism", - "basin", - "batch", - "bawled", - "bays", - "because", - "beer", - "befit", - "begun", - "behind", - "being", - "below", - "bemused", - "benches", - "berries", - "bested", - "betting", - "bevel", - "beware", - "beyond", - "bias", - "bicycle", - "bids", - "bifocals", - "biggest", - "bikini", - "bimonthly", - "binocular", - "biology", - "biplane", - "birth", - "biscuit", - "bite", - "biweekly", - "blender", - "blip", - "bluntly", - "boat", - "bobsled", - "bodies", - "bogeys", - "boil", - "boldly", - "bomb", - "border", - "boss", - "both", - "bounced", - "bovine", - "bowling", - "boxes", - "boyfriend", - "broken", - "brunt", - "bubble", - "buckets", - "budget", - "buffet", - "bugs", - "building", - "bulb", - "bumper", - "bunch", - "business", - "butter", - "buying", - "buzzer", - "bygones", - "byline", - "bypass", - "cabin", - "cactus", - "cadets", - "cafe", - "cage", - "cajun", - "cake", - "calamity", - "camp", - "candy", - "casket", - "catch", - "cause", - "cavernous", - "cease", - "cedar", - "ceiling", - "cell", - "cement", - "cent", - "certain", - "chlorine", - "chrome", - "cider", - "cigar", - "cinema", - "circle", - "cistern", - "citadel", - "civilian", - "claim", - "click", - "clue", - "coal", - "cobra", - "cocoa", - "code", - "coexist", - "coffee", - "cogs", - "cohesive", - "coils", - "colony", - "comb", - "cool", - "copy", - "corrode", - "costume", - "cottage", - "cousin", - "cowl", - "criminal", - "cube", - "cucumber", - "cuddled", - "cuffs", - "cuisine", - "cunning", - "cupcake", - "custom", - "cycling", - "cylinder", - "cynical", - "dabbing", - "dads", - "daft", - "dagger", - "daily", - "damp", - "dangerous", - "dapper", - "darted", - "dash", - "dating", - "dauntless", - "dawn", - "daytime", - "dazed", - "debut", - "decay", - "dedicated", - "deepest", - "deftly", - "degrees", - "dehydrate", - "deity", - "dejected", - "delayed", - "demonstrate", - "dented", - "deodorant", - "depth", - "desk", - "devoid", - "dewdrop", - "dexterity", - "dialect", - "dice", - "diet", - "different", - "digit", - "dilute", - "dime", - "dinner", - "diode", - "diplomat", - "directed", - "distance", - "ditch", - "divers", - "dizzy", - "doctor", - "dodge", - "does", - "dogs", - "doing", - "dolphin", - "domestic", - "donuts", - "doorway", - "dormant", - "dosage", - "dotted", - "double", - "dove", - "down", - "dozen", - "dreams", - "drinks", - "drowning", - "drunk", - "drying", - "dual", - "dubbed", - "duckling", - "dude", - "duets", - "duke", - "dullness", - "dummy", - "dunes", - "duplex", - "duration", - "dusted", - "duties", - "dwarf", - "dwelt", - "dwindling", - "dying", - "dynamite", - "dyslexic", - "each", - "eagle", - "earth", - "easy", - "eating", - "eavesdrop", - "eccentric", - "echo", - "eclipse", - "economics", - "ecstatic", - "eden", - "edgy", - "edited", - "educated", - "eels", - "efficient", - "eggs", - "egotistic", - "eight", - "either", - "eject", - "elapse", - "elbow", - "eldest", - "eleven", - "elite", - "elope", - "else", - "eluded", - "emails", - "ember", - "emerge", - "emit", - "emotion", - "empty", - "emulate", - "energy", - "enforce", - "enhanced", - "enigma", - "enjoy", - "enlist", - "enmity", - "enough", - "enraged", - "ensign", - "entrance", - "envy", - "epoxy", - "equip", - "erase", - "erected", - "erosion", - "error", - "eskimos", - "espionage", - "essential", - "estate", - "etched", - "eternal", - "ethics", - "etiquette", - "evaluate", - "evenings", - "evicted", - "evolved", - "examine", - "excess", - "exhale", - "exit", - "exotic", - "exquisite", - "extra", - "exult", - "fabrics", - "factual", - "fading", - "fainted", - "faked", - "fall", - "family", - "fancy", - "farming", - "fatal", - "faulty", - "fawns", - "faxed", - "fazed", - "feast", - "february", - "federal", - "feel", - "feline", - "females", - "fences", - "ferry", - "festival", - "fetches", - "fever", - "fewest", - "fiat", - "fibula", - "fictional", - "fidget", - "fierce", - "fifteen", - "fight", - "films", - "firm", - "fishing", - "fitting", - "five", - "fixate", - "fizzle", - "fleet", - "flippant", - "flying", - "foamy", - "focus", - "foes", - "foggy", - "foiled", - "folding", - "fonts", - "foolish", - "fossil", - "fountain", - "fowls", - "foxes", - "foyer", - "framed", - "friendly", - "frown", - "fruit", - "frying", - "fudge", - "fuel", - "fugitive", - "fully", - "fuming", - "fungal", - "furnished", - "fuselage", - "future", - "fuzzy", - "gables", - "gadget", - "gags", - "gained", - "galaxy", - "gambit", - "gang", - "gasp", - "gather", - "gauze", - "gave", - "gawk", - "gaze", - "gearbox", - "gecko", - "geek", - "gels", - "gemstone", - "general", - "geometry", - "germs", - "gesture", - "getting", - "geyser", - "ghetto", - "ghost", - "giant", - "giddy", - "gifts", - "gigantic", - "gills", - "gimmick", - "ginger", - "girth", - "giving", - "glass", - "gleeful", - "glide", - "gnaw", - "gnome", - "goat", - "goblet", - "godfather", - "goes", - "goggles", - "going", - "goldfish", - "gone", - "goodbye", - "gopher", - "gorilla", - "gossip", - "gotten", - "gourmet", - "governing", - "gown", - "greater", - "grunt", - "guarded", - "guest", - "guide", - "gulp", - "gumball", - "guru", - "gusts", - "gutter", - "guys", - "gymnast", - "gypsy", - "gyrate", - "habitat", - "hacksaw", - "haggled", - "hairy", - "hamburger", - "happens", - "hashing", - "hatchet", - "haunted", - "having", - "hawk", - "haystack", - "hazard", - "hectare", - "hedgehog", - "heels", - "hefty", - "height", - "hemlock", - "hence", - "heron", - "hesitate", - "hexagon", - "hickory", - "hiding", - "highway", - "hijack", - "hiker", - "hills", - "himself", - "hinder", - "hippo", - "hire", - "history", - "hitched", - "hive", - "hoax", - "hobby", - "hockey", - "hoisting", - "hold", - "honked", - "hookup", - "hope", - "hornet", - "hospital", - "hotel", - "hounded", - "hover", - "howls", - "hubcaps", - "huddle", - "huge", - "hull", - "humid", - "hunter", - "hurried", - "husband", - "huts", - "hybrid", - "hydrogen", - "hyper", - "iceberg", - "icing", - "icon", - "identity", - "idiom", - "idled", - "idols", - "igloo", - "ignore", - "iguana", - "illness", - "imagine", - "imbalance", - "imitate", - "impel", - "inactive", - "inbound", - "incur", - "industrial", - "inexact", - "inflamed", - "ingested", - "initiate", - "injury", - "inkling", - "inline", - "inmate", - "innocent", - "inorganic", - "input", - "inquest", - "inroads", - "insult", - "intended", - "inundate", - "invoke", - "inwardly", - "ionic", - "irate", - "iris", - "irony", - "irritate", - "island", - "isolated", - "issued", - "italics", - "itches", - "items", - "itinerary", - "itself", - "ivory", - "jabbed", - "jackets", - "jaded", - "jagged", - "jailed", - "jamming", - "january", - "jargon", - "jaunt", - "javelin", - "jaws", - "jazz", - "jeans", - "jeers", - "jellyfish", - "jeopardy", - "jerseys", - "jester", - "jetting", - "jewels", - "jigsaw", - "jingle", - "jittery", - "jive", - "jobs", - "jockey", - "jogger", - "joining", - "joking", - "jolted", - "jostle", - "journal", - "joyous", - "jubilee", - "judge", - "juggled", - "juicy", - "jukebox", - "july", - "jump", - "junk", - "jury", - "justice", - "juvenile", - "kangaroo", - "karate", - "keep", - "kennel", - "kept", - "kernels", - "kettle", - "keyboard", - "kickoff", - "kidneys", - "king", - "kiosk", - "kisses", - "kitchens", - "kiwi", - "knapsack", - "knee", - "knife", - "knowledge", - "knuckle", - "koala", - "laboratory", - "ladder", - "lagoon", - "lair", - "lakes", - "lamb", - "language", - "laptop", - "large", - "last", - "later", - "launching", - "lava", - "lawsuit", - "layout", - "lazy", - "lectures", - "ledge", - "leech", - "left", - "legion", - "leisure", - "lemon", - "lending", - "leopard", - "lesson", - "lettuce", - "lexicon", - "liar", - "library", - "licks", - "lids", - "lied", - "lifestyle", - "light", - "likewise", - "lilac", - "limits", - "linen", - "lion", - "lipstick", - "liquid", - "listen", - "lively", - "loaded", - "lobster", - "locker", - "lodge", - "lofty", - "logic", - "loincloth", - "long", - "looking", - "lopped", - "lordship", - "losing", - "lottery", - "loudly", - "love", - "lower", - "loyal", - "lucky", - "luggage", - "lukewarm", - "lullaby", - "lumber", - "lunar", - "lurk", - "lush", - "luxury", - "lymph", - "lynx", - "lyrics", - "macro", - "madness", - "magically", - "mailed", - "major", - "makeup", - "malady", - "mammal", - "maps", - "masterful", - "match", - "maul", - "maverick", - "maximum", - "mayor", - "maze", - "meant", - "mechanic", - "medicate", - "meeting", - "megabyte", - "melting", - "memoir", - "menu", - "merger", - "mesh", - "metro", - "mews", - "mice", - "midst", - "mighty", - "mime", - "mirror", - "misery", - "mittens", - "mixture", - "moat", - "mobile", - "mocked", - "mohawk", - "moisture", - "molten", - "moment", - "money", - "moon", - "mops", - "morsel", - "mostly", - "motherly", - "mouth", - "movement", - "mowing", - "much", - "muddy", - "muffin", - "mugged", - "mullet", - "mumble", - "mundane", - "muppet", - "mural", - "musical", - "muzzle", - "myriad", - "mystery", - "myth", - "nabbing", - "nagged", - "nail", - "names", - "nanny", - "napkin", - "narrate", - "nasty", - "natural", - "nautical", - "navy", - "nearby", - "necklace", - "needed", - "negative", - "neither", - "neon", - "nephew", - "nerves", - "nestle", - "network", - "neutral", - "never", - "newt", - "nexus", - "nibs", - "niche", - "niece", - "nifty", - "nightly", - "nimbly", - "nineteen", - "nirvana", - "nitrogen", - "nobody", - "nocturnal", - "nodes", - "noises", - "nomad", - "noodles", - "northern", - "nostril", - "noted", - "nouns", - "novelty", - "nowhere", - "nozzle", - "nuance", - "nucleus", - "nudged", - "nugget", - "nuisance", - "null", - "number", - "nuns", - "nurse", - "nutshell", - "nylon", - "oaks", - "oars", - "oasis", - "oatmeal", - "obedient", - "object", - "obliged", - "obnoxious", - "observant", - "obtains", - "obvious", - "occur", - "ocean", - "october", - "odds", - "odometer", - "offend", - "often", - "oilfield", - "ointment", - "okay", - "older", - "olive", - "olympics", - "omega", - "omission", - "omnibus", - "onboard", - "oncoming", - "oneself", - "ongoing", - "onion", - "online", - "onslaught", - "onto", - "onward", - "oozed", - "opacity", - "opened", - "opposite", - "optical", - "opus", - "orange", - "orbit", - "orchid", - "orders", - "organs", - "origin", - "ornament", - "orphans", - "oscar", - "ostrich", - "otherwise", - "otter", - "ouch", - "ought", - "ounce", - "ourselves", - "oust", - "outbreak", - "oval", - "oven", - "owed", - "owls", - "owner", - "oxidant", - "oxygen", - "oyster", - "ozone", - "pact", - "paddles", - "pager", - "pairing", - "palace", - "pamphlet", - "pancakes", - "paper", - "paradise", - "pastry", - "patio", - "pause", - "pavements", - "pawnshop", - "payment", - "peaches", - "pebbles", - "peculiar", - "pedantic", - "peeled", - "pegs", - "pelican", - "pencil", - "people", - "pepper", - "perfect", - "pests", - "petals", - "phase", - "pheasants", - "phone", - "phrases", - "physics", - "piano", - "picked", - "pierce", - "pigment", - "piloted", - "pimple", - "pinched", - "pioneer", - "pipeline", - "pirate", - "pistons", - "pitched", - "pivot", - "pixels", - "pizza", - "playful", - "pledge", - "pliers", - "plotting", - "plus", - "plywood", - "poaching", - "pockets", - "podcast", - "poetry", - "point", - "poker", - "polar", - "ponies", - "pool", - "popular", - "portents", - "possible", - "potato", - "pouch", - "poverty", - "powder", - "pram", - "present", - "pride", - "problems", - "pruned", - "prying", - "psychic", - "public", - "puck", - "puddle", - "puffin", - "pulp", - "pumpkins", - "punch", - "puppy", - "purged", - "push", - "putty", - "puzzled", - "pylons", - "pyramid", - "python", - "queen", - "quick", - "quote", - "rabbits", - "racetrack", - "radar", - "rafts", - "rage", - "railway", - "raking", - "rally", - "ramped", - "randomly", - "rapid", - "rarest", - "rash", - "rated", - "ravine", - "rays", - "razor", - "react", - "rebel", - "recipe", - "reduce", - "reef", - "refer", - "regular", - "reheat", - "reinvest", - "rejoices", - "rekindle", - "relic", - "remedy", - "renting", - "reorder", - "repent", - "request", - "reruns", - "rest", - "return", - "reunion", - "revamp", - "rewind", - "rhino", - "rhythm", - "ribbon", - "richly", - "ridges", - "rift", - "rigid", - "rims", - "ringing", - "riots", - "ripped", - "rising", - "ritual", - "river", - "roared", - "robot", - "rockets", - "rodent", - "rogue", - "roles", - "romance", - "roomy", - "roped", - "roster", - "rotate", - "rounded", - "rover", - "rowboat", - "royal", - "ruby", - "rudely", - "ruffled", - "rugged", - "ruined", - "ruling", - "rumble", - "runway", - "rural", - "rustled", - "ruthless", - "sabotage", - "sack", - "sadness", - "safety", - "saga", - "sailor", - "sake", - "salads", - "sample", - "sanity", - "sapling", - "sarcasm", - "sash", - "satin", - "saucepan", - "saved", - "sawmill", - "saxophone", - "sayings", - "scamper", - "scenic", - "school", - "science", - "scoop", - "scrub", - "scuba", - "seasons", - "second", - "sedan", - "seeded", - "segments", - "seismic", - "selfish", - "semifinal", - "sensible", - "september", - "sequence", - "serving", - "session", - "setup", - "seventh", - "sewage", - "shackles", - "shelter", - "shipped", - "shocking", - "shrugged", - "shuffled", - "shyness", - "siblings", - "sickness", - "sidekick", - "sieve", - "sifting", - "sighting", - "silk", - "simplest", - "sincerely", - "sipped", - "siren", - "situated", - "sixteen", - "sizes", - "skater", - "skew", - "skirting", - "skulls", - "skydive", - "slackens", - "sleepless", - "slid", - "slower", - "slug", - "smash", - "smelting", - "smidgen", - "smog", - "smuggled", - "snake", - "sneeze", - "sniff", - "snout", - "snug", - "soapy", - "sober", - "soccer", - "soda", - "software", - "soggy", - "soil", - "solved", - "somewhere", - "sonic", - "soothe", - "soprano", - "sorry", - "southern", - "sovereign", - "sowed", - "soya", - "space", - "speedy", - "sphere", - "spiders", - "splendid", - "spout", - "sprig", - "spud", - "spying", - "square", - "stacking", - "stellar", - "stick", - "stockpile", - "strained", - "stunning", - "stylishly", - "subtly", - "succeed", - "suddenly", - "suede", - "suffice", - "sugar", - "suitcase", - "sulking", - "summon", - "sunken", - "superior", - "surfer", - "sushi", - "suture", - "swagger", - "swept", - "swiftly", - "sword", - "swung", - "syllabus", - "symptoms", - "syndrome", - "syringe", - "system", - "taboo", - "tacit", - "tadpoles", - "tagged", - "tail", - "taken", - "talent", - "tamper", - "tanks", - "tapestry", - "tarnished", - "tasked", - "tattoo", - "taunts", - "tavern", - "tawny", - "taxi", - "teardrop", - "technical", - "tedious", - "teeming", - "tell", - "template", - "tender", - "tepid", - "tequila", - "terminal", - "testing", - "tether", - "textbook", - "thaw", - "theatrics", - "thirsty", - "thorn", - "threaten", - "thumbs", - "thwart", - "ticket", - "tidy", - "tiers", - "tiger", - "tilt", - "timber", - "tinted", - "tipsy", - "tirade", - "tissue", - "titans", - "toaster", - "tobacco", - "today", - "toenail", - "toffee", - "together", - "toilet", - "token", - "tolerant", - "tomorrow", - "tonic", - "toolbox", - "topic", - "torch", - "tossed", - "total", - "touchy", - "towel", - "toxic", - "toyed", - "trash", - "trendy", - "tribal", - "trolling", - "truth", - "trying", - "tsunami", - "tubes", - "tucks", - "tudor", - "tuesday", - "tufts", - "tugs", - "tuition", - "tulips", - "tumbling", - "tunnel", - "turnip", - "tusks", - "tutor", - "tuxedo", - "twang", - "tweezers", - "twice", - "twofold", - "tycoon", - "typist", - "tyrant", - "ugly", - "ulcers", - "ultimate", - "umbrella", - "umpire", - "unafraid", - "unbending", - "uncle", - "under", - "uneven", - "unfit", - "ungainly", - "unhappy", - "union", - "unjustly", - "unknown", - "unlikely", - "unmask", - "unnoticed", - "unopened", - "unplugs", - "unquoted", - "unrest", - "unsafe", - "until", - "unusual", - "unveil", - "unwind", - "unzip", - "upbeat", - "upcoming", - "update", - "upgrade", - "uphill", - "upkeep", - "upload", - "upon", - "upper", - "upright", - "upstairs", - "uptight", - "upwards", - "urban", - "urchins", - "urgent", - "usage", - "useful", - "usher", - "using", - "usual", - "utensils", - "utility", - "utmost", - "utopia", - "uttered", - "vacation", - "vague", - "vain", - "value", - "vampire", - "vane", - "vapidly", - "vary", - "vastness", - "vats", - "vaults", - "vector", - "veered", - "vegan", - "vehicle", - "vein", - "velvet", - "venomous", - "verification", - "vessel", - "veteran", - "vexed", - "vials", - "vibrate", - "victim", - "video", - "viewpoint", - "vigilant", - "viking", - "village", - "vinegar", - "violin", - "vipers", - "virtual", - "visited", - "vitals", - "vivid", - "vixen", - "vocal", - "vogue", - "voice", - "volcano", - "vortex", - "voted", - "voucher", - "vowels", - "voyage", - "vulture", - "wade", - "waffle", - "wagtail", - "waist", - "waking", - "wallets", - "wanted", - "warped", - "washing", - "water", - "waveform", - "waxing", - "wayside", - "weavers", - "website", - "wedge", - "weekday", - "weird", - "welders", - "went", - "wept", - "were", - "western", - "wetsuit", - "whale", - "when", - "whipped", - "whole", - "wickets", - "width", - "wield", - "wife", - "wiggle", - "wildly", - "winter", - "wipeout", - "wiring", - "wise", - "withdrawn", - "wives", - "wizard", - "wobbly", - "woes", - "woken", - "wolf", - "womanly", - "wonders", - "woozy", - "worry", - "wounded", - "woven", - "wrap", - "wrist", - "wrong", - "yacht", - "yahoo", - "yanks", - "yard", - "yawning", - "yearbook", - "yellow", - "yesterday", - "yeti", - "yields", - "yodel", - "yoga", - "younger", - "yoyo", - "zapped", - "zeal", - "zebra", - "zero", - "zesty", - "zigzags", - "zinger", - "zippers", - "zodiac", - "zombie", - "zones", - "zoom" -]; diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 011fed169..838f7224c 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: args - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.0" asn1lib: dependency: transitive description: name: asn1lib - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.3" async: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: "direct dev" description: @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "7.2.10" built_collection: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: built_value - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.4.3" + version: "8.9.2" characters: dependency: transitive description: @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" clock: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.10.0" collection: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cw_core: dependency: "direct main" description: @@ -188,10 +188,10 @@ packages: dependency: "direct main" description: name: encrypt - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.3" fake_async: dependency: transitive description: @@ -204,10 +204,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -233,10 +233,10 @@ packages: dependency: "direct main" description: name: flutter_mobx - sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e" url: "https://pub.dev" source: hosted - version: "2.0.6+5" + version: "2.2.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -246,42 +246,42 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: name: graphs - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" hashlib: dependency: transitive description: name: hashlib - sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96" + sha256: "5037d3b8c36384c03a728543ae67d962a56970c5432a50862279fe68ee4c8411" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.19.1" hashlib_codecs: dependency: transitive description: name: hashlib_codecs - sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626" + sha256: "2b570061f5a4b378425be28a576c1e11783450355ad4345a19f606ff3d96db0f" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.5.0" hive: dependency: transitive description: @@ -302,10 +302,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -342,18 +342,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -382,10 +382,10 @@ packages: dependency: transitive description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" matcher: dependency: transitive description: @@ -414,33 +414,33 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobx: dependency: "direct main" description: name: mobx - sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a + sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e" url: "https://pub.dev" source: hosted - version: "2.1.3+1" + version: "2.3.3+2" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181" + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.0" monero: dependency: "direct main" description: - path: "." - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - resolved-ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - url: "https://github.com/mrcyjanek/monero.dart" + path: "impls/monero.dart" + ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" + resolved-ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" + url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" mutex: @@ -451,6 +451,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -471,26 +479,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -503,10 +511,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -519,26 +527,26 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.9.1" polyseed: dependency: "direct main" description: @@ -555,46 +563,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: + provider: dependency: transitive description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "6.1.2" pub_semver: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -604,10 +612,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" source_gen: dependency: transitive description: @@ -692,10 +700,10 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" unorm_dart: dependency: transitive description: @@ -728,38 +736,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.5" win32: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "5.5.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.6" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 53e50877f..b5a13a126 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -24,8 +24,9 @@ dependencies: path: ../cw_core monero: git: - url: https://github.com/mrcyjanek/monero.dart - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 + url: https://github.com/mrcyjanek/monero_c + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash + path: impls/monero.dart mutex: ^3.1.0 dev_dependencies: diff --git a/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart b/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart deleted file mode 100644 index 483b0a174..000000000 --- a/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart +++ /dev/null @@ -1,5 +0,0 @@ -class ConnectionToNodeException implements Exception { - ConnectionToNodeException({required this.message}); - - final String message; -} \ No newline at end of file diff --git a/cw_wownero/lib/api/structs/account_row.dart b/cw_wownero/lib/api/structs/account_row.dart deleted file mode 100644 index aa492ee0f..000000000 --- a/cw_wownero/lib/api/structs/account_row.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class AccountRow extends Struct { - @Int64() - external int id; - - external Pointer<Utf8> label; - - String getLabel() => label.toDartString(); - int getId() => id; -} diff --git a/cw_wownero/lib/api/structs/coins_info_row.dart b/cw_wownero/lib/api/structs/coins_info_row.dart deleted file mode 100644 index ff6f6ce73..000000000 --- a/cw_wownero/lib/api/structs/coins_info_row.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class CoinsInfoRow extends Struct { - @Int64() - external int blockHeight; - - external Pointer<Utf8> hash; - - @Uint64() - external int internalOutputIndex; - - @Uint64() - external int globalOutputIndex; - - @Int8() - external int spent; - - @Int8() - external int frozen; - - @Uint64() - external int spentHeight; - - @Uint64() - external int amount; - - @Int8() - external int rct; - - @Int8() - external int keyImageKnown; - - @Uint64() - external int pkIndex; - - @Uint32() - external int subaddrIndex; - - @Uint32() - external int subaddrAccount; - - external Pointer<Utf8> address; - - external Pointer<Utf8> addressLabel; - - external Pointer<Utf8> keyImage; - - @Uint64() - external int unlockTime; - - @Int8() - external int unlocked; - - external Pointer<Utf8> pubKey; - - @Int8() - external int coinbase; - - external Pointer<Utf8> description; - - String getHash() => hash.toDartString(); - - String getAddress() => address.toDartString(); - - String getAddressLabel() => addressLabel.toDartString(); - - String getKeyImage() => keyImage.toDartString(); - - String getPubKey() => pubKey.toDartString(); - - String getDescription() => description.toDartString(); -} diff --git a/cw_wownero/lib/api/structs/subaddress_row.dart b/cw_wownero/lib/api/structs/subaddress_row.dart deleted file mode 100644 index d593a793d..000000000 --- a/cw_wownero/lib/api/structs/subaddress_row.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class SubaddressRow extends Struct { - @Int64() - external int id; - - external Pointer<Utf8> address; - - external Pointer<Utf8> label; - - String getLabel() => label.toDartString(); - String getAddress() => address.toDartString(); - int getId() => id; -} \ No newline at end of file diff --git a/cw_wownero/lib/api/structs/transaction_info_row.dart b/cw_wownero/lib/api/structs/transaction_info_row.dart deleted file mode 100644 index bdcc64d3f..000000000 --- a/cw_wownero/lib/api/structs/transaction_info_row.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class TransactionInfoRow extends Struct { - @Uint64() - external int amount; - - @Uint64() - external int fee; - - @Uint64() - external int blockHeight; - - @Uint64() - external int confirmations; - - @Uint32() - external int subaddrAccount; - - @Int8() - external int direction; - - @Int8() - external int isPending; - - @Uint32() - external int subaddrIndex; - - external Pointer<Utf8> hash; - - external Pointer<Utf8> paymentId; - - @Int64() - external int datetime; - - int getDatetime() => datetime; - int getAmount() => amount >= 0 ? amount : amount * -1; - bool getIsPending() => isPending != 0; - String getHash() => hash.toDartString(); - String getPaymentId() => paymentId.toDartString(); -} diff --git a/cw_wownero/lib/api/structs/ut8_box.dart b/cw_wownero/lib/api/structs/ut8_box.dart deleted file mode 100644 index 53e678c88..000000000 --- a/cw_wownero/lib/api/structs/ut8_box.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; - -class Utf8Box extends Struct { - external Pointer<Utf8> value; - - String getValue() => value.toDartString(); -} diff --git a/cw_wownero/lib/api/transaction_history.dart b/cw_wownero/lib/api/transaction_history.dart index 3ccd0b3c6..a1e1e3c9b 100644 --- a/cw_wownero/lib/api/transaction_history.dart +++ b/cw_wownero/lib/api/transaction_history.dart @@ -285,7 +285,7 @@ class Transaction { }; } - // S finalubAddress? subAddress; + // final SubAddress? subAddress; // List<Transfer> transfers = []; // final int txIndex; final wownero.TransactionInfo txInfo; @@ -321,4 +321,4 @@ class Transaction { required this.key, required this.txInfo }); -} \ No newline at end of file +} diff --git a/cw_wownero/lib/api/wallet_manager.dart b/cw_wownero/lib/api/wallet_manager.dart index afcc536e7..7915373bb 100644 --- a/cw_wownero/lib/api/wallet_manager.dart +++ b/cw_wownero/lib/api/wallet_manager.dart @@ -1,4 +1,5 @@ import 'dart:ffi'; +import 'dart:io'; import 'dart:isolate'; import 'package:cw_wownero/api/account_list.dart'; @@ -8,8 +9,42 @@ import 'package:cw_wownero/api/exceptions/wallet_restore_from_keys_exception.dar import 'package:cw_wownero/api/exceptions/wallet_restore_from_seed_exception.dart'; import 'package:cw_wownero/api/transaction_history.dart'; import 'package:cw_wownero/api/wallet.dart'; +import 'package:flutter/foundation.dart'; import 'package:monero/wownero.dart' as wownero; +class MoneroCException implements Exception { + final String message; + + MoneroCException(this.message); + + @override + String toString() { + return message; + } +} + +void checkIfMoneroCIsFine() { + final cppCsCpp = wownero.WOWNERO_checksum_wallet2_api_c_cpp(); + final cppCsH = wownero.WOWNERO_checksum_wallet2_api_c_h(); + final cppCsExp = wownero.WOWNERO_checksum_wallet2_api_c_exp(); + + final dartCsCpp = wownero.wallet2_api_c_cpp_sha256; + final dartCsH = wownero.wallet2_api_c_h_sha256; + final dartCsExp = wownero.wallet2_api_c_exp_sha256; + + if (cppCsCpp != dartCsCpp) { + throw MoneroCException("monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'"); + } + + if (cppCsH != dartCsH) { + throw MoneroCException("monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'"); + } + + if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) { + throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'"); + } +} + wownero.WalletManager? _wmPtr; final wownero.WalletManager wmPtr = Pointer.fromAddress((() { try { diff --git a/cw_wownero/lib/cw_wownero.dart b/cw_wownero/lib/cw_wownero.dart deleted file mode 100644 index 33a55e305..000000000 --- a/cw_wownero/lib/cw_wownero.dart +++ /dev/null @@ -1,8 +0,0 @@ - -import 'cw_wownero_platform_interface.dart'; - -class CwWownero { - Future<String?> getPlatformVersion() { - return CwWowneroPlatform.instance.getPlatformVersion(); - } -} diff --git a/cw_wownero/lib/cw_wownero_method_channel.dart b/cw_wownero/lib/cw_wownero_method_channel.dart deleted file mode 100644 index d797f5f81..000000000 --- a/cw_wownero/lib/cw_wownero_method_channel.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import 'cw_wownero_platform_interface.dart'; - -/// An implementation of [CwWowneroPlatform] that uses method channels. -class MethodChannelCwWownero extends CwWowneroPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel('cw_wownero'); - - @override - Future<String?> getPlatformVersion() async { - final version = await methodChannel.invokeMethod<String>('getPlatformVersion'); - return version; - } -} diff --git a/cw_wownero/lib/cw_wownero_platform_interface.dart b/cw_wownero/lib/cw_wownero_platform_interface.dart deleted file mode 100644 index 78b21592c..000000000 --- a/cw_wownero/lib/cw_wownero_platform_interface.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'cw_wownero_method_channel.dart'; - -abstract class CwWowneroPlatform extends PlatformInterface { - /// Constructs a CwWowneroPlatform. - CwWowneroPlatform() : super(token: _token); - - static final Object _token = Object(); - - static CwWowneroPlatform _instance = MethodChannelCwWownero(); - - /// The default instance of [CwWowneroPlatform] to use. - /// - /// Defaults to [MethodChannelCwWownero]. - static CwWowneroPlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [CwWowneroPlatform] when - /// they register themselves. - static set instance(CwWowneroPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future<String?> getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } -} diff --git a/cw_wownero/lib/mywownero.dart b/cw_wownero/lib/mywownero.dart deleted file mode 100644 index d50e48b64..000000000 --- a/cw_wownero/lib/mywownero.dart +++ /dev/null @@ -1,1689 +0,0 @@ -const prefixLength = 3; - -String swapEndianBytes(String original) { - if (original.length != 8) { - return ''; - } - - return original[6] + - original[7] + - original[4] + - original[5] + - original[2] + - original[3] + - original[0] + - original[1]; -} - -List<String> tructWords(List<String> wordSet) { - final start = 0; - final end = prefixLength; - - return wordSet.map((word) => word.substring(start, end)).toList(); -} - -String mnemonicDecode(String seed) { - final n = englistWordSet.length; - var out = ''; - var wlist = seed.split(' '); - wlist.removeLast(); - - for (var i = 0; i < wlist.length; i += 3) { - final w1 = - tructWords(englistWordSet).indexOf(wlist[i].substring(0, prefixLength)); - final w2 = tructWords(englistWordSet) - .indexOf(wlist[i + 1].substring(0, prefixLength)); - final w3 = tructWords(englistWordSet) - .indexOf(wlist[i + 2].substring(0, prefixLength)); - - if (w1 == -1 || w2 == -1 || w3 == -1) { - print("invalid word in mnemonic"); - return ''; - } - - final x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n); - - if (x % n != w1) { - print("Something went wrong when decoding your private key, please try again"); - return ''; - } - - final _res = '0000000' + x.toRadixString(16); - final start = _res.length - 8; - final end = _res.length; - final res = _res.substring(start, end); - - out += swapEndianBytes(res); - } - - return out; -} - -final englistWordSet = [ - "abbey", - "abducts", - "ability", - "ablaze", - "abnormal", - "abort", - "abrasive", - "absorb", - "abyss", - "academy", - "aces", - "aching", - "acidic", - "acoustic", - "acquire", - "across", - "actress", - "acumen", - "adapt", - "addicted", - "adept", - "adhesive", - "adjust", - "adopt", - "adrenalin", - "adult", - "adventure", - "aerial", - "afar", - "affair", - "afield", - "afloat", - "afoot", - "afraid", - "after", - "against", - "agenda", - "aggravate", - "agile", - "aglow", - "agnostic", - "agony", - "agreed", - "ahead", - "aided", - "ailments", - "aimless", - "airport", - "aisle", - "ajar", - "akin", - "alarms", - "album", - "alchemy", - "alerts", - "algebra", - "alkaline", - "alley", - "almost", - "aloof", - "alpine", - "already", - "also", - "altitude", - "alumni", - "always", - "amaze", - "ambush", - "amended", - "amidst", - "ammo", - "amnesty", - "among", - "amply", - "amused", - "anchor", - "android", - "anecdote", - "angled", - "ankle", - "annoyed", - "answers", - "antics", - "anvil", - "anxiety", - "anybody", - "apart", - "apex", - "aphid", - "aplomb", - "apology", - "apply", - "apricot", - "aptitude", - "aquarium", - "arbitrary", - "archer", - "ardent", - "arena", - "argue", - "arises", - "army", - "around", - "arrow", - "arsenic", - "artistic", - "ascend", - "ashtray", - "aside", - "asked", - "asleep", - "aspire", - "assorted", - "asylum", - "athlete", - "atlas", - "atom", - "atrium", - "attire", - "auburn", - "auctions", - "audio", - "august", - "aunt", - "austere", - "autumn", - "avatar", - "avidly", - "avoid", - "awakened", - "awesome", - "awful", - "awkward", - "awning", - "awoken", - "axes", - "axis", - "axle", - "aztec", - "azure", - "baby", - "bacon", - "badge", - "baffles", - "bagpipe", - "bailed", - "bakery", - "balding", - "bamboo", - "banjo", - "baptism", - "basin", - "batch", - "bawled", - "bays", - "because", - "beer", - "befit", - "begun", - "behind", - "being", - "below", - "bemused", - "benches", - "berries", - "bested", - "betting", - "bevel", - "beware", - "beyond", - "bias", - "bicycle", - "bids", - "bifocals", - "biggest", - "bikini", - "bimonthly", - "binocular", - "biology", - "biplane", - "birth", - "biscuit", - "bite", - "biweekly", - "blender", - "blip", - "bluntly", - "boat", - "bobsled", - "bodies", - "bogeys", - "boil", - "boldly", - "bomb", - "border", - "boss", - "both", - "bounced", - "bovine", - "bowling", - "boxes", - "boyfriend", - "broken", - "brunt", - "bubble", - "buckets", - "budget", - "buffet", - "bugs", - "building", - "bulb", - "bumper", - "bunch", - "business", - "butter", - "buying", - "buzzer", - "bygones", - "byline", - "bypass", - "cabin", - "cactus", - "cadets", - "cafe", - "cage", - "cajun", - "cake", - "calamity", - "camp", - "candy", - "casket", - "catch", - "cause", - "cavernous", - "cease", - "cedar", - "ceiling", - "cell", - "cement", - "cent", - "certain", - "chlorine", - "chrome", - "cider", - "cigar", - "cinema", - "circle", - "cistern", - "citadel", - "civilian", - "claim", - "click", - "clue", - "coal", - "cobra", - "cocoa", - "code", - "coexist", - "coffee", - "cogs", - "cohesive", - "coils", - "colony", - "comb", - "cool", - "copy", - "corrode", - "costume", - "cottage", - "cousin", - "cowl", - "criminal", - "cube", - "cucumber", - "cuddled", - "cuffs", - "cuisine", - "cunning", - "cupcake", - "custom", - "cycling", - "cylinder", - "cynical", - "dabbing", - "dads", - "daft", - "dagger", - "daily", - "damp", - "dangerous", - "dapper", - "darted", - "dash", - "dating", - "dauntless", - "dawn", - "daytime", - "dazed", - "debut", - "decay", - "dedicated", - "deepest", - "deftly", - "degrees", - "dehydrate", - "deity", - "dejected", - "delayed", - "demonstrate", - "dented", - "deodorant", - "depth", - "desk", - "devoid", - "dewdrop", - "dexterity", - "dialect", - "dice", - "diet", - "different", - "digit", - "dilute", - "dime", - "dinner", - "diode", - "diplomat", - "directed", - "distance", - "ditch", - "divers", - "dizzy", - "doctor", - "dodge", - "does", - "dogs", - "doing", - "dolphin", - "domestic", - "donuts", - "doorway", - "dormant", - "dosage", - "dotted", - "double", - "dove", - "down", - "dozen", - "dreams", - "drinks", - "drowning", - "drunk", - "drying", - "dual", - "dubbed", - "duckling", - "dude", - "duets", - "duke", - "dullness", - "dummy", - "dunes", - "duplex", - "duration", - "dusted", - "duties", - "dwarf", - "dwelt", - "dwindling", - "dying", - "dynamite", - "dyslexic", - "each", - "eagle", - "earth", - "easy", - "eating", - "eavesdrop", - "eccentric", - "echo", - "eclipse", - "economics", - "ecstatic", - "eden", - "edgy", - "edited", - "educated", - "eels", - "efficient", - "eggs", - "egotistic", - "eight", - "either", - "eject", - "elapse", - "elbow", - "eldest", - "eleven", - "elite", - "elope", - "else", - "eluded", - "emails", - "ember", - "emerge", - "emit", - "emotion", - "empty", - "emulate", - "energy", - "enforce", - "enhanced", - "enigma", - "enjoy", - "enlist", - "enmity", - "enough", - "enraged", - "ensign", - "entrance", - "envy", - "epoxy", - "equip", - "erase", - "erected", - "erosion", - "error", - "eskimos", - "espionage", - "essential", - "estate", - "etched", - "eternal", - "ethics", - "etiquette", - "evaluate", - "evenings", - "evicted", - "evolved", - "examine", - "excess", - "exhale", - "exit", - "exotic", - "exquisite", - "extra", - "exult", - "fabrics", - "factual", - "fading", - "fainted", - "faked", - "fall", - "family", - "fancy", - "farming", - "fatal", - "faulty", - "fawns", - "faxed", - "fazed", - "feast", - "february", - "federal", - "feel", - "feline", - "females", - "fences", - "ferry", - "festival", - "fetches", - "fever", - "fewest", - "fiat", - "fibula", - "fictional", - "fidget", - "fierce", - "fifteen", - "fight", - "films", - "firm", - "fishing", - "fitting", - "five", - "fixate", - "fizzle", - "fleet", - "flippant", - "flying", - "foamy", - "focus", - "foes", - "foggy", - "foiled", - "folding", - "fonts", - "foolish", - "fossil", - "fountain", - "fowls", - "foxes", - "foyer", - "framed", - "friendly", - "frown", - "fruit", - "frying", - "fudge", - "fuel", - "fugitive", - "fully", - "fuming", - "fungal", - "furnished", - "fuselage", - "future", - "fuzzy", - "gables", - "gadget", - "gags", - "gained", - "galaxy", - "gambit", - "gang", - "gasp", - "gather", - "gauze", - "gave", - "gawk", - "gaze", - "gearbox", - "gecko", - "geek", - "gels", - "gemstone", - "general", - "geometry", - "germs", - "gesture", - "getting", - "geyser", - "ghetto", - "ghost", - "giant", - "giddy", - "gifts", - "gigantic", - "gills", - "gimmick", - "ginger", - "girth", - "giving", - "glass", - "gleeful", - "glide", - "gnaw", - "gnome", - "goat", - "goblet", - "godfather", - "goes", - "goggles", - "going", - "goldfish", - "gone", - "goodbye", - "gopher", - "gorilla", - "gossip", - "gotten", - "gourmet", - "governing", - "gown", - "greater", - "grunt", - "guarded", - "guest", - "guide", - "gulp", - "gumball", - "guru", - "gusts", - "gutter", - "guys", - "gymnast", - "gypsy", - "gyrate", - "habitat", - "hacksaw", - "haggled", - "hairy", - "hamburger", - "happens", - "hashing", - "hatchet", - "haunted", - "having", - "hawk", - "haystack", - "hazard", - "hectare", - "hedgehog", - "heels", - "hefty", - "height", - "hemlock", - "hence", - "heron", - "hesitate", - "hexagon", - "hickory", - "hiding", - "highway", - "hijack", - "hiker", - "hills", - "himself", - "hinder", - "hippo", - "hire", - "history", - "hitched", - "hive", - "hoax", - "hobby", - "hockey", - "hoisting", - "hold", - "honked", - "hookup", - "hope", - "hornet", - "hospital", - "hotel", - "hounded", - "hover", - "howls", - "hubcaps", - "huddle", - "huge", - "hull", - "humid", - "hunter", - "hurried", - "husband", - "huts", - "hybrid", - "hydrogen", - "hyper", - "iceberg", - "icing", - "icon", - "identity", - "idiom", - "idled", - "idols", - "igloo", - "ignore", - "iguana", - "illness", - "imagine", - "imbalance", - "imitate", - "impel", - "inactive", - "inbound", - "incur", - "industrial", - "inexact", - "inflamed", - "ingested", - "initiate", - "injury", - "inkling", - "inline", - "inmate", - "innocent", - "inorganic", - "input", - "inquest", - "inroads", - "insult", - "intended", - "inundate", - "invoke", - "inwardly", - "ionic", - "irate", - "iris", - "irony", - "irritate", - "island", - "isolated", - "issued", - "italics", - "itches", - "items", - "itinerary", - "itself", - "ivory", - "jabbed", - "jackets", - "jaded", - "jagged", - "jailed", - "jamming", - "january", - "jargon", - "jaunt", - "javelin", - "jaws", - "jazz", - "jeans", - "jeers", - "jellyfish", - "jeopardy", - "jerseys", - "jester", - "jetting", - "jewels", - "jigsaw", - "jingle", - "jittery", - "jive", - "jobs", - "jockey", - "jogger", - "joining", - "joking", - "jolted", - "jostle", - "journal", - "joyous", - "jubilee", - "judge", - "juggled", - "juicy", - "jukebox", - "july", - "jump", - "junk", - "jury", - "justice", - "juvenile", - "kangaroo", - "karate", - "keep", - "kennel", - "kept", - "kernels", - "kettle", - "keyboard", - "kickoff", - "kidneys", - "king", - "kiosk", - "kisses", - "kitchens", - "kiwi", - "knapsack", - "knee", - "knife", - "knowledge", - "knuckle", - "koala", - "laboratory", - "ladder", - "lagoon", - "lair", - "lakes", - "lamb", - "language", - "laptop", - "large", - "last", - "later", - "launching", - "lava", - "lawsuit", - "layout", - "lazy", - "lectures", - "ledge", - "leech", - "left", - "legion", - "leisure", - "lemon", - "lending", - "leopard", - "lesson", - "lettuce", - "lexicon", - "liar", - "library", - "licks", - "lids", - "lied", - "lifestyle", - "light", - "likewise", - "lilac", - "limits", - "linen", - "lion", - "lipstick", - "liquid", - "listen", - "lively", - "loaded", - "lobster", - "locker", - "lodge", - "lofty", - "logic", - "loincloth", - "long", - "looking", - "lopped", - "lordship", - "losing", - "lottery", - "loudly", - "love", - "lower", - "loyal", - "lucky", - "luggage", - "lukewarm", - "lullaby", - "lumber", - "lunar", - "lurk", - "lush", - "luxury", - "lymph", - "lynx", - "lyrics", - "macro", - "madness", - "magically", - "mailed", - "major", - "makeup", - "malady", - "mammal", - "maps", - "masterful", - "match", - "maul", - "maverick", - "maximum", - "mayor", - "maze", - "meant", - "mechanic", - "medicate", - "meeting", - "megabyte", - "melting", - "memoir", - "menu", - "merger", - "mesh", - "metro", - "mews", - "mice", - "midst", - "mighty", - "mime", - "mirror", - "misery", - "mittens", - "mixture", - "moat", - "mobile", - "mocked", - "mohawk", - "moisture", - "molten", - "moment", - "money", - "moon", - "mops", - "morsel", - "mostly", - "motherly", - "mouth", - "movement", - "mowing", - "much", - "muddy", - "muffin", - "mugged", - "mullet", - "mumble", - "mundane", - "muppet", - "mural", - "musical", - "muzzle", - "myriad", - "mystery", - "myth", - "nabbing", - "nagged", - "nail", - "names", - "nanny", - "napkin", - "narrate", - "nasty", - "natural", - "nautical", - "navy", - "nearby", - "necklace", - "needed", - "negative", - "neither", - "neon", - "nephew", - "nerves", - "nestle", - "network", - "neutral", - "never", - "newt", - "nexus", - "nibs", - "niche", - "niece", - "nifty", - "nightly", - "nimbly", - "nineteen", - "nirvana", - "nitrogen", - "nobody", - "nocturnal", - "nodes", - "noises", - "nomad", - "noodles", - "northern", - "nostril", - "noted", - "nouns", - "novelty", - "nowhere", - "nozzle", - "nuance", - "nucleus", - "nudged", - "nugget", - "nuisance", - "null", - "number", - "nuns", - "nurse", - "nutshell", - "nylon", - "oaks", - "oars", - "oasis", - "oatmeal", - "obedient", - "object", - "obliged", - "obnoxious", - "observant", - "obtains", - "obvious", - "occur", - "ocean", - "october", - "odds", - "odometer", - "offend", - "often", - "oilfield", - "ointment", - "okay", - "older", - "olive", - "olympics", - "omega", - "omission", - "omnibus", - "onboard", - "oncoming", - "oneself", - "ongoing", - "onion", - "online", - "onslaught", - "onto", - "onward", - "oozed", - "opacity", - "opened", - "opposite", - "optical", - "opus", - "orange", - "orbit", - "orchid", - "orders", - "organs", - "origin", - "ornament", - "orphans", - "oscar", - "ostrich", - "otherwise", - "otter", - "ouch", - "ought", - "ounce", - "ourselves", - "oust", - "outbreak", - "oval", - "oven", - "owed", - "owls", - "owner", - "oxidant", - "oxygen", - "oyster", - "ozone", - "pact", - "paddles", - "pager", - "pairing", - "palace", - "pamphlet", - "pancakes", - "paper", - "paradise", - "pastry", - "patio", - "pause", - "pavements", - "pawnshop", - "payment", - "peaches", - "pebbles", - "peculiar", - "pedantic", - "peeled", - "pegs", - "pelican", - "pencil", - "people", - "pepper", - "perfect", - "pests", - "petals", - "phase", - "pheasants", - "phone", - "phrases", - "physics", - "piano", - "picked", - "pierce", - "pigment", - "piloted", - "pimple", - "pinched", - "pioneer", - "pipeline", - "pirate", - "pistons", - "pitched", - "pivot", - "pixels", - "pizza", - "playful", - "pledge", - "pliers", - "plotting", - "plus", - "plywood", - "poaching", - "pockets", - "podcast", - "poetry", - "point", - "poker", - "polar", - "ponies", - "pool", - "popular", - "portents", - "possible", - "potato", - "pouch", - "poverty", - "powder", - "pram", - "present", - "pride", - "problems", - "pruned", - "prying", - "psychic", - "public", - "puck", - "puddle", - "puffin", - "pulp", - "pumpkins", - "punch", - "puppy", - "purged", - "push", - "putty", - "puzzled", - "pylons", - "pyramid", - "python", - "queen", - "quick", - "quote", - "rabbits", - "racetrack", - "radar", - "rafts", - "rage", - "railway", - "raking", - "rally", - "ramped", - "randomly", - "rapid", - "rarest", - "rash", - "rated", - "ravine", - "rays", - "razor", - "react", - "rebel", - "recipe", - "reduce", - "reef", - "refer", - "regular", - "reheat", - "reinvest", - "rejoices", - "rekindle", - "relic", - "remedy", - "renting", - "reorder", - "repent", - "request", - "reruns", - "rest", - "return", - "reunion", - "revamp", - "rewind", - "rhino", - "rhythm", - "ribbon", - "richly", - "ridges", - "rift", - "rigid", - "rims", - "ringing", - "riots", - "ripped", - "rising", - "ritual", - "river", - "roared", - "robot", - "rockets", - "rodent", - "rogue", - "roles", - "romance", - "roomy", - "roped", - "roster", - "rotate", - "rounded", - "rover", - "rowboat", - "royal", - "ruby", - "rudely", - "ruffled", - "rugged", - "ruined", - "ruling", - "rumble", - "runway", - "rural", - "rustled", - "ruthless", - "sabotage", - "sack", - "sadness", - "safety", - "saga", - "sailor", - "sake", - "salads", - "sample", - "sanity", - "sapling", - "sarcasm", - "sash", - "satin", - "saucepan", - "saved", - "sawmill", - "saxophone", - "sayings", - "scamper", - "scenic", - "school", - "science", - "scoop", - "scrub", - "scuba", - "seasons", - "second", - "sedan", - "seeded", - "segments", - "seismic", - "selfish", - "semifinal", - "sensible", - "september", - "sequence", - "serving", - "session", - "setup", - "seventh", - "sewage", - "shackles", - "shelter", - "shipped", - "shocking", - "shrugged", - "shuffled", - "shyness", - "siblings", - "sickness", - "sidekick", - "sieve", - "sifting", - "sighting", - "silk", - "simplest", - "sincerely", - "sipped", - "siren", - "situated", - "sixteen", - "sizes", - "skater", - "skew", - "skirting", - "skulls", - "skydive", - "slackens", - "sleepless", - "slid", - "slower", - "slug", - "smash", - "smelting", - "smidgen", - "smog", - "smuggled", - "snake", - "sneeze", - "sniff", - "snout", - "snug", - "soapy", - "sober", - "soccer", - "soda", - "software", - "soggy", - "soil", - "solved", - "somewhere", - "sonic", - "soothe", - "soprano", - "sorry", - "southern", - "sovereign", - "sowed", - "soya", - "space", - "speedy", - "sphere", - "spiders", - "splendid", - "spout", - "sprig", - "spud", - "spying", - "square", - "stacking", - "stellar", - "stick", - "stockpile", - "strained", - "stunning", - "stylishly", - "subtly", - "succeed", - "suddenly", - "suede", - "suffice", - "sugar", - "suitcase", - "sulking", - "summon", - "sunken", - "superior", - "surfer", - "sushi", - "suture", - "swagger", - "swept", - "swiftly", - "sword", - "swung", - "syllabus", - "symptoms", - "syndrome", - "syringe", - "system", - "taboo", - "tacit", - "tadpoles", - "tagged", - "tail", - "taken", - "talent", - "tamper", - "tanks", - "tapestry", - "tarnished", - "tasked", - "tattoo", - "taunts", - "tavern", - "tawny", - "taxi", - "teardrop", - "technical", - "tedious", - "teeming", - "tell", - "template", - "tender", - "tepid", - "tequila", - "terminal", - "testing", - "tether", - "textbook", - "thaw", - "theatrics", - "thirsty", - "thorn", - "threaten", - "thumbs", - "thwart", - "ticket", - "tidy", - "tiers", - "tiger", - "tilt", - "timber", - "tinted", - "tipsy", - "tirade", - "tissue", - "titans", - "toaster", - "tobacco", - "today", - "toenail", - "toffee", - "together", - "toilet", - "token", - "tolerant", - "tomorrow", - "tonic", - "toolbox", - "topic", - "torch", - "tossed", - "total", - "touchy", - "towel", - "toxic", - "toyed", - "trash", - "trendy", - "tribal", - "trolling", - "truth", - "trying", - "tsunami", - "tubes", - "tucks", - "tudor", - "tuesday", - "tufts", - "tugs", - "tuition", - "tulips", - "tumbling", - "tunnel", - "turnip", - "tusks", - "tutor", - "tuxedo", - "twang", - "tweezers", - "twice", - "twofold", - "tycoon", - "typist", - "tyrant", - "ugly", - "ulcers", - "ultimate", - "umbrella", - "umpire", - "unafraid", - "unbending", - "uncle", - "under", - "uneven", - "unfit", - "ungainly", - "unhappy", - "union", - "unjustly", - "unknown", - "unlikely", - "unmask", - "unnoticed", - "unopened", - "unplugs", - "unquoted", - "unrest", - "unsafe", - "until", - "unusual", - "unveil", - "unwind", - "unzip", - "upbeat", - "upcoming", - "update", - "upgrade", - "uphill", - "upkeep", - "upload", - "upon", - "upper", - "upright", - "upstairs", - "uptight", - "upwards", - "urban", - "urchins", - "urgent", - "usage", - "useful", - "usher", - "using", - "usual", - "utensils", - "utility", - "utmost", - "utopia", - "uttered", - "vacation", - "vague", - "vain", - "value", - "vampire", - "vane", - "vapidly", - "vary", - "vastness", - "vats", - "vaults", - "vector", - "veered", - "vegan", - "vehicle", - "vein", - "velvet", - "venomous", - "verification", - "vessel", - "veteran", - "vexed", - "vials", - "vibrate", - "victim", - "video", - "viewpoint", - "vigilant", - "viking", - "village", - "vinegar", - "violin", - "vipers", - "virtual", - "visited", - "vitals", - "vivid", - "vixen", - "vocal", - "vogue", - "voice", - "volcano", - "vortex", - "voted", - "voucher", - "vowels", - "voyage", - "vulture", - "wade", - "waffle", - "wagtail", - "waist", - "waking", - "wallets", - "wanted", - "warped", - "washing", - "water", - "waveform", - "waxing", - "wayside", - "weavers", - "website", - "wedge", - "weekday", - "weird", - "welders", - "went", - "wept", - "were", - "western", - "wetsuit", - "whale", - "when", - "whipped", - "whole", - "wickets", - "width", - "wield", - "wife", - "wiggle", - "wildly", - "winter", - "wipeout", - "wiring", - "wise", - "withdrawn", - "wives", - "wizard", - "wobbly", - "woes", - "woken", - "wolf", - "womanly", - "wonders", - "woozy", - "worry", - "wounded", - "woven", - "wrap", - "wrist", - "wrong", - "yacht", - "yahoo", - "yanks", - "yard", - "yawning", - "yearbook", - "yellow", - "yesterday", - "yeti", - "yields", - "yodel", - "yoga", - "younger", - "yoyo", - "zapped", - "zeal", - "zebra", - "zero", - "zesty", - "zigzags", - "zinger", - "zippers", - "zodiac", - "zombie", - "zones", - "zoom" -]; diff --git a/cw_wownero/lib/wownero_transaction_info.dart b/cw_wownero/lib/wownero_transaction_info.dart index 7b0073452..db5345e5d 100644 --- a/cw_wownero/lib/wownero_transaction_info.dart +++ b/cw_wownero/lib/wownero_transaction_info.dart @@ -1,6 +1,5 @@ import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wownero_amount_format.dart'; -import 'package:cw_wownero/api/structs/transaction_info_row.dart'; import 'package:cw_core/parseBoolFromString.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/format_amount.dart'; @@ -35,26 +34,6 @@ class WowneroTransactionInfo extends TransactionInfo { }; } - WowneroTransactionInfo.fromRow(TransactionInfoRow row) - : id = "${row.getHash()}_${row.getAmount()}_${row.subaddrAccount}_${row.subaddrIndex}", - txHash = row.getHash(), - height = row.blockHeight, - direction = parseTransactionDirectionFromInt(row.direction), - date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000), - isPending = row.isPending != 0, - amount = row.getAmount(), - accountIndex = row.subaddrAccount, - addressIndex = row.subaddrIndex, - confirmations = row.confirmations, - key = getTxKey(row.getHash()), - fee = row.fee { - additionalInfo = <String, dynamic>{ - 'key': key, - 'accountIndex': accountIndex, - 'addressIndex': addressIndex - }; - } - final String id; final String txHash; final int height; diff --git a/cw_wownero/lib/wownero_unspent.dart b/cw_wownero/lib/wownero_unspent.dart index a79106886..fdfdfc7a4 100644 --- a/cw_wownero/lib/wownero_unspent.dart +++ b/cw_wownero/lib/wownero_unspent.dart @@ -1,5 +1,4 @@ import 'package:cw_core/unspent_transaction_output.dart'; -import 'package:cw_wownero/api/structs/coins_info_row.dart'; class WowneroUnspent extends Unspent { WowneroUnspent( @@ -8,13 +7,5 @@ class WowneroUnspent extends Unspent { this.isFrozen = isFrozen; } - factory WowneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => WowneroUnspent( - coinsInfoRow.getAddress(), - coinsInfoRow.getHash(), - coinsInfoRow.getKeyImage(), - coinsInfoRow.amount, - coinsInfoRow.frozen == 1, - coinsInfoRow.unlocked == 1); - final bool isUnlocked; } diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index 52f84e26a..e02c0ec2e 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -107,9 +107,7 @@ abstract class WowneroWalletBase @override String get seed => wownero_wallet.getSeed(); - String seedLegacy(String? language) { - return wownero_wallet.getSeedLegacy(language); - } + String seedLegacy(String? language) => wownero_wallet.getSeedLegacy(language); @override MoneroWalletKeys get keys => MoneroWalletKeys( @@ -182,12 +180,12 @@ abstract class WowneroWalletBase @override Future<void> startSync() async { try { - _setInitialHeight(); + _assertInitialHeight(); } catch (_) { // our restore height wasn't correct, so lets see if using the backup works: try { await resetCache(name); - _setInitialHeight(); + _assertInitialHeight(); } catch (e) { // we still couldn't get a valid height from the backup?!: // try to use the date instead: @@ -604,18 +602,14 @@ abstract class WowneroWalletBase _listener = wownero_wallet.setListeners(_onNewBlock, _onNewTransaction); } - // check if the height is correct: - void _setInitialHeight() { - if (walletInfo.isRecovery) { - return; - } + /// Asserts the current height to be above [MIN_RESTORE_HEIGHT] + void _assertInitialHeight() { + if (walletInfo.isRecovery) return; final height = wownero_wallet.getCurrentHeight(); - if (height > MIN_RESTORE_HEIGHT) { - // the restore height is probably correct, so we do nothing: - return; - } + // the restore height is probably correct, so we do nothing: + if (height > MIN_RESTORE_HEIGHT) return; throw Exception("height isn't > $MIN_RESTORE_HEIGHT!"); } diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 011fed169..d91922ac9 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -437,10 +437,10 @@ packages: monero: dependency: "direct main" description: - path: "." - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - resolved-ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 - url: "https://github.com/mrcyjanek/monero.dart" + path: "impls/monero.dart" + ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" + resolved-ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" + url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" mutex: diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml index 4537955ab..7a45eb628 100644 --- a/cw_wownero/pubspec.yaml +++ b/cw_wownero/pubspec.yaml @@ -24,8 +24,9 @@ dependencies: path: ../cw_core monero: git: - url: https://github.com/mrcyjanek/monero.dart - ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5 + url: https://github.com/mrcyjanek/monero_c + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash + path: impls/monero.dart mutex: ^3.1.0 dev_dependencies: diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index c1384a3df..1f1888b44 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -346,4 +346,9 @@ class CWMonero extends Monero { Future<int> getCurrentHeight() async { return monero_wallet_api.getCurrentHeight(); } + + @override + void monerocCheck() { + checkIfMoneroCIsFine(); + } } diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 11abdeb58..1cf3e3e0c 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -124,6 +124,36 @@ class CryptoBalanceWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ + Observer( + builder: (_) { + if (dashboardViewModel.getMoneroError != null) { + return Padding( + padding: const EdgeInsets.fromLTRB(16,0,16,16), + child: DashBoardRoundedCardWidget( + title: "Invalid monero bindings", + subTitle: dashboardViewModel.getMoneroError.toString(), + onTap: () {}, + ), + ); + } + return Container(); + }, + ), + Observer( + builder: (_) { + if (dashboardViewModel.getWowneroError != null) { + return Padding( + padding: const EdgeInsets.fromLTRB(16,0,16,16), + child: DashBoardRoundedCardWidget( + title: "Invalid wownero bindings", + subTitle: dashboardViewModel.getWowneroError.toString(), + onTap: () {}, + ) + ); + } + return Container(); + }, + ), Observer( builder: (_) => dashboardViewModel.balanceViewModel.hasAccounts ? HomeScreenAccountWidget( diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 06c565035..1baea76cd 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/entities/service_status.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/wownero/wownero.dart' as wow; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -336,6 +337,27 @@ abstract class DashboardViewModelBase with Store { wallet.type == WalletType.haven; @computed + String? get getMoneroError { + if (wallet.type != WalletType.monero) return null; + try { + monero!.monerocCheck(); + } catch (e) { + return e.toString(); + } + return null; + } + + @computed + String? get getWowneroError { + if (wallet.type != WalletType.wownero) return null; + try { + wow.wownero!.wownerocCheck(); + } catch (e) { + return e.toString(); + } + return null; + } + List<String> get isMoneroWalletBrokenReasons { if (wallet.type != WalletType.monero) return []; final keys = monero!.getKeys(wallet); diff --git a/lib/wownero/cw_wownero.dart b/lib/wownero/cw_wownero.dart index 03bebc463..0e0b00fd4 100644 --- a/lib/wownero/cw_wownero.dart +++ b/lib/wownero/cw_wownero.dart @@ -347,4 +347,9 @@ class CWWownero extends Wownero { String getLegacySeed(Object wallet, String langName) => (wallet as WowneroWalletBase).seedLegacy(langName); + + @override + void wownerocCheck() { + checkIfMoneroCIsFine(); + } } diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index cac5d3ad2..2e53a54ea 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -8,7 +8,7 @@ if [[ ! -d "monero_c" ]]; then git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip cd monero_c - git checkout c094ed5da69d2274747bf6edd7ca24124487bd34 + git checkout bcb328a4956105dc182afd0ce2e48fe263f5f20b git reset --hard git submodule update --init --force --recursive ./apply_patches.sh monero diff --git a/tool/configure.dart b/tool/configure.dart index 853d06448..32b470979 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -262,6 +262,7 @@ import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_monero/monero_unspent.dart'; import 'package:cw_monero/monero_wallet_service.dart'; +import 'package:cw_monero/api/wallet_manager.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_transaction_info.dart'; import 'package:cw_monero/monero_transaction_creation_credentials.dart'; @@ -377,6 +378,7 @@ abstract class Monero { double formatterMoneroAmountToDouble({required int amount}); int formatterMoneroParseAmount({required String amount}); Account getCurrentAccount(Object wallet); + void monerocCheck(); void setCurrentAccount(Object wallet, int id, String label, String? balance); void onStartup(); int getTransactionInfoAccountId(TransactionInfo tx); @@ -449,6 +451,7 @@ import 'package:cw_wownero/wownero_transaction_info.dart'; import 'package:cw_wownero/wownero_transaction_creation_credentials.dart'; import 'package:cw_core/account.dart' as wownero_account; import 'package:cw_wownero/api/wallet.dart' as wownero_wallet_api; +import 'package:cw_wownero/api/wallet_manager.dart'; import 'package:cw_wownero/mnemonics/english.dart'; import 'package:cw_wownero/mnemonics/chinese_simplified.dart'; import 'package:cw_wownero/mnemonics/dutch.dart'; @@ -540,6 +543,7 @@ abstract class Wownero { Future<void> updateUnspents(Object wallet); Future<int> getCurrentHeight(); + void wownerocCheck(); WalletCredentials createWowneroRestoreWalletFromKeysCredentials({ required String name, From 8e4082d6806a89a256f827952036952a9ed21b47 Mon Sep 17 00:00:00 2001 From: Omar Hatem <omarh.ismail1@gmail.com> Date: Fri, 9 Aug 2024 22:18:32 +0300 Subject: [PATCH 11/19] Generic fixes (#1583) * add litecoin nodes minor ui fix * update build macos to build universal archs [skip ci] * minor fix [skip ci] * update share package * change trocador onion url --- lib/anonpay/anonpay_api.dart | 2 +- .../screens/new_wallet/new_wallet_page.dart | 30 ++++++++++--------- .../address_edit_or_create_page.dart | 6 ++-- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec_base.yaml | 2 +- scripts/macos/build_all.sh | 2 +- .../flutter/generated_plugin_registrant.cc | 3 ++ windows/flutter/generated_plugins.cmake | 1 + 8 files changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/anonpay/anonpay_api.dart b/lib/anonpay/anonpay_api.dart index e46499407..acab662d1 100644 --- a/lib/anonpay/anonpay_api.dart +++ b/lib/anonpay/anonpay_api.dart @@ -20,7 +20,7 @@ class AnonPayApi { final WalletBase wallet; static const anonpayRef = secrets.anonPayReferralCode; - static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion'; + static const onionApiAuthority = 'tqzngtf2hybjbexznel6dhgsvbynjzezoybvtv6iofomx7gchqfssgqd.onion'; static const clearNetAuthority = 'trocador.app'; static const markup = secrets.trocadorExchangeMarkup; static const anonPayPath = '/anonpay'; diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 306c41479..d9427af0a 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -40,11 +40,11 @@ class NewWalletPage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.focusedChild?.unfocus(); + } + }; @override Widget body(BuildContext context) => WalletNameForm( @@ -88,15 +88,17 @@ class _WalletNameFormState extends State<WalletNameForm> { if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp<void>( - context: context, - builder: (_) { - return AlertWithOneAction( - alertTitle: S.current.new_wallet, - alertContent: state.error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + if (context.mounted) { + showPopUp<void>( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: S.current.new_wallet, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } }); } }); diff --git a/lib/src/screens/subaddress/address_edit_or_create_page.dart b/lib/src/screens/subaddress/address_edit_or_create_page.dart index e067c78d0..b69a6d8df 100644 --- a/lib/src/screens/subaddress/address_edit_or_create_page.dart +++ b/lib/src/screens/subaddress/address_edit_or_create_page.dart @@ -58,7 +58,7 @@ class AddressEditOrCreatePage extends BasePage { isLoading: addressEditOrCreateViewModel.state is AddressIsSaving, isDisabled: - addressEditOrCreateViewModel.label?.isEmpty ?? true, + addressEditOrCreateViewModel.label.isEmpty, ), ) ], @@ -74,7 +74,9 @@ class AddressEditOrCreatePage extends BasePage { (AddressEditOrCreateState state) { if (state is AddressSavedSuccessfully) { WidgetsBinding.instance - .addPostFrameCallback((_) => Navigator.of(context).pop()); + .addPostFrameCallback((_) { + if (context.mounted) Navigator.of(context).pop(); + }); } }); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 873d50649..338ece4ce 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -15,7 +15,7 @@ import in_app_review import package_info import package_info_plus import path_provider_foundation -import share_plus_macos +import share_plus import shared_preferences_foundation import url_launcher_macos import wakelock_plus diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 84b4631fc..463c04988 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -21,7 +21,7 @@ dependencies: mobx: ^2.1.4 flutter_mobx: ^2.0.6+5 flutter_slidable: ^3.0.1 - share_plus: ^4.0.10 + share_plus: ^10.0.0 # date_range_picker: ^1.0.6 #https://api.flutter.dev/flutter/material/showDateRangePicker.html dio: ^4.0.6 diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh index 4116704bf..030617f7d 100755 --- a/scripts/macos/build_all.sh +++ b/scripts/macos/build_all.sh @@ -1,3 +1,3 @@ #!/bin/sh -./build_monero_all.sh \ No newline at end of file +./build_monero_all.sh universal \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 323f53c9f..c6444e09c 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include <flutter_local_authentication/flutter_local_authentication_plugin_c_api.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h> +#include <share_plus/share_plus_windows_plugin_c_api.h> #include <url_launcher_windows/url_launcher_windows.h> void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index d6d9b0a49..0a0b2f9eb 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_local_authentication flutter_secure_storage_windows permission_handler_windows + share_plus url_launcher_windows ) From fb33a6f23dce32172d9b80f343972dc4e07289c3 Mon Sep 17 00:00:00 2001 From: Omar Hatem <omarh.ismail1@gmail.com> Date: Fri, 9 Aug 2024 23:15:30 +0300 Subject: [PATCH 12/19] Cw 688 avoid wallet file corruption (#1582) * CW-688 Store Seed and keys in .keys file * CW-688 Open wallet from keys in .keys file and migrate wallets using the old file * CW-688 Open wallet from keys in .keys file and migrate wallets using the old file * CW-688 Restore .keys file from .keys.backup * CW-688 Restore .keys file from .keys.backup * CW-688 Move saving .keys files into the save function instead of the service * CW-688 Handle corrupt wallets * CW-688 Handle corrupt wallets * CW-688 Remove code duplication * CW-688 Reduce cache dependency * wrap any file reading/writing function with try/catch [skip ci] --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> --- cw_bitcoin/lib/bitcoin_wallet.dart | 66 +++++++---- cw_bitcoin/lib/bitcoin_wallet_service.dart | 2 + cw_bitcoin/lib/electrum_wallet.dart | 14 ++- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 8 +- cw_bitcoin/lib/litecoin_wallet.dart | 55 ++++++--- cw_bitcoin/lib/litecoin_wallet_service.dart | 1 + .../lib/src/bitcoin_cash_wallet.dart | 35 ++++-- .../lib/src/bitcoin_cash_wallet_service.dart | 2 + cw_core/lib/wallet_keys_file.dart | 110 ++++++++++++++++++ cw_ethereum/lib/ethereum_wallet.dart | 33 ++++-- cw_evm/lib/evm_chain_wallet.dart | 11 +- cw_nano/lib/nano_wallet.dart | 81 ++++++++----- cw_nano/lib/nano_wallet_service.dart | 2 +- cw_polygon/lib/polygon_wallet.dart | 37 ++++-- cw_polygon/lib/polygon_wallet_service.dart | 2 - cw_solana/lib/solana_wallet.dart | 47 ++++++-- cw_tron/lib/tron_wallet.dart | 66 +++++++---- cw_tron/lib/tron_wallet_service.dart | 5 +- 18 files changed, 433 insertions(+), 144 deletions(-) create mode 100644 cw_core/lib/wallet_keys_file.dart diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index d061480ed..ce3e2caa8 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -6,15 +6,16 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:convert/convert.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/psbt_transaction_builder.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; @@ -143,49 +144,66 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final network = walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : BitcoinNetwork.mainnet; - final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network); - walletInfo.derivationInfo ??= DerivationInfo( - derivationType: snp.derivationType ?? DerivationType.electrum, - derivationPath: snp.derivationPath, - ); + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + + ElectrumWalletSnapshot? snp = null; + + try { + snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network); + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + keysData = + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + + walletInfo.derivationInfo ??= DerivationInfo(); // set the default if not present: - walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum; + walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; + walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; + final mnemonic = keysData.mnemonic; + final passphrase = keysData.passphrase; - if (snp.mnemonic != null) { + if (mnemonic != null) { switch (walletInfo.derivationInfo!.derivationType) { case DerivationType.electrum: - seedBytes = await mnemonicToSeedBytes(snp.mnemonic!); + seedBytes = await mnemonicToSeedBytes(mnemonic); break; case DerivationType.bip39: default: seedBytes = await bip39.mnemonicToSeed( - snp.mnemonic!, - passphrase: snp.passphrase ?? '', + mnemonic, + passphrase: passphrase ?? '', ); break; } } return BitcoinWallet( - mnemonic: snp.mnemonic, - xpub: snp.xpub, + mnemonic: mnemonic, + xpub: keysData.xPub, password: password, - passphrase: snp.passphrase, + passphrase: passphrase, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses, - initialSilentAddresses: snp.silentAddresses, - initialSilentAddressIndex: snp.silentAddressIndex, - initialBalance: snp.balance, + initialAddresses: snp?.addresses, + initialSilentAddresses: snp?.silentAddresses, + initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, + initialBalance: snp?.balance, seedBytes: seedBytes, - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex, - addressPageType: snp.addressPageType, + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: snp?.addressPageType, networkParam: network, alwaysScan: alwaysScan, ); @@ -249,8 +267,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final accountPath = walletInfo.derivationInfo?.derivationPath; final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; - final signature = await _bitcoinLedgerApp! - .signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath); + final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!, + message: ascii.encode(message), signDerivationPath: derivationPath); return base64Encode(signature); } diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index a9a6d96db..cf93aa29d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -41,8 +41,10 @@ class BitcoinWalletService extends WalletService< unspentCoinsInfo: unspentCoinsInfoSource, network: network, ); + await wallet.save(); await wallet.init(); + return wallet; } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 39cf95009..e55e5ed0e 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -37,6 +37,7 @@ import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:flutter/foundation.dart'; @@ -54,7 +55,7 @@ const int TWEAKS_COUNT = 25; abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo> - with Store { + with Store, WalletKeysFile { ElectrumWalletBase({ required String password, required WalletInfo walletInfo, @@ -169,6 +170,10 @@ abstract class ElectrumWalletBase @override String? get seed => _mnemonic; + @override + WalletKeysData get walletKeysData => + WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase); + bitcoin.NetworkType networkType; BasedUtxoNetwork network; @@ -1076,6 +1081,11 @@ abstract class ElectrumWalletBase @override Future<void> save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + final path = await makePath(); await write(path: path, password: _password, data: toJSON()); await transactionHistory.save(); @@ -1131,8 +1141,6 @@ abstract class ElectrumWalletBase _autoSaveTimer?.cancel(); } - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - @action Future<void> updateAllUnspents() async { List<BitcoinUnspent> updatedUnspentCoins = []; diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 15ad1cf63..082460f72 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -32,15 +32,21 @@ class ElectrumWalletSnapshot { final WalletType type; final String? addressPageType; + @deprecated String? mnemonic; + + @deprecated String? xpub; + + @deprecated + String? passphrase; + List<BitcoinAddressRecord> addresses; List<BitcoinSilentPaymentAddressRecord> silentAddresses; ElectrumBalance balance; Map<String, int> regularAddressIndex; Map<String, int> changeAddressIndex; int silentAddressIndex; - String? passphrase; DerivationType? derivationType; String? derivationPath; diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 209ddc774..bfb9a1b16 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,20 +1,21 @@ +import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; -import 'package:bip39/bip39.dart' as bip39; part 'litecoin_wallet.g.dart'; @@ -101,19 +102,37 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { required Box<UnspentCoinsInfo> unspentCoinsInfo, required String password, }) async { - final snp = - await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet); + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + + ElectrumWalletSnapshot? snp = null; + + try { + snp = await ElectrumWalletSnapshot.load( + name, walletInfo.type, password, LitecoinNetwork.mainnet); + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + keysData = + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return LitecoinWallet( - mnemonic: snp.mnemonic!, + mnemonic: keysData.mnemonic!, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses, - initialBalance: snp.balance, - seedBytes: await mnemonicToSeedBytes(snp.mnemonic!), - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex, - addressPageType: snp.addressPageType, + initialAddresses: snp?.addresses, + initialBalance: snp?.balance, + seedBytes: await mnemonicToSeedBytes(keysData.mnemonic!), + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: snp?.addressPageType, ); } diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index bb51a4eaa..7025b72e5 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -33,6 +33,7 @@ class LitecoinWalletService extends WalletService< passphrase: credentials.passphrase, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.save(); await wallet.init(); diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 51bd3612d..f15eed10d 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -12,6 +12,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -89,14 +90,32 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { required Box<UnspentCoinsInfo> unspentCoinsInfo, required String password, }) async { - final snp = await ElectrumWalletSnapshot.load( - name, walletInfo.type, password, BitcoinCashNetwork.mainnet); + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + + ElectrumWalletSnapshot? snp = null; + + try { + snp = await ElectrumWalletSnapshot.load( + name, walletInfo.type, password, BitcoinCashNetwork.mainnet); + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + keysData = + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return BitcoinCashWallet( - mnemonic: snp.mnemonic!, + mnemonic: keysData.mnemonic!, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses.map((addr) { + initialAddresses: snp?.addresses.map((addr) { try { BitcoinCashAddress(addr.address); return BitcoinAddressRecord( @@ -116,10 +135,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { ); } }).toList(), - initialBalance: snp.balance, - seedBytes: await Mnemonic.toSeed(snp.mnemonic!), - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex, + initialBalance: snp?.balance, + seedBytes: await Mnemonic.toSeed(keysData.mnemonic!), + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, addressPageType: P2pkhAddressType.p2pkh, ); } 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 e6c0cad07..01ae8ace3 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart @@ -34,8 +34,10 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent password: credentials.password!, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.save(); await wallet.init(); + return wallet; } diff --git a/cw_core/lib/wallet_keys_file.dart b/cw_core/lib/wallet_keys_file.dart new file mode 100644 index 000000000..45539e09d --- /dev/null +++ b/cw_core/lib/wallet_keys_file.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'dart:developer' as dev; +import 'dart:io'; + +import 'package:cw_core/balance.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/utils/file.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; + +mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends TransactionHistoryBase, + TransactionType extends TransactionInfo> + on WalletBase<BalanceType, HistoryType, TransactionType> { + Future<String> makePath() => pathForWallet(name: walletInfo.name, type: walletInfo.type); + + // this needs to be overridden + WalletKeysData get walletKeysData; + + Future<String> makeKeysFilePath() async => "${await makePath()}.keys"; + + Future<void> saveKeysFile(String password, [bool isBackup = false]) async { + try { + final rootPath = await makeKeysFilePath(); + final path = "$rootPath${isBackup ? ".backup" : ""}"; + dev.log("Saving .keys file '$path'"); + await write(path: path, password: password, data: walletKeysData.toJSON()); + } catch (_) {} + } + + static Future<void> createKeysFile( + String name, WalletType type, String password, WalletKeysData walletKeysData, + [bool withBackup = true]) async { + try { + final rootPath = await pathForWallet(name: name, type: type); + final path = "$rootPath.keys"; + + dev.log("Saving .keys file '$path'"); + await write(path: path, password: password, data: walletKeysData.toJSON()); + + if (withBackup) { + dev.log("Saving .keys.backup file '$path.backup'"); + await write(path: "$path.backup", password: password, data: walletKeysData.toJSON()); + } + } catch (_) {} + } + + static Future<bool> hasKeysFile(String name, WalletType type) async { + try { + final path = await pathForWallet(name: name, type: type); + return File("$path.keys").existsSync() || File("$path.keys.backup").existsSync(); + } catch (_) { + return false; + } + } + + static Future<WalletKeysData> readKeysFile(String name, WalletType type, String password) async { + final path = await pathForWallet(name: name, type: type); + + var readPath = "$path.keys"; + try { + if (!File(readPath).existsSync()) throw Exception("No .keys file found for $name $type"); + + final jsonSource = await read(path: readPath, password: password); + final data = json.decode(jsonSource) as Map<String, dynamic>; + return WalletKeysData.fromJSON(data); + } catch (e) { + dev.log("Failed to read .keys file. Trying .keys.backup file..."); + + readPath = "$readPath.backup"; + if (!File(readPath).existsSync()) + throw Exception("No .keys nor a .keys.backup file found for $name $type"); + + final jsonSource = await read(path: readPath, password: password); + final data = json.decode(jsonSource) as Map<String, dynamic>; + final keysData = WalletKeysData.fromJSON(data); + + dev.log("Restoring .keys from .keys.backup"); + createKeysFile(name, type, password, keysData, false); + return keysData; + } + } +} + +class WalletKeysData { + final String? privateKey; + final String? mnemonic; + final String? altMnemonic; + final String? passphrase; + final String? xPub; + + WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub}); + + String toJSON() => jsonEncode({ + "privateKey": privateKey, + "mnemonic": mnemonic, + if (altMnemonic != null) "altMnemonic": altMnemonic, + if (passphrase != null) "passphrase": passphrase, + if (xPub != null) "xPub": xPub + }); + + static WalletKeysData fromJSON(Map<String, dynamic> json) => WalletKeysData( + privateKey: json["privateKey"] as String?, + mnemonic: json["mnemonic"] as String?, + altMnemonic: json["altMnemonic"] as String?, + passphrase: json["passphrase"] as String?, + xPub: json["xPub"] as String?, + ); +} diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 2c58cd31d..7bcd55cf4 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -6,6 +6,7 @@ import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart'; import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_transaction_history.dart'; @@ -122,19 +123,37 @@ class EthereumWallet extends EVMChainWallet { static Future<EthereumWallet> open( {required String name, required String password, required WalletInfo walletInfo}) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ?? + + Map<String, dynamic>? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map<String, dynamic>; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ?? EVMChainERC20Balance(BigInt.zero); + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return EthereumWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, client: EthereumClient(), ); diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 760c50a04..55dcea959 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_evm/evm_chain_client.dart'; import 'package:cw_evm/evm_chain_exceptions.dart'; @@ -58,7 +59,7 @@ abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet; abstract class EVMChainWalletBase extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo> - with Store { + with Store, WalletKeysFile { EVMChainWalletBase({ required WalletInfo walletInfo, required EVMChainClient client, @@ -508,6 +509,11 @@ abstract class EVMChainWalletBase @override Future<void> save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -522,7 +528,8 @@ abstract class EVMChainWalletBase ? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey) : null; - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey); String toJSON() => json.encode({ 'mnemonic': _mnemonic, diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index 5efe3006d..55e01d10b 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -1,8 +1,12 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:bip39/bip39.dart' as bip39; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/n2_node.dart'; +import 'package:cw_core/nano_account.dart'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; @@ -10,23 +14,20 @@ import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_nano/file.dart'; -import 'package:cw_core/nano_account.dart'; -import 'package:cw_core/n2_node.dart'; import 'package:cw_nano/nano_balance.dart'; import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_transaction_credentials.dart'; import 'package:cw_nano/nano_transaction_history.dart'; import 'package:cw_nano/nano_transaction_info.dart'; +import 'package:cw_nano/nano_wallet_addresses.dart'; import 'package:cw_nano/nano_wallet_keys.dart'; import 'package:cw_nano/pending_nano_transaction.dart'; import 'package:mobx/mobx.dart'; -import 'dart:async'; -import 'package:cw_nano/nano_wallet_addresses.dart'; -import 'package:cw_core/wallet_base.dart'; import 'package:nanodart/nanodart.dart'; -import 'package:bip39/bip39.dart' as bip39; import 'package:nanoutil/nanoutil.dart'; part 'nano_wallet.g.dart'; @@ -34,7 +35,8 @@ part 'nano_wallet.g.dart'; class NanoWallet = NanoWalletBase with _$NanoWallet; abstract class NanoWalletBase - extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store { + extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> + with Store, WalletKeysFile { NanoWalletBase({ required WalletInfo walletInfo, required String mnemonic, @@ -70,6 +72,7 @@ abstract class NanoWalletBase String? _representativeAddress; int repScore = 100; + bool get isRepOk => repScore >= 90; late final NanoClient _client; @@ -128,14 +131,10 @@ abstract class NanoWalletBase } @override - int calculateEstimatedFee(TransactionPriority priority, int? amount) { - return 0; // always 0 :) - } + int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; // always 0 :) @override - Future<void> changePassword(String password) { - throw UnimplementedError("changePassword"); - } + Future<void> changePassword(String password) => throw UnimplementedError("changePassword"); @override void close() { @@ -170,9 +169,7 @@ abstract class NanoWalletBase } @override - Future<void> connectToPowNode({required Node node}) async { - _client.connectPow(node); - } + Future<void> connectToPowNode({required Node node}) async => _client.connectPow(node); @override Future<PendingTransaction> createTransaction(Object credentials) async { @@ -296,9 +293,7 @@ abstract class NanoWalletBase } @override - NanoWalletKeys get keys { - return NanoWalletKeys(seedKey: _hexSeed!); - } + NanoWalletKeys get keys => NanoWalletKeys(seedKey: _hexSeed!); @override String? get privateKey => _privateKey!; @@ -312,6 +307,11 @@ abstract class NanoWalletBase @override Future<void> save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -323,6 +323,9 @@ abstract class NanoWalletBase String get hexSeed => _hexSeed!; + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, altMnemonic: hexSeed); + String get representative => _representativeAddress ?? ""; @action @@ -358,8 +361,6 @@ abstract class NanoWalletBase } } - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - String toJSON() => json.encode({ 'seedKey': _hexSeed, 'mnemonic': _mnemonic, @@ -373,31 +374,47 @@ abstract class NanoWalletBase required String password, required WalletInfo walletInfo, }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String; + Map<String, dynamic>? data = null; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map<String, dynamic>; + } catch (e) { + if (!hasKeysFile) rethrow; + } final balance = NanoBalance.fromRawString( - currentBalance: data['currentBalance'] as String? ?? "0", - receivableBalance: data['receivableBalance'] as String? ?? "0", + currentBalance: data?['currentBalance'] as String? ?? "0", + receivableBalance: data?['receivableBalance'] as String? ?? "0", ); + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String; + final isHexSeed = !mnemonic.contains(' '); + + keysData = WalletKeysData( + mnemonic: isHexSeed ? null : mnemonic, altMnemonic: isHexSeed ? mnemonic : null); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + DerivationType derivationType = DerivationType.nano; - if (data['derivationType'] == "DerivationType.bip39") { + if (data?['derivationType'] == "DerivationType.bip39") { derivationType = DerivationType.bip39; } walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType); - if (walletInfo.derivationInfo!.derivationType == null) { - walletInfo.derivationInfo!.derivationType = derivationType; - } + walletInfo.derivationInfo!.derivationType ??= derivationType; return NanoWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, + mnemonic: keysData.mnemonic!, initialBalance: balance, ); // init() should always be run after this! @@ -435,7 +452,7 @@ abstract class NanoWalletBase _representativeAddress = await _client.getRepFromPrefs(); throw Exception("Failed to get representative address $e"); } - + repScore = await _client.getRepScore(_representativeAddress!); } diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart index a1af3c872..755598705 100644 --- a/cw_nano/lib/nano_wallet_service.dart +++ b/cw_nano/lib/nano_wallet_service.dart @@ -39,7 +39,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials, mnemonic: mnemonic, password: credentials.password!, ); - wallet.init(); + await wallet.init(); return wallet; } diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index 60c7ad2ff..b0b0793b9 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -1,11 +1,12 @@ import 'dart:convert'; -import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; @@ -13,9 +14,9 @@ import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; import 'package:cw_evm/file.dart'; import 'package:cw_polygon/default_polygon_erc20_tokens.dart'; -import 'package:cw_polygon/polygon_transaction_info.dart'; import 'package:cw_polygon/polygon_client.dart'; import 'package:cw_polygon/polygon_transaction_history.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; class PolygonWallet extends EVMChainWallet { PolygonWallet({ @@ -97,19 +98,37 @@ class PolygonWallet extends EVMChainWallet { static Future<PolygonWallet> open( {required String name, required String password, required WalletInfo walletInfo}) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ?? + + Map<String, dynamic>? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map<String, dynamic>; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ?? EVMChainERC20Balance(BigInt.zero); + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } + return PolygonWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, client: PolygonClient(), ); diff --git a/cw_polygon/lib/polygon_wallet_service.dart b/cw_polygon/lib/polygon_wallet_service.dart index ee84a014e..14baffc44 100644 --- a/cw_polygon/lib/polygon_wallet_service.dart +++ b/cw_polygon/lib/polygon_wallet_service.dart @@ -35,7 +35,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> { await wallet.init(); wallet.addInitialTokens(); await wallet.save(); - return wallet; } @@ -83,7 +82,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> { await wallet.init(); wallet.addInitialTokens(); await wallet.save(); - return wallet; } diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 401968698..2b30a204c 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; + import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/node.dart'; @@ -12,6 +13,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_solana/default_spl_tokens.dart'; import 'package:cw_solana/file.dart'; import 'package:cw_solana/solana_balance.dart'; @@ -36,7 +38,8 @@ part 'solana_wallet.g.dart'; class SolanaWallet = SolanaWalletBase with _$SolanaWallet; abstract class SolanaWalletBase - extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo> with Store { + extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo> + with Store, WalletKeysFile { SolanaWalletBase({ required WalletInfo walletInfo, String? mnemonic, @@ -121,6 +124,9 @@ abstract class SolanaWalletBase return privateKey; } + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey); + Future<void> init() async { final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}"; @@ -336,6 +342,11 @@ abstract class SolanaWalletBase @override Future<void> save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -361,8 +372,6 @@ abstract class SolanaWalletBase } } - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - String toJSON() => json.encode({ 'mnemonic': _mnemonic, 'private_key': _hexPrivateKey, @@ -374,18 +383,36 @@ abstract class SolanaWalletBase required String password, required WalletInfo walletInfo, }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = SolanaBalance.fromJSON(data['balance'] as String) ?? SolanaBalance(0.0); + + Map<String, dynamic>? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map<String, dynamic>; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = SolanaBalance.fromJSON(data?['balance'] as String) ?? SolanaBalance(0.0); + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } return SolanaWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, ); } diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index 96f92e450..cb4c9c024 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_tron/default_tron_tokens.dart'; import 'package:cw_tron/file.dart'; @@ -37,7 +38,8 @@ part 'tron_wallet.g.dart'; class TronWallet = TronWalletBase with _$TronWallet; abstract class TronWalletBase - extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo> with Store { + extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo> + with Store, WalletKeysFile { TronWalletBase({ required WalletInfo walletInfo, String? mnemonic, @@ -124,18 +126,36 @@ abstract class TronWalletBase required String password, required WalletInfo walletInfo, }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = TronBalance.fromJSON(data['balance'] as String) ?? TronBalance(BigInt.zero); + + Map<String, dynamic>? data; + try { + final jsonSource = await read(path: path, password: password); + + data = json.decode(jsonSource) as Map<String, dynamic>; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = TronBalance.fromJSON(data?['balance'] as String) ?? TronBalance(BigInt.zero); + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); + } else { + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password); + } return TronWallet( walletInfo: walletInfo, password: password, - mnemonic: mnemonic, - privateKey: privateKey, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, initialBalance: balance, ); } @@ -163,9 +183,7 @@ abstract class TronWalletBase }) async { assert(mnemonic != null || privateKey != null); - if (privateKey != null) { - return TronPrivateKey(privateKey); - } + if (privateKey != null) return TronPrivateKey(privateKey); final seed = bip39.mnemonicToSeed(mnemonic!); @@ -181,14 +199,10 @@ abstract class TronWalletBase int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; @override - Future<void> changePassword(String password) { - throw UnimplementedError("changePassword"); - } + Future<void> changePassword(String password) => throw UnimplementedError("changePassword"); @override - void close() { - _transactionsUpdateTimer?.cancel(); - } + void close() => _transactionsUpdateTimer?.cancel(); @action @override @@ -406,12 +420,15 @@ abstract class TronWalletBase Object get keys => throw UnimplementedError("keys"); @override - Future<void> rescan({required int height}) { - throw UnimplementedError("rescan"); - } + Future<void> rescan({required int height}) => throw UnimplementedError("rescan"); @override Future<void> save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password); + saveKeysFile(_password, true); + } + await walletAddresses.updateAddressesInBox(); final path = await makePath(); await write(path: path, password: _password, data: toJSON()); @@ -424,7 +441,8 @@ abstract class TronWalletBase @override String get privateKey => _tronPrivateKey.toHex(); - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + @override + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey); String toJSON() => json.encode({ 'mnemonic': _mnemonic, @@ -512,7 +530,7 @@ abstract class TronWalletBase @override Future<void> renameWalletFiles(String newWalletName) async { - String transactionHistoryFileNameForWallet = 'tron_transactions.json'; + const transactionHistoryFileNameForWallet = 'tron_transactions.json'; final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); final currentWalletFile = File(currentWalletPath); @@ -550,9 +568,7 @@ abstract class TronWalletBase Future<String> signMessage(String message, {String? address}) async => _tronPrivateKey.signPersonalMessage(ascii.encode(message)); - String getTronBase58AddressFromHex(String hexAddress) { - return TronAddress(hexAddress).toAddress(); - } + String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress(); void updateScanProviderUsageState(bool isEnabled) { if (isEnabled) { diff --git a/cw_tron/lib/tron_wallet_service.dart b/cw_tron/lib/tron_wallet_service.dart index c8344d5f4..ba217a265 100644 --- a/cw_tron/lib/tron_wallet_service.dart +++ b/cw_tron/lib/tron_wallet_service.dart @@ -1,6 +1,7 @@ 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/pathForWallet.dart'; import 'package:cw_core/transaction_history.dart'; @@ -14,7 +15,6 @@ import 'package:cw_tron/tron_exception.dart'; import 'package:cw_tron/tron_wallet.dart'; import 'package:cw_tron/tron_wallet_creation_credentials.dart'; import 'package:hive/hive.dart'; -import 'package:collection/collection.dart'; class TronWalletService extends WalletService< TronNewWalletCredentials, @@ -153,7 +153,8 @@ class TronWalletService extends WalletService< } @override - Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(TronNewWalletCredentials credentials) { + Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> + restoreFromHardwareWallet(TronNewWalletCredentials credentials) { // TODO: implement restoreFromHardwareWallet throw UnimplementedError(); } From 14e99daa7398791cff5dbb9134e66f86f2142043 Mon Sep 17 00:00:00 2001 From: Serhii <borodenko.sv@gmail.com> Date: Sat, 10 Aug 2024 00:48:36 +0300 Subject: [PATCH 13/19] align the hint with the prefix in the text field (#1571) * Update send_card.dart * update currency amount text field widget * Update qr_widget.dart --- lib/src/screens/exchange/exchange_page.dart | 39 +-- .../exchange/widgets/exchange_card.dart | 131 +------- .../receive/widgets/currency_input_field.dart | 301 +++++++++++------- .../screens/receive/widgets/qr_widget.dart | 44 ++- lib/src/screens/send/send_template_page.dart | 2 +- .../widgets/prefix_currency_icon_widget.dart | 65 ---- lib/src/screens/send/widgets/send_card.dart | 228 ++----------- .../send/widgets/send_template_card.dart | 183 +++++------ .../send/send_template_view_model.dart | 5 + lib/view_model/send/template_view_model.dart | 23 +- 10 files changed, 371 insertions(+), 650 deletions(-) delete mode 100644 lib/src/screens/send/widgets/prefix_currency_icon_widget.dart diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 5c064df27..2c717a3c8 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -67,17 +67,6 @@ class ExchangePage extends BasePage { Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500)); var _isReactionsSet = false; - final arrowBottomPurple = Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, - height: 8, - ); - final arrowBottomCakeGreen = Image.asset( - 'assets/images/arrow_bottom_cake_green.png', - color: Colors.white, - height: 8, - ); - late final String? depositWalletName; late final String? receiveWalletName; @@ -101,11 +90,11 @@ class ExchangePage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.focusedChild?.unfocus(); + } + }; @override Widget middle(BuildContext context) => Row( @@ -340,7 +329,6 @@ class ExchangePage extends BasePage { void applyTemplate( BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { - final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency); final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency); @@ -354,10 +342,12 @@ class ExchangePage extends BasePage { exchangeViewModel.isFixedRateMode = false; var domain = template.depositAddress; - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, depositCryptoCurrency); domain = template.receiveAddress; - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, receiveCryptoCurrency); } void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) { @@ -529,14 +519,16 @@ class ExchangePage extends BasePage { _depositAddressFocus.addListener(() async { if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) { final domain = depositAddressController.text; - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); } }); _receiveAddressFocus.addListener(() async { if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) { final domain = receiveAddressController.text; - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); } }); @@ -589,7 +581,8 @@ class ExchangePage extends BasePage { } } - Future<String> fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async { + Future<String> fetchParsedAddress( + BuildContext context, String domain, CryptoCurrency currency) async { final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency); final address = await extractAddressFromParsed(context, parsedAddress); return address; @@ -658,7 +651,6 @@ class ExchangePage extends BasePage { exchangeViewModel.changeDepositCurrency(currency: currency); }, - imageArrow: arrowBottomPurple, currencyButtonColor: Colors.transparent, addressButtonsColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, @@ -705,7 +697,6 @@ class ExchangePage extends BasePage { currencies: exchangeViewModel.receiveCurrencies, onCurrencySelected: (currency) => exchangeViewModel.changeReceiveCurrency(currency: currency), - imageArrow: arrowBottomCakeGreen, currencyButtonColor: Colors.transparent, addressButtonsColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 760b0c137..02218f848 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cake_wallet/entities/contact_base.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; @@ -27,7 +28,7 @@ class ExchangeCard extends StatefulWidget { required this.isAmountEstimated, required this.currencies, required this.onCurrencySelected, - required this.imageArrow, + this.imageArrow, this.currencyValueValidator, this.addressTextFieldValidator, this.title = '', @@ -58,7 +59,7 @@ class ExchangeCard extends StatefulWidget { final bool isAmountEstimated; final bool hasRefundAddress; final bool isMoneroWallet; - final Image imageArrow; + final Image? imageArrow; final Color currencyButtonColor; final Color? addressButtonsColor; final Color borderColor; @@ -191,120 +192,18 @@ class ExchangeCardState extends State<ExchangeCard> { ) ], ), - Padding( - padding: EdgeInsets.only(top: 20), - child: Row( - children: [ - Container( - padding: EdgeInsets.only(right: 8), - height: 32, - color: widget.currencyButtonColor, - child: InkWell( - onTap: () => _presentPicker(context), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: <Widget>[ - Padding( - padding: EdgeInsets.only(right: 5), - child: widget.imageArrow, - ), - Text(_selectedCurrency.toString(), - style: TextStyle( - fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)) - ]), - ), - ), - if (_selectedCurrency.tag != null) - Padding( - padding: const EdgeInsets.only(right: 3.0), - child: Container( - height: 32, - decoration: BoxDecoration( - color: widget.addressButtonsColor ?? - Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Center( - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Text(_selectedCurrency.tag!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldButtonIconColor)), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Text(':', - style: TextStyle( - fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)), - ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: FocusTraversalOrder( - order: NumericFocusOrder(1), - child: BaseTextFormField( - focusNode: widget.amountFocusNode, - controller: amountController, - enabled: _isAmountEditable, - textAlign: TextAlign.left, - keyboardType: - TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')) - ], - hintText: '0.0000', - borderColor: Colors.transparent, - //widget.borderColor, - textStyle: TextStyle( - fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), - placeholderTextStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .extension<ExchangePageTheme>()! - .hintTextColor), - validator: _isAmountEditable - ? widget.currencyValueValidator - : null), - ), - ), - if (widget.hasAllAmount) - Container( - height: 32, - width: 32, - decoration: BoxDecoration( - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldButtonColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: InkWell( - onTap: () => widget.allAmount?.call(), - child: Center( - child: Text(S.of(context).all, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldButtonIconColor)), - ), - ), - ) - ], - ), - ), - ], - )), + CurrencyAmountTextField( + imageArrow: widget.imageArrow, + selectedCurrency: _selectedCurrency.toString(), + amountFocusNode: widget.amountFocusNode, + amountController: amountController, + onTapPicker: () => _presentPicker(context), + isAmountEditable: _isAmountEditable, + isPickerEnable: true, + allAmountButton: widget.hasAllAmount, + currencyValueValidator: widget.currencyValueValidator, + tag: _selectedCurrency.tag, + allAmountCallback: widget.allAmount), Divider(height: 1, color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor), Padding( padding: EdgeInsets.only(top: 5), diff --git a/lib/src/screens/receive/widgets/currency_input_field.dart b/lib/src/screens/receive/widgets/currency_input_field.dart index 84b2a7bca..ce3de9a6c 100644 --- a/lib/src/screens/receive/widgets/currency_input_field.dart +++ b/lib/src/screens/receive/widgets/currency_input_field.dart @@ -1,135 +1,210 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/utils/responsive_layout_util.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/currency.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; -import 'package:cake_wallet/themes/extensions/picker_theme.dart'; -import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; -class CurrencyInputField extends StatelessWidget { - const CurrencyInputField({ - super.key, - required this.onTapPicker, +class CurrencyAmountTextField extends StatelessWidget { + const CurrencyAmountTextField({ required this.selectedCurrency, - this.focusNode, - required this.controller, - required this.isLight, + required this.amountFocusNode, + required this.amountController, + required this.isAmountEditable, + this.allAmountButton = false, + this.isPickerEnable = false, + this.isSelected = false, + this.currentTheme = ThemeType.dark, + this.onTapPicker, + this.padding, + this.imageArrow, + this.hintText, + this.tag, + this.tagBackgroundColor, + this.currencyValueValidator, + this.allAmountCallback, }); - final Function() onTapPicker; - final Currency selectedCurrency; - final FocusNode? focusNode; - final TextEditingController controller; - final bool isLight; - - String get _currencyName { - if (selectedCurrency is CryptoCurrency) { - return (selectedCurrency as CryptoCurrency).title.toUpperCase(); - } - return selectedCurrency.name.toUpperCase(); - } + final Widget? imageArrow; + final String selectedCurrency; + final String? tag; + final String? hintText; + final Color? tagBackgroundColor; + final EdgeInsets? padding; + final FocusNode? amountFocusNode; + final TextEditingController amountController; + final bool isAmountEditable; + final FormFieldValidator<String>? currencyValueValidator; + final bool isPickerEnable; + final ThemeType currentTheme; + final bool isSelected; + final bool allAmountButton; + final VoidCallback? allAmountCallback; + final VoidCallback? onTapPicker; @override Widget build(BuildContext context) { - final arrowBottomPurple = Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor, - height: 8, - ); - // This magic number for wider screen sets the text input focus at center of the inputfield - final _width = - responsiveLayoutUtil.shouldRenderMobileUI ? MediaQuery.of(context).size.width : 500; - - return Column( + final textColor = currentTheme == ThemeType.light + ? Theme.of(context).appBarTheme.titleTextStyle!.color! + : Colors.white; + final _prefixContent = Row( children: [ - Padding( - padding: EdgeInsets.only(top: 20), - child: SizedBox( - height: 40, - child: BaseTextFormField( - focusNode: focusNode, - controller: controller, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,8}'))], - hintText: '0.000', - placeholderTextStyle: isLight - ? null - : TextStyle( - color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, - fontWeight: FontWeight.w600, - ), - borderColor: Theme.of(context).extension<PickerTheme>()!.dividerColor, - textColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor, - textStyle: TextStyle( - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor, - ), - prefixIcon: Padding( - padding: EdgeInsets.only( - left: _width / 4, + isPickerEnable + ? Container( + height: 32, + child: InkWell( + onTap: onTapPicker, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: <Widget>[ + Padding( + padding: const EdgeInsets.only(right: 5), + child: imageArrow ?? + Image.asset('assets/images/arrow_bottom_purple_icon.png', + color: textColor, height: 8)), + Text( + selectedCurrency, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: textColor, + ), + ), + ], + ), ), - child: Container( - padding: EdgeInsets.only(right: 8), - child: InkWell( - onTap: onTapPicker, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: <Widget>[ - Padding( - padding: EdgeInsets.only(right: 5), - child: arrowBottomPurple, - ), - Text( - _currencyName, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor, - ), - ), - if (selectedCurrency.tag != null) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3.0), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, - borderRadius: BorderRadius.all( - Radius.circular(6), - ), - ), - child: Center( - child: Text( - selectedCurrency.tag!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor, - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 3.0), - child: Text( - ':', - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 20, - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor, - ), - ), - ), - ]), + ) + : Text( + selectedCurrency, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: textColor, + ), + ), + if (tag != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3.0), + child: Container( + height: 32, + decoration: BoxDecoration( + color: tagBackgroundColor ?? + Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, + borderRadius: const BorderRadius.all(Radius.circular(6)), + ), + child: Center( + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + tag!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor, + ), ), ), ), ), ), + Padding( + padding: EdgeInsets.only(right: 4.0), + child: Text( + ':', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: textColor, + ), + ), ), ], ); + return Padding( + padding: padding ?? const EdgeInsets.only(top: 20), + child: Row( + children: [ + isSelected + ? Container( + child: _prefixContent, + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8), + margin: const EdgeInsets.only(right: 3), + decoration: BoxDecoration( + border: Border.all( + color: textColor, + ), + borderRadius: BorderRadius.circular(26), + color: Theme.of(context).primaryColor)) + : _prefixContent, + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: FocusTraversalOrder( + order: NumericFocusOrder(1), + child: BaseTextFormField( + focusNode: amountFocusNode, + controller: amountController, + enabled: isAmountEditable, + textAlign: TextAlign.left, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')), + ], + hintText: hintText ?? '0.0000', + borderColor: Colors.transparent, + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: textColor, + ), + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: currentTheme == ThemeType.light + ? Theme.of(context).appBarTheme.titleTextStyle!.color! + : Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor, + ), + validator: isAmountEditable ? currencyValueValidator : null, + ), + ), + ), + if (allAmountButton) + Container( + height: 32, + width: 32, + decoration: BoxDecoration( + color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, + borderRadius: const BorderRadius.all(Radius.circular(6)), + ), + child: InkWell( + onTap: allAmountCallback, + child: Center( + child: Text( + S.of(context).all, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .extension<SendPageTheme>()! + .textFieldButtonIconColor, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); } } diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index bbfd4d5c1..9f0db059a 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -1,11 +1,15 @@ import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'package:cake_wallet/themes/extensions/picker_theme.dart'; import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/brightness_util.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -38,6 +42,10 @@ class QRWidget extends StatelessWidget { final copyImage = Image.asset('assets/images/copy_address.png', color: Theme.of(context).extension<QRCodeTheme>()!.qrWidgetCopyButtonColor); + // This magic number for wider screen sets the text input focus at center of the inputfield + final _width = + responsiveLayoutUtil.shouldRenderMobileUI ? MediaQuery.of(context).size.width : 500; + return Center( child: SingleChildScrollView( child: Column( @@ -85,8 +93,9 @@ class QRWidget extends StatelessWidget { decoration: BoxDecoration( border: Border.all( width: 3, - color: - Theme.of(context).extension<DashboardPageTheme>()!.textColor, + color: Theme.of(context) + .extension<DashboardPageTheme>()! + .textColor, ), ), child: Container( @@ -116,20 +125,23 @@ class QRWidget extends StatelessWidget { children: <Widget>[ Expanded( child: Form( - key: formKey, - child: CurrencyInputField( - focusNode: amountTextFieldFocusNode, - controller: amountController, - onTapPicker: () => _presentPicker(context), - selectedCurrency: addressListViewModel.selectedCurrency, - isLight: isLight, - ), - ), + key: formKey, + child: CurrencyAmountTextField( + selectedCurrency: _currencyName, + amountFocusNode: amountTextFieldFocusNode, + amountController: amountController, + padding: EdgeInsets.only(top: 20, left: _width / 4), + currentTheme: isLight ? ThemeType.light : ThemeType.dark, + isAmountEditable: true, + tag: addressListViewModel.selectedCurrency.tag, + onTapPicker: () => _presentPicker(context), + isPickerEnable: true)), ), ], ), ); }), + Divider(height: 1, color: Theme.of(context).extension<PickerTheme>()!.dividerColor), Padding( padding: EdgeInsets.only(top: 20, bottom: 8), child: Builder( @@ -150,7 +162,8 @@ class QRWidget extends StatelessWidget { style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor), + color: + Theme.of(context).extension<DashboardPageTheme>()!.textColor), ), ), Padding( @@ -169,6 +182,13 @@ class QRWidget extends StatelessWidget { ); } + String get _currencyName { + if (addressListViewModel.selectedCurrency is CryptoCurrency) { + return (addressListViewModel.selectedCurrency as CryptoCurrency).title.toUpperCase(); + } + return addressListViewModel.selectedCurrency.name.toUpperCase(); + } + void _presentPicker(BuildContext context) async { await showPopUp<void>( builder: (_) => CurrencyPicker( diff --git a/lib/src/screens/send/send_template_page.dart b/lib/src/screens/send/send_template_page.dart index 76414ecb2..f7c9da082 100644 --- a/lib/src/screens/send/send_template_page.dart +++ b/lib/src/screens/send/send_template_page.dart @@ -144,7 +144,7 @@ class SendTemplatePage extends BasePage { .toList(); sendTemplateViewModel.addTemplate( - isCurrencySelected: mainTemplate.isCurrencySelected, + isCurrencySelected: mainTemplate.isCryptoSelected, name: mainTemplate.name, address: mainTemplate.address, cryptoCurrency: mainTemplate.selectedCurrency.title, diff --git a/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart b/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart deleted file mode 100644 index d30349066..000000000 --- a/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; -import 'package:flutter/material.dart'; - -class PrefixCurrencyIcon extends StatelessWidget { - PrefixCurrencyIcon({ - required this.isSelected, - required this.title, - this.onTap, - }); - - final bool isSelected; - final String title; - final Function()? onTap; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Padding( - padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0), - child: Column( - children: [ - Container( - padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(26), - color: isSelected - ? Theme.of(context) - .extension<SendPageTheme>()! - .templateSelectedCurrencyBackgroundColor - : Colors.transparent, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: <Widget>[ - if (onTap != null) - Padding( - padding: EdgeInsets.only(right: 5), - child: Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, - height: 8, - ), - ), - Text( - title + ':', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: isSelected - ? Theme.of(context) - .extension<SendPageTheme>()! - .templateSelectedCurrencyTitleColor - : Colors.white, - ), - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index ec833159f..e2e7f25da 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; @@ -207,166 +208,19 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S textStyle: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), validator: sendViewModel.addressValidator)), - Observer( - builder: (_) => Padding( - padding: const EdgeInsets.only(top: 20), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Row( - children: [ - sendViewModel.hasMultipleTokens - ? Container( - padding: EdgeInsets.only(right: 8), - height: 32, - child: InkWell( - onTap: () => _presentPicker(context), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: <Widget>[ - Padding( - padding: EdgeInsets.only(right: 5), - child: Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, - height: 8, - ), - ), - Text( - sendViewModel.selectedCryptoCurrency.title, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Colors.white), - ), - ], - ), - ), - ) - : Text( - sendViewModel.selectedCryptoCurrency.title, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Colors.white), - ), - sendViewModel.selectedCryptoCurrency.tag != null - ? Padding( - padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0), - child: Container( - height: 32, - decoration: BoxDecoration( - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldButtonColor, - borderRadius: BorderRadius.all( - Radius.circular(6), - )), - child: Center( - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Text( - sendViewModel.selectedCryptoCurrency.tag!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldButtonIconColor), - ), - ), - ), - ), - ) - : Container(), - Padding( - padding: const EdgeInsets.only(right: 10.0), - child: Text( - ':', - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: Colors.white), - ), - ), - ], - ), - ), - Expanded( - child: Stack( - children: [ - BaseTextFormField( - focusNode: cryptoAmountFocus, - controller: cryptoAmountController, - keyboardType: TextInputType.numberWithOptions( - signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')) - ], - suffixIcon: SizedBox( - width: prefixIconWidth, - ), - hintText: '0.0000', - borderColor: Colors.transparent, - textStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldHintColor, - fontWeight: FontWeight.w500, - fontSize: 14), - validator: output.sendAll - ? sendViewModel.allAmountValidator - : sendViewModel.amountValidator, - ), - if (!sendViewModel.isBatchSending) - Positioned( - top: 2, - right: 0, - child: Container( - width: prefixIconWidth, - height: prefixIconHeight, - child: InkWell( - onTap: () async { - output.setSendAll(sendViewModel.balance); - }, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldButtonColor, - borderRadius: BorderRadius.all( - Radius.circular(6), - ), - ), - child: Center( - child: Text( - S.of(context).all, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension<SendPageTheme>()! - .textFieldButtonIconColor, - ), - ), - ), - ), - ), - ), - ), - ], - ), - ), - ], - )), - ), + CurrencyAmountTextField( + selectedCurrency: sendViewModel.selectedCryptoCurrency.title, + amountFocusNode: cryptoAmountFocus, + amountController: cryptoAmountController, + isAmountEditable: true, + onTapPicker: () => _presentPicker(context), + isPickerEnable: sendViewModel.hasMultipleTokens, + tag: sendViewModel.selectedCryptoCurrency.tag, + allAmountButton: !sendViewModel.isBatchSending, + currencyValueValidator: output.sendAll + ? sendViewModel.allAmountValidator + : sendViewModel.amountValidator, + allAmountCallback: () async => output.setSendAll(sendViewModel.balance)), Divider( height: 1, color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor), @@ -402,41 +256,16 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S ), ), if (!sendViewModel.isFiatDisabled) - Padding( - padding: const EdgeInsets.only(top: 20), - child: BaseTextFormField( - focusNode: fiatAmountFocus, - controller: fiatAmountController, - keyboardType: - TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny( - RegExp('[\\-|\\ ]'), - ) - ], - prefixIcon: Padding( - padding: EdgeInsets.only(top: 9), - child: Text( - sendViewModel.fiat.title + ':', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), + CurrencyAmountTextField( + selectedCurrency: sendViewModel.fiat.title, + amountFocusNode: fiatAmountFocus, + amountController: fiatAmountController, hintText: '0.00', - borderColor: - Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, - textStyle: TextStyle( - fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), - placeholderTextStyle: TextStyle( - color: - Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, - fontWeight: FontWeight.w500, - fontSize: 14), - ), - ), + isAmountEditable: true, + allAmountButton: false), + Divider( + height: 1, + color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor), Padding( padding: EdgeInsets.only(top: 20), child: BaseTextFormField( @@ -715,12 +544,11 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S showPopUp<void>( context: context, builder: (_) => CurrencyPicker( - selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), - items: sendViewModel.currencies, - hintText: S.of(context).search_currency, - onItemSelected: (Currency cur) => - sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency), - ), + selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), + items: sendViewModel.currencies, + hintText: S.of(context).search_currency, + onItemSelected: (Currency cur) => + sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency)), ); } diff --git a/lib/src/screens/send/widgets/send_template_card.dart b/lib/src/screens/send/widgets/send_template_card.dart index 4f62616e3..bf2a66b73 100644 --- a/lib/src/screens/send/widgets/send_template_card.dart +++ b/lib/src/screens/send/widgets/send_template_card.dart @@ -1,5 +1,5 @@ import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; -import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -59,7 +59,8 @@ class SendTemplateCard extends StatelessWidget { hintText: sendTemplateViewModel.recipients.length > 1 ? S.of(context).template_name : S.of(context).send_name, - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, + borderColor: + Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), placeholderTextStyle: TextStyle( @@ -69,107 +70,87 @@ class SendTemplateCard extends StatelessWidget { validator: sendTemplateViewModel.templateValidator), Padding( padding: EdgeInsets.only(top: 20), - child: Observer( - builder: (context) { - return AddressTextField( - selectedCurrency: template.selectedCurrency, - controller: _addressController, - onURIScanned: (uri) { - final paymentRequest = PaymentRequest.fromUri(uri); - _addressController.text = paymentRequest.address; - _cryptoAmountController.text = paymentRequest.amount; - }, - options: [ - AddressTextFieldOption.paste, - AddressTextFieldOption.qrCode, - AddressTextFieldOption.addressBook - ], - onPushPasteButton: (context) async { - template.output.resetParsedAddress(); - await template.output.fetchParsedAddress(context); - }, - onPushAddressBookButton: (context) async { - template.output.resetParsedAddress(); - await template.output.fetchParsedAddress(context); - }, - buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, - textStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - hintStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, - ), - validator: sendTemplateViewModel.addressValidator, - ); - } - ), - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: Focus( - onFocusChange: (hasFocus) { - if (hasFocus) { - template.selectCurrency(); - } - }, - child: BaseTextFormField( - focusNode: _cryptoAmountFocus, - controller: _cryptoAmountController, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))], - prefixIcon: Observer( - builder: (_) => PrefixCurrencyIcon( - title: template.selectedCurrency.title, - isSelected: template.isCurrencySelected, - onTap: sendTemplateViewModel.walletCurrencies.length > 1 - ? () => _presentPicker(context) - : null, - ), - ), - hintText: '0.0000', - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, - textStyle: - TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, - fontWeight: FontWeight.w500, - fontSize: 14), - validator: sendTemplateViewModel.amountValidator, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: Focus( - onFocusChange: (hasFocus) { - if (hasFocus) { - template.selectFiat(); - } - }, - child: BaseTextFormField( - focusNode: _fiatAmountFocus, - controller: _fiatAmountController, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))], - prefixIcon: Observer( - builder: (_) => PrefixCurrencyIcon( - title: sendTemplateViewModel.fiatCurrency, - isSelected: template.isFiatSelected)), - hintText: '0.00', - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, - textStyle: - TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, - fontWeight: FontWeight.w500, + child: Observer(builder: (context) { + return AddressTextField( + selectedCurrency: template.selectedCurrency, + controller: _addressController, + onURIScanned: (uri) { + final paymentRequest = PaymentRequest.fromUri(uri); + _addressController.text = paymentRequest.address; + _cryptoAmountController.text = paymentRequest.amount; + }, + options: [ + AddressTextFieldOption.paste, + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook + ], + onPushPasteButton: (context) async { + template.output.resetParsedAddress(); + await template.output.fetchParsedAddress(context); + }, + onPushAddressBookButton: (context) async { + template.output.resetParsedAddress(); + await template.output.fetchParsedAddress(context); + }, + buttonColor: + Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, + borderColor: + Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, + textStyle: TextStyle( fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white, ), - ), + hintStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, + ), + validator: sendTemplateViewModel.addressValidator, + ); + }), + ), + Focus( + onFocusChange: (hasFocus) { + if (hasFocus) template.setCryptoCurrency(true); + }, + child: Column( + children: [ + Observer( + builder: (context) => CurrencyAmountTextField( + selectedCurrency: template.selectedCurrency.title, + amountFocusNode: _cryptoAmountFocus, + amountController: _cryptoAmountController, + isSelected: template.isCryptoSelected, + tag: template.selectedCurrency.tag, + isPickerEnable: sendTemplateViewModel.hasMultipleTokens, + onTapPicker: () => _presentPicker(context), + currencyValueValidator: sendTemplateViewModel.amountValidator, + isAmountEditable: true)), + Divider( + height: 1, + color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor) + ], + ), + ), + Focus( + onFocusChange: (hasFocus) { + if (hasFocus) template.setCryptoCurrency(false); + }, + child: Column( + children: [ + Observer( + builder: (context) => CurrencyAmountTextField( + selectedCurrency: sendTemplateViewModel.fiatCurrency, + amountFocusNode: _fiatAmountFocus, + amountController: _fiatAmountController, + isSelected: !template.isCryptoSelected, + hintText: '0.00', + isAmountEditable: true)), + Divider( + height: 1, + color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor) + ], ), ), ], diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart index 66a3c37c8..3c78f3000 100644 --- a/lib/view_model/send/send_template_view_model.dart +++ b/lib/view_model/send/send_template_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/view_model/send/template_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; @@ -97,4 +98,8 @@ abstract class SendTemplateViewModelBase with Store { @computed List<CryptoCurrency> get walletCurrencies => _wallet.balance.keys.toList(); + + bool get hasMultipleTokens => isEVMCompatibleChain(_wallet.type) || + _wallet.type == WalletType.solana || + _wallet.type == WalletType.tron; } diff --git a/lib/view_model/send/template_view_model.dart b/lib/view_model/send/template_view_model.dart index 5b799c343..fcd40bd43 100644 --- a/lib/view_model/send/template_view_model.dart +++ b/lib/view_model/send/template_view_model.dart @@ -40,35 +40,22 @@ abstract class TemplateViewModelBase with Store { CryptoCurrency _currency; @observable - bool isCurrencySelected = true; - - @observable - bool isFiatSelected = false; + bool isCryptoSelected = true; @action - void selectCurrency() { - isCurrencySelected = true; - isFiatSelected = false; - } - - @action - void selectFiat() { - isFiatSelected = true; - isCurrencySelected = false; - } + void setCryptoCurrency(bool value) => isCryptoSelected = value; @action void reset() { name = ''; address = ''; - isCurrencySelected = true; - isFiatSelected = false; + isCryptoSelected = true; output.reset(); } Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) { return Template( - isCurrencySelectedRaw: isCurrencySelected, + isCurrencySelectedRaw: isCryptoSelected, nameRaw: name, addressRaw: address, cryptoCurrencyRaw: cryptoCurrency, @@ -79,7 +66,7 @@ abstract class TemplateViewModelBase with Store { @action void changeSelectedCurrency(CryptoCurrency currency) { - isCurrencySelected = true; + isCryptoSelected = true; _currency = currency; } From acadee6ed54f58c8001b136ff4cf439894a8aed5 Mon Sep 17 00:00:00 2001 From: Serhii <borodenko.sv@gmail.com> Date: Sat, 10 Aug 2024 00:49:27 +0300 Subject: [PATCH 14/19] fix custom rate issue (#1579) Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> --- lib/bitcoin/cw_bitcoin.dart | 7 +++++++ .../rbf_details_list_fee_picker_item.dart | 2 +- lib/src/widgets/standard_picker_list.dart | 5 +++-- lib/view_model/transaction_details_view_model.dart | 10 +++++++--- tool/configure.dart | 1 + 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index efb1211bc..a92aaad74 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -435,6 +435,13 @@ class CWBitcoin extends Bitcoin { ); } + @override + int feeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, + {int? size}) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.feeAmountWithFeeRate(feeRate, inputsCount, outputsCount, size: size); + } + @override int getMaxCustomFeeRate(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; diff --git a/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart index 7615065d7..db3d94500 100644 --- a/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart +++ b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart @@ -17,7 +17,7 @@ class StandardPickerListItem<T> extends TransactionDetailsListItem { final List<T> items; final String Function(T item, double sliderValue) displayItem; final Function(double) onSliderChanged; - final Function(T) onItemSelected; + final Function(T item, double sliderValue) onItemSelected; final int selectedIdx; final double? maxValue; final int customItemIndex; diff --git a/lib/src/widgets/standard_picker_list.dart b/lib/src/widgets/standard_picker_list.dart index ea8b07097..0e9831420 100644 --- a/lib/src/widgets/standard_picker_list.dart +++ b/lib/src/widgets/standard_picker_list.dart @@ -23,7 +23,7 @@ class StandardPickerList<T> extends StatefulWidget { final int customItemIndex; final String Function(T item, double sliderValue) displayItem; final Function(double) onSliderChanged; - final Function(T) onItemSelected; + final Function(T item, double sliderValue) onItemSelected; final String value; final int selectedIdx; final double customValue; @@ -50,6 +50,7 @@ class _StandardPickerListState<T> extends State<StandardPickerList<T>> { @override Widget build(BuildContext context) { String adaptedDisplayItem(T item) => widget.displayItem(item, customValue); + String adaptedOnItemSelected(T item) => widget.onItemSelected(item, customValue).toString(); return Column( children: [ @@ -74,7 +75,7 @@ class _StandardPickerListState<T> extends State<StandardPickerList<T>> { }, onItemSelected: (T item) { setState(() => selectedIdx = widget.items.indexOf(item)); - value = widget.onItemSelected(item).toString(); + value = adaptedOnItemSelected(item); }, ), ), diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 9e71837a7..ef6474974 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -378,9 +378,9 @@ abstract class TransactionDetailsViewModelBase with Store { sendViewModel.displayFeeRate(priority, sliderValue.round()), onSliderChanged: (double newValue) => setNewFee(value: newValue, priority: transactionPriority!), - onItemSelected: (dynamic item) { + onItemSelected: (dynamic item, double sliderValue) { transactionPriority = item as TransactionPriority; - return setNewFee(priority: transactionPriority!); + return setNewFee(value: sliderValue, priority: transactionPriority!); })); if (transactionInfo.inputAddresses != null) { @@ -427,7 +427,11 @@ abstract class TransactionDetailsViewModelBase with Store { String setNewFee({double? value, required TransactionPriority priority}) { newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null - ? bitcoin!.getEstimatedFeeWithFeeRate(wallet, value.round(), transactionInfo.amount) + ? bitcoin!.feeAmountWithFeeRate( + wallet, + value.round(), + transactionInfo.inputAddresses?.length ?? 1, + transactionInfo.outputAddresses?.length ?? 1) : bitcoin!.getFeeAmountForPriority( wallet, priority, diff --git a/tool/configure.dart b/tool/configure.dart index 32b470979..8b5af92b2 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -210,6 +210,7 @@ abstract class Bitcoin { int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size}); int getEstimatedFeeWithFeeRate(Object wallet, int feeRate, int? amount, {int? outputsCount, int? size}); + int feeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size}); int getHeightByDate({required DateTime date}); Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}); Future<bool> getNodeIsElectrsSPEnabled(Object wallet); From 9c29dbd6fd7823e4deb5da871b752087ca8ef06f Mon Sep 17 00:00:00 2001 From: Serhii <borodenko.sv@gmail.com> Date: Sat, 10 Aug 2024 01:18:55 +0300 Subject: [PATCH 15/19] fix zero initial fee rates in RBF rate picker (#1585) * fix zero initial fee rates in RBF rate picker * fix for other settings page[skip ci] --- lib/di.dart | 4 ++-- lib/src/screens/settings/other_settings_page.dart | 7 ++++++- .../transaction_details/transaction_details_page.dart | 7 ++++++- lib/src/widgets/picker.dart | 4 ++-- lib/view_model/settings/other_settings_view_model.dart | 4 +++- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index a37574f21..a64270f6d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -759,8 +759,8 @@ Future<void> setup({ getIt.registerFactory(() => TrocadorProvidersViewModel(getIt.get<SettingsStore>())); getIt.registerFactory(() { - return OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!); - }); + return OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!, + getIt.get<SendViewModel>());}); getIt.registerFactory(() { return SecuritySettingsViewModel(getIt.get<SettingsStore>()); diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 9bba51944..137f699f5 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -12,7 +13,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class OtherSettingsPage extends BasePage { - OtherSettingsPage(this._otherSettingsViewModel); + OtherSettingsPage(this._otherSettingsViewModel) { + if (_otherSettingsViewModel.sendViewModel.isElectrumWallet) { + bitcoin!.updateFeeRates(_otherSettingsViewModel.sendViewModel.wallet); + } + } @override String get title => S.current.other_settings; diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index 7734f37ed..d06b935dd 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -15,7 +16,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class TransactionDetailsPage extends BasePage { - TransactionDetailsPage({required this.transactionDetailsViewModel}); + TransactionDetailsPage({required this.transactionDetailsViewModel}) { + if (transactionDetailsViewModel.sendViewModel.isElectrumWallet) { + bitcoin!.updateFeeRates(transactionDetailsViewModel.sendViewModel.wallet); + } + } @override String get title => S.current.transaction_details_title; diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index b744d1db0..a7cb03a4e 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -499,10 +499,10 @@ class _PickerState<Item> extends State<Picker<Item>> { children: <Widget>[ Expanded( child: Slider( - value: widget.sliderValue ?? 1, + value: widget.sliderValue == null || widget.sliderValue! < 1 ? 1 : widget.sliderValue!, onChanged: isActivated ? widget.onSliderChanged : null, min: widget.minValue ?? 1, - max: widget.maxValue ?? 100, + max: (widget.maxValue == null || widget.maxValue! < 1) ? 100 : widget.maxValue!, divisions: 100, ), ), diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index bd04755fa..9af8c67cf 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/package_info.dart'; +import 'package:cake_wallet/view_model/send/send_view_model.dart'; // import 'package:package_info/package_info.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/balance.dart'; @@ -20,7 +21,7 @@ class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel; abstract class OtherSettingsViewModelBase with Store { - OtherSettingsViewModelBase(this._settingsStore, this._wallet) + OtherSettingsViewModelBase(this._settingsStore, this._wallet, this.sendViewModel) : walletType = _wallet.type, currentVersion = '' { PackageInfo.fromPlatform().then( @@ -42,6 +43,7 @@ abstract class OtherSettingsViewModelBase with Store { String currentVersion; final SettingsStore _settingsStore; + final SendViewModel sendViewModel; @computed TransactionPriority get transactionPriority { From b412d45f0e3a97758aa5b438c90979f2a39bb324 Mon Sep 17 00:00:00 2001 From: Serhii <borodenko.sv@gmail.com> Date: Sat, 10 Aug 2024 01:21:26 +0300 Subject: [PATCH 16/19] Cw 567 cant swipe through menus on desktop builds (#1563) * MaterialApp scrollBehavior * accessibility improvements --- lib/app_scroll_behavior.dart | 9 +++++ lib/main.dart | 3 ++ lib/src/screens/dashboard/dashboard_page.dart | 6 +++- .../screens/dashboard/pages/balance_page.dart | 23 +++++++----- .../screens/restore/wallet_restore_page.dart | 26 ++++++++------ lib/src/screens/send/send_page.dart | 9 +++-- lib/src/screens/send/send_template_page.dart | 36 +++++++++++-------- 7 files changed, 75 insertions(+), 37 deletions(-) create mode 100644 lib/app_scroll_behavior.dart diff --git a/lib/app_scroll_behavior.dart b/lib/app_scroll_behavior.dart new file mode 100644 index 000000000..d5abd5688 --- /dev/null +++ b/lib/app_scroll_behavior.dart @@ -0,0 +1,9 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class AppScrollBehavior extends MaterialScrollBehavior { + @override + Set<PointerDeviceKind> get dragDevices => + {PointerDeviceKind.touch, PointerDeviceKind.mouse, PointerDeviceKind.trackpad}; +} diff --git a/lib/main.dart b/lib/main.dart index 014d5f011..1c0078e16 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/app_scroll_behavior.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/di.dart'; @@ -75,6 +76,7 @@ Future<void> main() async { runApp( MaterialApp( debugShowCheckedModeBanner: false, + scrollBehavior: AppScrollBehavior(), home: Scaffold( body: SingleChildScrollView( child: Container( @@ -297,6 +299,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin { locale: Locale(settingsStore.languageCode), onGenerateRoute: (settings) => Router.createRoute(settings), initialRoute: initialRoute, + scrollBehavior: AppScrollBehavior(), home: _Home(), )); }); diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 7a2055930..ad6e68cd8 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -237,7 +237,11 @@ class _DashboardPageView extends BasePage { padding: EdgeInsets.only(bottom: 24, top: 10), child: Observer( builder: (context) { - return ExcludeSemantics( + return Semantics( + button: false, + label: 'Page Indicator', + hint: 'Swipe to change page', + excludeSemantics: true, child: SmoothPageIndicator( controller: controller, count: pages.length, diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 1cf3e3e0c..770cda6f9 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -23,6 +23,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -469,15 +470,19 @@ class BalanceRowWidget extends StatelessWidget { children: [ Row( children: [ - Text('${availableBalanceLabel}', - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension<BalancePageTheme>()! - .labelTextColor, - height: 1)), + Semantics( + hint: 'Double tap to see more information', + container: true, + child: Text('${availableBalanceLabel}', + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension<BalancePageTheme>()! + .labelTextColor, + height: 1)), + ), if (hasAdditionalBalance) Padding( padding: const EdgeInsets.symmetric(horizontal: 4), diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index a9bd52b26..746b73dca 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -177,16 +177,22 @@ class WalletRestorePage extends BasePage { if (_pages.length > 1) Padding( padding: EdgeInsets.only(top: 10), - child: SmoothPageIndicator( - controller: _controller, - count: _pages.length, - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).hintColor.withOpacity(0.5), - activeDotColor: Theme.of(context).hintColor, + child: Semantics( + button: false, + label: 'Page Indicator', + hint: 'Swipe to change restore mode', + excludeSemantics: true, + child: SmoothPageIndicator( + controller: _controller, + count: _pages.length, + effect: ColorTransitionEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context).hintColor.withOpacity(0.5), + activeDotColor: Theme.of(context).hintColor, + ), ), ), ), diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index b46a7f3db..97a7ad88d 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -212,7 +212,12 @@ class SendPage extends BasePage { final count = sendViewModel.outputs.length; return count > 1 - ? SmoothPageIndicator( + ? Semantics ( + label: 'Page Indicator', + hint: 'Swipe to change receiver', + excludeSemantics: true, + child: + SmoothPageIndicator( controller: controller, count: count, effect: ScrollingDotsEffect( @@ -226,7 +231,7 @@ class SendPage extends BasePage { activeDotColor: Theme.of(context) .extension<SendPageTheme>()! .templateBackgroundColor), - ) + )) : Offstage(); }, ), diff --git a/lib/src/screens/send/send_template_page.dart b/lib/src/screens/send/send_template_page.dart index f7c9da082..5db70c0eb 100644 --- a/lib/src/screens/send/send_template_page.dart +++ b/lib/src/screens/send/send_template_page.dart @@ -94,21 +94,27 @@ class SendTemplatePage extends BasePage { final count = sendTemplateViewModel.recipients.length; return count > 1 - ? SmoothPageIndicator( - controller: controller, - count: count, - effect: ScrollingDotsEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context) - .extension<SendPageTheme>()! - .indicatorDotColor, - activeDotColor: Theme.of(context) - .extension<DashboardPageTheme>()! - .indicatorDotTheme - .activeIndicatorColor)) + ? Semantics( + button: false, + label: 'Page Indicator', + hint: 'Swipe to change receiver', + excludeSemantics: true, + child: SmoothPageIndicator( + controller: controller, + count: count, + effect: ScrollingDotsEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context) + .extension<SendPageTheme>()! + .indicatorDotColor, + activeDotColor: Theme.of(context) + .extension<DashboardPageTheme>()! + .indicatorDotTheme + .activeIndicatorColor)), + ) : Offstage(); }, ), From 96baf460f3492d1d5e4320b581bae79180b33790 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Sat, 10 Aug 2024 00:02:47 +0100 Subject: [PATCH 17/19] Filters out TRC10 spam transactions and modifies Solana error messages (#1587) * fix: Tron and solana fixes * fix: Disable send all for solana wallets * fix: Add localization and add tostring to get more info on error * fix: Fix spelling for comment --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> --- cw_solana/lib/solana_client.dart | 2 +- cw_solana/pubspec.yaml | 2 +- cw_tron/lib/tron_wallet.dart | 5 +++++ lib/src/screens/send/widgets/send_card.dart | 2 +- lib/view_model/send/send_view_model.dart | 10 ++++++++-- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 31 files changed, 42 insertions(+), 5 deletions(-) diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 38f2864df..23e88fe5e 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -456,7 +456,7 @@ class SolanaWalletClient { funder: ownerKeypair, ); } catch (e) { - throw Exception('Insufficient SOL balance to complete this transaction'); + throw Exception('Insufficient SOL balance to complete this transaction: ${e.toString()}'); } // Input by the user diff --git a/cw_solana/pubspec.yaml b/cw_solana/pubspec.yaml index 6b59282b4..6fd5cd97c 100644 --- a/cw_solana/pubspec.yaml +++ b/cw_solana/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - solana: ^0.30.1 + solana: ^0.30.4 cw_core: path: ../cw_core http: ^1.1.0 diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index cb4c9c024..3566dcd94 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -349,6 +349,11 @@ abstract class TronWalletBase continue; } + // Filter out spam transaactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions + if (transactionModel.contracts?.first.type == "TransferAssetContract") { + continue; + } + String? tokenSymbol; if (transactionModel.contractAddress != null) { final tokenAddress = TronAddress(transactionModel.contractAddress!); diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index e2e7f25da..0a3de3e58 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -216,7 +216,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S onTapPicker: () => _presentPicker(context), isPickerEnable: sendViewModel.hasMultipleTokens, tag: sendViewModel.selectedCryptoCurrency.tag, - allAmountButton: !sendViewModel.isBatchSending, + allAmountButton: !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL, currencyValueValidator: output.sendAll ? sendViewModel.allAmountValidator : sendViewModel.amountValidator, diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index a1997e81d..d0514bb19 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -118,7 +118,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get isBatchSending => outputs.length > 1; - bool get shouldDisplaySendALL => walletType != WalletType.solana || walletType != WalletType.tron; + bool get shouldDisplaySendALL => walletType != WalletType.solana; @computed String get pendingTransactionFiatAmount { @@ -582,9 +582,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ) { String errorMessage = error.toString(); + if (walletType == WalletType.solana) { + if (errorMessage.contains('insufficient funds for rent')) { + return S.current.insufficientFundsForRentError; + } + + return errorMessage; + } if (walletType == WalletType.ethereum || walletType == WalletType.polygon || - walletType == WalletType.solana || walletType == WalletType.haven) { if (errorMessage.contains('gas required exceeds allowance') || errorMessage.contains('insufficient funds')) { diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 435c7ccde..d543706fc 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -337,6 +337,7 @@ "incoming": "الواردة", "incorrect_seed": "النص الذي تم إدخاله غير صالح.", "inputs": "المدخلات", + "insufficientFundsForRentError": "ليس لديك ما يكفي من SOL لتغطية رسوم المعاملة والإيجار للحساب. يرجى إضافة المزيد من sol إلى محفظتك أو تقليل مبلغ sol الذي ترسله", "introducing_cake_pay": "نقدم لكم Cake Pay!", "invalid_input": "مدخل غير صالح", "invoice_details": "تفاصيل الفاتورة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 84bcabf9f..ede60567d 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -337,6 +337,7 @@ "incoming": "Входящи", "incorrect_seed": "Въведеният текст е невалиден.", "inputs": "Входове", + "insufficientFundsForRentError": "Нямате достатъчно SOL, за да покриете таксата за транзакцията и наемането на сметката. Моля, добавете повече SOL към портфейла си или намалете сумата на SOL, която изпращате", "introducing_cake_pay": "Запознайте се с Cake Pay!", "invalid_input": "Невалиден вход", "invoice_details": "IДанни за фактура", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 731513bd7..8f2cda1e0 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -337,6 +337,7 @@ "incoming": "Příchozí", "incorrect_seed": "Zadaný text není správný.", "inputs": "Vstupy", + "insufficientFundsForRentError": "Nemáte dostatek SOL na pokrytí transakčního poplatku a nájemného za účet. Laskavě přidejte do své peněženky více SOL nebo snižte množství Sol, kterou odesíláte", "introducing_cake_pay": "Představujeme Cake Pay!", "invalid_input": "Neplatný vstup", "invoice_details": "detaily faktury", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 0f43e831d..59a23222c 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -337,6 +337,7 @@ "incoming": "Eingehend", "incorrect_seed": "Der eingegebene Text ist ungültig.", "inputs": "Eingänge", + "insufficientFundsForRentError": "Sie haben nicht genug SOL, um die Transaktionsgebühr und die Miete für das Konto zu decken. Bitte fügen Sie mehr Sol zu Ihrer Brieftasche hinzu oder reduzieren Sie den von Ihnen gesendeten Sol -Betrag", "introducing_cake_pay": "Einführung von Cake Pay!", "invalid_input": "Ungültige Eingabe", "invoice_details": "Rechnungs-Details", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e4ef2119d..1bd3fc241 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -337,6 +337,7 @@ "incoming": "Incoming", "incorrect_seed": "The text entered is not valid.", "inputs": "Inputs", + "insufficientFundsForRentError": "You do not have enough SOL to cover the transaction fee and rent for the account. Kindly add more SOL to your wallet or reduce the SOL amount you\\'re sending", "introducing_cake_pay": "Introducing Cake Pay!", "invalid_input": "Invalid input", "invoice_details": "Invoice details", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 0b7dc4a28..dc8aa3b95 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -337,6 +337,7 @@ "incoming": "Entrante", "incorrect_seed": "El texto ingresado no es válido.", "inputs": "Entradas", + "insufficientFundsForRentError": "No tiene suficiente SOL para cubrir la tarifa de transacción y alquilar para la cuenta. Por favor, agregue más sol a su billetera o reduzca la cantidad de sol que está enviando", "introducing_cake_pay": "¡Presentamos Cake Pay!", "invalid_input": "Entrada inválida", "invoice_details": "Detalles de la factura", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index fad9576e8..f7c45f7ef 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -337,6 +337,7 @@ "incoming": "Entrantes", "incorrect_seed": "Le texte entré est invalide.", "inputs": "Contributions", + "insufficientFundsForRentError": "Vous n'avez pas assez de SOL pour couvrir les frais de transaction et le loyer pour le compte. Veuillez ajouter plus de Sol à votre portefeuille ou réduire la quantité de sol que vous envoyez", "introducing_cake_pay": "Présentation de Cake Pay !", "invalid_input": "Entrée invalide", "invoice_details": "Détails de la facture", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index de963a52d..a5805bbb8 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -337,6 +337,7 @@ "incoming": "Mai shigowa", "incorrect_seed": "rubutun da aka shigar ba shi da inganci.", "inputs": "Abubuwan da ke ciki", + "insufficientFundsForRentError": "Ba ku da isasshen Sol don rufe kuɗin ma'amala da haya don asusun. Da kyau ƙara ƙarin sool zuwa walat ɗinku ko rage adadin Sol ɗin da kuke aikawa", "introducing_cake_pay": "Gabatar da Cake Pay!", "invalid_input": "Shigar da ba daidai ba", "invoice_details": "Bayanin wadannan", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index edc301efe..4ab8e7534 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -337,6 +337,7 @@ "incoming": "आने वाली", "incorrect_seed": "दर्ज किया गया पाठ मान्य नहीं है।", "inputs": "इनपुट", + "insufficientFundsForRentError": "आपके पास लेन -देन शुल्क और खाते के लिए किराए को कवर करने के लिए पर्याप्त सोल नहीं है। कृपया अपने बटुए में अधिक सोल जोड़ें या सोल राशि को कम करें जिसे आप भेज रहे हैं", "introducing_cake_pay": "परिचय Cake Pay!", "invalid_input": "अमान्य निवेश", "invoice_details": "चालान विवरण", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 03dfcd2a1..67095ba8f 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -337,6 +337,7 @@ "incoming": "Dolazno", "incorrect_seed": "Uneseni tekst nije valjan.", "inputs": "Unosi", + "insufficientFundsForRentError": "Nemate dovoljno SOL -a za pokrivanje naknade za transakciju i najamninu za račun. Ljubazno dodajte više sol u svoj novčanik ili smanjite količinu SOL -a koju šaljete", "introducing_cake_pay": "Predstavljamo Cake Pay!", "invalid_input": "Pogrešan unos", "invoice_details": "Podaci o fakturi", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index f3276ff6c..939b938fe 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -337,6 +337,7 @@ "incoming": "Masuk", "incorrect_seed": "Teks yang dimasukkan tidak valid.", "inputs": "Input", + "insufficientFundsForRentError": "Anda tidak memiliki cukup SOL untuk menutupi biaya transaksi dan menyewa untuk akun tersebut. Mohon tambahkan lebih banyak sol ke dompet Anda atau kurangi jumlah sol yang Anda kirim", "introducing_cake_pay": "Perkenalkan Cake Pay!", "invalid_input": "Masukan tidak valid", "invoice_details": "Detail faktur", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index d021e37a3..29a142d1e 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -338,6 +338,7 @@ "incoming": "In arrivo", "incorrect_seed": "Il testo inserito non è valido.", "inputs": "Input", + "insufficientFundsForRentError": "Non hai abbastanza SOL per coprire la tassa di transazione e l'affitto per il conto. Si prega di aggiungere più SOL al tuo portafoglio o ridurre l'importo SOL che stai inviando", "introducing_cake_pay": "Presentazione di Cake Pay!", "invalid_input": "Inserimento non valido", "invoice_details": "Dettagli della fattura", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index dd28f9688..3009aa115 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -338,6 +338,7 @@ "incoming": "着信", "incorrect_seed": "入力されたテキストは無効です。", "inputs": "入力", + "insufficientFundsForRentError": "アカウントの取引料金とレンタルをカバーするのに十分なソルがありません。財布にソルを追加するか、送信するソル量を減らしてください", "introducing_cake_pay": "序章Cake Pay!", "invalid_input": "無効入力", "invoice_details": "請求の詳細", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 8b86c04c6..53b3cc875 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -337,6 +337,7 @@ "incoming": "들어오는", "incorrect_seed": "입력하신 텍스트가 유효하지 않습니다.", "inputs": "입력", + "insufficientFundsForRentError": "거래 수수료와 계좌 임대료를 충당하기에 충분한 SOL이 없습니다. 지갑에 더 많은 솔을 추가하거나 보내는 솔을 줄이십시오.", "introducing_cake_pay": "소개 Cake Pay!", "invalid_input": "잘못된 입력", "invoice_details": "인보이스 세부정보", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 42eb54f21..64a7a1ad1 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -337,6 +337,7 @@ "incoming": "ဝင်လာ", "incorrect_seed": "ထည့်သွင်းထားသော စာသားသည် မမှန်ကန်ပါ။", "inputs": "သွင်းငေှ", + "insufficientFundsForRentError": "သင်ငွေပေးချေမှုအခကြေးငွေကိုဖုံးအုပ်ရန်နှင့်အကောင့်ငှားရန်လုံလောက်သော sol ရှိသည်မဟုတ်ကြဘူး။ ကြင်နာစွာသင်၏ပိုက်ဆံအိတ်သို့ပိုမို sol ကိုပိုမိုထည့်ပါသို့မဟုတ်သင်ပို့ခြင်း sol ပမာဏကိုလျှော့ချပါ", "introducing_cake_pay": "Cake Pay ကို မိတ်ဆက်ခြင်း။", "invalid_input": "ထည့်သွင်းမှု မမှန်ကန်ပါ။", "invoice_details": "ပြေစာအသေးစိတ်", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 53db56332..86f6b8c0b 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -337,6 +337,7 @@ "incoming": "inkomend", "incorrect_seed": "De ingevoerde tekst is niet geldig.", "inputs": "Invoer", + "insufficientFundsForRentError": "U hebt niet genoeg SOL om de transactiekosten en huur voor de rekening te dekken. Voeg vriendelijk meer SOL toe aan uw portemonnee of verminder de SOL -hoeveelheid die u verzendt", "introducing_cake_pay": "Introductie van Cake Pay!", "invalid_input": "Ongeldige invoer", "invoice_details": "Factuurgegevens", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 898756982..34a8d57fe 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -337,6 +337,7 @@ "incoming": "Przychodzące", "incorrect_seed": "Wprowadzony seed jest nieprawidłowy.", "inputs": "Wejścia", + "insufficientFundsForRentError": "Nie masz wystarczającej ilości SOL, aby pokryć opłatę za transakcję i czynsz za konto. Uprzejmie dodaj więcej sol do portfela lub zmniejsz solę, którą wysyłasz", "introducing_cake_pay": "Przedstawiamy Cake Pay!", "invalid_input": "Nieprawidłowe dane wejściowe", "invoice_details": "Dane do faktury", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 9e99866cb..67d68988f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -337,6 +337,7 @@ "incoming": "Recebidas", "incorrect_seed": "O texto digitado não é válido.", "inputs": "Entradas", + "insufficientFundsForRentError": "Você não tem Sol suficiente para cobrir a taxa de transação e o aluguel da conta. Por favor, adicione mais sol à sua carteira ou reduza a quantidade de sol que você envia", "introducing_cake_pay": "Apresentando o Cake Pay!", "invalid_input": "Entrada inválida", "invoice_details": "Detalhes da fatura", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 8d3892ace..521cda83d 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -337,6 +337,7 @@ "incoming": "Входящие", "incorrect_seed": "Введённый текст некорректный.", "inputs": "Входы", + "insufficientFundsForRentError": "У вас недостаточно Sol, чтобы покрыть плату за транзакцию и аренду для счета. Пожалуйста, добавьте больше Sol в свой кошелек или уменьшите сумму Sol, которую вы отправляете", "introducing_cake_pay": "Представляем Cake Pay!", "invalid_input": "Неверный Ввод", "invoice_details": "Детали счета", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 9db9caa68..996472f47 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -337,6 +337,7 @@ "incoming": "ขาเข้า", "incorrect_seed": "ข้อความที่ป้อนไม่ถูกต้อง", "inputs": "อินพุต", + "insufficientFundsForRentError": "คุณไม่มีโซลเพียงพอที่จะครอบคลุมค่าธรรมเนียมการทำธุรกรรมและค่าเช่าสำหรับบัญชี กรุณาเพิ่มโซลให้มากขึ้นลงในกระเป๋าเงินของคุณหรือลดจำนวนโซลที่คุณส่งมา", "introducing_cake_pay": "ยินดีต้อนรับสู่ Cake Pay!", "invalid_input": "อินพุตไม่ถูกต้อง", "invoice_details": "รายละเอียดใบแจ้งหนี้", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 0f77f4813..27e4974bb 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -337,6 +337,7 @@ "incoming": "Papasok", "incorrect_seed": "Ang teksto na ipinasok ay hindi wasto.", "inputs": "Mga input", + "insufficientFundsForRentError": "Wala kang sapat na sol upang masakop ang bayad sa transaksyon at upa para sa account. Mabait na magdagdag ng higit pa sa iyong pitaka o bawasan ang halaga ng sol na iyong ipinapadala", "introducing_cake_pay": "Ipinakikilala ang cake pay!", "invalid_input": "Di -wastong input", "invoice_details": "Mga detalye ng invoice", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 961f0cf77..74b72581e 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -337,6 +337,7 @@ "incoming": "Gelen", "incorrect_seed": "Girilen metin geçerli değil.", "inputs": "Girişler", + "insufficientFundsForRentError": "İşlem ücretini karşılamak ve hesap için kiralamak için yeterli SOL'nuz yok. Lütfen cüzdanınıza daha fazla sol ekleyin veya gönderdiğiniz sol miktarını azaltın", "introducing_cake_pay": "Cake Pay ile tanışın!", "invalid_input": "Geçersiz Giriş", "invoice_details": "fatura detayları", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c9cda0854..74b2e4703 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -337,6 +337,7 @@ "incoming": "Вхідні", "incorrect_seed": "Введений текст невірний.", "inputs": "Вхoди", + "insufficientFundsForRentError": "У вас недостатньо SOL, щоб покрити плату за транзакцію та оренду на рахунок. Будь ласка, додайте до свого гаманця більше SOL або зменшіть суму, яку ви надсилаєте", "introducing_cake_pay": "Представляємо Cake Pay!", "invalid_input": "Неправильні дані", "invoice_details": "Реквізити рахунку-фактури", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 43cab6370..35d024188 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -337,6 +337,7 @@ "incoming": "آنے والا", "incorrect_seed": "درج کردہ متن درست نہیں ہے۔", "inputs": "آدانوں", + "insufficientFundsForRentError": "آپ کے پاس ٹرانزیکشن فیس اور اکاؤنٹ کے لئے کرایہ لینے کے ل enough اتنا SOL نہیں ہے۔ برائے مہربانی اپنے بٹوے میں مزید سول شامل کریں یا آپ کو بھیجنے والی سول رقم کو کم کریں", "introducing_cake_pay": "Cake پے کا تعارف!", "invalid_input": "غلط ان پٹ", "invoice_details": "رسید کی تفصیلات", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index b7d8ad828..29b8d9b71 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -338,6 +338,7 @@ "incoming": "Wọ́n tó ń bọ̀", "incorrect_seed": "Ọ̀rọ̀ tí a tẹ̀ kì í ṣe èyí.", "inputs": "Igbewọle", + "insufficientFundsForRentError": "O ko ni Sol kan lati bo owo isanwo naa ki o yalo fun iroyin naa. Fi agbara kun Sol diẹ sii si apamọwọ rẹ tabi dinku soso naa ti o \\ 'tun n firanṣẹ", "introducing_cake_pay": "Ẹ bá Cake Pay!", "invalid_input": "Iṣawọle ti ko tọ", "invoice_details": "Iru awọn ẹya ọrọ", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 389cf6c24..a30acad70 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -337,6 +337,7 @@ "incoming": "收到", "incorrect_seed": "输入的文字无效。", "inputs": "输入", + "insufficientFundsForRentError": "您没有足够的溶胶来支付该帐户的交易费和租金。请在钱包中添加更多溶胶或减少您发送的溶胶量", "introducing_cake_pay": "介绍 Cake Pay!", "invalid_input": "输入无效", "invoice_details": "发票明细", From bbba41396d32b0fde52b6d676ee310c9fa26f639 Mon Sep 17 00:00:00 2001 From: Rafael <github@rafael.saes.dev> Date: Sun, 11 Aug 2024 20:49:45 -0300 Subject: [PATCH 18/19] Fixes node connection, and sp, and electrum (#1577) * refactor: remove bitcoin_flutter, update deps, electrs node improvements * feat: connecting/disconnecting improvements, fix rescan by date, scanning message * chore: print * Update pubspec.yaml * Update pubspec.yaml * handle null sockets, retry connection on connect failure * fix imports * fix transaction history * fix RBF * minor fixes/readability enhancements [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> Co-authored-by: Matthew Fosse <matt@fosse.co> --- .../lib/bitcoin_hardware_wallet_service.dart | 5 +- cw_bitcoin/lib/bitcoin_wallet.dart | 18 +- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 5 +- cw_bitcoin/lib/electrum.dart | 111 ++++--- cw_bitcoin/lib/electrum_transaction_info.dart | 4 +- cw_bitcoin/lib/electrum_wallet.dart | 295 +++++++++++------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 30 +- cw_bitcoin/lib/litecoin_network.dart | 9 - cw_bitcoin/lib/litecoin_wallet.dart | 6 +- cw_bitcoin/lib/litecoin_wallet_addresses.dart | 6 +- cw_bitcoin/lib/utils.dart | 72 ++--- cw_bitcoin/pubspec.lock | 79 ++--- cw_bitcoin/pubspec.yaml | 12 +- .../lib/src/bitcoin_cash_wallet.dart | 26 +- .../src/bitcoin_cash_wallet_addresses.dart | 6 +- .../lib/src/bitcoin_cash_wallet_service.dart | 12 +- cw_bitcoin_cash/lib/src/mnemonic.dart | 2 +- cw_bitcoin_cash/pubspec.yaml | 8 +- cw_core/lib/get_height_by_date.dart | 7 +- cw_core/lib/node.dart | 9 +- cw_core/lib/sync_status.dart | 5 + cw_core/lib/transaction_info.dart | 5 +- cw_haven/pubspec.lock | 16 +- cw_monero/pubspec.lock | 4 +- cw_nano/pubspec.lock | 54 ++-- cw_nano/pubspec.yaml | 1 + cw_tron/pubspec.yaml | 4 +- cw_wownero/pubspec.lock | 20 +- ios/Podfile.lock | 12 +- lib/bitcoin/cw_bitcoin.dart | 50 +-- lib/core/sync_status_title.dart | 4 + .../cake_pay_confirm_purchase_card_page.dart | 48 +-- pubspec_base.yaml | 6 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + tool/configure.dart | 3 +- 60 files changed, 525 insertions(+), 455 deletions(-) delete mode 100644 cw_bitcoin/lib/litecoin_network.dart diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart index 345d645d1..de339175d 100644 --- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; @@ -25,7 +25,8 @@ class BitcoinHardwareWalletService { for (final i in indexRange) { final derivationPath = "m/84'/0'/$i'"; final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath); - HDWallet hd = HDWallet.fromBase58(xpub).derive(0); + Bip32Slip10Secp256k1 hd = + Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0)); final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index ce3e2caa8..7b8250541 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -2,8 +2,7 @@ import 'dart:convert'; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:convert/convert.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; @@ -51,11 +50,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: networkParam == null - ? bitcoin.bitcoin + network: networkParam == null + ? BitcoinNetwork.mainnet : networkParam == BitcoinNetwork.mainnet - ? bitcoin.bitcoin - : bitcoin.testnet, + ? BitcoinNetwork.mainnet + : BitcoinNetwork.testnet, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, @@ -76,10 +75,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialSilentAddresses: initialSilentAddresses, initialSilentAddressIndex: initialSilentAddressIndex, mainHd: hd, - sideHd: accountHD.derive(1), + sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: networkParam ?? network, - masterHd: - seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null, + masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, ); autorun((_) { @@ -253,7 +251,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); - return BtcTransaction.fromRaw(hex.encode(rawHex)); + return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); } @override diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 486e69b11..697719894 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,5 +1,5 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; +import 'package:blockchain_utils/bip/bip/bip32/bip32.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; @@ -24,7 +24,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S }) : super(walletInfo); @override - String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) { + String getAddress( + {required int index, required Bip32Slip10Secp256k1 hd, BitcoinAddressType? addressType}) { if (addressType == P2pkhAddressType.p2pkh) return generateP2PKHAddress(hd: hd, index: index, network: network); diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index e3925ca74..69b07d7c1 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -8,6 +8,8 @@ import 'package:cw_bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; +enum ConnectionStatus { connected, disconnected, connecting, failed } + String jsonrpcparams(List<Object> params) { final _params = params.map((val) => '"${val.toString()}"').join(','); return '[$_params]'; @@ -41,7 +43,7 @@ class ElectrumClient { bool get isConnected => _isConnected; Socket? socket; - void Function(bool?)? onConnectionStatusChange; + void Function(ConnectionStatus)? onConnectionStatusChange; int _id; final Map<String, SocketTask> _tasks; Map<String, SocketTask> get tasks => _tasks; @@ -60,17 +62,33 @@ class ElectrumClient { } Future<void> connect({required String host, required int port, bool? useSSL}) async { + _setConnectionStatus(ConnectionStatus.connecting); + try { await socket?.close(); } catch (_) {} - if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { - socket = await Socket.connect(host, port, timeout: connectionTimeout); - } else { - socket = await SecureSocket.connect(host, port, - timeout: connectionTimeout, onBadCertificate: (_) => true); + try { + if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { + socket = await Socket.connect(host, port, timeout: connectionTimeout); + } else { + socket = await SecureSocket.connect( + host, + port, + timeout: connectionTimeout, + onBadCertificate: (_) => true, + ); + } + } catch (_) { + _setConnectionStatus(ConnectionStatus.failed); + return; } - _setIsConnected(true); + + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return; + } + _setConnectionStatus(ConnectionStatus.connected); socket!.listen((Uint8List event) { try { @@ -86,13 +104,20 @@ class ElectrumClient { print(e.toString()); } }, onError: (Object error) { - print(error.toString()); + final errorMsg = error.toString(); + print(errorMsg); unterminatedString = ''; - _setIsConnected(false); + + final currentHost = socket?.address.host; + final isErrorForCurrentHost = errorMsg.contains(" ${currentHost} "); + + if (currentHost != null && isErrorForCurrentHost) + _setConnectionStatus(ConnectionStatus.failed); }, onDone: () { unterminatedString = ''; - _setIsConnected(null); + if (host == socket?.address.host) _setConnectionStatus(ConnectionStatus.disconnected); }); + keepAlive(); } @@ -144,9 +169,9 @@ class ElectrumClient { Future<void> ping() async { try { await callWithTimeout(method: 'server.ping'); - _setIsConnected(true); + _setConnectionStatus(ConnectionStatus.connected); } on RequestFailedTimeoutException catch (_) { - _setIsConnected(null); + _setConnectionStatus(ConnectionStatus.disconnected); } } @@ -236,37 +261,39 @@ class ElectrumClient { return []; }); - Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async { + Future<dynamic> getTransaction({required String hash, required bool verbose}) async { try { final result = await callWithTimeout( - method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000); + method: 'blockchain.transaction.get', params: [hash, verbose], timeout: 10000); if (result is Map<String, dynamic>) { return result; } } on RequestFailedTimeoutException catch (_) { return <String, dynamic>{}; } catch (e) { - print("getTransactionRaw: ${e.toString()}"); + print("getTransaction: ${e.toString()}"); return <String, dynamic>{}; } return <String, dynamic>{}; } - Future<String> getTransactionHex({required String hash}) async { - try { - final result = await callWithTimeout( - method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000); - if (result is String) { - return result; - } - } on RequestFailedTimeoutException catch (_) { - return ''; - } catch (e) { - print("getTransactionHex: ${e.toString()}"); - return ''; - } - return ''; - } + Future<Map<String, dynamic>> getTransactionVerbose({required String hash}) => + getTransaction(hash: hash, verbose: true).then((dynamic result) { + if (result is Map<String, dynamic>) { + return result; + } + + return <String, dynamic>{}; + }); + + Future<String> getTransactionHex({required String hash}) => + getTransaction(hash: hash, verbose: false).then((dynamic result) { + if (result is String) { + return result; + } + + return ''; + }); Future<String> broadcastTransaction( {required String transactionRaw, @@ -348,7 +375,7 @@ class ElectrumClient { try { final topDoubleString = await estimatefee(p: 1); final middleDoubleString = await estimatefee(p: 5); - final bottomDoubleString = await estimatefee(p: 100); + final bottomDoubleString = await estimatefee(p: 10); final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); @@ -398,6 +425,10 @@ class ElectrumClient { BehaviorSubject<T>? subscribe<T>( {required String id, required String method, List<Object> params = const []}) { try { + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return null; + } final subscription = BehaviorSubject<T>(); _regisrySubscription(id, subscription); socket!.write(jsonrpc(method: method, id: _id, params: params)); @@ -411,6 +442,10 @@ class ElectrumClient { Future<dynamic> call( {required String method, List<Object> params = const [], Function(int)? idCallback}) async { + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return null; + } final completer = Completer<dynamic>(); _id += 1; final id = _id; @@ -424,6 +459,10 @@ class ElectrumClient { Future<dynamic> callWithTimeout( {required String method, List<Object> params = const [], int timeout = 4000}) async { try { + if (socket == null) { + _setConnectionStatus(ConnectionStatus.failed); + return null; + } final completer = Completer<dynamic>(); _id += 1; final id = _id; @@ -445,6 +484,7 @@ class ElectrumClient { _aliveTimer?.cancel(); try { await socket?.close(); + socket = null; } catch (_) {} onConnectionStatusChange = null; } @@ -493,12 +533,9 @@ class ElectrumClient { } } - void _setIsConnected(bool? isConnected) { - if (_isConnected != isConnected) { - onConnectionStatusChange?.call(isConnected); - } - - _isConnected = isConnected ?? false; + void _setConnectionStatus(ConnectionStatus status) { + onConnectionStatusChange?.call(status); + _isConnected = status == ConnectionStatus.connected; } void _handleResponse(Map<String, dynamic> response) { diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index d06cfe9de..ea4a3de33 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -22,7 +22,7 @@ class ElectrumTransactionInfo extends TransactionInfo { ElectrumTransactionInfo(this.type, {required String id, - required int height, + int? height, required int amount, int? fee, List<String>? inputAddresses, @@ -99,7 +99,7 @@ class ElectrumTransactionInfo extends TransactionInfo { factory ElectrumTransactionInfo.fromElectrumBundle( ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network, - {required Set<String> addresses, required int height}) { + {required Set<String> addresses, int? height}) { final date = bundle.time != null ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000) : DateTime.now(); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index e55e5ed0e..e1b038beb 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -5,7 +5,6 @@ import 'dart:isolate'; import 'dart:math'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:collection/collection.dart'; import 'package:cw_bitcoin/address_from_output.dart'; @@ -22,7 +21,6 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/exceptions.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/utils.dart'; @@ -42,7 +40,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; -import 'package:http/http.dart' as http; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; @@ -60,7 +57,7 @@ abstract class ElectrumWalletBase required String password, required WalletInfo walletInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo, - required this.networkType, + required this.network, String? xpub, String? mnemonic, Uint8List? seedBytes, @@ -71,7 +68,7 @@ abstract class ElectrumWalletBase CryptoCurrency? currency, this.alwaysScan, }) : accountHD = - getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo), + getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = <int>[], @@ -90,8 +87,7 @@ abstract class ElectrumWalletBase } : {}), this.unspentCoinsInfo = unspentCoinsInfo, - this.network = _getNetwork(networkType, currency), - this.isTestnet = networkType == bitcoin.testnet, + this.isTestnet = network == BitcoinNetwork.testnet, this._mnemonic = mnemonic, super(walletInfo) { this.electrumClient = electrumClient ?? ElectrumClient(); @@ -101,12 +97,8 @@ abstract class ElectrumWalletBase reaction((_) => syncStatus, _syncStatusReaction); } - static bitcoin.HDWallet getAccountHDWallet( - CryptoCurrency? currency, - bitcoin.NetworkType networkType, - Uint8List? seedBytes, - String? xpub, - DerivationInfo? derivationInfo) { + static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network, + Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) { if (seedBytes == null && xpub == null) { throw Exception( "To create a Wallet you need either a seed or an xpub. This should not happen"); @@ -115,25 +107,26 @@ abstract class ElectrumWalletBase if (seedBytes != null) { return currency == CryptoCurrency.bch ? bitcoinCashHDWallet(seedBytes) - : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) - .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)); + : Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( + _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) + as Bip32Slip10Secp256k1; } - return bitcoin.HDWallet.fromBase58(xpub!); + return Bip32Slip10Secp256k1.fromExtendedKey(xpub!); } - static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => - bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'"); + static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => + Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1; static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; bool? alwaysScan; - final bitcoin.HDWallet accountHD; + final Bip32Slip10Secp256k1 accountHD; final String? _mnemonic; - bitcoin.HDWallet get hd => accountHD.derive(0); + Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0)); final String? passphrase; @override @@ -165,7 +158,7 @@ abstract class ElectrumWalletBase .map((addr) => scriptHash(addr.address, network: network)) .toList(); - String get xpub => accountHD.base58!; + String get xpub => accountHD.publicKey.toExtended; @override String? get seed => _mnemonic; @@ -174,7 +167,6 @@ abstract class ElectrumWalletBase WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase); - bitcoin.NetworkType networkType; BasedUtxoNetwork network; @override @@ -190,24 +182,21 @@ abstract class ElectrumWalletBase bool _isTryingToConnect = false; @action - Future<void> setSilentPaymentsScanning(bool active, bool usingElectrs) async { + Future<void> setSilentPaymentsScanning(bool active) async { silentPaymentsScanningActive = active; if (active) { - syncStatus = AttemptingSyncStatus(); + syncStatus = StartingScanSyncStatus(); final tip = await getUpdatedChainTip(); if (tip == walletInfo.restoreHeight) { syncStatus = SyncedTipSyncStatus(tip); + return; } if (tip > walletInfo.restoreHeight) { - _setListeners( - walletInfo.restoreHeight, - chainTipParam: _currentChainTip, - usingElectrs: usingElectrs, - ); + _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); } } else { alwaysScan = false; @@ -245,8 +234,11 @@ abstract class ElectrumWalletBase } @override - BitcoinWalletKeys get keys => - BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); + BitcoinWalletKeys get keys => BitcoinWalletKeys( + wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer), + privateKey: hd.privateKey.toHex(), + publicKey: hd.publicKey.toHex(), + ); String _password; List<BitcoinUnspent> unspentCoins; @@ -278,7 +270,7 @@ abstract class ElectrumWalletBase int height, { int? chainTipParam, bool? doSingleScan, - bool? usingElectrs, + bool? usingSupportedNode, }) async { final chainTip = chainTipParam ?? await getUpdatedChainTip(); @@ -287,7 +279,7 @@ abstract class ElectrumWalletBase return; } - syncStatus = AttemptingSyncStatus(); + syncStatus = StartingScanSyncStatus(); if (_isolate != null) { final runningIsolate = await _isolate!; @@ -305,7 +297,9 @@ abstract class ElectrumWalletBase chainTip: chainTip, electrumClient: ElectrumClient(), transactionHistoryIds: transactionHistory.transactions.keys.toList(), - node: usingElectrs == true ? ScanNode(node!.uri, node!.useSSL) : null, + node: (await getNodeSupportsSilentPayments()) == true + ? ScanNode(node!.uri, node!.useSSL) + : null, labels: walletAddresses.labels, labelIndexes: walletAddresses.silentAddresses .where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1) @@ -393,7 +387,7 @@ abstract class ElectrumWalletBase BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)), ) : silentAddress.B_spend, - hrp: silentAddress.hrp, + network: network, ); final addressRecord = walletAddresses.silentAddresses @@ -422,8 +416,6 @@ abstract class ElectrumWalletBase await updateAllUnspents(); await updateBalance(); - Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates()); - if (alwaysScan == true) { _setListeners(walletInfo.restoreHeight); } else { @@ -446,6 +438,58 @@ abstract class ElectrumWalletBase Node? node; + Future<bool> getNodeIsElectrs() async { + if (node == null) { + return false; + } + + final version = await electrumClient.version(); + + if (version.isNotEmpty) { + final server = version[0]; + + if (server.toLowerCase().contains('electrs')) { + node!.isElectrs = true; + node!.save(); + return node!.isElectrs!; + } + } + + + node!.isElectrs = false; + node!.save(); + return node!.isElectrs!; + } + + Future<bool> getNodeSupportsSilentPayments() async { + // As of today (august 2024), only ElectrumRS supports silent payments + if (!(await getNodeIsElectrs())) { + return false; + } + + if (node == null) { + return false; + } + + try { + final tweaksResponse = await electrumClient.getTweaks(height: 0); + + if (tweaksResponse != null) { + node!.supportsSilentPayments = true; + node!.save(); + return node!.supportsSilentPayments!; + } + } on RequestFailedTimeoutException catch (_) { + node!.supportsSilentPayments = false; + node!.save(); + return node!.supportsSilentPayments!; + } catch (_) {} + + node!.supportsSilentPayments = false; + node!.save(); + return node!.supportsSilentPayments!; + } + @action @override Future<void> connectToNode({required Node node}) async { @@ -507,13 +551,6 @@ abstract class ElectrumWalletBase final hd = utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; - final derivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" - "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" - "/${utx.bitcoinAddressRecord.index}"; - final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!; - - publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; @@ -530,6 +567,7 @@ abstract class ElectrumWalletBase } vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); + String pubKeyHex; if (privkey != null) { inputPrivKeyInfos.add(ECPrivateInfo( @@ -537,8 +575,18 @@ abstract class ElectrumWalletBase address.type == SegwitAddresType.p2tr, tweak: !isSilentPayment, )); + + pubKeyHex = privkey.getPublic().toHex(); + } else { + pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex(); } + final derivationPath = + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" + "/${utx.bitcoinAddressRecord.index}"; + publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); + utxos.add( UtxoWithAddress( utxo: BitcoinUtxo( @@ -1127,10 +1175,9 @@ abstract class ElectrumWalletBase int? chainTip, ScanData? scanData, bool? doSingleScan, - bool? usingElectrs, }) async { silentPaymentsScanningActive = true; - _setListeners(height, doSingleScan: doSingleScan, usingElectrs: usingElectrs); + _setListeners(height, doSingleScan: doSingleScan); } @override @@ -1228,7 +1275,7 @@ abstract class ElectrumWalletBase await Future.wait(unspents.map((unspent) async { try { final coin = BitcoinUnspent.fromJSON(address, unspent); - final tx = await fetchTransactionInfo(hash: coin.hash, height: 0); + final tx = await fetchTransactionInfo(hash: coin.hash); coin.isChange = address.isHidden; coin.confirmations = tx?.confirmations; @@ -1283,9 +1330,17 @@ abstract class ElectrumWalletBase } Future<bool> canReplaceByFee(String hash) async { - final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); - final confirmations = verboseTransaction['confirmations'] as int? ?? 0; - final transactionHex = verboseTransaction['hex'] as String?; + final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); + + final String? transactionHex; + int confirmations = 0; + + if (verboseTransaction.isEmpty) { + transactionHex = await electrumClient.getTransactionHex(hash: hash); + } else { + confirmations = verboseTransaction['confirmations'] as int? ?? 0; + transactionHex = verboseTransaction['hex'] as String?; + } if (confirmations > 0) return false; @@ -1293,10 +1348,7 @@ abstract class ElectrumWalletBase return false; } - final original = bitcoin.Transaction.fromHex(transactionHex); - - return original.ins - .any((element) => element.sequence != null && element.sequence! < 4294967293); + return BtcTransaction.fromRaw(transactionHex).canReplaceByFee; } Future<bool> isChangeSufficientForFee(String txId, int newFee) async { @@ -1455,50 +1507,73 @@ abstract class ElectrumWalletBase } } - Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async { + Future<ElectrumTransactionBundle> getTransactionExpanded( + {required String hash, int? height}) async { String transactionHex; + // TODO: time is not always available, and calculating it from height is not always accurate. + // Add settings to choose API provider and use and http server instead of electrum for this. int? time; - int confirmations = 0; - if (network == BitcoinNetwork.testnet) { - // Testnet public electrum server does not support verbose transaction fetching + int? confirmations; + + final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); + + if (verboseTransaction.isEmpty) { transactionHex = await electrumClient.getTransactionHex(hash: hash); - - final status = json.decode( - (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body); - - time = status["block_time"] as int?; - final height = status["block_height"] as int? ?? 0; - final tip = await getUpdatedChainTip(); - if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0; } else { - final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); - transactionHex = verboseTransaction['hex'] as String; time = verboseTransaction['time'] as int?; - confirmations = verboseTransaction['confirmations'] as int? ?? 0; + confirmations = verboseTransaction['confirmations'] as int?; + } + + if (height != null) { + if (time == null) { + time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round(); + } + + if (confirmations == null) { + final tip = await getUpdatedChainTip(); + if (tip > 0 && height > 0) { + // Add one because the block itself is the first confirmation + confirmations = tip - height + 1; + } + } } final original = BtcTransaction.fromRaw(transactionHex); final ins = <BtcTransaction>[]; for (final vin in original.inputs) { - ins.add(BtcTransaction.fromRaw(await electrumClient.getTransactionHex(hash: vin.txId))); + final verboseTransaction = await electrumClient.getTransactionVerbose(hash: vin.txId); + + final String inputTransactionHex; + + if (verboseTransaction.isEmpty) { + inputTransactionHex = await electrumClient.getTransactionHex(hash: hash); + } else { + inputTransactionHex = verboseTransaction['hex'] as String; + } + + ins.add(BtcTransaction.fromRaw(inputTransactionHex)); } return ElectrumTransactionBundle( original, ins: ins, time: time, - confirmations: confirmations, + confirmations: confirmations ?? 0, ); } Future<ElectrumTransactionInfo?> fetchTransactionInfo( - {required String hash, required int height, bool? retryOnFailure}) async { + {required String hash, int? height, bool? retryOnFailure}) async { try { return ElectrumTransactionInfo.fromElectrumBundle( - await getTransactionExpanded(hash: hash), walletInfo.type, network, - addresses: addressesSet, height: height); + await getTransactionExpanded(hash: hash, height: height), + walletInfo.type, + network, + addresses: addressesSet, + height: height, + ); } catch (e) { if (e is FormatException && retryOnFailure == true) { await Future.delayed(const Duration(seconds: 2)); @@ -1649,8 +1724,8 @@ abstract class ElectrumWalletBase await getCurrentChainTip(); transactionHistory.transactions.values.forEach((tx) async { - if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) { - tx.confirmations = await getCurrentChainTip() - tx.height + 1; + if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) { + tx.confirmations = await getCurrentChainTip() - tx.height! + 1; } }); @@ -1766,8 +1841,12 @@ abstract class ElectrumWalletBase final index = address != null ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index : null; - final HD = index == null ? hd : hd.derive(index); - return base64Encode(HD.signMessage(message)); + final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); + final priv = ECPrivate.fromWif( + WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer), + netVersion: network.wifNetVer, + ); + return priv.signMessage(StringUtils.encode(message)); } Future<void> _setInitialHeight() async { @@ -1793,43 +1872,42 @@ abstract class ElectrumWalletBase }); } - static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) { - if (networkType == bitcoin.bitcoin && currency == CryptoCurrency.bch) { - return BitcoinCashNetwork.mainnet; - } - - if (networkType == litecoinNetwork) { - return LitecoinNetwork.mainnet; - } - - if (networkType == bitcoin.testnet) { - return BitcoinNetwork.testnet; - } - - return BitcoinNetwork.mainnet; - } - static String _hardenedDerivationPath(String derivationPath) => derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1); @action - void _onConnectionStatusChange(bool? isConnected) { - if (syncStatus is SyncingSyncStatus) return; + void _onConnectionStatusChange(ConnectionStatus status) { + switch (status) { + case ConnectionStatus.connected: + if (syncStatus is NotConnectedSyncStatus || + syncStatus is LostConnectionSyncStatus || + syncStatus is ConnectingSyncStatus) { + syncStatus = AttemptingSyncStatus(); + startSync(); + } - if (isConnected == true && syncStatus is! SyncedSyncStatus) { - syncStatus = ConnectedSyncStatus(); - } else if (isConnected == false) { - syncStatus = LostConnectionSyncStatus(); - } else if (isConnected != true && syncStatus is! ConnectingSyncStatus) { - syncStatus = NotConnectedSyncStatus(); + break; + case ConnectionStatus.disconnected: + syncStatus = NotConnectedSyncStatus(); + break; + case ConnectionStatus.failed: + syncStatus = LostConnectionSyncStatus(); + // wait for 5 seconds and then try to reconnect: + Future.delayed(Duration(seconds: 5), () { + electrumClient.connectToUri( + node!.uri, + useSSL: node!.useSSL ?? false, + ); + }); + break; + case ConnectionStatus.connecting: + syncStatus = ConnectingSyncStatus(); + break; + default: } } void _syncStatusReaction(SyncStatus syncStatus) async { - if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus) { - silentPaymentsScanningActive = syncStatus is SyncingSyncStatus; - } - if (syncStatus is NotConnectedSyncStatus) { // Needs to re-subscribe to all scripthashes when reconnected _scripthashesUpdateSubject = {}; @@ -1950,8 +2028,8 @@ Future<void> startRefresh(ScanData scanData) async { final tweaks = t as Map<String, dynamic>; if (tweaks["message"] != null) { - // re-subscribe to continue receiving messages - electrumClient.tweaksSubscribe(height: syncHeight, count: count); + // re-subscribe to continue receiving messages, starting from the next unscanned height + electrumClient.tweaksSubscribe(height: syncHeight + 1, count: count); return; } @@ -2180,3 +2258,4 @@ class UtxoDetails { required this.spendsUnconfirmedTX, }); } + diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index b39821dbb..a0424c934 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,5 +1,4 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/wallet_addresses.dart'; @@ -30,7 +29,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { Map<String, int>? initialChangeAddressIndex, List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, int initialSilentAddressIndex = 0, - bitcoin.HDWallet? masterHd, + Bip32Slip10Secp256k1? masterHd, BitcoinAddressType? initialAddressPageType, }) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()), addressesByReceiveType = @@ -53,9 +52,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { super(walletInfo) { if (masterHd != null) { silentAddress = SilentPaymentOwner.fromPrivateKeys( - b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!), - b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!), - hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'); + b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()), + b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()), + network: network, + ); if (silentAddresses.length == 0) { silentAddresses.add(BitcoinSilentPaymentAddressRecord( @@ -92,8 +92,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final ObservableList<BitcoinAddressRecord> changeAddresses; final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses; final BasedUtxoNetwork network; - final bitcoin.HDWallet mainHd; - final bitcoin.HDWallet sideHd; + final Bip32Slip10Secp256k1 mainHd; + final Bip32Slip10Secp256k1 sideHd; @observable SilentPaymentOwner? silentAddress; @@ -318,7 +318,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } String getAddress( - {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) => + {required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType}) => ''; @override @@ -540,11 +542,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { void _validateAddresses() { _addresses.forEach((element) { - if (!element.isHidden && element.address != - getAddress(index: element.index, hd: mainHd, addressType: element.type)) { + if (!element.isHidden && + element.address != + getAddress(index: element.index, hd: mainHd, addressType: element.type)) { element.isHidden = true; - } else if (element.isHidden && element.address != - getAddress(index: element.index, hd: sideHd, addressType: element.type)) { + } else if (element.isHidden && + element.address != + getAddress(index: element.index, hd: sideHd, addressType: element.type)) { element.isHidden = false; } }); @@ -562,7 +566,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return _isAddressByType(addressRecord, addressPageType); } - bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd; + Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd; bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => !addr.isHidden && !addr.isUsed && addr.type == type; diff --git a/cw_bitcoin/lib/litecoin_network.dart b/cw_bitcoin/lib/litecoin_network.dart deleted file mode 100644 index d7ad2f837..000000000 --- a/cw_bitcoin/lib/litecoin_network.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; - -final litecoinNetwork = NetworkType( - messagePrefix: '\x19Litecoin Signed Message:\n', - bech32: 'ltc', - bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), - pubKeyHash: 0x30, - scriptHash: 0x32, - wif: 0xb0); diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index bfb9a1b16..64e53ca5d 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,12 +1,12 @@ import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -38,7 +38,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: litecoinNetwork, + network: LitecoinNetwork.mainnet, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, @@ -49,7 +49,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, - sideHd: accountHD.derive(1), + sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: network, ); autorun((_) { diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index 99b7445fc..6945db445 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -1,5 +1,5 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; @@ -22,6 +22,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with @override String getAddress( - {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) => + {required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType}) => generateP2WPKHAddress(hd: hd, index: index, network: network); } diff --git a/cw_bitcoin/lib/utils.dart b/cw_bitcoin/lib/utils.dart index e3ebc00db..29d7a9bf3 100644 --- a/cw_bitcoin/lib/utils.dart +++ b/cw_bitcoin/lib/utils.dart @@ -1,68 +1,54 @@ -import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:flutter/foundation.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; -import 'package:hex/hex.dart'; - -bitcoin.PaymentData generatePaymentData({ - required bitcoin.HDWallet hd, - required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey))); -} +import 'package:blockchain_utils/blockchain_utils.dart'; ECPrivate generateECPrivate({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final wif = hd.derive(index).wif!; - return ECPrivate.fromWif(wif, netVersion: network.wifNetVer); -} +}) => + ECPrivate(hd.childKey(Bip32KeyIndex(index)).privateKey); String generateP2WPKHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2wpkhAddress() + .toAddress(network); String generateP2SHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2wshInP2sh() + .toAddress(network); String generateP2WSHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2wshAddress() + .toAddress(network); String generateP2PKHAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toP2pkhAddress() + .toAddress(network); String generateP2TRAddress({ - required bitcoin.HDWallet hd, + required Bip32Slip10Secp256k1 hd, required BasedUtxoNetwork network, required int index, -}) { - final pubKey = hd.derive(index).pubKey!; - return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network); -} +}) => + ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey) + .toTaprootAddress() + .toAddress(network); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 15f7cdb43..be7862e26 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -41,15 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" - bech32: - dependency: transitive - description: - path: "." - ref: "cake-0.2.2" - resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192" - url: "https://github.com/cake-tech/bech32.git" - source: git - version: "0.2.2" bip32: dependency: transitive description: @@ -79,29 +70,20 @@ packages: dependency: "direct main" description: path: "." - ref: cake-update-v3 - resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149 + ref: cake-update-v4 + resolved-ref: "574486bfcdbbaf978dcd006b46fc8716f880da29" url: "https://github.com/cake-tech/bitcoin_base" source: git - version: "4.2.1" - bitcoin_flutter: - dependency: "direct main" - description: - path: "." - ref: cake-update-v4 - resolved-ref: e19ffb7e7977278a75b27e0479b3c6f4034223b3 - url: "https://github.com/cake-tech/bitcoin_flutter.git" - source: git - version: "2.1.0" + version: "4.7.0" blockchain_utils: dependency: "direct main" description: path: "." - ref: cake-update-v1 - resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3 + ref: cake-update-v2 + resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57" url: "https://github.com/cake-tech/blockchain_utils" source: git - version: "2.1.2" + version: "3.3.0" boolean_selector: dependency: transitive description: @@ -411,10 +393,10 @@ packages: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -499,11 +481,12 @@ packages: ledger_flutter: dependency: "direct main" description: - name: ledger_flutter - sha256: f1680060ed6ff78f275837e0024ccaf667715a59ba7aa29fa7354bc7752e71c8 - url: "https://pub.dev" - source: hosted - version: "1.0.1" + path: "." + ref: cake-v3 + resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4" + url: "https://github.com/cake-tech/ledger-flutter.git" + source: git + version: "1.0.2" ledger_usb: dependency: transitive description: @@ -596,10 +579,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: @@ -636,18 +619,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -700,10 +683,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" quiver: dependency: transitive description: @@ -761,10 +744,10 @@ packages: dependency: transitive description: name: socks5_proxy - sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83" + sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" url: "https://pub.dev" source: hosted - version: "1.0.5+dev.2" + version: "1.0.6" source_gen: dependency: transitive description: @@ -793,9 +776,9 @@ packages: dependency: "direct main" description: path: "." - ref: "sp_v2.0.0" - resolved-ref: "62c152b9086cd968019128845371072f7e1168de" - url: "https://github.com/cake-tech/sp_scanner" + ref: "sp_v4.0.0" + resolved-ref: "3b8ae38592c0584f53560071dc18bc570758fe13" + url: "https://github.com/rafael-xmr/sp_scanner" source: git version: "0.0.1" stack_trace: @@ -910,14 +893,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.5" - win32: - dependency: transitive - description: - name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" - url: "https://pub.dev" - source: hosted - version: "5.5.0" xdg_directories: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 69ff3d29b..449833220 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -19,10 +19,6 @@ dependencies: intl: ^0.18.0 cw_core: path: ../cw_core - bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git @@ -32,19 +28,19 @@ dependencies: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base - ref: cake-update-v3 + ref: cake-update-v4 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + ref: cake-update-v2 ledger_flutter: ^1.0.1 ledger_bitcoin: git: url: https://github.com/cake-tech/ledger-bitcoin sp_scanner: git: - url: https://github.com/cake-tech/sp_scanner - ref: sp_v2.0.0 + url: https://github.com/rafael-xmr/sp_scanner + ref: sp_v4.0.0 dev_dependencies: diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index f15eed10d..8323c01a8 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -1,8 +1,6 @@ -import 'dart:convert'; - import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; @@ -40,7 +38,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: bitcoin.bitcoin, + network: BitcoinCashNetwork.mainnet, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, @@ -51,7 +49,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, - sideHd: accountHD.derive(1), + sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: network, initialAddressPageType: addressPageType, ); @@ -77,7 +75,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, initialBalance: initialBalance, - seedBytes: await Mnemonic.toSeed(mnemonic), + seedBytes: await MnemonicBip39.toSeed(mnemonic), initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, addressPageType: P2pkhAddressType.p2pkh, @@ -136,15 +134,17 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { } }).toList(), initialBalance: snp?.balance, - seedBytes: await Mnemonic.toSeed(keysData.mnemonic!), + seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!), initialRegularAddressIndex: snp?.regularAddressIndex, initialChangeAddressIndex: snp?.changeAddressIndex, addressPageType: P2pkhAddressType.p2pkh, ); } - bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) => - bitbox.ECPair.fromWIF(hd.derive(index).wif!); + bitbox.ECPair generateKeyPair({required Bip32Slip10Secp256k1 hd, required int index}) => + bitbox.ECPair.fromPrivateKey( + Uint8List.fromList(hd.childKey(Bip32KeyIndex(index)).privateKey.raw), + ); int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) { int inputsCount = 0; @@ -190,7 +190,11 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) .index : null; - final HD = index == null ? hd : hd.derive(index); - return base64Encode(HD.signMessage(message)); + final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); + final priv = ECPrivate.fromWif( + WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer), + netVersion: network.wifNetVer, + ); + return priv.signMessage(StringUtils.encode(message)); } } diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart index d543e944c..7342dc7f5 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -1,5 +1,5 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; @@ -23,6 +23,8 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi @override String getAddress( - {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) => + {required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType}) => generateP2PKHAddress(hd: hd, index: index, network: network); } 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 01ae8ace3..002e52c4f 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart @@ -11,8 +11,11 @@ import 'package:cw_core/wallet_type.dart'; import 'package:collection/collection.dart'; import 'package:hive/hive.dart'; -class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials, - BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, BitcoinCashNewWalletCredentials> { +class BitcoinCashWalletService extends WalletService< + BitcoinCashNewWalletCredentials, + BitcoinCashRestoreWalletFromSeedCredentials, + BitcoinCashRestoreWalletFromWIFCredentials, + BitcoinCashNewWalletCredentials> { BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box<WalletInfo> walletInfoSource; @@ -30,7 +33,7 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent final strength = credentials.seedPhraseLength == 24 ? 256 : 128; final wallet = await BitcoinCashWalletBase.create( - mnemonic: await Mnemonic.generate(strength: strength), + mnemonic: await MnemonicBip39.generate(strength: strength), password: credentials.password!, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); @@ -97,7 +100,8 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent @override Future<BitcoinCashWallet> restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) { - throw UnimplementedError("Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!"); + throw UnimplementedError( + "Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!"); } @override diff --git a/cw_bitcoin_cash/lib/src/mnemonic.dart b/cw_bitcoin_cash/lib/src/mnemonic.dart index b1f1ee984..7aac1d5c4 100644 --- a/cw_bitcoin_cash/lib/src/mnemonic.dart +++ b/cw_bitcoin_cash/lib/src/mnemonic.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:bip39/bip39.dart' as bip39; -class Mnemonic { +class MnemonicBip39 { /// Generate bip39 mnemonic static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength); diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index a0ce889c1..3728bafc5 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -21,10 +21,6 @@ dependencies: path: ../cw_core cw_bitcoin: path: ../cw_bitcoin - bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git @@ -32,11 +28,11 @@ dependencies: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base - ref: cake-update-v3 + ref: cake-update-v4 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + ref: cake-update-v2 dev_dependencies: flutter_test: diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index 6f1b4078b..204f03d62 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -245,6 +245,8 @@ Future<int> getHavenCurrentHeight() async { // Data taken from https://timechaincalendar.com/ const bitcoinDates = { + "2024-08": 854889, + "2024-07": 850182, "2024-06": 846005, "2024-05": 841590, "2024-04": 837182, @@ -371,7 +373,8 @@ const wowDates = { int getWowneroHeightByDate({required DateTime date}) { String closestKey = - wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => ''); + wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => ''); return wowDates[closestKey] ?? 0; -} \ No newline at end of file +} + diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index f35ea589f..85c61de15 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -11,7 +11,8 @@ import 'package:http/io_client.dart' as ioc; part 'node.g.dart'; -Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!; +Uri createUriFromElectrumAddress(String address, String path) => + Uri.tryParse('tcp://$address$path')!; @HiveType(typeId: Node.typeId) class Node extends HiveObject with Keyable { @@ -72,6 +73,12 @@ class Node extends HiveObject with Keyable { @HiveField(7, defaultValue: '') String? path; + @HiveField(8) + bool? isElectrs; + + @HiveField(9) + bool? supportsSilentPayments; + bool get isSSL => useSSL ?? false; bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index 55c31877f..ea015340c 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -3,6 +3,11 @@ abstract class SyncStatus { double progress(); } +class StartingScanSyncStatus extends SyncStatus { + @override + double progress() => 0.0; +} + class SyncingSyncStatus extends SyncStatus { SyncingSyncStatus(this.blocksLeft, this.ptc); diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart index e363d88db..971e4ecdb 100644 --- a/cw_core/lib/transaction_info.dart +++ b/cw_core/lib/transaction_info.dart @@ -9,7 +9,7 @@ abstract class TransactionInfo extends Object with Keyable { late TransactionDirection direction; late bool isPending; late DateTime date; - late int height; + int? height; late int confirmations; String amountFormatted(); String fiatAmount(); @@ -25,4 +25,5 @@ abstract class TransactionInfo extends Object with Keyable { dynamic get keyIndex => id; late Map<String, dynamic> additionalInfo; -} \ No newline at end of file +} + diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index b8583d219..c34e164f4 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -254,10 +254,10 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: @@ -514,14 +514,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -707,10 +699,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 838f7224c..38299b2dc 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -438,8 +438,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" - resolved-ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b + resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index 70f2f6f0b..c3d4b26b6 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: asn1lib - sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0 + sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda" url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.5.3" async: dependency: transitive description: @@ -114,7 +114,7 @@ packages: source: hosted version: "2.4.9" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.6.1" + version: "8.9.2" characters: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.10.0" collection: dependency: transitive description: @@ -220,10 +220,10 @@ packages: dependency: "direct main" description: name: ed25519_hd_key - sha256: "326608234e986ea826a5db4cf4cd6826058d860875a3fff7926c0725fe1a604d" + sha256: c5c9f11a03f5789bf9dcd9ae88d641571c802640851f1cacdb13123f171b3a26 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" encrypt: dependency: transitive description: @@ -244,10 +244,10 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.2" file: dependency: transitive description: @@ -475,10 +475,10 @@ packages: dependency: "direct main" description: name: mobx - sha256: "0afcf88b3ee9d6819890bf16c11a727fc8c62cf736fda8e5d3b9b4eace4e62ea" + sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.3+2" mobx_codegen: dependency: "direct dev" description: @@ -572,10 +572,10 @@ packages: dependency: transitive description: name: pinenacl - sha256: e5fb0bce1717b7f136f35ee98b5c02b3e6383211f8a77ca882fa7812232a07b9 + sha256: "3a5503637587d635647c93ea9a8fecf48a420cc7deebe6f1fc85c2a5637ab327" url: "https://pub.dev" source: hosted - version: "0.3.4" + version: "0.5.1" platform: dependency: transitive description: @@ -588,10 +588,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.8" pointycastle: dependency: transitive description: @@ -652,10 +652,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.5.0" shared_preferences_linux: dependency: transitive description: @@ -668,10 +668,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" shared_preferences_web: dependency: transitive description: @@ -849,18 +849,18 @@ packages: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.5.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.4" yaml: dependency: transitive description: @@ -870,5 +870,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.6" diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml index 768c1bb4e..6fae6a895 100644 --- a/cw_nano/pubspec.yaml +++ b/cw_nano/pubspec.yaml @@ -38,6 +38,7 @@ dev_dependencies: dependency_overrides: watcher: ^1.1.0 + build_runner_core: 7.2.7+1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_tron/pubspec.yaml b/cw_tron/pubspec.yaml index f27e1b460..e69fd7ca0 100644 --- a/cw_tron/pubspec.yaml +++ b/cw_tron/pubspec.yaml @@ -18,11 +18,11 @@ dependencies: on_chain: git: url: https://github.com/cake-tech/On_chain - ref: cake-update-v1 + ref: cake-update-v2 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v1 + ref: cake-update-v2 mobx: ^2.3.0+1 bip39: ^1.0.6 hive: ^2.2.3 diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index d91922ac9..737743925 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -254,10 +254,10 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: @@ -438,8 +438,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" - resolved-ref: "bcb328a4956105dc182afd0ce2e48fe263f5f20b" + ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b + resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" @@ -555,14 +555,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -748,10 +740,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fddf6e24f..fb6dc4ecf 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -94,6 +94,8 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - sp_scanner (0.0.1): + - Flutter - SwiftProtobuf (1.26.0) - SwiftyGif (5.4.5) - Toast (4.1.1) @@ -132,6 +134,7 @@ DEPENDENCIES: - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sp_scanner (from `.symlinks/plugins/sp_scanner/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - UnstoppableDomainsResolution (~> 4.0.0) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -197,6 +200,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sp_scanner: + :path: ".symlinks/plugins/sp_scanner/ios" uni_links: :path: ".symlinks/plugins/uni_links/ios" url_launcher_ios: @@ -227,7 +232,7 @@ SPEC CHECKSUMS: MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2 @@ -235,15 +240,16 @@ SPEC CHECKSUMS: reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 - share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 + share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e uni_links: d97da20c7701486ba192624d99bffaaffcfc298a UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 + wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 PODFILE CHECKSUM: a2fe518be61cdbdc5b0e2da085ab543d556af2d3 diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index a92aaad74..edfc77acb 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -302,16 +302,13 @@ class CWBitcoin extends Bitcoin { await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); late BasedUtxoNetwork network; - btc.NetworkType networkType; switch (node.type) { case WalletType.litecoin: network = LitecoinNetwork.mainnet; - networkType = litecoinNetwork; break; case WalletType.bitcoin: default: network = BitcoinNetwork.mainnet; - networkType = btc.bitcoin; break; } @@ -341,10 +338,8 @@ class CWBitcoin extends Bitcoin { balancePath += "/0"; } - final hd = btc.HDWallet.fromSeed( - seedBytes, - network: networkType, - ).derivePath(balancePath); + final hd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(balancePath) + as Bip32Slip10Secp256k1; // derive address at index 0: String? address; @@ -515,10 +510,7 @@ class CWBitcoin extends Bitcoin { @override Future<void> setScanningActive(Object wallet, bool active) async { final bitcoinWallet = wallet as ElectrumWallet; - bitcoinWallet.setSilentPaymentsScanning( - active, - active && (await getNodeIsElectrsSPEnabled(wallet)), - ); + bitcoinWallet.setSilentPaymentsScanning(active); } @override @@ -536,44 +528,10 @@ class CWBitcoin extends Bitcoin { bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan); } - Future<bool> getNodeIsElectrs(Object wallet) async { - final bitcoinWallet = wallet as ElectrumWallet; - - final version = await bitcoinWallet.electrumClient.version(); - - if (version.isEmpty) { - return false; - } - - final server = version[0]; - - if (server.toLowerCase().contains('electrs')) { - return true; - } - - return false; - } - @override Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async { - if (!(await getNodeIsElectrs(wallet))) { - return false; - } - final bitcoinWallet = wallet as ElectrumWallet; - try { - final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0); - - if (tweaksResponse != null) { - return true; - } - } on RequestFailedTimeoutException catch (_) { - return false; - } catch (_) { - rethrow; - } - - return false; + return bitcoinWallet.getNodeSupportsSilentPayments(); } @override diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index c4cc3929f..465211f23 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -52,5 +52,9 @@ String syncStatusTitle(SyncStatus syncStatus) { return S.current.sync_status_syncronizing; } + if (syncStatus is StartingScanSyncStatus) { + return S.current.sync_status_starting_scan; + } + return ''; } diff --git a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart index fd8dce103..02ddf037d 100644 --- a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart +++ b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart @@ -34,7 +34,7 @@ class CakePayBuyCardDetailPage extends BasePage { @override Widget? middle(BuildContext context) { - return Text( + return Text( title, textAlign: TextAlign.center, maxLines: 2, @@ -359,7 +359,7 @@ class CakePayBuyCardDetailPage extends BasePage { reaction((_) => cakePayPurchaseViewModel.sendViewModel.state, (ExecutionState state) { if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { - showStateAlert(context, S.of(context).error, state.error); + if (context.mounted) showStateAlert(context, S.of(context).error, state.error); }); } @@ -381,31 +381,35 @@ class CakePayBuyCardDetailPage extends BasePage { } void showStateAlert(BuildContext context, String title, String content) { - showPopUp<void>( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: title, - alertContent: content, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + if (context.mounted) { + showPopUp<void>( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: title, + alertContent: content, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } } Future<void> showSentAlert(BuildContext context) async { + if (!context.mounted) { + return; + } final order = cakePayPurchaseViewModel.order!.orderId; final isCopy = await showPopUp<bool>( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).transaction_sent, - alertContent: - S.of(context).cake_pay_save_order + '\n${order}', - leftButtonText: S.of(context).ignor, - rightButtonText: S.of(context).copy, - actionLeftButton: () => Navigator.of(context).pop(false), - actionRightButton: () => Navigator.of(context).pop(true)); - }) ?? + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).transaction_sent, + alertContent: S.of(context).cake_pay_save_order + '\n${order}', + leftButtonText: S.of(context).ignor, + rightButtonText: S.of(context).copy, + actionLeftButton: () => Navigator.of(context).pop(false), + actionRightButton: () => Navigator.of(context).pop(true)); + }) ?? false; if (isCopy) { diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 463c04988..90072a7c1 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -87,10 +87,6 @@ dependencies: git: url: https://github.com/cake-tech/ens_dart.git ref: main - bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v4 fluttertoast: 8.1.4 # tor: # git: @@ -104,7 +100,7 @@ dependencies: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base - ref: cake-update-v3 + ref: cake-update-v4 ledger_flutter: ^1.0.1 hashlib: 1.12.0 diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index d543706fc..4cf9509f8 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "يتم التوصيل", "sync_status_failed_connect": "انقطع الاتصال", "sync_status_not_connected": "غير متصل", + "sync_status_starting_scan": "بدء المسح", "sync_status_starting_sync": "بدء المزامنة", "sync_status_syncronized": "متزامن", "sync_status_syncronizing": "يتم المزامنة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index ede60567d..b76401cf4 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "СВЪРЗВАНЕ", "sync_status_failed_connect": "НЕУСПЕШНО СВЪРЗВАНЕ", "sync_status_not_connected": "НЯМА ВРЪЗКА", + "sync_status_starting_scan": "Стартово сканиране", "sync_status_starting_sync": "ЗАПОЧВАНЕ НА СИНХРОНИЗАЦИЯ", "sync_status_syncronized": "СИНХРОНИЗИРАНО", "sync_status_syncronizing": "СИНХРОНИЗИРАНЕ", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 8f2cda1e0..c5d374dd0 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "PŘIPOJOVÁNÍ", "sync_status_failed_connect": "ODPOJENO", "sync_status_not_connected": "NEPŘIPOJENO", + "sync_status_starting_scan": "Počáteční skenování", "sync_status_starting_sync": "SPOUŠTĚNÍ SYNCHRONIZACE", "sync_status_syncronized": "SYNCHRONIZOVÁNO", "sync_status_syncronizing": "SYNCHRONIZUJI", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 59a23222c..7b6613dd6 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "VERBINDEN", "sync_status_failed_connect": "GETRENNT", "sync_status_not_connected": "NICHT VERBUNDEN", + "sync_status_starting_scan": "Scan beginnen", "sync_status_starting_sync": "STARTE SYNCHRONISIERUNG", "sync_status_syncronized": "SYNCHRONISIERT", "sync_status_syncronizing": "SYNCHRONISIERE", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 1bd3fc241..35712a780 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "CONNECTING", "sync_status_failed_connect": "DISCONNECTED", "sync_status_not_connected": "NOT CONNECTED", + "sync_status_starting_scan": "STARTING SCAN", "sync_status_starting_sync": "STARTING SYNC", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONIZING", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index dc8aa3b95..31a6e6865 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "CONECTANDO", "sync_status_failed_connect": "DESCONECTADO", "sync_status_not_connected": "NO CONECTADO", + "sync_status_starting_scan": "Escaneo inicial", "sync_status_starting_sync": "EMPEZANDO A SINCRONIZAR", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f7c45f7ef..03d4a73dd 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "CONNEXION EN COURS", "sync_status_failed_connect": "DÉCONNECTÉ", "sync_status_not_connected": "NON CONNECTÉ", + "sync_status_starting_scan": "Démarrage", "sync_status_starting_sync": "DÉBUT DE SYNCHRO", "sync_status_syncronized": "SYNCHRONISÉ", "sync_status_syncronizing": "SYNCHRONISATION EN COURS", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index a5805bbb8..922f9a51b 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -697,6 +697,7 @@ "sync_status_connecting": "HADA", "sync_status_failed_connect": "BABU INTERNET", "sync_status_not_connected": "BABU INTERNET", + "sync_status_starting_scan": "Fara scan", "sync_status_starting_sync": "KWAFI", "sync_status_syncronized": "KYAU", "sync_status_syncronizing": "KWAFI", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 4ab8e7534..db6940e8b 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -697,6 +697,7 @@ "sync_status_connecting": "कनेक्ट", "sync_status_failed_connect": "डिस्कनेक्ट किया गया", "sync_status_not_connected": "जुड़े नहीं हैं", + "sync_status_starting_scan": "स्कैन शुरू करना", "sync_status_starting_sync": "सिताज़ा करना", "sync_status_syncronized": "सिंक्रनाइज़", "sync_status_syncronizing": "सिंक्रनाइज़ करने", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 67095ba8f..57cf1361e 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "SPAJANJE", "sync_status_failed_connect": "ISKLJUČENO", "sync_status_not_connected": "NIJE POVEZANO", + "sync_status_starting_scan": "Početno skeniranje", "sync_status_starting_sync": "ZAPOČINJEMO SINKRONIZIRANJE", "sync_status_syncronized": "SINKRONIZIRANO", "sync_status_syncronizing": "SINKRONIZIRANJE", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 939b938fe..97a4afd3f 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -698,6 +698,7 @@ "sync_status_connecting": "MENGHUBUNGKAN", "sync_status_failed_connect": "GAGAL TERHUBUNG", "sync_status_not_connected": "TIDAK TERHUBUNG", + "sync_status_starting_scan": "Mulai pindai", "sync_status_starting_sync": "MULAI SINKRONISASI", "sync_status_syncronized": "SUDAH TERSINKRONISASI", "sync_status_syncronizing": "SEDANG SINKRONISASI", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 29a142d1e..42c2e628d 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -697,6 +697,7 @@ "sync_status_connecting": "CONNESSIONE", "sync_status_failed_connect": "DISCONNESSO", "sync_status_not_connected": "NON CONNESSO", + "sync_status_starting_scan": "Scansione di partenza", "sync_status_starting_sync": "INIZIO SINC", "sync_status_syncronized": "SINCRONIZZATO", "sync_status_syncronizing": "SINCRONIZZAZIONE", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 3009aa115..72b1f7d09 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "接続中", "sync_status_failed_connect": "切断されました", "sync_status_not_connected": "接続されていません", + "sync_status_starting_scan": "スキャンを開始します", "sync_status_starting_sync": "同期の開始", "sync_status_syncronized": "同期された", "sync_status_syncronizing": "同期", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 53b3cc875..b8cfee1b5 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "연결 중", "sync_status_failed_connect": "연결 해제", "sync_status_not_connected": "연결되지 않은", + "sync_status_starting_scan": "스캔 시작", "sync_status_starting_sync": "동기화 시작", "sync_status_syncronized": "동기화", "sync_status_syncronizing": "동기화", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 64a7a1ad1..52fe72ea6 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "ချိတ်ဆက်ခြင်း။", "sync_status_failed_connect": "အဆက်အသွယ်ဖြတ်ထားသည်။", "sync_status_not_connected": "မချိတ်ဆက်ပါ။", + "sync_status_starting_scan": "စကင်ဖတ်စစ်ဆေးမှု", "sync_status_starting_sync": "စင့်ခ်လုပ်ခြင်း။", "sync_status_syncronized": "ထပ်တူပြုထားသည်။", "sync_status_syncronizing": "ထပ်တူပြုခြင်း။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 86f6b8c0b..cde10506f 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "AANSLUITING", "sync_status_failed_connect": "LOSGEKOPPELD", "sync_status_not_connected": "NIET VERBONDEN", + "sync_status_starting_scan": "Startscan", "sync_status_starting_sync": "BEGINNEN MET SYNCHRONISEREN", "sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronizing": "SYNCHRONISEREN", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 34a8d57fe..a22034c96 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "ŁĄCZENIE", "sync_status_failed_connect": "POŁĄCZENIE NIEUDANE", "sync_status_not_connected": "NIE POŁĄCZONY", + "sync_status_starting_scan": "Rozpoczęcie skanowania", "sync_status_starting_sync": "ROZPOCZĘCIE SYNCHRONIZACJI", "sync_status_syncronized": "ZSYNCHRONIZOWANO", "sync_status_syncronizing": "SYNCHRONIZACJA", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 67d68988f..8f87ca59f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -697,6 +697,7 @@ "sync_status_connecting": "CONECTANDO", "sync_status_failed_connect": "DESCONECTADO", "sync_status_not_connected": "DESCONECTADO", + "sync_status_starting_scan": "Diretor inicial", "sync_status_starting_sync": "INICIANDO SINCRONIZAÇÃO", "sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronizing": "SINCRONIZANDO", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 521cda83d..9f360137f 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "ПОДКЛЮЧЕНИЕ", "sync_status_failed_connect": "ОТКЛЮЧЕНО", "sync_status_not_connected": "НЕ ПОДКЛЮЧЁН", + "sync_status_starting_scan": "Начальное сканирование", "sync_status_starting_sync": "НАЧАЛО СИНХРОНИЗАЦИИ", "sync_status_syncronized": "СИНХРОНИЗИРОВАН", "sync_status_syncronizing": "СИНХРОНИЗАЦИЯ", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 996472f47..a178d2452 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "กำลังเชื่อมต่อ", "sync_status_failed_connect": "การเชื่อมต่อล้มเหลว", "sync_status_not_connected": "ไม่ได้เชื่อมต่อ", + "sync_status_starting_scan": "เริ่มการสแกน", "sync_status_starting_sync": "กำลังเริ่มซิงโครไนซ์", "sync_status_syncronized": "ซิงโครไนซ์แล้ว", "sync_status_syncronizing": "กำลังซิงโครไนซ์", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 27e4974bb..f49d3ddee 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "Pagkonekta", "sync_status_failed_connect": "Naka -disconnect", "sync_status_not_connected": "HINDI KONEKTADO", + "sync_status_starting_scan": "Simula sa pag -scan", "sync_status_starting_sync": "Simula sa pag -sync", "sync_status_syncronized": "Naka -synchronize", "sync_status_syncronizing": "Pag -synchronize", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 74b72581e..c73765f64 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "BAĞLANILIYOR", "sync_status_failed_connect": "BAĞLANTI KESİLDİ", "sync_status_not_connected": "BAĞLI DEĞİL", + "sync_status_starting_scan": "Başlangıç taraması", "sync_status_starting_sync": "SENKRONİZE BAŞLATILIYOR", "sync_status_syncronized": "SENKRONİZE EDİLDİ", "sync_status_syncronizing": "SENKRONİZE EDİLİYOR", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 74b2e4703..d088dd1b2 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "ПІДКЛЮЧЕННЯ", "sync_status_failed_connect": "ВІДКЛЮЧЕНО", "sync_status_not_connected": "НЕ ПІДКЛЮЧЕННИЙ", + "sync_status_starting_scan": "Початок сканування", "sync_status_starting_sync": "ПОЧАТОК СИНХРОНІЗАЦІЇ", "sync_status_syncronized": "СИНХРОНІЗОВАНИЙ", "sync_status_syncronizing": "СИНХРОНІЗАЦІЯ", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 35d024188..0694463de 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -697,6 +697,7 @@ "sync_status_connecting": "جڑ رہا ہے۔", "sync_status_failed_connect": "منقطع", "sync_status_not_connected": "منسلک نہیں", + "sync_status_starting_scan": "اسکین شروع کرنا", "sync_status_starting_sync": "مطابقت پذیری شروع کر رہا ہے۔", "sync_status_syncronized": "مطابقت پذیر", "sync_status_syncronizing": "مطابقت پذیری", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 29b8d9b71..87df87aca 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -696,6 +696,7 @@ "sync_status_connecting": "Ń DÁRAPỌ̀ MỌ́", "sync_status_failed_connect": "ÌKÀNPỌ̀ TI KÚ", "sync_status_not_connected": "KÒ TI DÁRAPỌ̀ MỌ́ Ọ", + "sync_status_starting_scan": "Bibẹrẹ ọlọjẹ", "sync_status_starting_sync": "Ń BẸ̀RẸ̀ RẸ́", "sync_status_syncronized": "TI MÚDỌ́GBA", "sync_status_syncronizing": "Ń MÚDỌ́GBA", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index a30acad70..89eca2073 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -695,6 +695,7 @@ "sync_status_connecting": "连接中", "sync_status_failed_connect": "断线", "sync_status_not_connected": "未连接", + "sync_status_starting_scan": "开始扫描", "sync_status_starting_sync": "开始同步", "sync_status_syncronized": "已同步", "sync_status_syncronizing": "正在同步", diff --git a/tool/configure.dart b/tool/configure.dart index 8b5af92b2..c37946476 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -94,12 +94,11 @@ import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter/ledger_flutter.dart'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bip39/bip39.dart' as bip39; """; const bitcoinCWHeaders = """ import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; From 88a57c1541e06264a3f690c4d83bb8918e29621d Mon Sep 17 00:00:00 2001 From: Omar Hatem <omarh.ismail1@gmail.com> Date: Mon, 12 Aug 2024 02:53:13 +0300 Subject: [PATCH 19/19] V4.19.2 v1.16.2 (#1591) * refactor: remove bitcoin_flutter, update deps, electrs node improvements * feat: connecting/disconnecting improvements, fix rescan by date, scanning message * chore: print * Update pubspec.yaml * Update pubspec.yaml * handle null sockets, retry connection on connect failure * fix imports * update app versions * fix transaction history * fix RBF * update android build number [skip ci] --------- Co-authored-by: Rafael Saes <git@rafael.saes.dev> Co-authored-by: Matthew Fosse <matt@fosse.co> --- assets/text/Monerocom_Release_Notes.txt | 5 +++-- assets/text/Release_Notes.txt | 9 +++++---- scripts/android/app_env.sh | 8 ++++---- scripts/ios/app_env.sh | 8 ++++---- scripts/macos/app_env.sh | 8 ++++---- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 2a6c07abe..c90d54524 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,4 @@ -Monero enhancements -Synchronization improvements +Monero synchronization improvements +Enhance error handling +UI enhancements Bug fixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index d17a22c84..34bca2e5e 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,5 +1,6 @@ -Monero and Ethereum enhancements -Synchronization improvements -Exchange flow enhancements -Ledger improvements +Wallets enhancements +Monero synchronization improvements +Improve wallet backups +Enhance error handling +UI enhancements Bug fixes \ No newline at end of file diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index a270afab0..35444dcd5 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.16.1" -MONERO_COM_BUILD_NUMBER=95 +MONERO_COM_VERSION="1.16.2" +MONERO_COM_BUILD_NUMBER=96 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.19.1" -CAKEWALLET_BUILD_NUMBER=221 +CAKEWALLET_VERSION="4.19.2" +CAKEWALLET_BUILD_NUMBER=223 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 22daba5de..30573035a 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.16.1" -MONERO_COM_BUILD_NUMBER=93 +MONERO_COM_VERSION="1.16.2" +MONERO_COM_BUILD_NUMBER=94 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.19.1" -CAKEWALLET_BUILD_NUMBER=256 +CAKEWALLET_VERSION="4.19.2" +CAKEWALLET_BUILD_NUMBER=261 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index d46900405..2f6d51a93 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,13 +16,13 @@ if [ -n "$1" ]; then fi MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.1" -MONERO_COM_BUILD_NUMBER=26 +MONERO_COM_VERSION="1.6.2" +MONERO_COM_BUILD_NUMBER=27 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.12.1" -CAKEWALLET_BUILD_NUMBER=82 +CAKEWALLET_VERSION="1.12.2" +CAKEWALLET_BUILD_NUMBER=83 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then