diff --git a/.gitignore b/.gitignore index 597ed4be4..b61c79144 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ # is commented out by default. #.vscode/ +#CppWinRT manual install +Microsoft.Windows* + # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ @@ -29,6 +32,7 @@ .pub-cache/ .pub/ /build/ +android/app/.cxx # Web related lib/generated_plugin_registrant.dart @@ -58,8 +62,6 @@ coverage scripts/**/build /lib/external_api_keys.dart -libcw_monero.dll -libcw_wownero.dll libepic_cash_wallet.dll libmobileliblelantus.dll libtor_ffi.dll @@ -69,6 +71,10 @@ secp256k1.dll /lib/app_config.g.dart /android/app/src/main/app_icon-playstore.png +# Dart generated files (Freezed, Riverpod, GoRouter etc..) +lib/**/*.g.dart +lib/**/*.freezed.dart + ## other generated project files pubspec.yaml @@ -105,3 +111,4 @@ scripts/linux/build/libsecret/subprojects/gi-docgen/.meson-subproject-wrap-hash. crypto_plugins/cs_monero/built_outputs crypto_plugins/cs_monero/build +crypto_plugins/*.diff diff --git a/android/gradle.properties b/android/gradle.properties index 7803bf46c..24863d218 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3c85cfe05..afa1e8eb0 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index a38bba1fb..02fb0cfb0 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.6.0' apply false + id "com.android.application" version '8.7.0' apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/asset_sources/default_themes/stack_duo/dark.zip b/asset_sources/default_themes/stack_duo/dark.zip index 1e5f6136e..8b31f4278 100644 Binary files a/asset_sources/default_themes/stack_duo/dark.zip and b/asset_sources/default_themes/stack_duo/dark.zip differ diff --git a/asset_sources/default_themes/stack_duo/light.zip b/asset_sources/default_themes/stack_duo/light.zip index bc7a95771..120573ccf 100644 Binary files a/asset_sources/default_themes/stack_duo/light.zip and b/asset_sources/default_themes/stack_duo/light.zip differ diff --git a/asset_sources/default_themes/stack_wallet/dark.zip b/asset_sources/default_themes/stack_wallet/dark.zip index df24199c4..8b31f4278 100644 Binary files a/asset_sources/default_themes/stack_wallet/dark.zip and b/asset_sources/default_themes/stack_wallet/dark.zip differ diff --git a/asset_sources/default_themes/stack_wallet/light.zip b/asset_sources/default_themes/stack_wallet/light.zip index 9e0c82d9d..120573ccf 100644 Binary files a/asset_sources/default_themes/stack_wallet/light.zip and b/asset_sources/default_themes/stack_wallet/light.zip differ diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 0bb1b1ced..25e6cb3a3 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 0bb1b1ced6e0d3c66e383698f89825754c692986 +Subproject commit 25e6cb3a3e7bee04e425af6beccb47e8d0708fdb diff --git a/crypto_plugins/flutter_liblelantus b/crypto_plugins/flutter_liblelantus index 5b08645a5..7b325030b 160000 --- a/crypto_plugins/flutter_liblelantus +++ b/crypto_plugins/flutter_liblelantus @@ -1 +1 @@ -Subproject commit 5b08645a5b5d30955f4bde2a624ff89ef516e452 +Subproject commit 7b325030bce46a423aa46497d1a608b7a8a58976 diff --git a/crypto_plugins/frostdart b/crypto_plugins/frostdart index 2451deab8..6f1310ecc 160000 --- a/crypto_plugins/frostdart +++ b/crypto_plugins/frostdart @@ -1 +1 @@ -Subproject commit 2451deab817b456ad93d5579c0d0687cb681392a +Subproject commit 6f1310eccd336fb3c8dc00b61e39a3f0f3a2b59a diff --git a/docs/building.md b/docs/building.md index 492639b05..4ba1b7150 100644 --- a/docs/building.md +++ b/docs/building.md @@ -13,12 +13,12 @@ Here you will find instructions on how to install the necessary tools for buildi The following instructions are for building and running on a Linux host. Alternatively, see the [Mac](#mac-host) and/or [Windows](#windows-host) section. This entire section (except for the Android Studio section) needs to be completed in WSL if building on a Windows host. ### Flutter -Install Flutter 3.24.3 by [following their guide](https://docs.flutter.dev/get-started/install/linux/desktop?tab=download#install-the-flutter-sdk). You can also clone https://github.com/flutter/flutter, check out the `3.24.3` tag, and add its `flutter/bin` folder to your PATH as in +Install Flutter 3.29.2 by [following their guide](https://docs.flutter.dev/get-started/install/linux/desktop?tab=download#install-the-flutter-sdk). You can also clone https://github.com/flutter/flutter, check out the `3.29.2` tag, and add its `flutter/bin` folder to your PATH as in ```sh FLUTTER_DIR="$HOME/development/flutter" git clone https://github.com/flutter/flutter.git "$FLUTTER_DIR" cd "$FLUTTER_DIR" -git checkout 3.24.3 +git checkout 3.29.2 echo 'export PATH="$PATH:'"$FLUTTER_DIR"'/bin"' >> "$HOME/.profile" source "$HOME/.profile" flutter precache @@ -38,7 +38,7 @@ Use `Tools > SDK Manager` to install: - `SDK Tools > Android SDK command line tools` - `SDK Tools > CMake` and for Android builds, - - `SDK Tools > Android SDK (API 30)` + - `SDK Tools > Android SDK (API 35)` - `SDK Tools > NDK` Then in `File > Settings > Plugins`, install the **Flutter** and **Dart** plugins and restart the IDE. In `File > Settings > Languages & Frameworks > Flutter > Editor`, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`). Run `flutter doctor` to install any missing dependencies and review and agree to any license agreements. @@ -58,7 +58,7 @@ sudo apt-get install libssl-dev curl unzip automake build-essential file pkg-con For Ubuntu 20.04, ``` -sudo apt-get install vapigen +sudo apt-get install valac pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 ``` @@ -68,20 +68,13 @@ sudo apt install pipx libgcrypt20-dev libglib2.0-dev libsecret-1-dev pipx install meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 ``` -Install `libtinfo5` (required by [monero_c](https://github.com/MrCyjaneK/monero_c), should be dropped in the future): -``` -wget http://mirrors.kernel.org/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2ubuntu0.1_amd64.deb -O libtinfo5.deb \ - && apt install ./libtinfo5.deb \ - && rm libtinfo5.deb -``` - -Install [Rust](https://www.rust-lang.org/tools/install) via [rustup.rs](https://rustup.rs), the required Rust toolchains, and `cargo-ndk 2.12.7` with command: +Install [Rust](https://www.rust-lang.org/tools/install) via [rustup.rs](https://rustup.rs), the required Rust toolchains, and `cargo-ndk` with command: ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.bashrc -rustup install 1.67.1 1.71.0 1.72.0 1.73.0 -rustup default 1.67.1 -cargo install cargo-ndk --version 2.12.7 --locked +rustup install 1.85.1 1.81.0 +rustup default 1.85.1 +cargo install cargo-ndk ``` Android specific dependencies: @@ -162,19 +155,6 @@ cd scripts cd scripts ./build_app.sh -a stack_wallet -p linux ``` - #### Building plugins and configure for Windows Install dependencies like MXE: @@ -229,13 +209,13 @@ brew install brotli cairo coreutils gdbm gettext glib gmp libevent libidn2 libng ``` -Download and install [Rust](https://www.rust-lang.org/tools/install). [Rustup](https://rustup.rs/) is recommended for Rust setup. Use `rustc` to confirm successful installation. Install toolchains 1.67.1 and 1.72.0 and `cbindgen` and `cargo-lipo` too. You will also have to add the platform target(s) `aarch64-apple-ios` and/or `aarch64-apple-darwin`. You can use the command(s): +Download and install [Rust](https://www.rust-lang.org/tools/install). [Rustup](https://rustup.rs/) is recommended for Rust setup. Use `rustc` to confirm successful installation. Install toolchains 1.81.0 and 1.85.1 and `cbindgen` and `cargo-lipo` too. You will also have to add the platform target(s) `aarch64-apple-ios` and/or `aarch64-apple-darwin`. You can use the command(s): ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.bashrc -rustup install 1.67.1 1.71.0 1.72.0 1.73.0 -rustup default 1.67.1 -cargo install cargo-ndk --version 2.12.7 --locked +rustup install 1.85.1 1.81.0 +rustup default 1.85.1 +cargo install cargo-ndk cargo install cbindgen cargo-lipo rustup target add aarch64-apple-ios aarch64-apple-darwin ``` @@ -243,7 +223,7 @@ rustup target add aarch64-apple-ios aarch64-apple-darwin Optionally download [Android Studio](https://developer.android.com/studio) as an IDE and activate its Dart and Flutter plugins. VS Code may work as an alternative, but this is not recommended. ### Flutter -Install [Flutter](https://docs.flutter.dev/get-started/install) 3.24.3 on your Mac host by following [these instructions](https://docs.flutter.dev/get-started/install/macos). Run `flutter doctor` in a terminal to confirm its installation. +Install [Flutter](https://docs.flutter.dev/get-started/install) 3.29.2 on your Mac host by following [these instructions](https://docs.flutter.dev/get-started/install/macos). Run `flutter doctor` in a terminal to confirm its installation. ### Build plugins and configure #### Building plugins for iOS @@ -304,22 +284,19 @@ If the DLLs were built on the WSL filesystem instead of on Windows, copy the res - `stack_wallet/crypto_plugins/flutter_libepiccash/scripts/windows/build/libepic_cash_wallet.dll` - `stack_wallet/crypto_plugins/flutter_liblelantus/scripts/windows/build/libmobileliblelantus.dll` - + Frostdart will be built by the Windows host later. ### Install Flutter on Windows host -Install Flutter 3.24.3 on your Windows host (not in WSL2) by [following their guide](https://docs.flutter.dev/get-started/install/windows/desktop?tab=download#install-the-flutter-sdk) or by cloning https://github.com/flutter/flutter, checking out the `3.24.3` tag, and adding its `flutter/bin` folder to your PATH as in +Install Flutter 3.29.2 on your Windows host (not in WSL2) by [following their guide](https://docs.flutter.dev/get-started/install/windows/desktop?tab=download#install-the-flutter-sdk) or by cloning https://github.com/flutter/flutter, checking out the `3.29.2` tag, and adding its `flutter/bin` folder to your PATH as in ```bat @echo off set "FLUTTER_DIR=%USERPROFILE%\development\flutter" git clone https://github.com/flutter/flutter.git "%FLUTTER_DIR%" cd /d "%FLUTTER_DIR%" -git checkout 3.24.3 +git checkout 3.29.2 setx PATH "%PATH%;%FLUTTER_DIR%\bin" echo Flutter setup completed. Please restart your command prompt. ``` @@ -329,9 +306,9 @@ Run `flutter doctor` in PowerShell to confirm its installation. ### Rust Install [Rust](https://www.rust-lang.org/tools/install) on the Windows host (not in WSL2). Download the installer from [rustup.rs](https://rustup.rs), make sure it works on the commandline (you may need to open a new terminal), and install the following versions: ``` -rustup install 1.67.1 1.71.0 1.72.0 1.73.0 -rustup default 1.67.1 -cargo install cargo-ndk --version 2.12.7 --locked +rustup install 1.85.1 1.81.0 +rustup default 1.85.1 +cargo install cargo-ndk ``` ### Windows SDK and Developer Mode diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 000000000..899a0343b --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1 @@ +package_name("com.cypherstack.stackwallet") diff --git a/fastlane/metadata/android/en-US/changelogs/.gitkeep b/fastlane/metadata/android/en-US/changelogs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 000000000..1f7ac4b00 --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,11 @@ +Stack Wallet is a fully open source cryptocurrency wallet. With an easy to use user interface and quick and speedy transactions, this wallet is ideal for anyone no matter how much they know about the cryptocurrency space. The app is actively maintained to provide new user friendly features. + +Highlights include: +- 10 Different cryptocurrencies +- All private keys and seeds stay on device and are never shared. +- Easy backup and restore feature to save all the information that's important to you. +- Trading cryptocurrencies through our partners. +- Custom address book +- Favorite wallets with fast syncing +- Custom Nodes. +- Open source software. diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 000000000..f3597e4f6 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05a1aa12278525211a470eb8a4636ea21eac6172836117bc05b5446c25008abe.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05a1aa12278525211a470eb8a4636ea21eac6172836117bc05b5446c25008abe.png new file mode 100644 index 000000000..b06b0c5da Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/05a1aa12278525211a470eb8a4636ea21eac6172836117bc05b5446c25008abe.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1f346858134354959f6d0c4c7776245b125d92235b3d0190f92c44616dc8a509.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1f346858134354959f6d0c4c7776245b125d92235b3d0190f92c44616dc8a509.png new file mode 100644 index 000000000..e83eac3e2 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1f346858134354959f6d0c4c7776245b125d92235b3d0190f92c44616dc8a509.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/29bb7d3c55248043cdcb1db7528c01b3c8bd329b85e1de1e1cc9467a1885bd26.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/29bb7d3c55248043cdcb1db7528c01b3c8bd329b85e1de1e1cc9467a1885bd26.png new file mode 100644 index 000000000..e397b26d6 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/29bb7d3c55248043cdcb1db7528c01b3c8bd329b85e1de1e1cc9467a1885bd26.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/337c2f1ca500347ade5d2b42ace7f3d72455acbe8a200995789c8e1c6d1a1c38.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/337c2f1ca500347ade5d2b42ace7f3d72455acbe8a200995789c8e1c6d1a1c38.png new file mode 100644 index 000000000..3aa7b86ce Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/337c2f1ca500347ade5d2b42ace7f3d72455acbe8a200995789c8e1c6d1a1c38.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6e64d5cfe73fc22c796f621e9caa35b44799fde6ae444b93f9d1d4eaa656ea82.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6e64d5cfe73fc22c796f621e9caa35b44799fde6ae444b93f9d1d4eaa656ea82.png new file mode 100644 index 000000000..5acd5e8db Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6e64d5cfe73fc22c796f621e9caa35b44799fde6ae444b93f9d1d4eaa656ea82.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/990f447807ee10406e8992b6b86653308f97b95e602b7080df0138a3973a971a.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/990f447807ee10406e8992b6b86653308f97b95e602b7080df0138a3973a971a.png new file mode 100644 index 000000000..b00ab681d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/990f447807ee10406e8992b6b86653308f97b95e602b7080df0138a3973a971a.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/9bf57ffd707362f7780534786ec909a7bf3077c54bacc5e20631edcc3e2435db.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/9bf57ffd707362f7780534786ec909a7bf3077c54bacc5e20631edcc3e2435db.png new file mode 100644 index 000000000..d96bd3e97 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/9bf57ffd707362f7780534786ec909a7bf3077c54bacc5e20631edcc3e2435db.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 000000000..fa838d5bd --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +An open source, non-custodial cryptocurrency wallet. diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4ca2f124b..add8f4224 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -9,7 +9,7 @@ PODS: - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift - - cs_monero_flutter_libs (0.0.1): + - cs_monero_flutter_libs_ios (0.0.1): - Flutter - device_info_plus (0.0.1): - Flutter @@ -87,8 +87,6 @@ PODS: - "sqlite3 (3.46.0+1)": - "sqlite3/common (= 3.46.0+1)" - "sqlite3/common (3.46.0+1)" - - "sqlite3/dbstatvtab (3.46.0+1)": - - sqlite3/common - "sqlite3/fts5 (3.46.0+1)": - sqlite3/common - "sqlite3/perf-threadsafe (3.46.0+1)": @@ -97,8 +95,7 @@ PODS: - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - - "sqlite3 (~> 3.46.0+1)" - - sqlite3/dbstatvtab + - sqlite3 (~> 3.46.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree @@ -112,12 +109,14 @@ PODS: - Flutter - wakelock_plus (0.0.1): - Flutter + - xelis_flutter (0.0.1): + - Flutter DEPENDENCIES: - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - coinlib_flutter (from `.symlinks/plugins/coinlib_flutter/darwin`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - - cs_monero_flutter_libs (from `.symlinks/plugins/cs_monero_flutter_libs/ios`) + - cs_monero_flutter_libs_ios (from `.symlinks/plugins/cs_monero_flutter_libs_ios/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) @@ -141,6 +140,7 @@ DEPENDENCIES: - tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) + - xelis_flutter (from `.symlinks/plugins/xelis_flutter/ios`) SPEC REPOS: trunk: @@ -160,8 +160,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/coinlib_flutter/darwin" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" - cs_monero_flutter_libs: - :path: ".symlinks/plugins/cs_monero_flutter_libs/ios" + cs_monero_flutter_libs_ios: + :path: ".symlinks/plugins/cs_monero_flutter_libs_ios/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" devicelocale: @@ -208,17 +208,19 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" wakelock_plus: :path: ".symlinks/plugins/wakelock_plus/ios" + xelis_flutter: + :path: ".symlinks/plugins/xelis_flutter/ios" SPEC CHECKSUMS: barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0 coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5 connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a - cs_monero_flutter_libs: 43cda3474c2bc907f2b2b5bb26fd89cb864fcfc6 + cs_monero_flutter_libs_ios: fd353631682247f72a36493ff060d4328d6f720d device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 + file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_libepiccash: 36241aa7d3126f6521529985ccb3dc5eaf7bb317 flutter_libsparkmobile: 6373955cc3327a926d17059e7405dde2fb12f99f @@ -231,14 +233,14 @@ SPEC CHECKSUMS: lelantus: 417f0221260013dfc052cae9cf4b741b6479edba local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630 - sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b + sqlite3_flutter_libs: 0d611efdf6d1c9297d5ab03dab21b75aeebdae31 stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03 SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 @@ -248,4 +250,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5e31d3d34..c53e2b314 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -48,6 +48,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index fcf632669..e18c4b720 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -29,6 +29,8 @@ import '../utilities/constants.dart'; import '../utilities/flutter_secure_storage_interface.dart'; import '../utilities/logger.dart'; import '../utilities/prefs.dart'; +import '../utilities/stack_file_system.dart'; +import '../utilities/util.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; import 'hive/db.dart'; import 'isar/main_db.dart'; @@ -43,10 +45,7 @@ class DbVersionMigrator with WalletDB { // safe to skip to v11 for campfire fromVersion = 11; } - Logging.instance.log( - "Running migrate fromVersion $fromVersion", - level: LogLevel.Warning, - ); + Logging.instance.i("Running migrate fromVersion $fromVersion"); switch (fromVersion) { case 0: await DB.instance.hive.openBox(DB.boxNameAllWalletsData); @@ -100,12 +99,13 @@ class DbVersionMigrator with WalletDB { try { latestSetId = await client.getLelantusLatestCoinId(); - } catch (e) { + } catch (e, s) { // default to 2 for now latestSetId = 2; - Logging.instance.log( + Logging.instance.w( "Failed to fetch latest coin id during firo db migrate: $e \nUsing a default value of 2", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); } } @@ -144,7 +144,6 @@ class DbVersionMigrator with WalletDB { ), }); } - Logger.print("newcoins $coins", normalLength: false); await DB.instance.put( boxName: walletInfo.walletId, key: '_lelantus_coins', @@ -443,6 +442,20 @@ class DbVersionMigrator with WalletDB { // try to continue migrating return await migrate(13, secureStore: secureStore); + case 13: + // migrate + await _v13(secureStore); + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, + key: "hive_data_version", + value: 14, + ); + + // try to continue migrating + return await migrate(14, secureStore: secureStore); + default: // finally return return; @@ -734,4 +747,31 @@ class DbVersionMigrator with WalletDB { ); } } + + Future _v13(SecureStorageInterface secureStore) async { + if (!(Util.isArmLinux || Util.isTestEnv)) { + // open logs db + final isar = await Isar.open( + [isar_models.LogSchema], + directory: (await StackFileSystem.applicationIsarDirectory()).path, + inspector: false, + maxSizeMiB: 512, + ); + + // fetch all logs + final allLogs = await isar.logs.where().findAll(); + + // migrate to simple file based logs. Date/time may be out of order + for (final log in allLogs) { + Logging.instance.log( + log.logLevel.getLoggerLevel(), + "MIGRATED LOG::=> ${log.message}", + time: DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC), + ); + } + + // finally delete logs db + await isar.close(deleteFromDisk: true); + } + } } diff --git a/lib/db/hive/db.dart b/lib/db/hive/db.dart index c71826fbc..0e9449094 100644 --- a/lib/db/hive/db.dart +++ b/lib/db/hive/db.dart @@ -166,9 +166,10 @@ class DB { AppConfig.getCryptoCurrencyFor(jsonObject["coin"] as String); return false; } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Error, ${jsonObject["coin"]} does not exist, $name wallet cannot be loaded", - level: LogLevel.Error, + error: e, + stackTrace: s, ); return true; } @@ -343,7 +344,7 @@ class DB { await DB.instance.deleteBoxFromDisk(boxName: "theme"); return true; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("$e $s", error: e, stackTrace: s); return false; } } diff --git a/lib/db/sqlite/firo_cache.dart b/lib/db/sqlite/firo_cache.dart index eac511aaa..c2ebbfe66 100644 --- a/lib/db/sqlite/firo_cache.dart +++ b/lib/db/sqlite/firo_cache.dart @@ -2,13 +2,13 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; -import 'package:flutter/foundation.dart'; import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart'; import 'package:mutex/mutex.dart'; import 'package:sqlite3/sqlite3.dart'; import 'package:uuid/uuid.dart'; import '../../electrumx_rpc/electrumx_client.dart'; +import '../../models/electrumx_response/spark_models.dart'; import '../../utilities/extensions/extensions.dart'; import '../../utilities/logger.dart'; import '../../utilities/stack_file_system.dart'; @@ -19,18 +19,8 @@ part 'firo_cache_reader.dart'; part 'firo_cache_worker.dart'; part 'firo_cache_writer.dart'; -/// Temporary debugging log function for this file -void _debugLog(Object? object) { - if (kDebugMode) { - Logging.instance.log( - object, - level: LogLevel.Debug, - ); - } -} - abstract class _FiroCache { - static const int _setCacheVersion = 1; + static const int _setCacheVersion = 2; static const int _tagsCacheVersion = 2; static final networks = [ @@ -115,7 +105,8 @@ abstract class _FiroCache { VACUUM; """, ); - _debugLog( + + Logging.instance.d( "_deleteAllCache() " "duration = ${DateTime.now().difference(start)}", ); @@ -134,7 +125,7 @@ abstract class _FiroCache { blockHash TEXT NOT NULL, setHash TEXT NOT NULL, groupId INTEGER NOT NULL, - timestampUTC INTEGER NOT NULL, + size INTEGER NOT NULL, UNIQUE (blockHash, setHash, groupId) ); @@ -143,7 +134,8 @@ abstract class _FiroCache { serialized TEXT NOT NULL, txHash TEXT NOT NULL, context TEXT NOT NULL, - UNIQUE(serialized, txHash, context) + groupId INTEGER NOT NULL, + UNIQUE(serialized, txHash, context, groupId) ); CREATE TABLE SparkSetCoins ( diff --git a/lib/db/sqlite/firo_cache_coordinator.dart b/lib/db/sqlite/firo_cache_coordinator.dart index fe720f804..6adeba520 100644 --- a/lib/db/sqlite/firo_cache_coordinator.dart +++ b/lib/db/sqlite/firo_cache_coordinator.dart @@ -6,6 +6,8 @@ typedef LTagPair = ({String tag, String txid}); /// background isolate and [FiroCacheCoordinator] should manage that isolate abstract class FiroCacheCoordinator { static final Map _workers = {}; + static final Map _tagLocks = {}; + static final Map _setLocks = {}; static bool _init = false; static Future init() async { @@ -15,6 +17,8 @@ abstract class FiroCacheCoordinator { _init = true; await _FiroCache.init(); for (final network in _FiroCache.networks) { + _tagLocks[network] = Mutex(); + _setLocks[network] = Mutex(); _workers[network] = await _FiroCacheWorker.spawn(network); } } @@ -31,11 +35,17 @@ abstract class FiroCacheCoordinator { final usedTagsCacheFile = File( "${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}", ); - final int bytes = - ((await setCacheFile.exists()) ? await setCacheFile.length() : 0) + - ((await usedTagsCacheFile.exists()) - ? await usedTagsCacheFile.length() - : 0); + + final setSize = + (await setCacheFile.exists()) ? await setCacheFile.length() : 0; + final tagsSize = (await usedTagsCacheFile.exists()) + ? await usedTagsCacheFile.length() + : 0; + + Logging.instance.d("Spark cache used tags size: $tagsSize"); + Logging.instance.d("Spark cache anon set size: $setSize"); + + final int bytes = tagsSize + setSize; if (bytes < 1024) { return '$bytes B'; @@ -55,43 +65,93 @@ abstract class FiroCacheCoordinator { ElectrumXClient client, CryptoCurrencyNetwork network, ) async { - final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network); - final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes( - startNumber: count, - ); - if (unhashedTags.isNotEmpty) { - await _workers[network]!.runTask( - FCTask( - func: FCFuncName._updateSparkUsedTagsWith, - data: unhashedTags, - ), + await _tagLocks[network]!.protect(() async { + final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network); + final unhashedTags = + await client.getSparkUnhashedUsedCoinsTagsWithTxHashes( + startNumber: count, ); - } + if (unhashedTags.isNotEmpty) { + await _workers[network]!.runTask( + FCTask( + func: FCFuncName._updateSparkUsedTagsWith, + data: unhashedTags, + ), + ); + } + }); } static Future runFetchAndUpdateSparkAnonSetCacheForGroupId( int groupId, ElectrumXClient client, CryptoCurrencyNetwork network, + void Function(int countFetched, int totalCount)? progressUpdated, ) async { - final blockhashResult = - await FiroCacheCoordinator.getLatestSetInfoForGroupId( - groupId, - network, - ); - final blockHash = blockhashResult?.blockHash ?? ""; + await _setLocks[network]!.protect(() async { + const sectorSize = + 1500; // chosen as a somewhat decent value. Could be changed in the future if wanted/needed + final prevMeta = await FiroCacheCoordinator.getLatestSetInfoForGroupId( + groupId, + network, + ); - final json = await client.getSparkAnonymitySet( - coinGroupId: groupId.toString(), - startBlockHash: blockHash.toHexReversedFromBase64, - ); + final prevSize = prevMeta?.size ?? 0; - await _workers[network]!.runTask( - FCTask( - func: FCFuncName._updateSparkAnonSetCoinsWith, - data: (groupId, json), - ), - ); + final meta = await client.getSparkAnonymitySetMeta( + coinGroupId: groupId, + ); + + progressUpdated?.call(prevSize, meta.size); + + if (prevMeta?.blockHash == meta.blockHash) { + Logging.instance.d("prevMeta?.blockHash == meta.blockHash"); + return; + } + + final numberOfCoinsToFetch = meta.size - prevSize; + + final fullSectorCount = numberOfCoinsToFetch ~/ sectorSize; + final remainder = numberOfCoinsToFetch % sectorSize; + + final List coins = []; + + for (int i = 0; i < fullSectorCount; i++) { + final start = (i * sectorSize); + final data = await client.getSparkAnonymitySetBySector( + coinGroupId: groupId, + latestBlock: meta.blockHash, + startIndex: start, + endIndex: start + sectorSize, + ); + progressUpdated?.call(start + sectorSize, numberOfCoinsToFetch); + + coins.addAll(data); + } + + if (remainder > 0) { + final data = await client.getSparkAnonymitySetBySector( + coinGroupId: groupId, + latestBlock: meta.blockHash, + startIndex: numberOfCoinsToFetch - remainder, + endIndex: numberOfCoinsToFetch, + ); + progressUpdated?.call(numberOfCoinsToFetch, numberOfCoinsToFetch); + + coins.addAll(data); + } + + final result = coins + .map((e) => RawSparkCoin.fromRPCResponse(e as List, groupId)) + .toList(); + + await _workers[network]!.runTask( + FCTask( + func: FCFuncName._updateSparkAnonSetCoinsWith, + data: (meta, result), + ), + ); + }); } // =========================================================================== @@ -165,28 +225,29 @@ abstract class FiroCacheCoordinator { ); } - static Future< - List< - ({ - String serialized, - String txHash, - String context, - })>> getSetCoinsForGroupId( + static Future> getSetCoinsForGroupId( int groupId, { - int? newerThanTimeStamp, + String? afterBlockHash, required CryptoCurrencyNetwork network, }) async { - final resultSet = await _Reader._getSetCoinsForGroupId( - groupId, - db: _FiroCache.setCacheDB(network), - newerThanTimeStamp: newerThanTimeStamp, - ); + final resultSet = afterBlockHash == null + ? await _Reader._getSetCoinsForGroupId( + groupId, + db: _FiroCache.setCacheDB(network), + ) + : await _Reader._getSetCoinsForGroupIdAndBlockHash( + groupId, + afterBlockHash, + db: _FiroCache.setCacheDB(network), + ); + return resultSet .map( - (row) => ( + (row) => RawSparkCoin( serialized: row["serialized"] as String, txHash: row["txHash"] as String, context: row["context"] as String, + groupId: groupId, ), ) .toList() @@ -194,12 +255,7 @@ abstract class FiroCacheCoordinator { .toList(); } - static Future< - ({ - String blockHash, - String setHash, - int timestampUTC, - })?> getLatestSetInfoForGroupId( + static Future getLatestSetInfoForGroupId( int groupId, CryptoCurrencyNetwork network, ) async { @@ -212,10 +268,11 @@ abstract class FiroCacheCoordinator { return null; } - return ( + return SparkAnonymitySetMeta( + coinGroupId: groupId, blockHash: result.first["blockHash"] as String, setHash: result.first["setHash"] as String, - timestampUTC: result.first["timestampUTC"] as int, + size: result.first["size"] as int, ); } diff --git a/lib/db/sqlite/firo_cache_reader.dart b/lib/db/sqlite/firo_cache_reader.dart index 33763ba3e..67fea7764 100644 --- a/lib/db/sqlite/firo_cache_reader.dart +++ b/lib/db/sqlite/firo_cache_reader.dart @@ -8,21 +8,15 @@ abstract class _Reader { static Future _getSetCoinsForGroupId( int groupId, { required Database db, - int? newerThanTimeStamp, }) async { - String query = """ - SELECT sc.serialized, sc.txHash, sc.context + final query = """ + SELECT sc.serialized, sc.txHash, sc.context, sc.groupId FROM SparkSet AS ss JOIN SparkSetCoins AS ssc ON ss.id = ssc.setId JOIN SparkCoin AS sc ON ssc.coinId = sc.id - WHERE ss.groupId = $groupId + WHERE ss.groupId = $groupId; """; - if (newerThanTimeStamp != null) { - query += " AND ss.timestampUTC" - " > $newerThanTimeStamp"; - } - return db.select("$query;"); } @@ -31,16 +25,45 @@ abstract class _Reader { required Database db, }) async { final query = """ - SELECT ss.blockHash, ss.setHash, ss.timestampUTC + SELECT ss.blockHash, ss.setHash, ss.size FROM SparkSet ss WHERE ss.groupId = $groupId - ORDER BY ss.timestampUTC DESC + ORDER BY ss.size DESC LIMIT 1; """; return db.select("$query;"); } + static Future _getSetCoinsForGroupIdAndBlockHash( + int groupId, + String blockHash, { + required Database db, + }) async { + const query = """ + WITH TargetBlock AS ( + SELECT id + FROM SparkSet + WHERE blockHash = ? + ), + TargetSets AS ( + SELECT id AS setId + FROM SparkSet + WHERE groupId = ? AND id > (SELECT id FROM TargetBlock) + ) + SELECT + SparkCoin.serialized, + SparkCoin.txHash, + SparkCoin.context, + SparkCoin.groupId + FROM SparkSetCoins + JOIN SparkCoin ON SparkSetCoins.coinId = SparkCoin.id + WHERE SparkSetCoins.setId IN (SELECT setId FROM TargetSets); + """; + + return db.select("$query;", [blockHash, groupId]); + } + static Future _checkSetInfoForGroupIdExists( int groupId, { required Database db, diff --git a/lib/db/sqlite/firo_cache_worker.dart b/lib/db/sqlite/firo_cache_worker.dart index 71e407992..abaceb288 100644 --- a/lib/db/sqlite/firo_cache_worker.dart +++ b/lib/db/sqlite/firo_cache_worker.dart @@ -48,7 +48,11 @@ class _FiroCacheWorker { try { await Isolate.spawn( _startWorkerIsolate, - (initPort.sendPort, setCacheFilePath, usedTagsCacheFilePath), + ( + initPort.sendPort, + setCacheFilePath, + usedTagsCacheFilePath, + ), ); } catch (_) { initPort.close(); @@ -90,7 +94,8 @@ class _FiroCacheWorker { final FCResult result; switch (task.func) { case FCFuncName._updateSparkAnonSetCoinsWith: - final data = task.data as (int, Map); + final data = + task.data as (SparkAnonymitySetMeta, List); result = _updateSparkAnonSetCoinsWith( setCacheDb, data.$2, diff --git a/lib/db/sqlite/firo_cache_writer.dart b/lib/db/sqlite/firo_cache_writer.dart index 99d318444..63192d964 100644 --- a/lib/db/sqlite/firo_cache_writer.dart +++ b/lib/db/sqlite/firo_cache_writer.dart @@ -52,29 +52,13 @@ FCResult _updateSparkUsedTagsWith( // ================== write to spark anon set cache ========================== /// update the sqlite cache -/// Expected json format: -/// { -/// "blockHash": "someBlockHash", -/// "setHash": "someSetHash", -/// "coins": [ -/// ["serliazed1", "hash1", "context1"], -/// ["serliazed2", "hash2", "context2"], -/// ... -/// ["serliazed3", "hash3", "context3"], -/// ["serliazed4", "hash4", "context4"], -/// ], -/// } /// /// returns true if successful, otherwise false FCResult _updateSparkAnonSetCoinsWith( Database db, - Map json, - int groupId, + final List coinsRaw, + SparkAnonymitySetMeta meta, ) { - final blockHash = json["blockHash"] as String; - final setHash = json["setHash"] as String; - final coinsRaw = json["coins"] as List; - if (coinsRaw.isEmpty) { // no coins to actually insert return FCResult(success: true); @@ -87,9 +71,9 @@ FCResult _updateSparkAnonSetCoinsWith( WHERE blockHash = ? AND setHash = ? AND groupId = ?; """, [ - blockHash, - setHash, - groupId, + meta.blockHash, + meta.setHash, + meta.coinGroupId, ], ); @@ -98,59 +82,28 @@ FCResult _updateSparkAnonSetCoinsWith( return FCResult(success: true); } - final coins = coinsRaw - .map( - (e) => [ - e[0] as String, - e[1] as String, - e[2] as String, - ], - ) - .toList() - .reversed; - - final timestamp = DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000; + final coins = coinsRaw.reversed; db.execute("BEGIN;"); try { db.execute( """ - INSERT INTO SparkSet (blockHash, setHash, groupId, timestampUTC) + INSERT INTO SparkSet (blockHash, setHash, groupId, size) VALUES (?, ?, ?, ?); """, - [blockHash, setHash, groupId, timestamp], + [meta.blockHash, meta.setHash, meta.coinGroupId, meta.size], ); final setId = db.lastInsertRowId; for (final coin in coins) { - int coinId; - try { - // try to insert and get row id - db.execute( - """ - INSERT INTO SparkCoin (serialized, txHash, context) - VALUES (?, ?, ?); + db.execute( + """ + INSERT INTO SparkCoin (serialized, txHash, context, groupId) + VALUES (?, ?, ?, ?); """, - coin, - ); - coinId = db.lastInsertRowId; - } on SqliteException catch (e) { - // if there already is a matching coin in the db - // just grab its row id - if (e.extendedResultCode == 2067) { - final result = db.select( - """ - SELECT id - FROM SparkCoin - WHERE serialized = ? AND txHash = ? AND context = ?; - """, - coin, - ); - coinId = result.first["id"] as int; - } else { - rethrow; - } - } + [coin.serialized, coin.txHash, coin.context, coin.groupId], + ); + final coinId = db.lastInsertRowId; // finally add the row id to the newly added set db.execute( diff --git a/lib/electrumx_rpc/cached_electrumx_client.dart b/lib/electrumx_rpc/cached_electrumx_client.dart index 8b1ff10c8..c3b8ab56a 100644 --- a/lib/electrumx_rpc/cached_electrumx_client.dart +++ b/lib/electrumx_rpc/cached_electrumx_client.dart @@ -100,17 +100,17 @@ class CachedElectrumXClient { } // save set to db await box.put(groupId, set); - Logging.instance.log( + Logging.instance.d( "Updated current anonymity set for ${cryptoCurrency.identifier} with group ID $groupId", - level: LogLevel.Info, ); } return set; } catch (e, s) { - Logging.instance.log( - "Failed to process CachedElectrumX.getAnonymitySet(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Failed to process CachedElectrumX.getAnonymitySet(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -155,16 +155,17 @@ class CachedElectrumXClient { await box.put(txHash, result); } - // Logging.instance.log("using fetched result", level: LogLevel.Info); + // Logging.instance.log("using fetched result"); return result; } else { - // Logging.instance.log("using cached result", level: LogLevel.Info); + // Logging.instance.log("using cached result"); return Map.from(cachedTx); } } catch (e, s) { - Logging.instance.log( - "Failed to process CachedElectrumX.getTransaction(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Failed to process CachedElectrumX.getTransaction(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -212,9 +213,10 @@ class CachedElectrumXClient { return resultingList; } catch (e, s) { - Logging.instance.log( - "Failed to process CachedElectrumX.getUsedCoinSerials(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Failed to process CachedElectrumX.getUsedCoinSerials(): ", + error: e, + stackTrace: s, ); rethrow; } diff --git a/lib/electrumx_rpc/client_manager.dart b/lib/electrumx_rpc/client_manager.dart index aea7e34e6..8cd9d5324 100644 --- a/lib/electrumx_rpc/client_manager.dart +++ b/lib/electrumx_rpc/client_manager.dart @@ -69,9 +69,10 @@ class ClientManager { _heightCompleters[key]!.complete(event.height); } }, - onError: (Object err, StackTrace s) => Logging.instance.log( - "ClientManager listen: $err\n$s", - level: LogLevel.Error, + onError: (Object err, StackTrace s) => Logging.instance.e( + "ClientManager listen", + error: err, + stackTrace: s, ), ); } diff --git a/lib/electrumx_rpc/electrumx_client.dart b/lib/electrumx_rpc/electrumx_client.dart index dff2b68fd..2691adf5a 100644 --- a/lib/electrumx_rpc/electrumx_client.dart +++ b/lib/electrumx_rpc/electrumx_client.dart @@ -21,6 +21,7 @@ import 'package:mutex/mutex.dart'; import 'package:stream_channel/stream_channel.dart'; import '../exceptions/electrumx/no_such_transaction.dart'; +import '../models/electrumx_response/spark_models.dart'; import '../services/event_bus/events/global/tor_connection_status_changed_event.dart'; import '../services/event_bus/events/global/tor_status_changed_event.dart'; import '../services/event_bus/global_event_bus.dart'; @@ -34,13 +35,6 @@ import '../wallets/crypto_currency/crypto_currency.dart'; import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart'; import 'client_manager.dart'; -typedef SparkMempoolData = ({ - String txid, - List serialContext, - List lTags, - List coins, -}); - class WifiOnlyException implements Exception {} class TorOnlyException implements Exception {} @@ -108,7 +102,7 @@ class ElectrumXClient { late Prefs _prefs; late TorService _torService; - List? failovers; + late final List _failovers; int currentFailoverIndex = -1; final Duration connectionTimeoutForSpecialCaseJsonRPCClients; @@ -145,6 +139,7 @@ class ElectrumXClient { _host = host; _port = port; _useSSL = useSSL; + _failovers = failovers; final bus = globalEventBusForTesting ?? GlobalEventBus.instance; @@ -238,10 +233,9 @@ class ElectrumXClient { if (!_prefs.torKillSwitch) { // Then we'll just proceed and connect to ElectrumX through // clearnet at the bottom of this function. - Logging.instance.log( + Logging.instance.w( "Tor preference set but Tor is not enabled, killswitch not set," " connecting to Electrum adapter through clearnet", - level: LogLevel.Warning, ); } else { // ... But if the killswitch is set, then we throw an exception. @@ -284,9 +278,11 @@ class ElectrumXClient { usePort = port; useUseSSL = useSSL; } else { - useHost = failovers![currentFailoverIndex].address; - usePort = failovers![currentFailoverIndex].port; - useUseSSL = failovers![currentFailoverIndex].useSSL; + _electrumAdapterChannel = null; + await ClientManager.sharedInstance.remove(cryptoCurrency: cryptoCurrency); + useHost = _failovers[currentFailoverIndex].address; + usePort = _failovers[currentFailoverIndex].port; + useUseSSL = _failovers[currentFailoverIndex].useSSL; } _electrumAdapterChannel ??= await electrum_adapter.connect( @@ -401,8 +397,17 @@ class ElectrumXClient { } else { rethrow; } - } catch (e) { - if (failovers != null && currentFailoverIndex < failovers!.length - 1) { + } catch (e, s) { + final errorMessage = e.toString(); + Logging.instance.w( + "$host $e", + error: e, + stackTrace: s, + ); + if (errorMessage.contains("JSON-RPC error")) { + currentFailoverIndex = _failovers.length; + } + if (currentFailoverIndex < _failovers.length - 1) { currentFailoverIndex++; return request( command: command, @@ -495,7 +500,7 @@ class ElectrumXClient { rethrow; } } catch (e) { - if (failovers != null && currentFailoverIndex < failovers!.length - 1) { + if (currentFailoverIndex < _failovers.length - 1) { currentFailoverIndex++; return batchRequest( command: command, @@ -528,14 +533,13 @@ class ElectrumXClient { return await request( requestID: requestID, command: 'server.ping', - requestTimeout: const Duration(seconds: 3), + requestTimeout: const Duration(seconds: 30), retries: retryCount, ).timeout( - const Duration(seconds: 3), + const Duration(seconds: 30), onTimeout: () { - Logging.instance.log( + Logging.instance.d( "ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host", - level: LogLevel.Debug, ); }, ) as bool; @@ -560,10 +564,7 @@ class ElectrumXClient { command: 'blockchain.headers.subscribe', ); if (response == null) { - Logging.instance.log( - "getBlockHeadTip returned null response", - level: LogLevel.Error, - ); + Logging.instance.e("getBlockHeadTip returned null response"); throw 'getBlockHeadTip returned null response'; } return Map.from(response as Map); @@ -754,14 +755,15 @@ class ElectrumXClient { try { final data = List>.from(response[i] as List); result.add(data); - } catch (e) { + } catch (e, s) { // to ensure we keep same length of responses as requests/args // add empty list on error result.add([]); - Logging.instance.log( + Logging.instance.e( "getBatchUTXOs failed to parse response=${response[i]}: $e", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } } @@ -824,15 +826,13 @@ class ElectrumXClient { bool verbose = true, String? requestID, }) async { - Logging.instance.log( + Logging.instance.d( "attempting to fetch blockchain.transaction.get...", - level: LogLevel.Info, ); await checkElectrumAdapter(); final dynamic response = await getElectrumAdapter()!.getTransaction(txHash); - Logging.instance.log( + Logging.instance.d( "Fetching blockchain.transaction.get finished", - level: LogLevel.Info, ); if (!verbose) { @@ -861,17 +861,15 @@ class ElectrumXClient { String blockhash = "", String? requestID, }) async { - Logging.instance.log( + Logging.instance.d( "attempting to fetch lelantus.getanonymityset...", - level: LogLevel.Info, ); await checkElectrumAdapter(); final Map response = await (getElectrumAdapter() as FiroElectrumClient) .getLelantusAnonymitySet(groupId: groupId, blockHash: blockhash); - Logging.instance.log( + Logging.instance.d( "Fetching lelantus.getanonymityset finished", - level: LogLevel.Info, ); return response; } @@ -884,16 +882,14 @@ class ElectrumXClient { dynamic mints, String? requestID, }) async { - Logging.instance.log( + Logging.instance.d( "attempting to fetch lelantus.getmintmetadata...", - level: LogLevel.Info, ); await checkElectrumAdapter(); final dynamic response = await (getElectrumAdapter() as FiroElectrumClient) .getLelantusMintData(mints: mints); - Logging.instance.log( + Logging.instance.d( "Fetching lelantus.getmintmetadata finished", - level: LogLevel.Info, ); return response; } @@ -904,9 +900,8 @@ class ElectrumXClient { String? requestID, required int startNumber, }) async { - Logging.instance.log( + Logging.instance.d( "attempting to fetch lelantus.getusedcoinserials...", - level: LogLevel.Info, ); await checkElectrumAdapter(); @@ -917,9 +912,8 @@ class ElectrumXClient { response = await (getElectrumAdapter() as FiroElectrumClient) .getLelantusUsedCoinSerials(startNumber: startNumber); // TODO add 2 minute timeout. - Logging.instance.log( + Logging.instance.d( "Fetching lelantus.getusedcoinserials finished", - level: LogLevel.Info, ); retryCount--; @@ -932,16 +926,14 @@ class ElectrumXClient { /// /// ex: 1 Future getLelantusLatestCoinId({String? requestID}) async { - Logging.instance.log( + Logging.instance.d( "attempting to fetch lelantus.getlatestcoinid...", - level: LogLevel.Info, ); await checkElectrumAdapter(); final int response = await (getElectrumAdapter() as FiroElectrumClient).getLatestCoinId(); - Logging.instance.log( + Logging.instance.d( "Fetching lelantus.getlatestcoinid finished", - level: LogLevel.Info, ); return response; } @@ -975,12 +967,11 @@ class ElectrumXClient { coinGroupId: coinGroupId, startBlockHash: startBlockHash, ); - Logging.instance.log( + Logging.instance.d( "Finished ElectrumXClient.getSparkAnonymitySet(coinGroupId" "=$coinGroupId, startBlockHash=$startBlockHash). " "coins.length: ${(response["coins"] as List?)?.length}" "Duration=${DateTime.now().difference(start)}", - level: LogLevel.Info, ); return response; } catch (e) { @@ -1018,7 +1009,7 @@ class ElectrumXClient { // ); // // return tags; - // } catch (e) { + // } catch (e, s) { // Logging.instance.log(e, level: LogLevel.Error); // rethrow; // } @@ -1034,29 +1025,30 @@ class ElectrumXClient { /// "b476ed2b374bb081ea51d111f68f0136252521214e213d119b8dc67b92f5a390", /// ] /// } - Future>> getSparkMintMetaData({ - String? requestID, - required List sparkCoinHashes, - }) async { - try { - Logging.instance.log( - "attempting to fetch spark.getsparkmintmetadata...", - level: LogLevel.Info, - ); - await checkElectrumAdapter(); - final List response = - await (getElectrumAdapter() as FiroElectrumClient) - .getSparkMintMetaData(sparkCoinHashes: sparkCoinHashes); - Logging.instance.log( - "Fetching spark.getsparkmintmetadata finished", - level: LogLevel.Info, - ); - return List>.from(response); - } catch (e) { - Logging.instance.log(e, level: LogLevel.Error); - rethrow; - } - } + /// NOT USED? + // Future>> getSparkMintMetaData({ + // String? requestID, + // required List sparkCoinHashes, + // }) async { + // try { + // Logging.instance.log( + // "attempting to fetch spark.getsparkmintmetadata...", + // level: LogLevel.Info, + // ); + // await checkElectrumAdapter(); + // final List response = + // await (getElectrumAdapter() as FiroElectrumClient) + // .getSparkMintMetaData(sparkCoinHashes: sparkCoinHashes); + // Logging.instance.log( + // "Fetching spark.getsparkmintmetadata finished", + // level: LogLevel.Info, + // ); + // return List>.from(response); + // } catch (e, s) { + // Logging.instance.log(e, level: LogLevel.Error); + // rethrow; + // } + // } /// Returns the latest Spark set id /// @@ -1065,20 +1057,22 @@ class ElectrumXClient { String? requestID, }) async { try { - Logging.instance.log( + Logging.instance.d( "attempting to fetch spark.getsparklatestcoinid...", - level: LogLevel.Info, ); await checkElectrumAdapter(); final int response = await (getElectrumAdapter() as FiroElectrumClient) .getSparkLatestCoinId(); - Logging.instance.log( + Logging.instance.d( "Fetching spark.getsparklatestcoinid finished", - level: LogLevel.Info, ); return response; - } catch (e) { - Logging.instance.log(e, level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e( + e, + error: e, + stackTrace: s, + ); rethrow; } } @@ -1098,15 +1092,18 @@ class ElectrumXClient { .map((e) => e.toHexReversedFromBase64) .toSet(); - Logging.instance.log( + Logging.instance.d( "Finished ElectrumXClient.getMempoolTxids(). " "Duration=${DateTime.now().difference(start)}", - level: LogLevel.Info, ); return txids; - } catch (e) { - Logging.instance.log(e, level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e( + e, + error: e, + stackTrace: s, + ); rethrow; } } @@ -1132,7 +1129,7 @@ class ElectrumXClient { final List result = []; for (final entry in map.entries) { result.add( - ( + SparkMempoolData( txid: entry.key, serialContext: List.from(entry.value["serial_context"] as List), @@ -1143,15 +1140,14 @@ class ElectrumXClient { ); } - Logging.instance.log( + Logging.instance.d( "Finished ElectrumXClient.getMempoolSparkData(txids: $txids). " "Duration=${DateTime.now().difference(start)}", - level: LogLevel.Info, ); return result; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); rethrow; } } @@ -1175,19 +1171,119 @@ class ElectrumXClient { final map = Map.from(response as Map); final tags = List>.from(map["tagsandtxids"] as List); - Logging.instance.log( + Logging.instance.d( "Finished ElectrumXClient.getSparkUnhashedUsedCoinsTagsWithTxHashes(" "startNumber=$startNumber). # of tags fetched=${tags.length}, " "Duration=${DateTime.now().difference(start)}", - level: LogLevel.Info, ); return tags; - } catch (e) { - Logging.instance.log(e, level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e( + e, + error: e, + stackTrace: s, + ); + rethrow; + } + } + // ======== New Paginated Endpoints ========================================== + + Future getSparkAnonymitySetMeta({ + String? requestID, + required int coinGroupId, + }) async { + try { + const command = "spark.getsparkanonymitysetmeta"; + Logging.instance.d( + "[${getElectrumAdapter()?.host}] => attempting to fetch $command...", + ); + + final start = DateTime.now(); + final response = await request( + requestID: requestID, + command: command, + args: [ + "$coinGroupId", + ], + ); + + final map = Map.from(response as Map); + + final result = SparkAnonymitySetMeta( + coinGroupId: coinGroupId, + blockHash: map["blockHash"] as String, + setHash: map["setHash"] as String, + size: map["size"] as int, + ); + + Logging.instance.d( + "Finished ElectrumXClient.getSparkAnonymitySetMeta(" + "requestID=$requestID, " + "coinGroupId=$coinGroupId" + "). Set meta=$result, " + "Duration=${DateTime.now().difference(start)}", + ); + + return result; + } catch (e, s) { + Logging.instance.e( + e, + error: e, + stackTrace: s, + ); + rethrow; + } + } + + Future> getSparkAnonymitySetBySector({ + String? requestID, + required int coinGroupId, + required String latestBlock, + required int startIndex, // inclusive + required int endIndex, // exclusive + }) async { + try { + const command = + "spark.getsparkanonymitysetsector"; // TODO verify this will be correct + final start = DateTime.now(); + final response = await request( + requestID: requestID, + command: command, + args: [ + "$coinGroupId", + latestBlock, + "$startIndex", + "$endIndex", + ], + ); + + final map = Map.from(response as Map); + + final result = map["coins"] as List; + + Logging.instance.d( + "Finished ElectrumXClient.getSparkAnonymitySetBySector(" + "requestID=$requestID, " + "coinGroupId=$coinGroupId, " + "latestBlock=$latestBlock, " + "startIndex=$startIndex, " + "endIndex=$endIndex" + "). # of coins=${result.length}, " + "Duration=${DateTime.now().difference(start)}", + ); + + return result; + } catch (e, s) { + Logging.instance.e( + e, + error: e, + stackTrace: s, + ); rethrow; } } + // =========================================================================== Future isMasterNodeCollateral({ @@ -1206,16 +1302,19 @@ class ElectrumXClient { ], ); - Logging.instance.log( + Logging.instance.d( "Finished ElectrumXClient.isMasterNodeCollateral, " "response: $response, " "Duration=${DateTime.now().difference(start)}", - level: LogLevel.Info, ); return response as bool; - } catch (e) { - Logging.instance.log(e, level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e( + e, + error: e, + stackTrace: s, + ); rethrow; } } @@ -1274,7 +1373,7 @@ class ElectrumXClient { } catch (e, s) { final String msg = "Error parsing fee rate. Response: $response" "\nResult: $response\nError: $e\nStack trace: $s"; - Logging.instance.log(msg, level: LogLevel.Fatal); + Logging.instance.e(msg, error: e, stackTrace: s); throw Exception(msg); } } catch (e) { diff --git a/lib/electrumx_rpc/subscribable_electrumx_client.dart b/lib/electrumx_rpc/subscribable_electrumx_client.dart index f06771906..8c3e1849b 100644 --- a/lib/electrumx_rpc/subscribable_electrumx_client.dart +++ b/lib/electrumx_rpc/subscribable_electrumx_client.dart @@ -503,7 +503,7 @@ // _responseHandler(response); // } catch (e, s) { // Logging.instance -// .log("JsonRPC jsonDecode: $e\n$s", level: LogLevel.Error); +// .log("JsonRPC jsonDecode", error: e, stackTrace: s,); // rethrow; // } finally { // _responseData = []; diff --git a/lib/main.dart b/lib/main.dart index e2cae6f72..ccf2ecf6e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,10 +21,13 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:isar/isar.dart'; import 'package:keyboard_dismisser/keyboard_dismisser.dart'; +import 'package:logger/logger.dart'; import 'package:path_provider/path_provider.dart'; import 'package:window_size/window_size.dart'; +import 'package:xelis_flutter/src/api/api.dart' as xelis_api; +import 'package:xelis_flutter/src/api/logger.dart' as xelis_logging; +import 'package:xelis_flutter/src/frb_generated.dart' as xelis_rust; import 'app_config.dart'; import 'db/db_version_migration.dart'; @@ -35,7 +38,6 @@ import 'db/sqlite/firo_cache.dart'; import 'models/exchange/change_now/exchange_transaction.dart'; import 'models/exchange/change_now/exchange_transaction_status.dart'; import 'models/exchange/response_objects/trade.dart'; -import 'models/isar/models/isar_models.dart'; import 'models/models.dart'; import 'models/node_model.dart'; import 'models/notification_model.dart'; @@ -56,8 +58,6 @@ import 'providers/global/base_currencies_provider.dart'; import 'providers/global/trades_service_provider.dart'; import 'providers/providers.dart'; import 'route_generator.dart'; -// import 'package:stackwallet/services/buy/buy_data_loading_service.dart'; -import 'services/debug_service.dart'; import 'services/exchange/exchange_data_loading_service.dart'; import 'services/locale_service.dart'; import 'services/node_service.dart'; @@ -75,15 +75,47 @@ import 'utilities/prefs.dart'; import 'utilities/stack_file_system.dart'; import 'utilities/util.dart'; import 'wallets/isar/providers/all_wallets_info_provider.dart'; +import 'wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import 'widgets/crypto_notifications.dart'; -final openedFromSWBFileStringStateProvider = - StateProvider((ref) => null); +final openedFromSWBFileStringStateProvider = StateProvider( + (ref) => null, +); + +void startListeningToRustLogs() { + xelis_api.createLogStream().listen( + (logEntry) { + final Level level; + switch (logEntry.level) { + case xelis_logging.Level.error: + level = Level.error; + case xelis_logging.Level.warn: + level = Level.warning; + case xelis_logging.Level.info: + level = Level.info; + case xelis_logging.Level.debug: + level = Level.debug; + case xelis_logging.Level.trace: + level = Level.trace; + } + + Logging.instance.log( + level, + "[Xelis Rust Log] ${logEntry.tag}: ${logEntry.msg}", + ); + }, + onError: (dynamic e) { + Logging.instance.e("Error receiving Xelis Rust logs: $e"); + }, + ); +} // main() is the entry point to the app. It initializes Hive (local database), // runs the MyApp widget and checks for new users, caching the value in the // miscellaneous box for later use void main(List args) async { + // talker.info('initializing Rust lib ...'); + await xelis_rust.RustLib.init(); WidgetsFlutterBinding.ensureInitialized(); if (Util.isDesktop && args.length == 2 && args.first == "-d") { @@ -111,26 +143,11 @@ void main(List args) async { if (screenHeight != null) { // starting to height be 3/4 screen height or 900, whichever is smaller final height = min(screenHeight * 0.75, 900); - setWindowFrame( - Rect.fromLTWH(0, 0, 1220, height), - ); + setWindowFrame(Rect.fromLTWH(0, 0, 1220, height)); } } // FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); - if (!(Logging.isArmLinux || Logging.isTestEnv)) { - final isar = await Isar.open( - [LogSchema], - directory: (await StackFileSystem.applicationIsarDirectory()).path, - inspector: false, - maxSizeMiB: 512, - ); - await Logging.instance.init(isar); - await DebugService.instance.init(isar); - - // clear out all info logs on startup. No need to await and block - unawaited(DebugService.instance.deleteLogsOlderThan()); - } // Registering Transaction Model Adapters DB.instance.hive.registerAdapter(TransactionDataAdapter()); @@ -162,8 +179,9 @@ void main(List args) async { // node model adapter DB.instance.hive.registerAdapter(NodeModelAdapter()); - if (!DB.instance.hive - .isAdapterRegistered(lib_monero_compat.WalletInfoAdapter().typeId)) { + if (!DB.instance.hive.isAdapterRegistered( + lib_monero_compat.WalletInfoAdapter().typeId, + )) { DB.instance.hive.registerAdapter(lib_monero_compat.WalletInfoAdapter()); } @@ -179,6 +197,17 @@ void main(List args) async { await DB.instance.hive.openBox(DB.boxNamePrefs); await Prefs.instance.init(); + await Logging.instance.initialize( + (await StackFileSystem.applicationLogsDirectory(Prefs.instance)).path, + level: Prefs.instance.logLevel, + ); + + await xelis_api.setUpRustLogger(); + startListeningToRustLogs(); + + // setup lib spark logging + initSparkLogging(Prefs.instance.logLevel); + if (AppConfig.appName == "Campfire" && !Util.isDesktop && !CampfireMigration.didRun) { @@ -202,10 +231,12 @@ void main(List args) async { // Desktop migrate handled elsewhere (currently desktop_login_view.dart) if (!Util.isDesktop) { - final int dbVersion = DB.instance.get( - boxName: DB.boxNameDBInfo, - key: "hive_data_version", - ) as int? ?? + final int dbVersion = + DB.instance.get( + boxName: DB.boxNameDBInfo, + key: "hive_data_version", + ) + as int? ?? 0; if (dbVersion < Constants.currentDataVersion) { try { @@ -217,10 +248,10 @@ void main(List args) async { ), ); } catch (e, s) { - Logging.instance.log( - "Cannot migrate mobile database\n$e $s", - level: LogLevel.Error, - printFullLength: true, + Logging.instance.w( + "Cannot migrate mobile database", + error: e, + stackTrace: s, ); } } @@ -240,22 +271,25 @@ void main(List args) async { // verify current user preference theme and revert to default // if problems are found to prevent app being unusable - if (!(await ThemeService.instance - .verifyInstalled(themeId: Prefs.instance.themeId))) { + if (!(await ThemeService.instance.verifyInstalled( + themeId: Prefs.instance.themeId, + ))) { Prefs.instance.themeId = "light"; } // verify current user preference light brightness theme and revert to default // if problems are found to prevent app being unusable - if (!(await ThemeService.instance - .verifyInstalled(themeId: Prefs.instance.systemBrightnessLightThemeId))) { + if (!(await ThemeService.instance.verifyInstalled( + themeId: Prefs.instance.systemBrightnessLightThemeId, + ))) { Prefs.instance.systemBrightnessLightThemeId = "light"; } // verify current user preference dark brightness theme and revert to default // if problems are found to prevent app being unusable - if (!(await ThemeService.instance - .verifyInstalled(themeId: Prefs.instance.systemBrightnessDarkThemeId))) { + if (!(await ThemeService.instance.verifyInstalled( + themeId: Prefs.instance.systemBrightnessDarkThemeId, + ))) { Prefs.instance.systemBrightnessDarkThemeId = "dark"; } @@ -271,18 +305,14 @@ class MyApp extends StatelessWidget { final localeService = LocaleService(); localeService.loadLocale(); - return const KeyboardDismisser( - child: MaterialAppWithTheme(), - ); + return const KeyboardDismisser(child: MaterialAppWithTheme()); } } // Sidenote: MaterialAppWithTheme and InitView are only separated for clarity. No other reason. class MaterialAppWithTheme extends ConsumerStatefulWidget { - const MaterialAppWithTheme({ - super.key, - }); + const MaterialAppWithTheme({super.key}); @override ConsumerState createState() => @@ -356,7 +386,9 @@ class _MaterialAppWithThemeState extends ConsumerState prefs: ref.read(prefsChangeNotifierProvider), ); ref.read(priceAnd24hChangeNotifierProvider).start(true); - await ref.read(pWallets).load( + await ref + .read(pWallets) + .load( ref.read(prefsChangeNotifierProvider), ref.read(mainDBProvider), ); @@ -386,7 +418,9 @@ class _MaterialAppWithThemeState extends ConsumerState if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) { switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) { case BackupFrequencyType.everyTenMinutes: - ref.read(autoSWBServiceProvider).startPeriodicBackupTimer( + ref + .read(autoSWBServiceProvider) + .startPeriodicBackupTimer( duration: const Duration(minutes: 10), ); break; @@ -404,7 +438,7 @@ class _MaterialAppWithThemeState extends ConsumerState // .userID; // Just reading the ref should set it if it's not already set // We shouldn't need to do this, instead only generating an ID when (or if) the userID is looked up when creating a quote } catch (e, s) { - Logger.print("$e $s", normalLength: false); + Logging.instance.e("load failure", error: e, stackTrace: s); } } @@ -419,9 +453,10 @@ class _MaterialAppWithThemeState extends ConsumerState ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId; break; case Brightness.light: - themeId = ref - .read(prefsChangeNotifierProvider) - .systemBrightnessLightThemeId; + themeId = + ref + .read(prefsChangeNotifierProvider) + .systemBrightnessLightThemeId; break; } } else { @@ -440,9 +475,8 @@ class _MaterialAppWithThemeState extends ConsumerState ref.read(applicationThemesDirectoryPathProvider.notifier).state = StackFileSystem.themesDir!.path; - ref.read(themeProvider.state).state = ref.read(pThemeService).getTheme( - themeId: themeId, - )!; + ref.read(themeProvider.state).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; if (Platform.isAndroid) { // fetch open file if it exists @@ -470,18 +504,17 @@ class _MaterialAppWithThemeState extends ConsumerState ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId; break; case Brightness.light: - themeId = ref - .read(prefsChangeNotifierProvider) - .systemBrightnessLightThemeId; + themeId = + ref + .read(prefsChangeNotifierProvider) + .systemBrightnessLightThemeId; break; } WidgetsBinding.instance.addPostFrameCallback((_) { if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) { ref.read(themeProvider.state).state = - ref.read(pThemeService).getTheme( - themeId: themeId, - )!; + ref.read(pThemeService).getTheme(themeId: themeId)!; } }); }; @@ -560,15 +593,14 @@ class _MaterialAppWithThemeState extends ConsumerState /// should only be called on android currently Future getOpenFile() async { // update provider with new file content state - ref.read(openedFromSWBFileStringStateProvider.state).state = - await platform.invokeMethod("getOpenFile"); + ref.read(openedFromSWBFileStringStateProvider.state).state = await platform + .invokeMethod("getOpenFile"); // call reset to clear cached value await resetOpenPath(); - Logging.instance.log( + Logging.instance.d( "This is the .swb content from intent: ${ref.read(openedFromSWBFileStringStateProvider.state).state}", - level: LogLevel.Info, ); } @@ -579,9 +611,9 @@ class _MaterialAppWithThemeState extends ConsumerState Future goToRestoreSWB(String encrypted) async { if (!ref.read(prefsChangeNotifierProvider).hasPin) { - await Navigator.of(navigatorKey.currentContext!) - .pushNamed(CreatePinView.routeName, arguments: true) - .then((value) { + await Navigator.of( + navigatorKey.currentContext!, + ).pushNamed(CreatePinView.routeName, arguments: true).then((value) { if (value is! bool || value == false) { Navigator.of(navigatorKey.currentContext!).pushNamed( RestoreFromEncryptedStringView.routeName, @@ -595,16 +627,17 @@ class _MaterialAppWithThemeState extends ConsumerState navigatorKey.currentContext!, RouteGenerator.getRoute( shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: (_) => LockscreenView( - showBackButton: true, - routeOnSuccess: RestoreFromEncryptedStringView.routeName, - routeOnSuccessArguments: encrypted, - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to restore ${AppConfig.appName} backup", - biometricsAuthenticationTitle: - "Restore ${AppConfig.prefix} backup", - ), + builder: + (_) => LockscreenView( + showBackButton: true, + routeOnSuccess: RestoreFromEncryptedStringView.routeName, + routeOnSuccessArguments: encrypted, + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to restore ${AppConfig.appName} backup", + biometricsAuthenticationTitle: + "Restore ${AppConfig.prefix} backup", + ), settings: const RouteSettings(name: "/swbrestorelockscreen"), ), ), @@ -614,10 +647,7 @@ class _MaterialAppWithThemeState extends ConsumerState InputBorder _buildOutlineInputBorder(Color color) { return OutlineInputBorder( - borderSide: BorderSide( - width: 1, - color: color, - ), + borderSide: BorderSide(width: 1, color: color), borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), ); } @@ -655,9 +685,7 @@ class _MaterialAppWithThemeState extends ConsumerState ), // splashFactory: NoSplash.splashFactory, splashColor: Colors.transparent, - buttonTheme: ButtonThemeData( - splashColor: colorScheme.splash, - ), + buttonTheme: ButtonThemeData(splashColor: colorScheme.splash), textButtonTheme: TextButtonThemeData( style: ButtonStyle( // splashFactory: NoSplash.splashFactory, @@ -665,8 +693,9 @@ class _MaterialAppWithThemeState extends ConsumerState minimumSize: MaterialStateProperty.all(const Size(46, 46)), // textStyle: MaterialStateProperty.all( // STextStyles.button(context)), - foregroundColor: - MaterialStateProperty.all(colorScheme.buttonTextSecondary), + foregroundColor: MaterialStateProperty.all( + colorScheme.buttonTextSecondary, + ), backgroundColor: MaterialStateProperty.all( colorScheme.buttonBackSecondary, ), @@ -683,25 +712,22 @@ class _MaterialAppWithThemeState extends ConsumerState checkboxTheme: CheckboxThemeData( splashRadius: 0, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.checkboxBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.checkboxBorderRadius, + ), ), - checkColor: MaterialStateColor.resolveWith( - (state) { - if (state.contains(MaterialState.selected)) { - return colorScheme.checkboxIconChecked; - } + checkColor: MaterialStateColor.resolveWith((state) { + if (state.contains(MaterialState.selected)) { + return colorScheme.checkboxIconChecked; + } + return colorScheme.checkboxBGChecked; + }), + fillColor: MaterialStateColor.resolveWith((states) { + if (states.contains(MaterialState.selected)) { return colorScheme.checkboxBGChecked; - }, - ), - fillColor: MaterialStateColor.resolveWith( - (states) { - if (states.contains(MaterialState.selected)) { - return colorScheme.checkboxBGChecked; - } - return colorScheme.checkboxBorderEmpty; - }, - ), + } + return colorScheme.checkboxBorderEmpty; + }), ), appBarTheme: AppBarTheme( centerTitle: false, @@ -719,91 +745,101 @@ class _MaterialAppWithThemeState extends ConsumerState ), // labelStyle: STextStyles.fieldLabel(context), // hintStyle: STextStyles.fieldLabel(context), - enabledBorder: - _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), - focusedBorder: - _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), + enabledBorder: _buildOutlineInputBorder( + colorScheme.textFieldDefaultBG, + ), + focusedBorder: _buildOutlineInputBorder( + colorScheme.textFieldDefaultBG, + ), errorBorder: _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), - disabledBorder: - _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), - focusedErrorBorder: - _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), + disabledBorder: _buildOutlineInputBorder( + colorScheme.textFieldDefaultBG, + ), + focusedErrorBorder: _buildOutlineInputBorder( + colorScheme.textFieldDefaultBG, + ), ), ), home: CryptoNotifications( - child: Util.isDesktop - ? FutureBuilder( - future: loadShared(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (_desktopHasPassword) { - String? startupWalletId; - if (ref - .read(prefsChangeNotifierProvider) - .gotoWalletOnStartup) { - startupWalletId = ref + child: + Util.isDesktop + ? FutureBuilder( + future: loadShared(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (_desktopHasPassword) { + String? startupWalletId; + if (ref .read(prefsChangeNotifierProvider) - .startupWalletId; + .gotoWalletOnStartup) { + startupWalletId = + ref + .read(prefsChangeNotifierProvider) + .startupWalletId; + } + + return DesktopLoginView( + startupWalletId: startupWalletId, + load: load, + ); + } else { + return const IntroView(); } - - return DesktopLoginView( - startupWalletId: startupWalletId, - load: load, - ); } else { - return const IntroView(); + return const LoadingView(); } - } else { - return const LoadingView(); - } - }, - ) - : FutureBuilder( - future: load(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - // FlutterNativeSplash.remove(); - if (ref.read(pAllWalletsInfo).isNotEmpty || - ref.read(prefsChangeNotifierProvider).hasPin) { - // return HomeView(); - - String? startupWalletId; - if (ref - .read(prefsChangeNotifierProvider) - .gotoWalletOnStartup) { - startupWalletId = ref + }, + ) + : FutureBuilder( + future: load(), + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + if (snapshot.connectionState == ConnectionState.done) { + // FlutterNativeSplash.remove(); + if (ref.read(pAllWalletsInfo).isNotEmpty || + ref.read(prefsChangeNotifierProvider).hasPin) { + // return HomeView(); + + String? startupWalletId; + if (ref .read(prefsChangeNotifierProvider) - .startupWalletId; - } - - return LockscreenView( - isInitialAppLogin: true, - routeOnSuccess: HomeView.routeName, - routeOnSuccessArguments: startupWalletId, - biometricsAuthenticationTitle: - "Unlock ${AppConfig.prefix}", - biometricsLocalizedReason: - "Unlock your ${AppConfig.appName} using biometrics", - biometricsCancelButtonString: "Cancel", - ); - } else { - if (AppConfig.appName == "Campfire" && - !CampfireMigration.didRun && - CampfireMigration.hasOldWallets) { - return const CampfireMigrateView(); + .gotoWalletOnStartup) { + startupWalletId = + ref + .read(prefsChangeNotifierProvider) + .startupWalletId; + } + + return LockscreenView( + isInitialAppLogin: true, + routeOnSuccess: HomeView.routeName, + routeOnSuccessArguments: startupWalletId, + biometricsAuthenticationTitle: + "Unlock ${AppConfig.prefix}", + biometricsLocalizedReason: + "Unlock your ${AppConfig.appName} using biometrics", + biometricsCancelButtonString: "Cancel", + ); } else { - return const IntroView(); + if (AppConfig.appName == "Campfire" && + !CampfireMigration.didRun && + CampfireMigration.hasOldWallets) { + return const CampfireMigrateView(); + } else { + return const IntroView(); + } } + } else { + // CURRENTLY DISABLED as cannot be animated + // technically not needed as FlutterNativeSplash will overlay + // anything returned here until the future completes but + // FutureBuilder requires you to return something + return const LoadingView(); } - } else { - // CURRENTLY DISABLED as cannot be animated - // technically not needed as FlutterNativeSplash will overlay - // anything returned here until the future completes but - // FutureBuilder requires you to return something - return const LoadingView(); - } - }, - ), + }, + ), ), ); } diff --git a/lib/models/electrumx_response/spark_models.dart b/lib/models/electrumx_response/spark_models.dart new file mode 100644 index 000000000..43bf9f61f --- /dev/null +++ b/lib/models/electrumx_response/spark_models.dart @@ -0,0 +1,98 @@ +class SparkMempoolData { + final String txid; + final List serialContext; + final List lTags; + final List coins; + + SparkMempoolData({ + required this.txid, + required this.serialContext, + required this.lTags, + required this.coins, + }); + + @override + String toString() { + return "SparkMempoolData{" + "txid: $txid, " + "serialContext: $serialContext, " + "lTags: $lTags, " + "coins: $coins" + "}"; + } +} + +class SparkAnonymitySetMeta { + final int coinGroupId; + final String blockHash; + final String setHash; + final int size; + + SparkAnonymitySetMeta({ + required this.coinGroupId, + required this.blockHash, + required this.setHash, + required this.size, + }); + + @override + String toString() { + return "SparkAnonymitySetMeta{" + "coinGroupId: $coinGroupId, " + "blockHash: $blockHash, " + "setHash: $setHash, " + "size: $size" + "}"; + } +} + +class RawSparkCoin { + final String serialized; + final String txHash; + final String context; + final int groupId; + + RawSparkCoin({ + required this.serialized, + required this.txHash, + required this.context, + required this.groupId, + }); + + static RawSparkCoin fromRPCResponse(List data, int groupId) { + try { + if (data.length != 3) throw Exception(); + return RawSparkCoin( + serialized: data[0] as String, + txHash: data[1] as String, + context: data[2] as String, + groupId: groupId, + ); + } catch (_) { + throw Exception("Invalid coin data: $data"); + } + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! RawSparkCoin) return false; + return serialized == other.serialized && + txHash == other.txHash && + groupId == other.groupId && + context == other.context; + } + + @override + int get hashCode => Object.hash(serialized, txHash, context); + + @override + String toString() { + return "SparkAnonymitySetMeta{" + "serialized: $serialized, " + "txHash: $txHash, " + "context: $context, " + "groupId: $groupId" + "}"; + } +} diff --git a/lib/models/exchange/change_now/cn_exchange_estimate.dart b/lib/models/exchange/change_now/cn_exchange_estimate.dart index 8d3c43566..b5d75a40e 100644 --- a/lib/models/exchange/change_now/cn_exchange_estimate.dart +++ b/lib/models/exchange/change_now/cn_exchange_estimate.dart @@ -9,6 +9,7 @@ */ import 'package:decimal/decimal.dart'; + import '../../../utilities/logger.dart'; enum CNEstimateType { direct, reverse } @@ -112,8 +113,11 @@ class CNExchangeEstimate { toAmount: Decimal.parse(json["toAmount"].toString()), ); } catch (e, s) { - Logging.instance - .log("Failed to parse: $json \n$e\n$s", level: LogLevel.Fatal); + Logging.instance.e( + "Failed to parse: $json", + error: e, + stackTrace: s, + ); rethrow; } } diff --git a/lib/models/exchange/change_now/estimated_exchange_amount.dart b/lib/models/exchange/change_now/estimated_exchange_amount.dart index 550b39489..62eee2f60 100644 --- a/lib/models/exchange/change_now/estimated_exchange_amount.dart +++ b/lib/models/exchange/change_now/estimated_exchange_amount.dart @@ -57,8 +57,11 @@ class EstimatedExchangeAmount { networkFee: Decimal.tryParse(json["networkFee"].toString()), ); } catch (e, s) { - Logging.instance - .log("Failed to parse: $json \n$e\n$s", level: LogLevel.Fatal); + Logging.instance.e( + "Failed to parse: $json", + error: e, + stackTrace: s, + ); rethrow; } } diff --git a/lib/models/exchange/change_now/exchange_transaction_status.dart b/lib/models/exchange/change_now/exchange_transaction_status.dart index 3c3aebb7f..7874b8b80 100644 --- a/lib/models/exchange/change_now/exchange_transaction_status.dart +++ b/lib/models/exchange/change_now/exchange_transaction_status.dart @@ -187,7 +187,7 @@ class ExchangeTransactionStatus { }); factory ExchangeTransactionStatus.fromJson(Map json) { - Logging.instance.log(json, printFullLength: true, level: LogLevel.Info); + Logging.instance.d(json, stackTrace: StackTrace.current); try { return ExchangeTransactionStatus( status: changeNowTransactionStatusFromStringIgnoreCase( @@ -228,7 +228,7 @@ class ExchangeTransactionStatus { payload: json["payload"] as Object?, ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + Logging.instance.f("", error: e, stackTrace: s); rethrow; } } diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart index d51fda17c..6f4173ade 100644 --- a/lib/models/exchange/response_objects/estimate.dart +++ b/lib/models/exchange/response_objects/estimate.dart @@ -9,6 +9,7 @@ */ import 'package:decimal/decimal.dart'; + import '../../../utilities/logger.dart'; class Estimate { @@ -18,6 +19,7 @@ class Estimate { final String? warningMessage; final String? rateId; final String exchangeProvider; + final String? exchangeProviderLogo; final String? kycRating; Estimate({ @@ -27,6 +29,7 @@ class Estimate { this.warningMessage, this.rateId, required this.exchangeProvider, + this.exchangeProviderLogo, this.kycRating, }); @@ -46,7 +49,11 @@ class Estimate { kycRating: kycRating, ); } catch (e, s) { - Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "Estimate.fromMap()", + error: e, + stackTrace: s, + ); rethrow; } } diff --git a/lib/models/exchange/response_objects/fixed_rate_market.dart b/lib/models/exchange/response_objects/fixed_rate_market.dart index 103d9d99f..a52fe5768 100644 --- a/lib/models/exchange/response_objects/fixed_rate_market.dart +++ b/lib/models/exchange/response_objects/fixed_rate_market.dart @@ -9,6 +9,7 @@ */ import 'package:decimal/decimal.dart'; + import '../../../utilities/logger.dart'; class FixedRateMarket { @@ -53,10 +54,7 @@ class FixedRateMarket { minerFee: Decimal.tryParse(json["minerFee"].toString()), ); } catch (e, s) { - Logging.instance.log( - "FixedRateMarket.fromMap(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("FixedRateMarket.fromMap(): ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/models/exchange/simpleswap/sp_currency.dart b/lib/models/exchange/simpleswap/sp_currency.dart index 96905f44c..9de40720f 100644 --- a/lib/models/exchange/simpleswap/sp_currency.dart +++ b/lib/models/exchange/simpleswap/sp_currency.dart @@ -59,10 +59,7 @@ class SPCurrency { warningsTo: json["warnings_to"] as List, ); } catch (e, s) { - Logging.instance.log( - "SPCurrency.fromJson failed to parse: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("SPCurrency.fromJson failed to parse: ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/models/isar/models/blockchain_data/address.dart b/lib/models/isar/models/blockchain_data/address.dart index 584636050..8947d6c46 100644 --- a/lib/models/isar/models/blockchain_data/address.dart +++ b/lib/models/isar/models/blockchain_data/address.dart @@ -175,7 +175,8 @@ enum AddressType { frostMS, p2tr, solana, - cardanoShelley; + cardanoShelley, + xelis; String get readableName { switch (this) { @@ -213,6 +214,8 @@ enum AddressType { return "P2TR (taproot)"; case AddressType.cardanoShelley: return "Cardano Shelley"; + case AddressType.xelis: + return "Xelis"; } } } diff --git a/lib/models/isar/models/blockchain_data/address.g.dart b/lib/models/isar/models/blockchain_data/address.g.dart index 092198990..a9289a481 100644 --- a/lib/models/isar/models/blockchain_data/address.g.dart +++ b/lib/models/isar/models/blockchain_data/address.g.dart @@ -278,6 +278,8 @@ const _AddresstypeEnumValueMap = { 'frostMS': 13, 'p2tr': 14, 'solana': 15, + 'cardanoShelley': 16, + 'xelis': 17, }; const _AddresstypeValueEnumMap = { 0: AddressType.p2pkh, @@ -296,6 +298,8 @@ const _AddresstypeValueEnumMap = { 13: AddressType.frostMS, 14: AddressType.p2tr, 15: AddressType.solana, + 16: AddressType.cardanoShelley, + 17: AddressType.xelis, }; Id _addressGetId(Address object) { diff --git a/lib/models/isar/models/blockchain_data/utxo.dart b/lib/models/isar/models/blockchain_data/utxo.dart index 87558c541..57f9ede64 100644 --- a/lib/models/isar/models/blockchain_data/utxo.dart +++ b/lib/models/isar/models/blockchain_data/utxo.dart @@ -77,19 +77,31 @@ class UTXO { int getConfirmations(int currentChainHeight) { if (blockTime == null || blockHash == null) return 0; if (blockHeight == null || blockHeight! <= 0) return 0; - return max(0, currentChainHeight - (blockHeight! - 1)); + return _isMonero() + ? max(0, currentChainHeight - (blockHeight!)) + : max(0, currentChainHeight - (blockHeight! - 1)); } bool isConfirmed( int currentChainHeight, int minimumConfirms, - int minimumCoinbaseConfirms, - ) { + int minimumCoinbaseConfirms, { + int? overrideMinConfirms, // added to handle namecoin name op outputs + }) { final confirmations = getConfirmations(currentChainHeight); + + if (overrideMinConfirms != null) { + return confirmations >= overrideMinConfirms; + } return confirmations >= (isCoinbase ? minimumCoinbaseConfirms : minimumConfirms); } + // fuzzy + bool _isMonero() { + return keyImage != null; + } + @ignore String? get keyImage { if (otherData == null) { @@ -98,7 +110,7 @@ class UTXO { try { final map = jsonDecode(otherData!) as Map; - return map["keyImage"] as String; + return map[UTXOOtherDataKeys.keyImage] as String; } catch (_) { return null; } @@ -169,3 +181,9 @@ class UTXO { @ignore int get hashCode => Object.hashAll([walletId, txid, vout]); } + +abstract final class UTXOOtherDataKeys { + static const keyImage = "keyImage"; + static const spent = "spent"; + static const nameOpData = "nameOpData"; +} diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index 3d8903391..b7a92c26e 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -109,7 +109,9 @@ class TransactionV2 { int getConfirmations(int currentChainHeight) { if (height == null || height! <= 0) return 0; - return max(0, currentChainHeight - (height! - 1)); + return _isMonero() + ? max(0, currentChainHeight - (height!)) + : max(0, currentChainHeight - (height! - 1)); } bool isConfirmed( diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index ce5002a1d..efa2f7d05 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -13,6 +13,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:isar/isar.dart'; + import '../../app_config.dart'; import '../../utilities/extensions/impl/box_shadow.dart'; import '../../utilities/extensions/impl/gradient.dart'; @@ -1884,10 +1885,7 @@ class StackTheme { (map[mainNetId] as String).toBigIntFromHex.toInt(), ); } else { - Logging.instance.log( - "Color not found in theme for $mainNetId", - level: LogLevel.Error, - ); + Logging.instance.w("Color not found in theme for $mainNetId"); } } diff --git a/lib/models/namecoin_dns/dns_a_record_address_type.dart b/lib/models/namecoin_dns/dns_a_record_address_type.dart new file mode 100644 index 000000000..8709baa49 --- /dev/null +++ b/lib/models/namecoin_dns/dns_a_record_address_type.dart @@ -0,0 +1,25 @@ +enum DNSAddressType { + IPv4, + IPv6, + Tor, + Freenet, + I2P, + ZeroNet; + + String get key { + switch (this) { + case DNSAddressType.IPv4: + return "ip"; + case DNSAddressType.IPv6: + return "ip6"; + case DNSAddressType.Tor: + return "_tor"; + case DNSAddressType.Freenet: + return "freenet"; + case DNSAddressType.I2P: + return "i2p"; + case DNSAddressType.ZeroNet: + return "zeronet"; + } + } +} diff --git a/lib/models/namecoin_dns/dns_record.dart b/lib/models/namecoin_dns/dns_record.dart new file mode 100644 index 000000000..9c54bf98b --- /dev/null +++ b/lib/models/namecoin_dns/dns_record.dart @@ -0,0 +1,129 @@ +import 'dart:convert'; + +import 'package:meta/meta.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../utilities/extensions/extensions.dart'; +import 'dns_record_type.dart'; + +@Immutable() +abstract class DNSRecordBase { + final String name; + + DNSRecordBase({required this.name}); + + String getValueString(); +} + +@Immutable() +final class RawDNSRecord extends DNSRecordBase { + final String value; + + RawDNSRecord({required super.name, required this.value}); + + @override + String getValueString() => value; + + @override + String toString() { + return "RawDNSRecord(name: $name, value: $value)"; + } +} + +@Immutable() +final class DNSRecord extends DNSRecordBase { + final DNSRecordType type; + final Map data; + + DNSRecord({ + required super.name, + required this.type, + required this.data, + }); + + @override + String getValueString() { + // TODO error handling + dynamic value = data; + while (value is Map) { + value = value[value.keys.first]; + } + + return value.toString(); + } + + DNSRecord copyWith({ + DNSRecordType? type, + Map? data, + }) { + return DNSRecord( + type: type ?? this.type, + data: data ?? this.data, + name: name, + ); + } + + @override + String toString() { + return "DNSRecord(name: $name, type: $type, data: $data)"; + } + + static String merge(List records) { + final Map result = {}; + + for (final record in records) { + switch (record.type) { + case DNSRecordType.CNAME: + if (result[record.data.keys.first] != null) { + throw Exception("CNAME record already exists"); + } + _deepMerge(result, record.data); + break; + + case DNSRecordType.TLS: + case DNSRecordType.NS: + case DNSRecordType.DS: + case DNSRecordType.SRV: + case DNSRecordType.SSH: + case DNSRecordType.TXT: + case DNSRecordType.IMPORT: + case DNSRecordType.A: + _deepMerge(result, record.data); + break; + } + } + + final string = jsonEncode(result); + if (string.toUint8ListFromUtf8.length > valueMaxLength) { + throw Exception( + "Value length (${string.toUint8ListFromUtf8.length}) exceeds maximum" + " allowed ($valueMaxLength)", + ); + } + + return string; + } +} + +void _deepMerge(Map base, Map updates) { + updates.forEach((key, value) { + if (value is Map && base[key] is Map) { + _deepMerge(base[key] as Map, value); + } else if (value is List && base[key] is List) { + (base[key] as List).addAll(value); + } else { + if (base[key] != null) { + throw Exception( + "Attempted to overwrite value: ${base[key]} where key=$key", + ); + } + if (value is Map) { + base[key] = Map.from(value); + } else if (value is List) { + base[key] = List.from(value); + } else { + base[key] = value; + } + } + }); +} diff --git a/lib/models/namecoin_dns/dns_record_type.dart b/lib/models/namecoin_dns/dns_record_type.dart new file mode 100644 index 000000000..6e25cc038 --- /dev/null +++ b/lib/models/namecoin_dns/dns_record_type.dart @@ -0,0 +1,68 @@ +enum DNSRecordType { + A, + CNAME, + NS, + DS, + TLS, + SRV, + TXT, + IMPORT, + SSH; + + String get info { + switch (this) { + case DNSRecordType.A: + return "An A record maps your domain to an address (IPv4, IPv6, Tor," + " Freenet, I2P, or ZeroNet)."; + case DNSRecordType.CNAME: + return "A CNAME record redirects your domain to another domain," + " essentially acting as an alias."; + case DNSRecordType.NS: + return "An NS record specifies the nameservers that are authoritative" + " for your domain."; + case DNSRecordType.DS: + return "A DS record holds information about DNSSEC (DNS Security " + "Extensions) for your domain, helping with verification and " + "integrity."; + case DNSRecordType.TLS: + return "A TLS record is used for specifying details about how to " + "establish secure connections (like TLS certificates) for your" + " domain."; + case DNSRecordType.SRV: + return "An SRV record specifies the location of servers for specific" + " services, such as SIP, XMPP, or Minecraft servers."; + case DNSRecordType.TXT: + return "A TXT record allows you to add arbitrary text to your domain's" + " DNS record, often used for verification (e.g., SPF, DKIM)."; + case DNSRecordType.IMPORT: + return "An IMPORT record is used to bring in DNS records from an" + " external source into your domain's configuration."; + case DNSRecordType.SSH: + return "An SSH record provides information related to SSH public keys" + " for securely connecting to your domain's services."; + } + } + + String? get key { + switch (this) { + case DNSRecordType.A: + return null; + case DNSRecordType.CNAME: + return "alias"; + case DNSRecordType.NS: + return "ns"; + case DNSRecordType.DS: + return "ds"; + case DNSRecordType.TLS: + return "tls"; + case DNSRecordType.SRV: + return "srv"; + case DNSRecordType.TXT: + return "txt"; + case DNSRecordType.IMPORT: + return "import"; + case DNSRecordType.SSH: + return "sshfp"; + } + } +} diff --git a/lib/models/node_model.dart b/lib/models/node_model.dart index e4d3f66b6..1a27d1345 100644 --- a/lib/models/node_model.dart +++ b/lib/models/node_model.dart @@ -65,12 +65,12 @@ class NodeModel { int? port, String? name, bool? useSSL, - String? loginName, + required String? loginName, bool? enabled, String? coinName, bool? isFailover, bool? isDown, - bool? trusted, + required bool? trusted, bool? torEnabled, bool? clearnetEnabled, }) { @@ -80,12 +80,12 @@ class NodeModel { name: name ?? this.name, id: id, useSSL: useSSL ?? this.useSSL, - loginName: loginName ?? this.loginName, + loginName: loginName, enabled: enabled ?? this.enabled, coinName: coinName ?? this.coinName, isFailover: isFailover ?? this.isFailover, isDown: isDown ?? this.isDown, - trusted: trusted ?? this.trusted, + trusted: trusted, torEnabled: torEnabled ?? this.torEnabled, clearnetEnabled: clearnetEnabled ?? this.clearnetEnabled, ); diff --git a/lib/networking/http.dart b/lib/networking/http.dart index ae2a3b97b..80ea57c37 100644 --- a/lib/networking/http.dart +++ b/lib/networking/http.dart @@ -53,10 +53,7 @@ class HTTP { response.statusCode, ); } catch (e, s) { - Logging.instance.log( - "HTTP.get() rethrew: $e\n$s", - level: LogLevel.Info, - ); + Logging.instance.w("HTTP.get() rethrew: ", error: e, stackTrace: s); rethrow; } finally { httpClient.close(force: true); @@ -99,10 +96,7 @@ class HTTP { response.statusCode, ); } catch (e, s) { - Logging.instance.log( - "HTTP.post() rethrew: $e\n$s", - level: LogLevel.Info, - ); + Logging.instance.w("HTTP.post() rethrew: ", error: e, stackTrace: s); rethrow; } finally { httpClient.close(force: true); @@ -119,9 +113,10 @@ class HTTP { onDone: () => completer.complete( Uint8List.fromList(bytes), ), - onError: (Object err, StackTrace s) => Logging.instance.log( - "Http wrapper layer listen: $err\n$s", - level: LogLevel.Error, + onError: (Object err, StackTrace s) => Logging.instance.e( + "Http wrapper layer listen", + error: err, + stackTrace: s, ), ); return completer.future; diff --git a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart index 03d289237..748f5074e 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart @@ -153,7 +153,12 @@ class _AddWalletViewState extends ConsumerState { } WidgetsBinding.instance.addPostFrameCallback((_) { - ref.refresh(addWalletSelectedEntityStateProvider); + if (mounted) { + ref.refresh(addWalletSelectedEntityStateProvider); + if (isDesktop) { + _searchFocusNode.requestFocus(); + } + } }); super.initState(); diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart index a57148fee..d8aae6d6d 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../../../../../frost_route_generator.dart'; -import '../../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; import '../../../../../providers/frost_wallet/frost_wallet_providers.dart'; import '../../../../../services/frost.dart'; import '../../../../../utilities/logger.dart'; @@ -15,6 +15,7 @@ import '../../../../../widgets/dialogs/frost/frost_error_dialog.dart'; import '../../../../../widgets/frost_step_user_steps.dart'; import '../../../../../widgets/stack_dialog.dart'; import '../../../../../widgets/textfields/frost_step_field.dart'; +import '../../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; class FrostCreateStep2 extends ConsumerStatefulWidget { const FrostCreateStep2({ @@ -177,10 +178,7 @@ class _FrostCreateStep2State extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (context.mounted) { return await showDialog( context: context, diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart index 20130a2da..ec565cb59 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart @@ -178,10 +178,7 @@ class _FrostCreateStep3State extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (context.mounted) { return await showDialog( diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart index 4640e0abb..3c59c7524 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart @@ -219,10 +219,7 @@ class _FrostCreateStep5State extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); // pop progress dialog if (context.mounted && !progressPopped) { diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart index a83e22d40..740baafc3 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart @@ -81,10 +81,7 @@ class _FrostReshareStep1aState extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart index e29d1110b..7224e2acb 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart @@ -117,10 +117,7 @@ class _FrostReshareStep1bState extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart index 962e207bf..b10730c3a 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart @@ -204,10 +204,7 @@ class _FrostReshareStep1cState extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (context.mounted) { await showDialog( diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart index a95d8ec47..b54b6041b 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart @@ -79,10 +79,7 @@ class _FrostReshareStep2abdState extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( @@ -208,7 +205,7 @@ class _FrostReshareStep2abdState extends ConsumerState { label: "Continue", enabled: _userVerifyContinue && (amOutgoingParticipant || - !fieldIsEmptyFlags.reduce((v, e) => v |= e)), + !fieldIsEmptyFlags.fold(false, (v, e) => v || e)), onPressed: _onPressed, ), ], diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart index b134ea5e4..92a3a195b 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart @@ -59,10 +59,7 @@ class _FrostReshareStep2cState extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart index a899ddda1..d4fc876c1 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart @@ -70,10 +70,7 @@ class _FrostReshareStep3abdState extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( context: context, diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart index f476c9096..dd081d121 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart @@ -88,10 +88,7 @@ class _FrostReshareStep4State extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( context: context, @@ -238,7 +235,7 @@ class _FrostReshareStep4State extends ConsumerState { label: amOutgoingParticipant ? "Done" : "Complete", enabled: (amNewParticipant || _userVerifyContinue) && (amOutgoingParticipant || - !fieldIsEmptyFlags.reduce((v, e) => v |= e)), + !fieldIsEmptyFlags.fold(false, (v, e) => v || e)), onPressed: _onPressed, ), ], diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart index 834a0a638..83fa55944 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart @@ -104,10 +104,7 @@ class _FrostReshareStep5State extends ConsumerState { } } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( context: context, diff --git a/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart b/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart index 75c5cbc7d..88f7c3b26 100644 --- a/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart +++ b/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart @@ -171,9 +171,10 @@ class _RestoreFrostMsWalletViewState ); } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, + Logging.instance.e( + "", + error: e, + stackTrace: s, ); if (mounted) { @@ -228,31 +229,29 @@ class _RestoreFrostMsWalletViewState }); } else { // Platform.isLinux, Platform.isWindows, or Platform.isMacOS. - await showDialog( + final qrResult = await showDialog( context: context, - builder: (context) { - return QrCodeScannerDialog( - onQrCodeDetected: (qrCodeData) { - try { - // TODO [prio=low]: Validate QR code data. - configFieldController.text = qrCodeData; - - setState(() { - _configEmpty = configFieldController.text.isEmpty; - }); - } catch (e, s) { - Logging.instance.log("Error processing QR code data: $e\n$s", - level: LogLevel.Error); - } - }, - ); - }, + builder: (context) => const QrCodeScannerDialog(), ); + + if (qrResult == null) { + Logging.instance.d( + "Qr scanning cancelled", + ); + } else { + // TODO [prio=low]: Validate QR code data. + configFieldController.text = qrResult; + + setState(() { + _configEmpty = configFieldController.text.isEmpty; + }); + } } } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to get camera permissions while trying to scan qr code: ", + error: e, + stackTrace: s, ); } } diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index 73acccb9d..7d93d038a 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart @@ -26,18 +26,20 @@ import '../../../services/transaction_notification_tracker.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/logger.dart'; +import '../../../utilities/show_loading.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../wallets/isar/models/wallet_info.dart'; +import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/desktop/desktop_app_bar.dart'; import '../../../widgets/desktop/desktop_scaffold.dart'; -import '../../../widgets/loading_indicator.dart'; import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/stack_dialog.dart'; import '../new_wallet_options/new_wallet_options_view.dart'; import '../new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart'; import 'recovery_phrase_explanation_dialog.dart'; @@ -65,6 +67,218 @@ class _NewWalletRecoveryPhraseWarningViewState late final String walletName; late final bool isDesktop; + Future _initNewWallet() async { + Exception? ex; + final result = await showLoading( + whileFuture: _initNewFuture(), + context: context, + message: "Generating...", + onException: (e) => ex = e, + ); + + // on failure show error message + if (result == null) { + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Create Wallet Error", + message: ex?.toString() ?? "Unknown error", + maxWidth: 600, + ), + ); + } + return; + } else { + if (mounted) { + final nav = Navigator.of(context); + unawaited( + nav.pushNamed( + NewWalletRecoveryPhraseView.routeName, + arguments: Tuple2( + result.$1, + result.$2, + ), + ), + ); + } + } + } + + Future<(Wallet, List)> _initNewFuture() async { + try { + String? otherDataJsonString; + if (widget.coin is Tezos) { + otherDataJsonString = jsonEncode({ + WalletInfoKeys.tezosDerivationPath: + Tezos.standardDerivationPath.value, + }); + // }//todo: probably not needed (broken anyways) + // else if (widget.coin is Epiccash) { + // final int secondsSinceEpoch = + // DateTime.now().millisecondsSinceEpoch ~/ 1000; + // const int epicCashFirstBlock = 1565370278; + // const double overestimateSecondsPerBlock = 61; + // int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; + // int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; + // / + // // debugPrint( + // // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); + // height = approximateHeight; + // if (height < 0) { + // height = 0; + // } + // + // otherDataJsonString = jsonEncode( + // { + // WalletInfoKeys.epiccashData: jsonEncode( + // ExtraEpiccashWalletInfo( + // receivingIndex: 0, + // changeIndex: 0, + // slatesToAddresses: {}, + // slatesToCommits: {}, + // lastScannedBlock: epicCashFirstBlock, + // restoreHeight: height, + // creationHeight: height, + // ).toMap(), + // ), + // }, + // ); + } else if (widget.coin is Firo) { + otherDataJsonString = jsonEncode( + { + WalletInfoKeys.lelantusCoinIsarRescanRequired: false, + }, + ); + } + + final info = WalletInfo.createNew( + coin: widget.coin, + name: widget.walletName, + otherDataJsonString: otherDataJsonString, + ); + + var node = ref + .read( + nodeServiceChangeNotifierProvider, + ) + .getPrimaryNodeFor( + currency: coin, + ); + + if (node == null) { + node = coin.defaultNode; + await ref + .read( + nodeServiceChangeNotifierProvider, + ) + .setPrimaryNodeFor( + coin: coin, + node: node, + ); + } + + final txTracker = TransactionNotificationTracker( + walletId: info.walletId, + ); + + String? mnemonicPassphrase; + String? mnemonic; + String? privateKey; + + // set some sane default + int wordCount = info.coin.defaultSeedPhraseLength; + + // TODO: Refactor these to generate each coin in their respective classes + // This code should not be in a random view page file + if (coin is Monero || coin is Wownero || coin is Xelis) { + // currently a special case due to the + // xmr/wow libraries handling their + // own mnemonic generation + wordCount = ref.read(pNewWalletOptions)?.mnemonicWordsCount ?? + info.coin.defaultSeedPhraseLength; + } else if (wordCount > 0) { + if (ref + .read( + pNewWalletOptions.state, + ) + .state != + null) { + if (coin.hasMnemonicPassphraseSupport) { + mnemonicPassphrase = ref + .read( + pNewWalletOptions.state, + ) + .state! + .mnemonicPassphrase; + } else { + // this may not be epiccash specific? + if (coin is Epiccash) { + mnemonicPassphrase = ""; + } + } + + wordCount = ref + .read( + pNewWalletOptions.state, + ) + .state! + .mnemonicWordsCount; + } else { + mnemonicPassphrase = ""; + } + + if (wordCount < 12 || 24 < wordCount || wordCount % 3 != 0) { + throw Exception( + "Invalid word count", + ); + } + + final strength = (wordCount ~/ 3) * 32; + + mnemonic = bip39.generateMnemonic( + strength: strength, + ); + } + + final wallet = await Wallet.create( + walletInfo: info, + mainDB: ref.read(mainDBProvider), + secureStorageInterface: ref.read(secureStoreProvider), + nodeService: ref.read( + nodeServiceChangeNotifierProvider, + ), + prefs: ref.read( + prefsChangeNotifierProvider, + ), + mnemonicPassphrase: mnemonicPassphrase, + mnemonic: mnemonic, + privateKey: privateKey, + ); + + if (wallet is LibMoneroWallet) { + await wallet.init(wordCount: wordCount); + } else { + await wallet.init(); + } + + // set checkbox back to unchecked to annoy users to agree again :P + ref + .read( + checkBoxStateProvider.state, + ) + .state = false; + + final fetchedMnemonic = + await (wallet as MnemonicInterface).getMnemonicAsWords(); + + return (wallet, fetchedMnemonic); + } catch (e, s) { + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); + rethrow; + } + } + @override void initState() { coin = widget.coin; @@ -454,222 +668,7 @@ class _NewWalletRecoveryPhraseWarningViewState onPressed: ref .read(checkBoxStateProvider.state) .state - ? () async { - try { - unawaited( - showDialog( - context: context, - barrierDismissible: false, - useSafeArea: true, - builder: (ctx) { - return const Center( - child: LoadingIndicator( - width: 50, - height: 50, - ), - ); - }, - ), - ); - String? otherDataJsonString; - if (widget.coin is Tezos) { - otherDataJsonString = jsonEncode({ - WalletInfoKeys - .tezosDerivationPath: - Tezos.standardDerivationPath - .value, - }); - // }//todo: probably not needed (broken anyways) - // else if (widget.coin is Epiccash) { - // final int secondsSinceEpoch = - // DateTime.now().millisecondsSinceEpoch ~/ 1000; - // const int epicCashFirstBlock = 1565370278; - // const double overestimateSecondsPerBlock = 61; - // int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; - // int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; - // / - // // debugPrint( - // // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); - // height = approximateHeight; - // if (height < 0) { - // height = 0; - // } - // - // otherDataJsonString = jsonEncode( - // { - // WalletInfoKeys.epiccashData: jsonEncode( - // ExtraEpiccashWalletInfo( - // receivingIndex: 0, - // changeIndex: 0, - // slatesToAddresses: {}, - // slatesToCommits: {}, - // lastScannedBlock: epicCashFirstBlock, - // restoreHeight: height, - // creationHeight: height, - // ).toMap(), - // ), - // }, - // ); - } else if (widget.coin is Firo) { - otherDataJsonString = jsonEncode( - { - WalletInfoKeys - .lelantusCoinIsarRescanRequired: - false, - }, - ); - } - - final info = WalletInfo.createNew( - coin: widget.coin, - name: widget.walletName, - otherDataJsonString: - otherDataJsonString, - ); - - var node = ref - .read( - nodeServiceChangeNotifierProvider, - ) - .getPrimaryNodeFor( - currency: coin, - ); - - if (node == null) { - node = coin.defaultNode; - await ref - .read( - nodeServiceChangeNotifierProvider, - ) - .setPrimaryNodeFor( - coin: coin, - node: node, - ); - } - - final txTracker = - TransactionNotificationTracker( - walletId: info.walletId, - ); - - int? wordCount; - String? mnemonicPassphrase; - String? mnemonic; - String? privateKey; - - wordCount = info - .coin.defaultSeedPhraseLength; - - // TODO: Refactor these to generate each coin in their respective classes - // This code should not be in a random view page file - if (coin is Monero || - coin is Wownero) { - // currently a special case due to the - // xmr/wow libraries handling their - // own mnemonic generation - } else if (wordCount > 0) { - if (ref - .read( - pNewWalletOptions.state, - ) - .state != - null) { - if (coin - .hasMnemonicPassphraseSupport) { - mnemonicPassphrase = ref - .read( - pNewWalletOptions.state, - ) - .state! - .mnemonicPassphrase; - } else { - // this may not be epiccash specific? - if (coin is Epiccash) { - mnemonicPassphrase = ""; - } - } - - wordCount = ref - .read( - pNewWalletOptions.state, - ) - .state! - .mnemonicWordsCount; - } else { - mnemonicPassphrase = ""; - } - - if (wordCount < 12 || - 24 < wordCount || - wordCount % 3 != 0) { - throw Exception( - "Invalid word count", - ); - } - - final strength = - (wordCount ~/ 3) * 32; - - mnemonic = bip39.generateMnemonic( - strength: strength, - ); - } - - final wallet = await Wallet.create( - walletInfo: info, - mainDB: ref.read(mainDBProvider), - secureStorageInterface: - ref.read(secureStoreProvider), - nodeService: ref.read( - nodeServiceChangeNotifierProvider, - ), - prefs: ref.read( - prefsChangeNotifierProvider, - ), - mnemonicPassphrase: - mnemonicPassphrase, - mnemonic: mnemonic, - privateKey: privateKey, - ); - - await wallet.init(); - - // pop progress dialog - if (context.mounted) { - Navigator.pop(context); - } - // set checkbox back to unchecked to annoy users to agree again :P - ref - .read( - checkBoxStateProvider.state, - ) - .state = false; - - if (context.mounted) { - final nav = Navigator.of(context); - unawaited( - nav.pushNamed( - NewWalletRecoveryPhraseView - .routeName, - arguments: Tuple2( - wallet, - await (wallet - as MnemonicInterface) - .getMnemonicAsWords(), - ), - ), - ); - } - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - // TODO: handle gracefully - // any network/socket exception here will break new wallet creation - rethrow; - } - } + ? _initNewWallet : null, style: ref .read(checkBoxStateProvider.state) diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart index 3a7131762..9123b2f46 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart @@ -31,6 +31,7 @@ import '../../../wallets/isar/models/wallet_info.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; +import '../../../wallets/wallet/impl/xelis_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -264,6 +265,10 @@ class _RestoreViewOnlyWalletViewState await (wallet as WowneroWallet).init(isRestore: true); break; + case const (XelisWallet): + await (wallet as XelisWallet).init(isRestore: true); + break; + default: await wallet.init(); } diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 2fae2352c..972012c00 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -25,6 +25,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; +import 'package:xelis_flutter/src/api/seed_search_engine.dart' as x_seed; + import '../../../notifications/show_flush_bar.dart'; import '../../../pages_desktop_specific/desktop_home_view.dart'; import '../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; @@ -48,6 +50,8 @@ import '../../../wallets/isar/models/wallet_info.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; +import '../../../wallets/wallet/intermediate/external_wallet.dart'; +import '../../../wallets/wallet/impl/xelis_wallet.dart'; import '../../../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -102,6 +106,7 @@ class _RestoreWalletViewState extends ConsumerState { late final int _seedWordCount; late final bool isDesktop; + x_seed.SearchEngine? _xelisSeedSearch; final HashSet _wordListHashSet = HashSet.from(bip39wordlist.WORDLIST); final ScrollController controller = ScrollController(); @@ -113,6 +118,8 @@ class _RestoreWalletViewState extends ConsumerState { late final TextSelectionControls textSelectionControls; + bool _hideSeedWords = false; + Future onControlsPaste(TextSelectionDelegate delegate) async { final data = await widget.clipboard.getData(Clipboard.kTextPlain); if (data?.text == null) { @@ -164,6 +171,10 @@ class _RestoreWalletViewState extends ConsumerState { // _focusNodes.add(FocusNode()); } + if (widget.coin is Xelis) { + _xelisSeedSearch = x_seed.SearchEngine.init(languageIndex: BigInt.from(0)); + } + super.initState(); } @@ -196,6 +207,9 @@ class _RestoreWalletViewState extends ConsumerState { ); return wowneroWordList.contains(word); } + if (widget.coin is Xelis) { + return _xelisSeedSearch!.search(query: word).length > 0; + } return _wordListHashSet.contains(word); } @@ -211,6 +225,8 @@ class _RestoreWalletViewState extends ConsumerState { Future attemptRestore() async { if (_formKey.currentState!.validate()) { + if (mounted) setState(() => _hideSeedWords = true); + String mnemonic = ""; for (final element in _controllers) { mnemonic += " ${element.text.trim().toLowerCase()}"; @@ -278,9 +294,9 @@ class _RestoreWalletViewState extends ConsumerState { ); } - // TODO: do actual check to make sure it is a valid mnemonic for monero + // TODO: do actual check to make sure it is a valid mnemonic for monero + xelis if (bip39.validateMnemonic(mnemonic) == false && - !(widget.coin is Monero || widget.coin is Wownero)) { + !(widget.coin is Monero || widget.coin is Wownero || widget.coin is Xelis)) { unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -312,6 +328,8 @@ class _RestoreWalletViewState extends ConsumerState { onCancel: () async { isRestoring = false; + if (mounted) setState(() => _hideSeedWords = false); + await ref.read(pWallets).deleteWallet( info, ref.read(secureStoreProvider), @@ -363,12 +381,20 @@ class _RestoreWalletViewState extends ConsumerState { await (wallet as WowneroWallet).init(isRestore: true); break; + case const (XelisWallet): + await (wallet as XelisWallet).init(isRestore: true); + break; + default: await wallet.init(); } await wallet.recover(isRescan: false); + if (wallet is ExternalWallet) { + await wallet.exit(); + } + // check if state is still active before continuing if (mounted) { await wallet.info.setMnemonicVerified( @@ -466,6 +492,8 @@ class _RestoreWalletViewState extends ConsumerState { ); }, ); + + if (mounted) setState(() => _hideSeedWords = false); } } @@ -614,24 +642,24 @@ class _RestoreWalletViewState extends ConsumerState { final results = AddressUtils.decodeQRSeedData(qrResult.rawContent); - Logging.instance.log("scan parsed: $results", level: LogLevel.Info); - if (results["mnemonic"] != null) { final list = (results["mnemonic"] as List) .map((value) => value as String) .toList(growable: false); if (list.isNotEmpty) { _clearAndPopulateMnemonic(list); - Logging.instance.log("mnemonic populated", level: LogLevel.Info); + Logging.instance.i("mnemonic populated"); } else { - Logging.instance - .log("mnemonic failed to populate", level: LogLevel.Info); + Logging.instance.i("mnemonic failed to populate"); } } - } on PlatformException catch (e) { + } on PlatformException catch (e, s) { // likely failed to get camera permissions - Logging.instance - .log("Restore wallet qr scan failed: $e", level: LogLevel.Warning); + Logging.instance.e( + "Restore wallet qr scan failed: $e", + error: e, + stackTrace: s, + ); } } @@ -863,6 +891,7 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + obscureText: _hideSeedWords, autocorrect: !isDesktop, enableSuggestions: !isDesktop, textCapitalization: @@ -996,6 +1025,7 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + obscureText: _hideSeedWords, autocorrect: !isDesktop, enableSuggestions: !isDesktop, textCapitalization: @@ -1130,6 +1160,7 @@ class _RestoreWalletViewState extends ConsumerState { padding: const EdgeInsets.symmetric(vertical: 4), child: TextFormField( + obscureText: _hideSeedWords, autocorrect: !isDesktop, enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, diff --git a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart index 1ae4a5efd..7419583e7 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../../../../providers/global/secure_store_provider.dart'; import '../../../../providers/providers.dart'; import '../../../../themes/stack_colors.dart'; @@ -72,11 +73,8 @@ class _RestoreFailedDialogState extends ConsumerState { ref.read(secureStoreProvider), ); } catch (e, s) { - Logging.instance.log( - "Error while getting wallet info in restore failed dialog\n" - "Error: $e\nStack trace: $s", - level: LogLevel.Error, - ); + Logging.instance.e("Error while getting wallet info in restore failed dialog\n" + "Error: $e\nStack trace: $s"); } finally { if (mounted) { Navigator.of(context).pop(); diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart index e95e33539..04dc94d33 100644 --- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart @@ -40,6 +40,7 @@ import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; +import '../../../wallets/wallet/impl/xelis_wallet.dart'; import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; @@ -225,6 +226,10 @@ class _VerifyRecoveryPhraseViewState await (voWallet as WowneroWallet).init(isRestore: true); break; + case const (XelisWallet): + await (voWallet as XelisWallet).init(isRestore: true); + break; + default: await voWallet.init(); } @@ -306,10 +311,7 @@ class _VerifyRecoveryPhraseViewState throw ex!; } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( diff --git a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart index d84cee567..f1f399f13 100644 --- a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart +++ b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart @@ -115,10 +115,7 @@ class _NewContactAddressEntryFormState // .read(shouldShowLockscreenOnResumeStateProvider // .state) // .state = true; - Logging.instance.log( - "Failed to get camera permissions to scan address qr code: $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("Failed to get camera permissions to scan address qr code: ", error: e, stackTrace: s); } } diff --git a/lib/pages/buy_view/buy_form.dart b/lib/pages/buy_view/buy_form.dart index f47831547..d68a02e4f 100644 --- a/lib/pages/buy_view/buy_form.dart +++ b/lib/pages/buy_view/buy_form.dart @@ -311,10 +311,7 @@ class _BuyFormState extends ConsumerState { .read(simplexProvider) .updateSupportedCryptos(response.value!); // TODO validate } else { - Logging.instance.log( - "_loadSimplexCurrencies: $response", - level: LogLevel.Warning, - ); + Logging.instance.d("_loadSimplexCurrencies: $response"); } } @@ -326,10 +323,7 @@ class _BuyFormState extends ConsumerState { .read(simplexProvider) .updateSupportedFiats(response.value!); // TODO validate } else { - Logging.instance.log( - "_loadSimplexCurrencies: $response", - level: LogLevel.Warning, - ); + Logging.instance.d("_loadSimplexCurrencies: $response"); } } @@ -626,10 +620,7 @@ class _BuyFormState extends ConsumerState { ref.read(simplexProvider).updateQuote(response.value!); return BuyResponse(value: response.value!); } else { - Logging.instance.log( - "_loadQuote: $response", - level: LogLevel.Warning, - ); + Logging.instance.d("_loadQuote: $response"); return BuyResponse( exception: response.exception ?? BuyException( @@ -724,20 +715,14 @@ class _BuyFormState extends ConsumerState { final qrResult = await scanner.scan(); - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info, - ); + Logging.instance.d("qrResult content: ${qrResult.rawContent}"); final paymentData = AddressUtils.parsePaymentUri( qrResult.rawContent, logging: Logging.instance, ); - Logging.instance.log( - "qrResult parsed: $paymentData", - level: LogLevel.Info, - ); + Logging.instance.d("qrResult parsed: $paymentData"); if (paymentData != null) { // auto fill address @@ -760,9 +745,10 @@ class _BuyFormState extends ConsumerState { } on PlatformException catch (e, s) { // here we ignore the exception caused by not giving permission // to use the camera to scan a qr code - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, + Logging.instance.e( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, ); } } @@ -1241,7 +1227,11 @@ class _BuyFormState extends ConsumerState { } }); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); + Logging.instance.w( + "", + error: e, + stackTrace: s, + ); } }, ), diff --git a/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart index 3bc0b220b..432a3435a 100644 --- a/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart +++ b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart @@ -304,7 +304,7 @@ class CoinIconForTicker extends ConsumerWidget { height: size, ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + Logging.instance.f("", error: e, stackTrace: s); rethrow; } } diff --git a/lib/pages/coin_control/coin_control_view.dart b/lib/pages/coin_control/coin_control_view.dart index 07703a149..cb288f845 100644 --- a/lib/pages/coin_control/coin_control_view.dart +++ b/lib/pages/coin_control/coin_control_view.dart @@ -26,6 +26,8 @@ import '../../utilities/assets.dart'; import '../../utilities/constants.dart'; import '../../utilities/text_styles.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../wallets/wallet/wallet.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; import '../../widgets/animated_widgets/rotate_icon.dart'; import '../../widgets/app_bar_field.dart'; @@ -88,6 +90,18 @@ class _CoinControlViewState extends ConsumerState { await coinControlInterface.updateBalance(); } + bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) { + if (wallet is NamecoinWallet) { + return wallet.checkUtxoConfirmed(utxo, currentChainHeight); + } else { + return utxo.isConfirmed( + currentChainHeight, + wallet.cryptoCurrency.minConfirms, + wallet.cryptoCurrency.minCoinbaseConfirms, + ); + } + } + @override void initState() { if (widget.selectedUTXOs != null) { @@ -347,10 +361,15 @@ class _CoinControlViewState extends ConsumerState { CoinControlViewType.manage || (widget.type == CoinControlViewType.use && !utxo.isBlocked && - utxo.isConfirmed( + _isConfirmed( + utxo, currentHeight, - minConfirms, - coin.minCoinbaseConfirms, + ref.watch( + pWallets.select( + (s) => s + .getWallet(widget.walletId), + ), + ), )), initialSelectedState: isSelected, onSelectedChanged: (value) { @@ -412,10 +431,16 @@ class _CoinControlViewState extends ConsumerState { (widget.type == CoinControlViewType.use && !_showBlocked && - utxo.isConfirmed( + _isConfirmed( + utxo, currentHeight, - minConfirms, - coin.minCoinbaseConfirms, + ref.watch( + pWallets.select( + (s) => s.getWallet( + widget.walletId, + ), + ), + ), )), initialSelectedState: isSelected, onSelectedChanged: (value) { @@ -557,10 +582,16 @@ class _CoinControlViewState extends ConsumerState { CoinControlViewType .use && !utxo.isBlocked && - utxo.isConfirmed( + _isConfirmed( + utxo, currentHeight, - minConfirms, - coin.minCoinbaseConfirms, + ref.watch( + pWallets.select( + (s) => s.getWallet( + widget.walletId, + ), + ), + ), )), initialSelectedState: isSelected, onSelectedChanged: (value) { diff --git a/lib/pages/coin_control/utxo_card.dart b/lib/pages/coin_control/utxo_card.dart index 0881c7eb6..624b41eee 100644 --- a/lib/pages/coin_control/utxo_card.dart +++ b/lib/pages/coin_control/utxo_card.dart @@ -20,6 +20,8 @@ import '../../utilities/amount/amount_formatter.dart'; import '../../utilities/constants.dart'; import '../../utilities/text_styles.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../wallets/wallet/wallet.dart'; import '../../widgets/conditional_parent.dart'; import '../../widgets/icon_widgets/utxo_status_icon.dart'; import '../../widgets/rounded_container.dart'; @@ -52,6 +54,18 @@ class _UtxoCardState extends ConsumerState { late bool _selected; + bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) { + if (wallet is NamecoinWallet) { + return wallet.checkUtxoConfirmed(utxo, currentChainHeight); + } else { + return utxo.isConfirmed( + currentChainHeight, + wallet.cryptoCurrency.minConfirms, + wallet.cryptoCurrency.minCoinbaseConfirms, + ); + } + } + @override void initState() { _selected = widget.initialSelectedState; @@ -110,18 +124,16 @@ class _UtxoCardState extends ConsumerState { ), child: UTXOStatusIcon( blocked: utxo.isBlocked, - status: utxo.isConfirmed( + status: _isConfirmed( + utxo, currentHeight, - ref - .watch(pWallets) - .getWallet(widget.walletId) - .cryptoCurrency - .minConfirms, - ref - .watch(pWallets) - .getWallet(widget.walletId) - .cryptoCurrency - .minCoinbaseConfirms, + ref.watch( + pWallets.select( + (s) => s.getWallet( + widget.walletId, + ), + ), + ), ) ? UTXOStatusIconStatus.confirmed : UTXOStatusIconStatus.unconfirmed, diff --git a/lib/pages/coin_control/utxo_details_view.dart b/lib/pages/coin_control/utxo_details_view.dart index ba71f7a01..9959abdda 100644 --- a/lib/pages/coin_control/utxo_details_view.dart +++ b/lib/pages/coin_control/utxo_details_view.dart @@ -23,6 +23,8 @@ import '../../utilities/amount/amount_formatter.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../wallets/wallet/wallet.dart'; import '../../widgets/background.dart'; import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -67,6 +69,18 @@ class _UtxoDetailsViewState extends ConsumerState { await MainDB.instance.putUTXO(utxo!.copyWith(isBlocked: !utxo!.isBlocked)); } + bool _isConfirmed(UTXO utxo, int currentChainHeight, Wallet wallet) { + if (wallet is NamecoinWallet) { + return wallet.checkUtxoConfirmed(utxo, currentChainHeight); + } else { + return utxo.isConfirmed( + currentChainHeight, + wallet.cryptoCurrency.minConfirms, + wallet.cryptoCurrency.minCoinbaseConfirms, + ); + } + } + @override void initState() { utxo = MainDB.instance.isar.utxos @@ -95,14 +109,14 @@ class _UtxoDetailsViewState extends ConsumerState { final coin = ref.watch(pWalletCoin(widget.walletId)); final currentHeight = ref.watch(pWalletChainHeight(widget.walletId)); - final confirmed = utxo!.isConfirmed( + final confirmed = _isConfirmed( + utxo!, currentHeight, - ref.watch(pWallets).getWallet(widget.walletId).cryptoCurrency.minConfirms, - ref - .watch(pWallets) - .getWallet(widget.walletId) - .cryptoCurrency - .minCoinbaseConfirms, + ref.watch( + pWallets.select( + (s) => s.getWallet(widget.walletId), + ), + ), ); return ConditionalParent( diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 1b52dfdbe..999fca764 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -162,10 +162,7 @@ class _ConfirmChangeNowSendViewState Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName)); } } catch (e, s) { - Logging.instance.log( - "Broadcast transaction failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Broadcast transaction failed: ", error: e, stackTrace: s); // pop sending dialog Navigator.of(context).pop(); diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart index 028ce0ac4..951c211b9 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart @@ -98,9 +98,10 @@ class _Step2ViewState extends ConsumerState { }); } } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, ); } } @@ -135,9 +136,10 @@ class _Step2ViewState extends ConsumerState { }); } } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, ); } } @@ -303,8 +305,11 @@ class _Step2ViewState extends ConsumerState { } }); } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Info); + Logging.instance.e( + "", + error: e, + stackTrace: s, + ); } }, ), @@ -543,9 +548,10 @@ class _Step2ViewState extends ConsumerState { }); }); } catch (e, s) { - Logging.instance.log( + Logging.instance.i( "$e\n$s", - level: LogLevel.Info, + error: e, + stackTrace: s, ); } }, diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index 69bd4ea01..7b867aab3 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -317,7 +317,7 @@ class _Step4ViewState extends ConsumerState { } } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); if (mounted && !wasCancelled) { // pop building dialog Navigator.of(context).pop(); diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index f499775b1..7e97017a3 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -35,7 +35,7 @@ import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; import '../../wallets/models/tx_data.dart'; import '../../wallets/wallet/impl/firo_wallet.dart'; -import '../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../wallets/wallet/intermediate/external_wallet.dart'; import '../../widgets/background.dart'; import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -277,7 +277,7 @@ class _SendFromCardState extends ConsumerState { // access to this screen but this is needed to get past an error that // would occur only to lead to another error which is why xmr/wow wallets // don't have access to this screen currently - if (wallet is LibMoneroWallet) { + if (wallet is ExternalWallet) { await wallet.init(); await wallet.open(); } @@ -387,7 +387,7 @@ class _SendFromCardState extends ConsumerState { } } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); if (mounted) { // pop building dialog Navigator.of(context).pop(); diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart index 7ba126659..5b11876e8 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -173,9 +173,8 @@ class _ExchangeOptionState extends ConsumerState { ], ); } else { - Logging.instance.log( + Logging.instance.w( "$runtimeType rate unavailable for ${widget.exchange.name}: $data", - level: LogLevel.Warning, ); return Consumer( @@ -315,7 +314,43 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { child: SizedBox( width: isDesktop ? 32 : 24, height: isDesktop ? 32 : 24, - child: SvgPicture.asset( + child: widget.estimate?.exchangeProviderLogo != null && + widget + .estimate! + .exchangeProviderLogo! + .isNotEmpty + ? ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.network( + widget.estimate!.exchangeProviderLogo!, + loadingBuilder: ( + context, + child, + loadingProgress, + ) { + if (loadingProgress == null) { + return child; + } else { + return const Center( + child: + CircularProgressIndicator(), + ); + } + }, + errorBuilder: (context, error, stackTrace) { + return SvgPicture.asset( + Assets.exchange.getIconFor( + exchangeName: widget.exchange.name, + ), + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ); + }, + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), + ) + : SvgPicture.asset( Assets.exchange.getIconFor( exchangeName: widget.exchange.name, ), diff --git a/lib/pages/namecoin_names/add_dns_record/add_dns_step_1.dart b/lib/pages/namecoin_names/add_dns_record/add_dns_step_1.dart new file mode 100644 index 000000000..dd9119cac --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/add_dns_step_1.dart @@ -0,0 +1,257 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../route_generator.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/stack_dialog.dart'; +import 'add_dns_step_2.dart'; + +class AddDnsStep1 extends StatefulWidget { + const AddDnsStep1({super.key, required this.name}); + + final String name; + + @override + State createState() => _AddDnsStep1State(); +} + +class _AddDnsStep1State extends State { + DNSRecordType? _recordType; + + bool _nextLock = false; + void _next() { + if (_nextLock) return; + _nextLock = true; + try { + if (mounted) { + Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (context) { + return Util.isDesktop + ? DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Add DNS record", + style: STextStyles.desktopH3( + context, + ), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: AddDnsStep2( + recordType: _recordType!, + name: widget.name, + ), + ), + ], + ), + ) + : StackDialogBase( + keyboardPaddingAmount: + MediaQuery.of(context).viewInsets.bottom, + child: AddDnsStep2( + recordType: _recordType!, + name: widget.name, + ), + ); + }, + ), + ); + } + } finally { + _nextLock = false; + } + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!Util.isDesktop) + Text( + "Add DNS record", + style: STextStyles.pageTitleH2(context), + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + Text( + "Choose a record type", + style: Util.isDesktop + ? STextStyles.w500_12(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ) + : STextStyles.w500_14(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ), + SizedBox( + height: Util.isDesktop ? 12 : 8, + ), + DropdownButtonHideUnderline( + child: DropdownButton2( + hint: Text( + "Choose a record type", + style: STextStyles.fieldLabel(context), + ), + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + maxHeight: Util.isDesktop ? null : 200, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + ), + isExpanded: true, + value: _recordType, + onChanged: (value) { + if (value is DNSRecordType && _recordType != value) { + setState(() { + _recordType = value; + }); + } + }, + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 10, + height: 5, + color: Theme.of(context).extension()!.textDark3, + ), + ), + ), + items: [ + ...DNSRecordType.values.map( + (e) => DropdownMenuItem( + value: e, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + e.name, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ), + ), + ], + ), + ), + if (_recordType != null) + SizedBox( + height: Util.isDesktop ? 10 : 6, + ), + if (_recordType != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + Expanded( + child: Text( + _recordType!.info, + style: Util.isDesktop + ? STextStyles.w500_10(context).copyWith( + color: Theme.of(context) + .extension()! + .infoItemLabel, + ) + : STextStyles.w500_8(context).copyWith( + color: Theme.of(context) + .extension()! + .infoItemLabel, + ), + ), + ), + ], + ), + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Next", + enabled: _recordType != null, + onPressed: _next, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + ), + ), + ], + ), + if (Util.isDesktop) + const SizedBox( + height: 32, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/add_dns_step_2.dart b/lib/pages/namecoin_names/add_dns_record/add_dns_step_2.dart new file mode 100644 index 000000000..f6bb22f67 --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/add_dns_step_2.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; + +import '../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/stack_dialog.dart'; +import 'name_form_interface.dart'; +import 'sub_widgets/a_form.dart'; +import 'sub_widgets/cname_form.dart'; +import 'sub_widgets/ds_form.dart'; +import 'sub_widgets/import_form.dart'; +import 'sub_widgets/ns_form.dart'; +import 'sub_widgets/srv_form.dart'; +import 'sub_widgets/ssh_form.dart'; +import 'sub_widgets/tls_form.dart'; +import 'sub_widgets/txt_form.dart'; + +class AddDnsStep2 extends StatefulWidget { + const AddDnsStep2({ + super.key, + required this.recordType, + required this.name, + }); + + final String name; + final DNSRecordType recordType; + + @override + State createState() => _AddDnsStep2State(); +} + +class _AddDnsStep2State extends State { + final GlobalKey _formStateKey = GlobalKey(); + + bool _nextLock = false; + void _nextPressed() { + if (_nextLock) return; + _nextLock = true; + try { + final record = _formStateKey.currentState!.buildRecord(); + Navigator.of(context, rootNavigator: true).pop( + record, + ); + } catch (e, s) { + Logging.instance.e( + runtimeType, + error: e, + stackTrace: s, + ); + + final String err; + switch (e.runtimeType) { + case const (ArgumentError): + err = e.toString().replaceFirst( + "Invalid Arguments(s): ", + "", + ); + + case const (Exception): + err = e.toString().replaceFirst( + "Exception: ", + "", + ); + + default: + err = e.toString(); + } + + showDialog( + context: context, + useRootNavigator: true, + builder: (context) { + return StackOkDialog( + desktopPopRootNavigator: true, // mobile as well due to sub nav flow + title: "Error", + maxWidth: 500, + message: err, + ); + }, + ); + } finally { + _nextLock = false; + } + } + + NameFormStatefulWidget? _form; + NameFormStatefulWidget get form => _form ??= _buildForm(); + + NameFormStatefulWidget _buildForm() { + switch (widget.recordType) { + case DNSRecordType.A: + return AForm(key: _formStateKey, name: widget.name); + case DNSRecordType.CNAME: + return CNAMEForm(key: _formStateKey, name: widget.name); + case DNSRecordType.NS: + return NSForm(key: _formStateKey, name: widget.name); + case DNSRecordType.DS: + return DSForm(key: _formStateKey, name: widget.name); + case DNSRecordType.TLS: + return TLSForm(key: _formStateKey, name: widget.name); + case DNSRecordType.SRV: + return SRVForm(key: _formStateKey, name: widget.name); + case DNSRecordType.TXT: + return TXTForm(key: _formStateKey, name: widget.name); + case DNSRecordType.IMPORT: + return IMPORTForm(key: _formStateKey, name: widget.name); + case DNSRecordType.SSH: + return SSHForm(key: _formStateKey, name: widget.name); + } + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!Util.isDesktop) + Text( + "Add DNS record", + style: STextStyles.pageTitleH2(context), + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + form, + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Next", + onPressed: _nextPressed, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + ), + ), + ], + ), + if (Util.isDesktop) + const SizedBox( + height: 32, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/name_form_interface.dart b/lib/pages/namecoin_names/add_dns_record/name_form_interface.dart new file mode 100644 index 000000000..e5fc996bd --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/name_form_interface.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; + +import '../../../models/namecoin_dns/dns_record.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; + +abstract class NameFormStatefulWidget extends StatefulWidget { + const NameFormStatefulWidget({super.key, required this.name}); + + final String name; +} + +abstract class NameFormState + extends State { + DNSRecord buildRecord(); +} + +class DNSFieldText extends StatelessWidget { + const DNSFieldText(this.text, {super.key}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + text, + style: Util.isDesktop + ? STextStyles.w500_12(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ) + : STextStyles.w500_14(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ); + } +} + +class DNSFormField extends StatelessWidget { + const DNSFormField({super.key, required this.controller, this.keyboardType}); + + final TextEditingController controller; + final TextInputType? keyboardType; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: controller, + textAlignVertical: TextAlignVertical.center, + keyboardType: keyboardType, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.all(16), + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + ), + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/a_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/a_form.dart new file mode 100644 index 000000000..ec6728f9a --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/a_form.dart @@ -0,0 +1,239 @@ +import 'dart:io'; + +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../../models/namecoin_dns/dns_a_record_address_type.dart'; +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/assets.dart'; +import '../../../../utilities/constants.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class AForm extends NameFormStatefulWidget { + const AForm({super.key, required super.name}); + + @override + NameFormState createState() => _AFormState(); +} + +class _AFormState extends NameFormState { + final _addressDataController = TextEditingController(); + final _addressDataFieldFocus = FocusNode(); + + DNSAddressType _addressType = DNSAddressType.IPv4; + + @override + DNSRecord buildRecord() { + final parts = _addressDataController.text.split(",").map((e) => e.trim()); + + final List addresses = []; + + for (final part in parts) { + switch (_addressType) { + case DNSAddressType.IPv4: + final address = + InternetAddress(part.trim(), type: InternetAddressType.IPv4); + addresses.add(address.address); + break; + + case DNSAddressType.IPv6: + final address = InternetAddress(part, type: InternetAddressType.IPv6); + addresses.add(address.address); + break; + + case DNSAddressType.Tor: + final regex = RegExp(r'^[a-z2-7]{56}\.onion$'); + if (regex.hasMatch(part)) { + addresses.add(part); + } else { + throw Exception("Invalid tor address: $part"); + } + + case DNSAddressType.Freenet: + // TODO: verify + final regex = RegExp(r'(CHK|SSK|USK)@[a-zA-Z0-9~-]{43,}/?'); + final kskRegex = RegExp(r'KSK@[\w\-.~]+'); + if (regex.hasMatch(part) || kskRegex.hasMatch(part)) { + addresses.add(part); + } else { + throw Exception("Invalid freenet address: $part"); + } + + case DNSAddressType.I2P: + // TODO: verify + final b32Regex = RegExp(r'^[a-z2-7]{52}\.b32\.i2p$'); + final b64Regex = RegExp(r'^[A-Za-z0-9+/=]{516,}$'); + if (b32Regex.hasMatch(part) || b64Regex.hasMatch(part)) { + addresses.add(part); + } else { + throw Exception("Invalid i2p address: $part"); + } + + case DNSAddressType.ZeroNet: + // TODO: verify + final regex = RegExp(r'^[13][a-km-zA-HJ-NP-Z1-9]{32,33}$'); + if (regex.hasMatch(part)) { + addresses.add(part); + } else { + throw Exception("Invalid zeronet address: $part"); + } + } + } + + final Map map; + + if (_addressType == DNSAddressType.Tor) { + map = { + "map": { + "_tor": { + "txt": addresses, + }, + }, + }; + } else { + map = { + _addressType!.key: addresses, + }; + } + + return DNSRecord( + name: widget.name, + type: DNSRecordType.A, + data: map, + ); + } + + @override + void dispose() { + _addressDataController.dispose(); + _addressDataFieldFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const DNSFieldText( + "Address type", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DropdownButtonHideUnderline( + child: DropdownButton2( + hint: Text( + "Choose address type", + style: STextStyles.fieldLabel(context), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + maxHeight: Util.isDesktop ? null : 200, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + ), + isExpanded: true, + value: _addressType, + onChanged: (value) { + if (value is DNSAddressType && _addressType != value) { + setState(() { + _addressType = value; + }); + } + }, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 10, + height: 5, + color: Theme.of(context).extension()!.textDark3, + ), + ), + ), + items: [ + ...DNSAddressType.values.map( + (e) => DropdownMenuItem( + value: e, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + e.name, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ), + ), + ], + ), + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Value", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + focusNode: _addressDataFieldFocus, + controller: _addressDataController, + textAlignVertical: TextAlignVertical.center, + maxLines: 3, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.all(16), + hintText: "e.g. 255.255.255.255, " + "76f4a520a262c269dcba66bc1f560452e30a44e14ce6b37ce20b8.onion", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/cname_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/cname_form.dart new file mode 100644 index 000000000..34cc7a600 --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/cname_form.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class CNAMEForm extends NameFormStatefulWidget { + const CNAMEForm({super.key, required super.name}); + + @override + NameFormState createState() => _CNAMEFormState(); +} + +class _CNAMEFormState extends NameFormState { + final _aliasController = TextEditingController(); + + @override + DNSRecord buildRecord() { + final address = _aliasController.text.trim(); + + return DNSRecord( + name: widget.name, + type: DNSRecordType.CNAME, + data: {"alias": address}, + ); + } + + @override + void dispose() { + _aliasController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "Alias of", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _aliasController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/ds_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/ds_form.dart new file mode 100644 index 000000000..29cdbaf6f --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/ds_form.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class DSForm extends NameFormStatefulWidget { + const DSForm({super.key, required super.name}); + + @override + NameFormState createState() => _DSFormState(); +} + +class _DSFormState extends NameFormState { + final _keytagController = TextEditingController(); + final _algoController = TextEditingController(); + final _typeController = TextEditingController(); + final _hashController = TextEditingController(); + + @override + DNSRecord buildRecord() { + return DNSRecord( + name: widget.name, + type: DNSRecordType.DS, + data: { + "ds": [ + [ + int.parse(_keytagController.text.trim()), + int.parse(_algoController.text.trim()), + int.parse(_typeController.text.trim()), + _hashController.text.trim(), + ], + ], + }, + ); + } + + @override + void dispose() { + _keytagController.dispose(); + _algoController.dispose(); + _typeController.dispose(); + _hashController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "Keytag", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _keytagController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Algorithm", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _algoController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Hash type", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _typeController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Hash (base64)", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _hashController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/import_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/import_form.dart new file mode 100644 index 000000000..cfcaa0d23 --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/import_form.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class IMPORTForm extends NameFormStatefulWidget { + const IMPORTForm({super.key, required super.name}); + + @override + NameFormState createState() => _IMPORTFormState(); +} + +class _IMPORTFormState extends NameFormState { + final _nameController = TextEditingController(); + final _subdomainController = TextEditingController(); + + @override + DNSRecord buildRecord() { + return DNSRecord( + name: widget.name, + type: DNSRecordType.IMPORT, + data: { + "import": [ + [ + _nameController.text.trim(), + if (_subdomainController.text.trim().isNotEmpty) + _subdomainController.text.trim(), + ], + ], + }, + ); + } + + @override + void dispose() { + _nameController.dispose(); + _subdomainController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "Namecoin name", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _nameController, + ), + const DNSFieldText( + "Subdomain (optional)", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _subdomainController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/ns_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/ns_form.dart new file mode 100644 index 000000000..6d6eb914c --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/ns_form.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class NSForm extends NameFormStatefulWidget { + const NSForm({super.key, required super.name}); + + @override + NameFormState createState() => _NSFormState(); +} + +class _NSFormState extends NameFormState { + final _serverController = TextEditingController(); + + @override + DNSRecord buildRecord() { + final address = _serverController.text.trim(); + + return DNSRecord( + name: widget.name, + type: DNSRecordType.NS, + data: { + "ns": [address], + }, + ); + } + + @override + void dispose() { + _serverController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "Nameserver", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _serverController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/srv_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/srv_form.dart new file mode 100644 index 000000000..db3c01a0c --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/srv_form.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class SRVForm extends NameFormStatefulWidget { + const SRVForm({super.key, required super.name}); + + @override + NameFormState createState() => _SRVFormState(); +} + +class _SRVFormState extends NameFormState { + final _priorityController = TextEditingController(); + final _weightController = TextEditingController(); + final _portController = TextEditingController(); + final _hostController = TextEditingController(); + + @override + DNSRecord buildRecord() { + return DNSRecord( + name: widget.name, + type: DNSRecordType.SRV, + data: { + "srv": [ + [ + int.parse(_priorityController.text.trim()), + int.parse(_weightController.text.trim()), + int.parse(_portController.text.trim()), + _hostController.text.trim(), + ], + ], + }, + ); + } + + @override + void dispose() { + _priorityController.dispose(); + _weightController.dispose(); + _portController.dispose(); + _hostController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "Priority", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _priorityController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Weight", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _weightController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Port", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _portController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Host", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _hostController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/ssh_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/ssh_form.dart new file mode 100644 index 000000000..9503544be --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/ssh_form.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class SSHForm extends NameFormStatefulWidget { + const SSHForm({super.key, required super.name}); + + @override + NameFormState createState() => _SSHFormState(); +} + +class _SSHFormState extends NameFormState { + final _algoController = TextEditingController(); + final _fingerprintTypeController = TextEditingController(); + final _fingerprintController = TextEditingController(); + + @override + DNSRecord buildRecord() { + return DNSRecord( + name: widget.name, + type: DNSRecordType.SSH, + data: { + "sshfp": [ + [ + int.parse(_algoController.text.trim()), + int.parse(_fingerprintTypeController.text.trim()), + _fingerprintController.text.trim(), + ], + ], + }, + ); + } + + @override + void dispose() { + _algoController.dispose(); + _fingerprintTypeController.dispose(); + _fingerprintController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "Algorithm", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _algoController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Fingerprint type", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _fingerprintTypeController, + keyboardType: TextInputType.number, + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + const DNSFieldText( + "Fingerprint (base64)", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _fingerprintController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/tls_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/tls_form.dart new file mode 100644 index 000000000..509e0b201 --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/tls_form.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class TLSForm extends NameFormStatefulWidget { + const TLSForm({super.key, required super.name}); + + @override + NameFormState createState() => _TLSFormState(); +} + +class _TLSFormState extends NameFormState { + final _pubkeyController = TextEditingController(); + + @override + DNSRecord buildRecord() { + return DNSRecord( + name: widget.name, + type: DNSRecordType.TLS, + data: { + "map": { + "*": { + "tls": [ + [ + 2, + 1, + 0, + _pubkeyController.text.trim(), + ], + ], + }, + }, + }, + ); + } + + @override + void dispose() { + _pubkeyController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "DANE-TA public key (base64)", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _pubkeyController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/add_dns_record/sub_widgets/txt_form.dart b/lib/pages/namecoin_names/add_dns_record/sub_widgets/txt_form.dart new file mode 100644 index 000000000..bd331cd21 --- /dev/null +++ b/lib/pages/namecoin_names/add_dns_record/sub_widgets/txt_form.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +import '../../../../models/namecoin_dns/dns_record.dart'; +import '../../../../models/namecoin_dns/dns_record_type.dart'; +import '../../../../utilities/util.dart'; +import '../name_form_interface.dart'; + +class TXTForm extends NameFormStatefulWidget { + const TXTForm({super.key, required super.name}); + + @override + NameFormState createState() => _TXTFormState(); +} + +class _TXTFormState extends NameFormState { + final _valueController = TextEditingController(); + + @override + DNSRecord buildRecord() { + return DNSRecord( + name: widget.name, + type: DNSRecordType.TXT, + data: { + "txt": [_valueController.text.trim()], + }, + ); + } + + @override + void dispose() { + _valueController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const DNSFieldText( + "Value", + ), + SizedBox( + height: Util.isDesktop ? 10 : 8, + ), + DNSFormField( + controller: _valueController, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/buy_domain_view.dart b/lib/pages/namecoin_names/buy_domain_view.dart new file mode 100644 index 000000000..d0dddda3e --- /dev/null +++ b/lib/pages/namecoin_names/buy_domain_view.dart @@ -0,0 +1,541 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../providers/providers.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/models/name_op_state.dart'; +import '../../../wallets/models/tx_data.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/stack_dialog.dart'; +import '../../models/namecoin_dns/dns_a_record_address_type.dart'; +import '../../models/namecoin_dns/dns_record.dart'; +import '../../models/namecoin_dns/dns_record_type.dart'; +import '../../route_generator.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/show_loading.dart'; +import '../../utilities/text_styles.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/custom_buttons/blue_text_button.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/dialogs/s_dialog.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'add_dns_record/add_dns_step_1.dart'; +import 'confirm_name_transaction_view.dart'; + +class BuyDomainView extends ConsumerStatefulWidget { + const BuyDomainView({ + super.key, + required this.walletId, + required this.domainName, + }); + + final String walletId; + final String domainName; + + static const routeName = "/buyDomainView"; + + @override + ConsumerState createState() => _BuyDomainWidgetState(); +} + +class _BuyDomainWidgetState extends ConsumerState { + bool _settingsHidden = true; + final List _dnsRecords = []; + + String _getFormattedDNSRecords() { + if (_dnsRecords.isEmpty) return ""; + + return DNSRecord.merge(_dnsRecords); + } + + String _getNameFormattedForInternal() { + String formattedName = widget.domainName; + if (!formattedName.startsWith("d/")) { + formattedName = "d/$formattedName"; + } + if (formattedName.endsWith(".bit")) { + formattedName.split(".bit").first; + } + return formattedName; + } + + Future _preRegFuture() async { + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet; + final myAddress = await wallet.getCurrentReceivingAddress(); + if (myAddress == null) { + throw Exception("No receiving address found"); + } + + final value = _getFormattedDNSRecords(); + + Logging.instance.t("Formatted namecoin name value: $value"); + + // get address private key for deterministic salt + final pk = await wallet.getPrivateKey(myAddress); + + final formattedName = _getNameFormattedForInternal(); + + final data = await compute(_computeScriptNameNew, (formattedName, pk.data)); + + TxData txData = TxData( + opNameState: NameOpState( + name: formattedName, + saltHex: data.$2, + commitment: data.$3, + value: value, + nameScriptHex: data.$1, + type: OpName.nameNew, + outputPosition: -1, //currently unknown, updated later + ), + note: "Reserve ${widget.domainName.substring(2)}.bit", + feeRateType: kNameTxDefaultFeeRate, // TODO: make configurable? + recipients: [ + ( + address: myAddress.value, + isChange: false, + amount: Amount( + rawValue: BigInt.from(kNameNewAmountSats), + fractionDigits: wallet.cryptoCurrency.fractionDigits, + ), + ), + ], + ); + + txData = await wallet.prepareNameSend(txData: txData); + return txData; + } + + bool _preRegLock = false; + Future _preRegister() async { + if (_preRegLock) return; + _preRegLock = true; + try { + final txData = (await showLoading( + whileFuture: _preRegFuture(), + context: context, + message: "Preparing transaction...", + onException: (e) { + throw e; + }, + ))!; + + if (mounted) { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => SDialog( + child: SizedBox( + width: 580, + child: ConfirmNameTransactionView( + txData: txData, + walletId: widget.walletId, + ), + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + ConfirmNameTransactionView.routeName, + arguments: (txData, widget.walletId), + ); + } + } + } catch (e, s) { + Logging.instance.e("_preRegister failed", error: e, stackTrace: s); + + if (mounted) { + String err = e.toString(); + if (err.startsWith("Exception: ")) { + err = err.replaceFirst("Exception: ", ""); + } + + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Error", + message: err, + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 600 : null, + ), + ); + } + } finally { + _preRegLock = false; + } + } + + bool _addLock = false; + Future _addRecord() async { + if (_addLock) return; + _addLock = true; + try { + final value = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return Navigator( + onGenerateRoute: (settings) { + return RouteGenerator.getRoute( + builder: (context) { + return Util.isDesktop + ? SDialog( + child: SizedBox( + width: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Add DNS record", + style: STextStyles.desktopH3(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + }, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: AddDnsStep1( + name: _getNameFormattedForInternal(), + ), + ), + ], + ), + ), + ) + : StackDialogBase( + child: AddDnsStep1( + name: _getNameFormattedForInternal(), + ), + ); + }, + ); + }, + ); + }, + ); + + if (mounted && value != null) { + setState(() { + _dnsRecords.add(value); + }); + } + } catch (e, s) { + Logging.instance.e("Add DNS record failed", error: e, stackTrace: s); + + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Add DNS record failed", + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 600 : null, + ), + ); + } + } finally { + _addLock = false; + } + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(pWalletCoin(widget.walletId)); + return ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + leading: const AppBarBackButton(), + titleSpacing: 0, + title: Text( + "Buy domain", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (ctx, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: child, + ), + ), + ), + ); + }, + ), + ), + ), + ); + }, + child: Column( + crossAxisAlignment: Util.isDesktop + ? CrossAxisAlignment.start + : CrossAxisAlignment.stretch, + children: [ + if (!Util.isDesktop) + Text( + "Buy domain", + style: Util.isDesktop + ? STextStyles.desktopH3(context) + : STextStyles.pageTitleH2(context), + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + Row( + mainAxisAlignment: Util.isDesktop + ? MainAxisAlignment.center + : MainAxisAlignment.start, + children: [ + Text( + "Name registration will take approximately 2 to 4 hours.", + style: Util.isDesktop + ? STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ) + : STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + ], + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Domain name", + style: Util.isDesktop + ? STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .infoItemLabel, + ) + : STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .infoItemLabel, + ), + ), + Text( + "${widget.domainName.substring(2)}.bit", + style: Util.isDesktop + ? STextStyles.w500_14(context) + : STextStyles.w500_12(context), + ), + ], + ), + ), + SizedBox( + height: Util.isDesktop ? 16 : 8, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: Util.isDesktop + ? STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .infoItemLabel, + ) + : STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .infoItemLabel, + ), + ), + Text( + ref.watch(pAmountFormatter(coin)).format( + Amount( + rawValue: BigInt.from(kNameNewAmountSats), + fractionDigits: coin.fractionDigits, + ), + ), + style: Util.isDesktop + ? STextStyles.w500_14(context) + : STextStyles.w500_12(context), + ), + ], + ), + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + ConditionalParent( + condition: !Util.isDesktop, + builder: (child) => Row( + children: [child], + ), + child: CustomTextButton( + text: _settingsHidden ? "More settings" : "Hide settings", + onTap: () { + setState(() { + _settingsHidden = !_settingsHidden; + }); + }, + ), + ), + if (!_settingsHidden) + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + if (!_settingsHidden) + if (_dnsRecords.isEmpty) + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Add DNS records to your domain name", + style: STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + if (!_settingsHidden) + ConditionalParent( + condition: !Util.isDesktop, + builder: (child) => Expanded(child: child), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ..._dnsRecords.map( + (e) => DNSRecordCard( + key: ValueKey(e), + record: e, + onRemoveTapped: () => setState(() { + _dnsRecords.remove(e); + }), + ), + ), + SizedBox( + height: Util.isDesktop ? 16 : 8, + ), + SecondaryButton( + label: _dnsRecords.isEmpty + ? "Add DNS record" + : "Add another DNS record", + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: _addRecord, + ), + ], + ), + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + if (!Util.isDesktop && _settingsHidden) const Spacer(), + PrimaryButton( + label: "Buy", + // width: Util.isDesktop ? 160 : double.infinity, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: _preRegister, + ), + SizedBox( + height: Util.isDesktop ? 32 : 16, + ), + ], + ), + ); + } +} + +(String, String, String) _computeScriptNameNew((String, Uint8List) args) { + return scriptNameNew(args.$1, args.$2); +} + +class DNSRecordCard extends StatelessWidget { + const DNSRecordCard({ + super.key, + required this.record, + required this.onRemoveTapped, + }); + + final DNSRecord record; + final VoidCallback onRemoveTapped; + + String get _extraInfo { + if (record.type == DNSRecordType.A) { + // TODO error handling + return " - ${DNSAddressType.values.firstWhere((e) => e.key == record.data.keys.first).name}"; + } + + return ""; + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${record.type.name}$_extraInfo", + ), + CustomTextButton( + text: "Remove", + onTap: onRemoveTapped, + ), + ], + ), + Text(record.getValueString()), + ], + ), + ); + } +} diff --git a/lib/pages/namecoin_names/confirm_name_transaction_view.dart b/lib/pages/namecoin_names/confirm_name_transaction_view.dart new file mode 100644 index 000000000..ec0fc926a --- /dev/null +++ b/lib/pages/namecoin_names/confirm_name_transaction_view.dart @@ -0,0 +1,1081 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:async'; +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../models/isar/models/transaction_note.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; +import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; +import '../../providers/db/main_db_provider.dart'; +import '../../providers/global/secure_store_provider.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../themes/stack_colors.dart'; +import '../../themes/theme_providers.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/models/tx_data.dart'; +import '../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/icon_widgets/x_icon.dart'; +import '../../widgets/rounded_container.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../widgets/stack_text_field.dart'; +import '../../widgets/textfield_icon_button.dart'; +import '../pinpad_views/lock_screen_view.dart'; +import '../send_view/sub_widgets/sending_transaction_dialog.dart'; + +class ConfirmNameTransactionView extends ConsumerStatefulWidget { + const ConfirmNameTransactionView({ + super.key, + required this.txData, + required this.walletId, + }); + + static const String routeName = "/confirmNameTransactionView"; + + final TxData txData; + final String walletId; + + @override + ConsumerState createState() => + _ConfirmNameTransactionViewState(); +} + +class _ConfirmNameTransactionViewState + extends ConsumerState { + late final String walletId; + late final bool isDesktop; + + late final FocusNode _noteFocusNode; + late final TextEditingController noteController; + + Future _attemptSend() async { + final wallet = ref.read(pWallets).getWallet(walletId); + final coin = wallet.info.coin; + + final sendProgressController = ProgressAndSuccessController(); + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return SendingTransactionDialog( + coin: coin, + controller: sendProgressController, + ); + }, + ), + ); + + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + final List txids = []; + Future txDataFuture; + + final note = noteController.text; + + try { + txDataFuture = wallet.confirmSend(txData: widget.txData); + + // await futures in parallel + final futureResults = await Future.wait([ + txDataFuture, + time, + ]); + + final txData = (futureResults.first as TxData); + + sendProgressController.triggerSuccess?.call(); + + // await futures in parallel + await Future.wait([ + // wait for animation + Future.delayed(const Duration(seconds: 5)), + + // associated name data for reg tx + ref.read(secureStoreProvider).write( + key: nameSaltKeyBuilder( + txData.txid!, + walletId, + txData.opNameState!.outputPosition, + ), + value: encodeNameSaltData( + txData.opNameState!.name, + txData.opNameState!.saltHex, + txData.opNameState!.value, + ), + ), + ]); + + txids.add(txData.txid!); + ref.refresh(desktopUseUTXOs); + + // save note + for (final txid in txids) { + await ref.read(mainDBProvider).putTransactionNote( + TransactionNote( + walletId: walletId, + txid: txid, + value: note, + ), + ); + } + + unawaited(wallet.refresh()); + + if (mounted) { + // pop sending dialog + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + // pop confirm send view + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + // pop buy popup + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + + // pop name details view + if (txData.opNameState!.type == OpName.nameUpdate) { + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + } + } + } catch (e, s) { + const niceError = "Broadcast name transaction failed"; + + Logging.instance.e(niceError, error: e, stackTrace: s); + + if (mounted) { + // pop sending dialog + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + niceError, + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Flexible( + child: SingleChildScrollView( + child: SelectableText( + e.toString(), + style: STextStyles.smallMed14(context), + ), + ), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: Navigator.of(context).pop, + ), + ), + ], + ), + ], + ), + ), + ); + } else { + return StackDialog( + title: niceError, + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } + }, + ); + } + } + } + + @override + void initState() { + isDesktop = Util.isDesktop; + walletId = widget.walletId; + _noteFocusNode = FocusNode(); + noteController = TextEditingController(); + noteController.text = widget.txData.note ?? ""; + + super.initState(); + } + + @override + void dispose() { + noteController.dispose(); + + _noteFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(pWalletCoin(walletId)); + + final unit = coin.ticker; + + final fee = widget.txData.fee; + final amountWithoutChange = widget.txData.amountWithoutChange!; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ), + ); + }, + ), + ), + ), + child: ConditionalParent( + condition: isDesktop, + builder: (child) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + AppBarBackButton( + size: 40, + iconSize: 24, + onPressed: () => Navigator.of( + context, + rootNavigator: true, + ).pop(), + ), + Text( + "Confirm transaction", + style: STextStyles.desktopH3(context), + ), + ], + ), + Flexible( + child: SingleChildScrollView( + child: child, + ), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, + children: [ + if (!isDesktop) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Confirm Name transaction", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Name", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + Text( + widget.txData.opNameState!.name, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Value", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + Text( + widget.txData.opNameState!.value, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Recipient", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + Text( + widget.txData.recipients!.first.address, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: STextStyles.smallMed12(context), + ), + SelectableText( + ref.watch(pAmountFormatter(coin)).format( + amountWithoutChange, + ), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction fee", + style: STextStyles.smallMed12(context), + ), + SelectableText( + ref.watch(pAmountFormatter(coin)).format(fee!), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + if (widget.txData.fee != null && widget.txData.vSize != null) + const SizedBox( + height: 12, + ), + if (widget.txData.fee != null && widget.txData.vSize != null) + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "sats/vByte", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + "~${fee.raw.toInt() ~/ widget.txData.vSize!}", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + if (widget.txData.note != null && + widget.txData.note!.isNotEmpty) + const SizedBox( + height: 12, + ), + if (widget.txData.note != null && + widget.txData.note!.isNotEmpty) + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Note", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + widget.txData.note!, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + ], + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + right: 32, + bottom: 50, + ), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: + Theme.of(context).extension()!.background, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .background, + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 22, + ), + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.send, + ), + ), + ), + width: 32, + height: 32, + ), + const SizedBox( + width: 16, + ), + Text( + "Send $unit Name transaction", + style: STextStyles.desktopTextMedium(context), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Name", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + widget.txData.opNameState!.name, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + Container( + height: 1, + color: Theme.of(context) + .extension()! + .background, + ), + Padding( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Value", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + widget.txData.opNameState!.value, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + ], + ), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + "Note (optional)", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + autocorrect: isDesktop ? false : true, + enableSuggestions: isDesktop ? false : true, + controller: noteController, + focusNode: _noteFocusNode, + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Type something...", + _noteFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ), + suffixIcon: noteController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState( + () => noteController.text = "", + ); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 20, + ), + ], + ), + ), + + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + ), + child: Text( + "Amount", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 32, + right: 32, + ), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Builder( + builder: (context) { + final externalCalls = ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ); + String fiatAmount = "N/A"; + + if (externalCalls) { + final price = ref + .read( + priceAnd24hChangeNotifierProvider, + ) + .getPrice(coin) + .item1; + if (price > Decimal.zero) { + fiatAmount = (amountWithoutChange.decimal * price) + .toAmount(fractionDigits: 2) + .fiatString( + locale: ref + .read( + localeServiceChangeNotifierProvider, + ) + .locale, + ); + } + } + + return Row( + children: [ + SelectableText( + ref.watch(pAmountFormatter(coin)).format( + amountWithoutChange, + ), + style: STextStyles.itemSubtitle( + context, + ), + ), + if (externalCalls) + Text( + " | ", + style: STextStyles.itemSubtitle( + context, + ), + ), + if (externalCalls) + SelectableText( + "~$fiatAmount ${ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), + )}", + style: STextStyles.itemSubtitle( + context, + ), + ), + ], + ); + }, + ), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + ), + child: Text( + "Recipient", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 32, + right: 32, + ), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: SelectableText( + widget.txData.recipients!.first.address, + style: STextStyles.itemSubtitle(context), + ), + ), + ), + // todo amoutn here + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + ), + child: Text( + "Transaction fee", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 32, + right: 32, + ), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: SelectableText( + ref.watch(pAmountFormatter(coin)).format(fee!), + style: STextStyles.itemSubtitle(context), + ), + ), + ), + if (isDesktop && + widget.txData.fee != null && + widget.txData.vSize != null) + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 32, + ), + child: Text( + "sats/vByte", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + if (isDesktop && + widget.txData.fee != null && + widget.txData.vSize != null) + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 32, + right: 32, + ), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: SelectableText( + "~${fee!.raw.toInt() ~/ widget.txData.vSize!}", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + if (!isDesktop) const Spacer(), + SizedBox( + height: isDesktop ? 23 : 12, + ), + Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 32, + ) + : const EdgeInsets.all(0), + child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ) + : const EdgeInsets.all(12), + color: Theme.of(context) + .extension()! + .snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isDesktop ? "Total amount to send" : "Total amount", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ) + : STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + ), + SelectableText( + ref + .watch(pAmountFormatter(coin)) + .format(amountWithoutChange + fee!), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ) + : STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ), + ], + ), + ), + ), + SizedBox( + height: isDesktop ? 28 : 16, + ), + Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 32, + ) + : const EdgeInsets.all(0), + child: PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + final dynamic unlocked; + + if (isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend( + coin: coin, + ), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: + const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } + + if (mounted) { + if (unlocked == true) { + unawaited(_attemptSend()); + } else { + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: Util.isDesktop + ? "Invalid passphrase" + : "Invalid PIN", + context: context, + ), + ); + } + } + } + }, + ), + ), + if (isDesktop) + const SizedBox( + height: 32, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/namecoin_names/manage_domain_view.dart b/lib/pages/namecoin_names/manage_domain_view.dart new file mode 100644 index 000000000..6d2679d60 --- /dev/null +++ b/lib/pages/namecoin_names/manage_domain_view.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; + +import '../../models/isar/models/blockchain_data/utxo.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/toggle.dart'; +import 'sub_widgets/transfer_option_widget.dart'; +import 'sub_widgets/update_option_widget.dart'; + +class ManageDomainView extends StatefulWidget { + const ManageDomainView({ + super.key, + required this.walletId, + required this.utxo, + }); + + final String walletId; + final UTXO utxo; + + static const routeName = "/manageDomainView"; + + @override + State createState() => _ManageDomainViewState(); +} + +class _ManageDomainViewState extends State { + bool _onTransfer = true; + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + leading: const AppBarBackButton(), + titleSpacing: 0, + title: Text( + "Manage domain", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + SizedBox( + height: 48, + child: Toggle( + key: UniqueKey(), + onColor: + Theme.of(context).extension()!.popupBG, + offColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + onText: "Transfer", + offText: "Update", + isOn: !_onTransfer, + onValueChanged: (value) { + FocusManager.instance.primaryFocus?.unfocus(); + setState(() { + _onTransfer = !value; + }); + }, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + Expanded( + child: IndexedStack( + index: _onTransfer ? 0 : 1, + children: [ + TransferOptionWidget( + walletId: widget.walletId, + utxo: widget.utxo, + ), + UpdateOptionWidget( + walletId: widget.walletId, + utxo: widget.utxo, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/namecoin_names/namecoin_names_home_view.dart b/lib/pages/namecoin_names/namecoin_names_home_view.dart new file mode 100644 index 000000000..8970de7c6 --- /dev/null +++ b/lib/pages/namecoin_names/namecoin_names_home_view.dart @@ -0,0 +1,251 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_app_bar.dart'; +import '../../widgets/desktop/desktop_scaffold.dart'; +import '../../widgets/toggle.dart'; +import 'sub_widgets/buy_domain_option_widget.dart'; +import 'sub_widgets/manage_domains_option_widget.dart'; + +class NamecoinNamesHomeView extends ConsumerStatefulWidget { + const NamecoinNamesHomeView({ + super.key, + required this.walletId, + }); + + final String walletId; + + static const String routeName = "/namecoinNamesHomeView"; + + @override + ConsumerState createState() => + _NamecoinNamesHomeViewState(); +} + +class _NamecoinNamesHomeViewState extends ConsumerState { + bool _onManage = true; + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + final isDesktop = Util.isDesktop; + + return MasterScaffold( + isDesktop: isDesktop, + appBar: isDesktop + ? DesktopAppBar( + isCompactHeight: true, + background: Theme.of(context).extension()!.popupBG, + leading: Row( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 24, + right: 20, + ), + child: AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + ), + SvgPicture.asset( + Assets.svg.robotHead, + width: 32, + height: 32, + color: Theme.of(context).extension()!.textDark, + ), + const SizedBox( + width: 10, + ), + Text( + "Domains", + style: STextStyles.desktopH3(context), + ), + ], + ), + ) + : AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "Domains", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ), + body: ConditionalParent( + condition: !isDesktop, + builder: (child) => SafeArea( + child: Padding( + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + ), + child: child, + ), + ), + child: Util.isDesktop + ? Padding( + padding: const EdgeInsets.only( + top: 24, + left: 24, + right: 24, + ), + child: Row( + children: [ + SizedBox( + width: 460, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Buy domain", + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + ), + ], + ), + const SizedBox( + height: 14, + ), + Flexible( + child: BuyDomainOptionWidget( + walletId: widget.walletId, + ), + ), + ], + ), + ), + const SizedBox( + width: 24, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Manage domains", + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + ), + ], + ), + const SizedBox( + height: 14, + ), + Flexible( + child: SingleChildScrollView( + child: ManageDomainsOptionWidget( + walletId: widget.walletId, + ), + ), + ), + ], + ), + ), + ], + ), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: 48, + child: Toggle( + key: UniqueKey(), + onColor: + Theme.of(context).extension()!.popupBG, + offColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + onText: "Buy domain", + offText: "Manage domains", + isOn: !_onManage, + onValueChanged: (value) { + FocusManager.instance.primaryFocus?.unfocus(); + setState(() { + _onManage = !value; + }); + }, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + Expanded( + child: IndexedStack( + index: _onManage ? 0 : 1, + children: [ + BuyDomainOptionWidget( + walletId: widget.walletId, + ), + LayoutBuilder( + builder: (context, constraints) { + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: SingleChildScrollView( + child: IntrinsicHeight( + child: ManageDomainsOptionWidget( + walletId: widget.walletId, + ), + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/namecoin_names/sub_widgets/buy_domain_option_widget.dart b/lib/pages/namecoin_names/sub_widgets/buy_domain_option_widget.dart new file mode 100644 index 000000000..1040dda4f --- /dev/null +++ b/lib/pages/namecoin_names/sub_widgets/buy_domain_option_widget.dart @@ -0,0 +1,396 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../providers/providers.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/extensions/impl/string.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/show_loading.dart'; +import '../../../utilities/text_formatters.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; +import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/stack_dialog.dart'; +import '../buy_domain_view.dart'; + +class BuyDomainOptionWidget extends ConsumerStatefulWidget { + const BuyDomainOptionWidget({super.key, required this.walletId}); + + final String walletId; + + @override + ConsumerState createState() => _BuyDomainWidgetState(); +} + +class _BuyDomainWidgetState extends ConsumerState { + static const kMaxByteLength = nameMaxLength - 2; // subtract length of "d/" + + final _nameController = TextEditingController(); + final _nameFieldFocus = FocusNode(); + + String? get formattedNameInField { + if (_nameController.text.isNotEmpty) { + if (_nameController.text.startsWith("d/")) { + return _nameController.text; + } else { + return "d/${_nameController.text}"; + } + } + return null; + } + + bool _isAvailable = false; + String? _lastLookedUpName; + + bool _lookupLock = false; + Future _lookup() async { + if (_lookupLock) return; + _lookupLock = true; + try { + _isAvailable = false; + + _lastLookedUpName = formattedNameInField; + final result = await showLoading( + whileFuture: + (ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet) + .lookupName(_lastLookedUpName!), + context: context, + message: "Searching...", + onException: (e) => throw e, + rootNavigator: Util.isDesktop, + delay: const Duration(seconds: 2), + ); + + _isAvailable = result?.nameState == NameState.available; + + if (mounted) { + setState(() {}); + } + + Logging.instance.i("LOOKUP RESULT: $result"); + } catch (e, s) { + Logging.instance.e("_lookup failed", error: e, stackTrace: s); + + String? err; + if (e.toString().contains("Contains invalid characters")) { + err = "Contains invalid characters"; + } + + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Name lookup failed", + message: err, + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 600 : null, + ), + ); + } + } finally { + _lookupLock = false; + } + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _nameFieldFocus.requestFocus(); + } + }); + } + + @override + void dispose() { + _nameController.dispose(); + _nameFieldFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final double dotBitBoxLength = Util.isDesktop ? 100 : 74; + return Column( + crossAxisAlignment: + Util.isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.center, + children: [ + SizedBox( + height: 48, + child: Row( + children: [ + Expanded( + child: Container( + height: 48, + width: 100, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), // Adjust radius as needed + bottomLeft: + Radius.circular(Constants.size.circularBorderRadius), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: TextField( + inputFormatters: [ + Utf8ByteLengthLimitingTextInputFormatter( + kMaxByteLength, + ), + ], + textInputAction: TextInputAction.search, + focusNode: _nameFieldFocus, + controller: _nameController, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.zero, + prefixIcon: Padding( + padding: const EdgeInsets.all(14), + child: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + ), + fillColor: Colors.transparent, + hintText: "Find a domain name", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + onSubmitted: (_) { + if (_nameController.text.isNotEmpty) { + _lookup(); + } + }, + onChanged: (value) { + // trigger look up button enabled/disabled state change + setState(() {}); + }, + ), + ), + ], + ), + ), + ), + Container( + height: 48, + width: dotBitBoxLength, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .buttonBackPrimary, + borderRadius: BorderRadius.only( + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), // Adjust radius as needed + bottomRight: + Radius.circular(Constants.size.circularBorderRadius), + ), + ), + child: Center( + child: Text( + ".bit", + style: STextStyles.w600_14(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + ), + ), + ), + ], + ), + ), + const SizedBox( + height: 4, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only(right: dotBitBoxLength), + child: Builder( + builder: (context) { + final length = + _nameController.text.toUint8ListFromUtf8.lengthInBytes; + return Text( + "$length/$kMaxByteLength", + style: STextStyles.w500_10(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle2, + ), + ); + }, + ), + ), + ], + ), + SizedBox( + height: Util.isDesktop ? 24 : 16, + ), + SecondaryButton( + label: "Lookup", + enabled: _nameController.text.isNotEmpty, + // width: Util.isDesktop ? 160 : double.infinity, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: _lookup, + ), + const SizedBox( + height: 32, + ), + if (_lastLookedUpName != null) + _NameCard( + walletId: widget.walletId, + isAvailable: _isAvailable, + formattedName: _lastLookedUpName!, + ), + ], + ); + } +} + +class _NameCard extends ConsumerWidget { + const _NameCard({ + super.key, + required this.walletId, + required this.isAvailable, + required this.formattedName, + }); + + final String walletId; + final bool isAvailable; + final String formattedName; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final availability = isAvailable ? "Available" : "Unavailable"; + final color = isAvailable + ? Theme.of(context).extension()!.accentColorGreen + : Theme.of(context).extension()!.accentColorRed; + + final style = (Util.isDesktop + ? STextStyles.w500_16(context) + : STextStyles.w500_12(context)); + + return RoundedWhiteContainer( + padding: EdgeInsets.all(Util.isDesktop ? 24 : 16), + child: IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${formattedName.substring(2)}.bit", + style: style, + ), + const SizedBox( + height: 4, + ), + Text( + availability, + style: style.copyWith( + color: color, + ), + ), + ], + ), + ), + Column( + children: [ + PrimaryButton( + label: "Buy domain", + enabled: isAvailable, + buttonHeight: + Util.isDesktop ? ButtonHeight.m : ButtonHeight.l, + width: Util.isDesktop ? 140 : 120, + onPressed: () async { + if (context.mounted) { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => SDialog( + child: SizedBox( + width: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Buy domain", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: BuyDomainView( + walletId: walletId, + domainName: formattedName, + ), + ), + ], + ), + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + BuyDomainView.routeName, + arguments: ( + walletId: walletId, + domainName: formattedName + ), + ); + } + } + }, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/namecoin_names/sub_widgets/manage_domains_option_widget.dart b/lib/pages/namecoin_names/sub_widgets/manage_domains_option_widget.dart new file mode 100644 index 000000000..f4a7df875 --- /dev/null +++ b/lib/pages/namecoin_names/sub_widgets/manage_domains_option_widget.dart @@ -0,0 +1,104 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/blockchain_data/utxo.dart'; +import '../../../providers/db/main_db_provider.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; +import 'owned_name_card.dart'; + +class ManageDomainsOptionWidget extends ConsumerStatefulWidget { + const ManageDomainsOptionWidget({ + super.key, + required this.walletId, + }); + + final String walletId; + + @override + ConsumerState createState() => + _ManageDomainsWidgetState(); +} + +class _ManageDomainsWidgetState + extends ConsumerState { + double _tempWidth = 0; + double? _width; + int _count = 0; + + void _sillyHack(double value, int length) { + if (value > _tempWidth) _tempWidth = value; + _count++; + if (_count == length) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _width = _tempWidth; + _tempWidth = 0; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + final height = ref.watch(pWalletChainHeight(widget.walletId)); + return StreamBuilder( + stream: ref.watch( + mainDBProvider.select( + (s) => s.isar.utxos + .where() + .walletIdEqualTo(widget.walletId) + .filter() + .otherDataIsNotNull() + .watch(fireImmediately: true), + ), + ), + builder: (context, snapshot) { + List<(UTXO, OpNameData)> list = []; + if (snapshot.hasData) { + list = snapshot.data!.map((utxo) { + final data = jsonDecode(utxo.otherData!) as Map; + + final nameData = jsonDecode(data["nameOpData"] as String) as Map; + + return ( + utxo, + OpNameData(nameData.cast(), utxo.blockHeight ?? height) + ); + }).toList(growable: false); + } + + return Column( + children: [ + ...list.map( + (e) => Padding( + padding: const EdgeInsets.only( + bottom: 10, + ), + child: OwnedNameCard( + key: ValueKey(e), + utxo: e.$1, + opNameData: e.$2, + firstColWidth: _width, + calculatedFirstColWidth: (value) => _sillyHack( + value, + list.length, + ), + ), + ), + ), + SizedBox( + height: Util.isDesktop ? 14 : 6, + ), + ], + ); + }, + ); + } +} diff --git a/lib/pages/namecoin_names/sub_widgets/name_details.dart b/lib/pages/namecoin_names/sub_widgets/name_details.dart new file mode 100644 index 000000000..7fa77f807 --- /dev/null +++ b/lib/pages/namecoin_names/sub_widgets/name_details.dart @@ -0,0 +1,731 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/isar_models.dart'; +import '../../../providers/db/main_db_provider.dart'; +import '../../../providers/global/secure_store_provider.dart'; +import '../../../providers/global/wallets_provider.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/background.dart'; +import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../widgets/custom_buttons/blue_text_button.dart'; +import '../../../widgets/custom_buttons/simple_copy_button.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; +import '../../../widgets/rounded_container.dart'; +import '../../wallet_view/transaction_views/transaction_details_view.dart'; +import '../manage_domain_view.dart'; +import 'transfer_option_widget.dart'; +import 'update_option_widget.dart'; + +class NameDetailsView extends ConsumerStatefulWidget { + const NameDetailsView({ + super.key, + required this.utxoId, + required this.walletId, + }); + + static const routeName = "/namecoinNameDetails"; + + final Id utxoId; + final String walletId; + + @override + ConsumerState createState() => _ManageDomainsWidgetState(); +} + +class _ManageDomainsWidgetState extends ConsumerState { + late Stream streamUTXO; + UTXO? utxo; + OpNameData? opNameData; + + String? constructedName, value; + + Stream? streamLabel; + AddressLabel? label; + + void setUtxo(UTXO? utxo, int currentHeight) { + if (utxo != null) { + this.utxo = utxo; + final data = jsonDecode(utxo.otherData!) as Map; + + final nameData = jsonDecode(data["nameOpData"] as String) as Map; + opNameData = + OpNameData(nameData.cast(), utxo.blockHeight ?? currentHeight); + + _setName(); + } + } + + void _setName() { + try { + constructedName = opNameData!.constructedName; + value = opNameData!.value; + } catch (_) { + if (opNameData?.op == OpName.nameNew) { + ref + .read(secureStoreProvider) + .read( + key: nameSaltKeyBuilder( + utxo!.txid, + widget.walletId, + utxo!.vout, + ), + ) + .then((onValue) { + if (onValue != null) { + final data = (jsonDecode(onValue) as Map).cast(); + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = data["name"]!; + value = data["value"]!; + if (mounted) { + setState(() {}); + } + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = "UNKNOWN"; + value = ""; + if (mounted) { + setState(() {}); + } + }); + } + }); + } + } + } + + (String, Color) _getExpiry(int currentChainHeight, StackColors theme) { + final String message; + final Color color; + + if (utxo?.blockHash == null) { + message = "Expires in $blocksNameExpiration+ blocks"; + color = theme.accentColorGreen; + } else { + final remaining = opNameData?.expiredBlockLeft( + currentChainHeight, + false, + ); + final semiRemaining = opNameData?.expiredBlockLeft( + currentChainHeight, + true, + ); + + if (remaining == null) { + color = theme.accentColorRed; + message = "Expired"; + } else { + message = "Expires in $remaining blocks"; + if (semiRemaining == null) { + color = theme.accentColorYellow; + } else { + color = theme.accentColorGreen; + } + } + } + + return (message, color); + } + + bool _checkConfirmedUtxo(int currentHeight) { + return (ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet) + .checkUtxoConfirmed( + utxo!, + currentHeight, + ); + } + + @override + void initState() { + super.initState(); + + setUtxo( + ref + .read(mainDBProvider) + .isar + .utxos + .where() + .idEqualTo(widget.utxoId) + .findFirstSync(), + ref.read(pWalletChainHeight(widget.walletId)), + ); + + _setName(); + + if (utxo?.address != null) { + label = ref.read(mainDBProvider).getAddressLabelSync( + widget.walletId, + utxo!.address!, + ); + + if (label != null) { + streamLabel = ref.read(mainDBProvider).watchAddressLabel(id: label!.id); + } + } + + streamUTXO = ref.read(mainDBProvider).watchUTXO(id: widget.utxoId); + } + + @override + Widget build(BuildContext context) { + final currentHeight = ref.watch(pWalletChainHeight(widget.walletId)); + + final (message, color) = _getExpiry( + currentHeight, + Theme.of(context).extension()!, + ); + + final canManage = utxo != null && + _checkConfirmedUtxo(currentHeight) && + (opNameData?.op == OpName.nameUpdate || + opNameData?.op == OpName.nameFirstUpdate); + + return ConditionalParent( + condition: !Util.isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: Colors.transparent, + // Theme.of(context).extension()!.background, + leading: const AppBarBackButton(), + title: Text( + "Domain details", + style: STextStyles.navBarTitle(context), + ), + actions: canManage + ? [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: CustomTextButton( + key: const Key("addAddressBookEntryFavoriteButtonKey"), + text: "Manage", + onTap: () { + Navigator.of(context).pushNamed( + ManageDomainView.routeName, + arguments: (walletId: widget.walletId, utxo: utxo!), + ); + }, + ), + ), + ] + : null, + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ), + ); + }, + ), + ), + ), + ), + child: ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return SizedBox( + width: 641, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Domain details", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + top: 10, + ), + child: RoundedContainer( + padding: EdgeInsets.zero, + color: Colors.transparent, + borderColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: child, + ), + ), + if (canManage) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Transfer", + buttonHeight: ButtonHeight.l, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return SDialog( + child: SizedBox( + width: 641, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Transfer domain", + style: STextStyles.desktopH3( + context, + ), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + top: 16, + ), + child: TransferOptionWidget( + walletId: widget.walletId, + utxo: utxo!, + ), + ), + ], + ), + ), + ); + }, + ); + }, + ), + ), + const SizedBox( + width: 32, + ), + Expanded( + child: SecondaryButton( + label: "Update", + buttonHeight: ButtonHeight.l, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return SDialog( + child: SizedBox( + width: 641, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Update domain", + style: STextStyles.desktopH3( + context, + ), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: UpdateOptionWidget( + walletId: widget.walletId, + utxo: utxo!, + ), + ), + ], + ), + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + if (canManage) + const SizedBox( + height: 32, + ), + ], + ), + ); + }, + child: StreamBuilder( + stream: streamUTXO, + builder: (context, snapshot) { + if (snapshot.hasData) { + setUtxo(snapshot.data!, currentHeight); + } + + return utxo == null + ? Center( + child: Text( + "Missing output. Was it used recently?", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorRed, + ), + ), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // if (!isDesktop) + // const SizedBox( + // height: 10, + // ), + RoundedContainer( + padding: const EdgeInsets.all(12), + color: Util.isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + constructedName ?? "", + style: STextStyles.pageTitleH2(context), + ), + if (Util.isDesktop) + SelectableText( + opNameData!.op.name, + style: STextStyles.w500_14(context), + ), + ], + ), + if (!Util.isDesktop) + SelectableText( + opNameData!.op.name, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: Util.isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Value", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + const SizedBox( + height: 4, + ), + SelectableText( + value ?? "", + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: Util.isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + Util.isDesktop + ? IconCopyButton( + data: utxo!.address!, + ) + : SimpleCopyButton( + data: utxo!.address!, + ), + ], + ), + const SizedBox( + height: 4, + ), + SelectableText( + utxo!.address!, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + if (label != null && label!.value.isNotEmpty) + const _Div(), + if (label != null && label!.value.isNotEmpty) + RoundedContainer( + padding: Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: Util.isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address label", + style: + STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + Util.isDesktop + ? IconCopyButton( + data: label!.value, + ) + : SimpleCopyButton( + data: label!.value, + ), + ], + ), + const SizedBox( + height: 4, + ), + SelectableText( + label!.value, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: Util.isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction ID", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + Util.isDesktop + ? IconCopyButton( + data: utxo!.txid, + ) + : SimpleCopyButton( + data: utxo!.txid, + ), + ], + ), + const SizedBox( + height: 4, + ), + SelectableText( + utxo!.txid, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: Util.isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Expiry", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + const SizedBox( + height: 4, + ), + SelectableText( + message, + style: STextStyles.w500_14(context).copyWith( + color: color, + ), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: Util.isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Confirmations", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + const SizedBox( + height: 4, + ), + SelectableText( + "${utxo!.getConfirmations(currentHeight)}", + style: STextStyles.w500_14(context), + ), + ], + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +class _Div extends StatelessWidget { + const _Div({super.key}); + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return Container( + width: double.infinity, + height: 1.0, + color: Theme.of(context).extension()!.textFieldDefaultBG, + ); + } else { + return const SizedBox( + height: 12, + ); + } + } +} diff --git a/lib/pages/namecoin_names/sub_widgets/owned_name_card.dart b/lib/pages/namecoin_names/sub_widgets/owned_name_card.dart new file mode 100644 index 000000000..b345966c6 --- /dev/null +++ b/lib/pages/namecoin_names/sub_widgets/owned_name_card.dart @@ -0,0 +1,227 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/isar_models.dart'; +import '../../../providers/global/secure_store_provider.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; +import '../../../widgets/rounded_white_container.dart'; +import 'name_details.dart'; + +class OwnedNameCard extends ConsumerStatefulWidget { + const OwnedNameCard({ + super.key, + required this.opNameData, + required this.utxo, + this.firstColWidth, + this.calculatedFirstColWidth, + }); + + final OpNameData opNameData; + final UTXO utxo; + + final double? firstColWidth; + final void Function(double)? calculatedFirstColWidth; + + @override + ConsumerState createState() => _OwnedNameCardState(); +} + +class _OwnedNameCardState extends ConsumerState { + String? constructedName, value; + + (String, Color) _getExpiry(int currentChainHeight, StackColors theme) { + final String message; + final Color color; + + if (widget.utxo.blockHash == null) { + message = "Expires in $blocksNameExpiration+ blocks"; + color = theme.accentColorGreen; + } else { + final remaining = widget.opNameData.expiredBlockLeft( + currentChainHeight, + false, + ); + final semiRemaining = widget.opNameData.expiredBlockLeft( + currentChainHeight, + true, + ); + + if (remaining == null) { + color = theme.accentColorRed; + message = "Expired"; + } else { + message = "Expires in $remaining blocks"; + if (semiRemaining == null) { + color = theme.accentColorYellow; + } else { + color = theme.accentColorGreen; + } + } + } + + return (message, color); + } + + bool _lock = false; + + Future _showDetails() async { + if (_lock) return; + _lock = true; + try { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => SDialog( + child: NameDetailsView( + utxoId: widget.utxo.id, + walletId: widget.utxo.walletId, + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + NameDetailsView.routeName, + arguments: ( + widget.utxo.id, + widget.utxo.walletId, + ), + ); + } + } finally { + _lock = false; + } + } + + void _setName() { + try { + constructedName = widget.opNameData.constructedName; + value = widget.opNameData.value; + } catch (_) { + if (widget.opNameData.op == OpName.nameNew) { + ref + .read(secureStoreProvider) + .read( + key: nameSaltKeyBuilder( + widget.utxo.txid, + widget.utxo.walletId, + widget.utxo.vout, + ), + ) + .then((onValue) { + if (onValue != null) { + final data = (jsonDecode(onValue) as Map).cast(); + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = data["name"]!; + value = data["value"]!; + if (mounted) { + setState(() {}); + } + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = "UNKNOWN"; + value = ""; + if (mounted) { + setState(() {}); + } + }); + } + }); + } + } + } + + @override + void initState() { + super.initState(); + _setName(); + } + + double _callbackWidth = 0; + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final (message, color) = _getExpiry( + ref.watch(pWalletChainHeight(widget.utxo.walletId)), + Theme.of(context).extension()!, + ); + + return RoundedWhiteContainer( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ConditionalParent( + condition: widget.firstColWidth != null && Util.isDesktop, + builder: (child) => ConstrainedBox( + constraints: BoxConstraints(maxWidth: widget.firstColWidth!), + child: child, + ), + child: ConditionalParent( + condition: widget.firstColWidth == null && Util.isDesktop, + builder: (child) => LayoutBuilder( + builder: (context, constraints) { + if (widget.firstColWidth == null && + _callbackWidth != constraints.maxWidth) { + _callbackWidth = constraints.maxWidth; + widget.calculatedFirstColWidth?.call(_callbackWidth); + } + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: constraints.maxWidth), + child: child, + ); + }, + ), + child: Padding( + padding: const EdgeInsets.only(right: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText(constructedName ?? ""), + const SizedBox( + height: 8, + ), + SelectableText( + message, + style: STextStyles.w500_12(context).copyWith( + color: color, + ), + ), + ], + ), + ), + ), + ), + if (Util.isDesktop) + Expanded( + child: SelectableText( + value ?? "", + style: STextStyles.w500_12(context), + ), + ), + if (Util.isDesktop) + const SizedBox( + width: 12, + ), + PrimaryButton( + label: "Details", + buttonHeight: Util.isDesktop ? ButtonHeight.xs : ButtonHeight.l, + onPressed: _showDetails, + ), + ], + ), + ); + } +} diff --git a/lib/pages/namecoin_names/sub_widgets/transfer_option_widget.dart b/lib/pages/namecoin_names/sub_widgets/transfer_option_widget.dart new file mode 100644 index 000000000..dcd8e1282 --- /dev/null +++ b/lib/pages/namecoin_names/sub_widgets/transfer_option_widget.dart @@ -0,0 +1,480 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/blockchain_data/utxo.dart'; +import '../../../providers/providers.dart'; +import '../../../utilities/address_utils.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/barcode_scanner_interface.dart'; +import '../../../utilities/clipboard_interface.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../wallets/models/name_op_state.dart'; +import '../../../wallets/models/tx_data.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; +import '../../../widgets/icon_widgets/addressbook_icon.dart'; +import '../../../widgets/icon_widgets/clipboard_icon.dart'; +import '../../../widgets/icon_widgets/qrcode_icon.dart'; +import '../../../widgets/icon_widgets/x_icon.dart'; +import '../../../widgets/stack_dialog.dart'; +import '../../../widgets/stack_text_field.dart'; +import '../../../widgets/textfield_icon_button.dart'; +import '../../address_book_views/address_book_view.dart'; +import '../../send_view/sub_widgets/building_transaction_dialog.dart'; +import '../confirm_name_transaction_view.dart'; + +class TransferOptionWidget extends ConsumerStatefulWidget { + const TransferOptionWidget({ + super.key, + required this.walletId, + required this.utxo, + this.clipboard = const ClipboardWrapper(), + this.barcodeScanner = const BarcodeScannerWrapper(), + }); + + final String walletId; + final UTXO utxo; + + final ClipboardInterface clipboard; + final BarcodeScannerInterface barcodeScanner; + + @override + ConsumerState createState() => + _TransferOptionWidgetState(); +} + +class _TransferOptionWidgetState extends ConsumerState { + late final String walletId; + late final ClipboardInterface clipboard; + late final BarcodeScannerInterface scanner; + late final TextEditingController _addressController; + late final FocusNode _addressFocusNode; + + String? _address; + + bool _previewLock = false; + Future _preview() async { + if (_previewLock) return; + _previewLock = true; + + // wait for keyboard to disappear + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 100), + ); + + try { + final wallet = ref.read(pWallets).getWallet(walletId) as NamecoinWallet; + + bool wasCancelled = false; + + if (mounted) { + if (Util.isDesktop) { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 400, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.all(32), + child: BuildingTransactionDialog( + coin: wallet.info.coin, + isSpark: false, + onCancel: () { + wasCancelled = true; + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + ), + ); + }, + ), + ); + } else { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + coin: wallet.info.coin, + isSpark: false, + onCancel: () { + wasCancelled = true; + Navigator.of(context).pop(); + }, + ); + }, + ), + ); + } + } + + final opName = wallet.getOpNameDataFrom(widget.utxo)!; + + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + final nameScriptHex = scriptNameUpdate(opName.fullname, opName.value); + + final txDataFuture = wallet.prepareNameSend( + txData: TxData( + feeRateType: kNameTxDefaultFeeRate, // TODO: make configurable? + recipients: [ + ( + address: _address!, + isChange: false, + amount: Amount( + rawValue: BigInt.from(kNameAmountSats), + fractionDigits: wallet.cryptoCurrency.fractionDigits, + ), + ), + ], + note: "Transfer ${opName.constructedName}", + opNameState: NameOpState( + name: opName.fullname, + saltHex: "", + commitment: "", + value: opName.value, + nameScriptHex: nameScriptHex, + type: OpName.nameUpdate, + output: widget.utxo, + outputPosition: -1, //currently unknown, updated later + ), + ), + ); + + final results = await Future.wait([ + txDataFuture, + time, + ]); + + final txData = results.first as TxData; + + if (!wasCancelled && mounted) { + // pop building dialog + Navigator.of(context).pop(); + + if (mounted) { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => SDialog( + child: SizedBox( + width: 580, + child: ConfirmNameTransactionView( + txData: txData, + walletId: widget.walletId, + ), + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + ConfirmNameTransactionView.routeName, + arguments: (txData, widget.walletId), + ); + } + } + } + } catch (e, s) { + Logging.instance.e( + "_preview transfer name failed", + error: e, + stackTrace: s, + ); + + if (mounted) { + String err = e.toString(); + if (err.startsWith("Exception: ")) { + err = err.replaceFirst("Exception: ", ""); + } + + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Error", + message: err, + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 600 : null, + ), + ); + } + } finally { + _previewLock = false; + } + } + + bool _enableButton = false; + + void _setValidAddressProviders(String? address) { + _enableButton = ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .validateAddress(address ?? ""); + if (mounted) { + setState(() {}); + } + } + + Future _scanQr() async { + try { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + + final qrResult = await scanner.scan(); + final coin = ref.read(pWalletCoin(walletId)); + + Logging.instance.d("qrResult content: ${qrResult.rawContent}"); + + final paymentData = AddressUtils.parsePaymentUri( + qrResult.rawContent, + logging: Logging.instance, + ); + + if (paymentData != null && + paymentData.coin?.uriScheme == coin.uriScheme) { + // auto fill address + _address = paymentData.address.trim(); + _addressController.text = _address!; + + _setValidAddressProviders(_address); + + // now check for non standard encoded basic address + } else { + _address = qrResult.rawContent.split("\n").first.trim(); + _addressController.text = _address ?? ""; + + _setValidAddressProviders(_address); + } + } on PlatformException catch (e, s) { + // here we ignore the exception caused by not giving permission + // to use the camera to scan a qr code + Logging.instance.e( + "Failed to get camera permissions while trying to scan qr code in" + " $runtimeType", + error: e, + stackTrace: s, + ); + } + } + + @override + void initState() { + super.initState(); + walletId = widget.walletId; + clipboard = widget.clipboard; + scanner = widget.barcodeScanner; + _addressController = TextEditingController(); + _addressFocusNode = FocusNode(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _addressFocusNode.requestFocus(); + } + }); + } + + @override + void dispose() { + _addressController.dispose(); + _addressFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + Util.isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("nameTransferViewAddressFieldKey"), + controller: _addressController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + _address = newValue.trim(); + _setValidAddressProviders(_address); + }, + focusNode: _addressFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter ${ref.watch(pWalletCoin(walletId)).ticker} address", + _addressFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _addressController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _addressController.text.isNotEmpty + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Address Field Input.", + key: const Key( + "nameTransferClearAddressFieldButtonKey", + ), + onTap: () { + _addressController.text = ""; + _address = ""; + _setValidAddressProviders( + _address, + ); + setState(() {}); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Address Field Input.", + key: const Key( + "nameTransferPasteAddressFieldButtonKey", + ), + onTap: () async { + final ClipboardData? data = + await clipboard.getData( + Clipboard.kTextPlain, + ); + if (data?.text != null && + data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring( + 0, + content.indexOf( + "\n", + ), + ); + } + + _addressController.text = content.trim(); + _address = content.trim(); + + _setValidAddressProviders( + _address, + ); + } + }, + child: _addressController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_addressController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Address Book Button. Opens Address Book For Address Field.", + key: const Key( + "nameTransferAddressBookButtonKey", + ), + onTap: () { + Navigator.of(context).pushNamed( + AddressBookView.routeName, + arguments: ref.read(pWalletCoin(walletId)), + ); + }, + child: const AddressBookIcon(), + ), + if (_addressController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Scan QR Button. Opens Camera For Scanning QR Code.", + key: const Key( + "nameTransferScanQrButtonKey", + ), + onTap: _scanQr, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + SizedBox( + height: Util.isDesktop ? 42 : 16, + ), + if (!Util.isDesktop) const Spacer(), + ConditionalParent( + condition: Util.isDesktop, + builder: (child) => Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of( + context, + rootNavigator: Util.isDesktop, + ).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: child, + ), + ], + ), + child: PrimaryButton( + label: "Transfer", + enabled: _enableButton, + // width: Util.isDesktop ? 160 : double.infinity, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: _preview, + ), + ), + if (!Util.isDesktop) + const SizedBox( + height: 16, + ), + ], + ); + } +} diff --git a/lib/pages/namecoin_names/sub_widgets/update_option_widget.dart b/lib/pages/namecoin_names/sub_widgets/update_option_widget.dart new file mode 100644 index 000000000..80c016245 --- /dev/null +++ b/lib/pages/namecoin_names/sub_widgets/update_option_widget.dart @@ -0,0 +1,356 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:namecoin/namecoin.dart'; + +import '../../../models/isar/models/blockchain_data/utxo.dart'; +import '../../../providers/global/wallets_provider.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/barcode_scanner_interface.dart'; +import '../../../utilities/clipboard_interface.dart'; +import '../../../utilities/extensions/extensions.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/text_formatters.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/models/name_op_state.dart'; +import '../../../wallets/models/tx_data.dart'; +import '../../../wallets/wallet/impl/namecoin_wallet.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; +import '../../../widgets/stack_dialog.dart'; +import '../../send_view/sub_widgets/building_transaction_dialog.dart'; +import '../confirm_name_transaction_view.dart'; + +class UpdateOptionWidget extends ConsumerStatefulWidget { + const UpdateOptionWidget({ + super.key, + required this.walletId, + required this.utxo, + this.clipboard = const ClipboardWrapper(), + this.barcodeScanner = const BarcodeScannerWrapper(), + }); + + final String walletId; + final UTXO utxo; + + final ClipboardInterface clipboard; + final BarcodeScannerInterface barcodeScanner; + + @override + ConsumerState createState() => _BuyDomainWidgetState(); +} + +class _BuyDomainWidgetState extends ConsumerState { + final _controller = TextEditingController(); + + late final bool wasJson; + late final String _currentValue; + + String _getNewValue() { + final value = _controller.text; + try { + final json = jsonDecode(value); + final minified = jsonEncode(json); + return minified; + } catch (_) {} + return value; + } + + int _countLength() { + try { + final json = jsonDecode(_controller.text); + final minified = jsonEncode(json); + return minified.toUint8ListFromUtf8.lengthInBytes; + } catch (_) {} + + return _controller.text.toUint8ListFromUtf8.lengthInBytes; + } + + bool _previewLock = false; + Future _previewUpdate() async { + if (_previewLock) return; + _previewLock = true; + try { + final newValue = _getNewValue(); + if (newValue == _currentValue) { + throw Exception("Value was not changed!"); + } + + // wait for keyboard to disappear + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 100), + ); + + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet; + + bool wasCancelled = false; + + if (mounted) { + if (Util.isDesktop) { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 400, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.all(32), + child: BuildingTransactionDialog( + coin: wallet.info.coin, + isSpark: false, + onCancel: () { + wasCancelled = true; + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + ), + ); + }, + ), + ); + } else { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + coin: wallet.info.coin, + isSpark: false, + onCancel: () { + wasCancelled = true; + Navigator.of(context).pop(); + }, + ); + }, + ), + ); + } + } + + final _address = await wallet.getCurrentReceivingAddress(); + + final opName = wallet.getOpNameDataFrom(widget.utxo)!; + + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + final nameScriptHex = scriptNameUpdate(opName.fullname, newValue); + + final txDataFuture = wallet.prepareNameSend( + txData: TxData( + feeRateType: kNameTxDefaultFeeRate, // TODO: make configurable? + recipients: [ + ( + address: _address!.value, + isChange: false, + amount: Amount( + rawValue: BigInt.from(kNameAmountSats), + fractionDigits: wallet.cryptoCurrency.fractionDigits, + ), + ), + ], + note: "Update ${opName.constructedName} (${opName.fullname})", + opNameState: NameOpState( + name: opName.fullname, + saltHex: "", + commitment: "", + value: newValue, + nameScriptHex: nameScriptHex, + type: OpName.nameUpdate, + output: widget.utxo, + outputPosition: -1, //currently unknown, updated later + ), + ), + ); + + final results = await Future.wait([ + txDataFuture, + time, + ]); + + final txData = results.first as TxData; + + if (!wasCancelled && mounted) { + // pop building dialog + Navigator.of(context).pop(); + + if (mounted) { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => SDialog( + child: SizedBox( + width: 580, + child: ConfirmNameTransactionView( + txData: txData, + walletId: widget.walletId, + ), + ), + ), + ); + } else { + await Navigator.of(context).pushNamed( + ConfirmNameTransactionView.routeName, + arguments: (txData, widget.walletId), + ); + } + } + } + } catch (e, s) { + Logging.instance.e( + "_preview update name failed", + error: e, + stackTrace: s, + ); + + String? err; + if (e.toString().contains("Contains invalid characters")) { + err = "Contains invalid characters"; + } + + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Update failed", + message: err, + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 600 : null, + ), + ); + } + } finally { + _previewLock = false; + } + } + + @override + void initState() { + super.initState(); + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet; + + _currentValue = wallet.getOpNameDataFrom(widget.utxo)!.value; + + // see if json, if so format nicely + try { + final json = jsonDecode(_currentValue); + _controller.text = const JsonEncoder.withIndent(" ").convert(json); + wasJson = true; + } catch (_) { + _controller.text = _currentValue; + wasJson = false; + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: Util.isDesktop + ? CrossAxisAlignment.start + : CrossAxisAlignment.stretch, + children: [ + Text( + "Edit value", + style: STextStyles.label(context), + ), + const SizedBox( + height: 6, + ), + TextField( + controller: _controller, + maxLines: null, + autocorrect: false, + enableSuggestions: false, + style: const TextStyle(fontFamily: "monospace"), + onChanged: (_) { + setState(() {}); + }, + inputFormatters: [ + Utf8ByteLengthLimitingTextInputFormatter( + valueMaxLength, + tryMinifyJson: true, + ), + ], + ), + const SizedBox( + height: 4, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Builder( + builder: (context) { + final length = _countLength(); + return Text( + "$length/$valueMaxLength", + style: STextStyles.w500_10(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle2, + ), + ); + }, + ), + ], + ), + SizedBox( + height: Util.isDesktop ? 32 : 16, + ), + if (!Util.isDesktop) const Spacer(), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: Navigator.of( + context, + rootNavigator: Util.isDesktop, + ).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Update", + enabled: _controller.text.isNotEmpty, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: _previewUpdate, + ), + ), + ], + ), + if (!Util.isDesktop) + const SizedBox( + height: 16, + ), + ], + ); + } +} diff --git a/lib/pages/paynym/subwidgets/paynym_followers_list.dart b/lib/pages/paynym/subwidgets/paynym_followers_list.dart index 43fdf0223..857fc9c8a 100644 --- a/lib/pages/paynym/subwidgets/paynym_followers_list.dart +++ b/lib/pages/paynym/subwidgets/paynym_followers_list.dart @@ -12,7 +12,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'paynym_card_button.dart'; + import '../../../providers/global/paynym_api_provider.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../providers/wallet/my_paynym_account_state_provider.dart'; @@ -24,6 +24,7 @@ import '../../../utilities/util.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import '../../../widgets/conditional_parent.dart'; import '../../../widgets/rounded_white_container.dart'; +import 'paynym_card_button.dart'; class PaynymFollowersList extends ConsumerStatefulWidget { const PaynymFollowersList({ @@ -92,10 +93,11 @@ class _PaynymFollowersListState extends ConsumerState { ref.read(myPaynymAccountStateProvider.state).state = account.value!; } - } catch (e) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.w( "Failed pull down refresh of paynym home page: $e", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); } }, diff --git a/lib/pages/paynym/subwidgets/paynym_following_list.dart b/lib/pages/paynym/subwidgets/paynym_following_list.dart index 486e138e2..9fa77cebf 100644 --- a/lib/pages/paynym/subwidgets/paynym_following_list.dart +++ b/lib/pages/paynym/subwidgets/paynym_following_list.dart @@ -12,7 +12,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'paynym_card_button.dart'; + import '../../../providers/global/paynym_api_provider.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../providers/wallet/my_paynym_account_state_provider.dart'; @@ -24,6 +24,7 @@ import '../../../utilities/util.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import '../../../widgets/conditional_parent.dart'; import '../../../widgets/rounded_white_container.dart'; +import 'paynym_card_button.dart'; class PaynymFollowingList extends ConsumerStatefulWidget { const PaynymFollowingList({ @@ -92,10 +93,11 @@ class _PaynymFollowingListState extends ConsumerState { ref.read(myPaynymAccountStateProvider.state).state = account.value!; } - } catch (e) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.w( "Failed pull down refresh of paynym home page: $e", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); } }, diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index 5d630ba4f..c7d6bb254 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -29,7 +29,7 @@ import '../../utilities/show_loading.dart'; import '../../utilities/show_node_tor_settings_mismatch.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; -import '../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../wallets/wallet/intermediate/external_wallet.dart'; import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; @@ -119,7 +119,7 @@ class _LockscreenViewState extends ConsumerState { } final Future loadFuture; - if (wallet is LibMoneroWallet) { + if (wallet is ExternalWallet) { loadFuture = wallet.init().then((value) async => await (wallet).open()); } else { diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index dfe85d934..7b02e525f 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -188,10 +188,7 @@ class _GenerateUriQrCodeViewState extends State { queryParams, ); - Logging.instance.log( - "Generated receiving QR code for: $uriString", - level: LogLevel.Info, - ); + Logging.instance.d("Generated receiving QR code for: $uriString"); return uriString; } diff --git a/lib/pages/send_view/frost_ms/recipient.dart b/lib/pages/send_view/frost_ms/recipient.dart index ce38153e7..ba8b9dbd7 100644 --- a/lib/pages/send_view/frost_ms/recipient.dart +++ b/lib/pages/send_view/frost_ms/recipient.dart @@ -133,20 +133,14 @@ class _RecipientState extends ConsumerState { final qrResult = await ref.read(pBarcodeScanner).scan(); - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info, - ); + Logging.instance.d("qrResult content: ${qrResult.rawContent}"); final paymentData = AddressUtils.parsePaymentUri( qrResult.rawContent, logging: Logging.instance, ); - Logging.instance.log( - "qrResult parsed: $paymentData", - level: LogLevel.Info, - ); + Logging.instance.d("qrResult parsed: $paymentData"); if (paymentData != null && paymentData.coin?.uriScheme == widget.coin.uriScheme) { @@ -175,10 +169,11 @@ class _RecipientState extends ConsumerState { _updateRecipientData(); } on PlatformException catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Failed to get camera permissions while " "trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); } } diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart index 0ec725d00..31eb7b4d4 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart @@ -67,10 +67,7 @@ class _FrostSendStep1aState extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); } finally { _attemptSignLock = false; } diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart index b7c95ea2c..ade993b22 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart @@ -108,10 +108,7 @@ class _FrostSendStep1bState extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); if (mounted) { await showDialog( context: context, diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart index 724e62092..febae8b54 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart @@ -291,10 +291,7 @@ class _FrostSendStep2State extends ConsumerState { // arguments: widget.walletId, // ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (context.mounted) { return await showDialog( diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart index c96f15233..6ce475a0b 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart @@ -233,10 +233,7 @@ class _FrostSendStep3State extends ConsumerState { .routeName, ); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (context.mounted) { return await showDialog( diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart index a560d7151..ee45a6e70 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart @@ -237,10 +237,7 @@ class _FrostSendStep4State extends ConsumerState { } } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (context.mounted) { return await showDialog( context: context, diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 1dd9b68ef..4bdcef6e2 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -135,6 +135,44 @@ class _SendViewState extends ConsumerState { Set selectedUTXOs = {}; + void _applyUri(PaymentUriData paymentData) { + try { + // auto fill address + _address = paymentData.address.trim(); + sendToController.text = _address!; + + // autofill notes field + if (paymentData.message != null) { + noteController.text = paymentData.message!; + } else if (paymentData.label != null) { + noteController.text = paymentData.label!; + } + + // autofill amount field + if (paymentData.amount != null) { + final Amount amount = Decimal.parse(paymentData.amount!).toAmount( + fractionDigits: coin.fractionDigits, + ); + cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( + amount, + withUnitName: false, + ); + ref.read(pSendAmount.notifier).state = amount; + } + + _setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } catch (e, s) { + Logging.instance.e( + "Failed to apply uri in SendView: ", + error: e, + stackTrace: s, + ); + } + } + Future _scanQr() async { try { // ref @@ -158,10 +196,7 @@ class _SendViewState extends ConsumerState { // .state = true, // ); - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info, - ); + Logging.instance.d("qrResult content: ${qrResult.rawContent}"); final paymentData = AddressUtils.parsePaymentUri( qrResult.rawContent, @@ -170,35 +205,7 @@ class _SendViewState extends ConsumerState { if (paymentData != null && paymentData.coin?.uriScheme == coin.uriScheme) { - // auto fill address - _address = paymentData.address.trim(); - sendToController.text = _address!; - - // autofill notes field - if (paymentData.message != null) { - noteController.text = paymentData.message!; - } else if (paymentData.label != null) { - noteController.text = paymentData.label!; - } - - // autofill amount field - if (paymentData.amount != null) { - final Amount amount = Decimal.parse(paymentData.amount!).toAmount( - fractionDigits: coin.fractionDigits, - ); - cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( - amount, - withUnitName: false, - ); - ref.read(pSendAmount.notifier).state = amount; - } - - _setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = sendToController.text.isNotEmpty; - }); - - // now check for non standard encoded basic address + _applyUri(paymentData); } else { _address = qrResult.rawContent.split("\n").first.trim(); sendToController.text = _address ?? ""; @@ -216,9 +223,10 @@ class _SendViewState extends ConsumerState { // .state = true; // here we ignore the exception caused by not giving permission // to use the camera to scan a qr code - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, + Logging.instance.e( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, ); } } @@ -248,8 +256,6 @@ class _SendViewState extends ConsumerState { return; } _cachedAmountToSend = amount; - Logging.instance - .log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info); final amountString = ref.read(pAmountFormatter(coin)).format( amount, @@ -285,16 +291,12 @@ class _SendViewState extends ConsumerState { return; } _cachedAmountToSend = amount; - Logging.instance.log( - "it changed $amount $_cachedAmountToSend", - level: LogLevel.Info, - ); final price = ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (price > Decimal.zero) { - baseAmountController.text = (amount!.decimal * price) + baseAmountController.text = (amount.decimal * price) .toAmount( fractionDigits: 2, ) @@ -314,9 +316,7 @@ class _SendViewState extends ConsumerState { if (coin is! Epiccash && !_baseFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( - amount == null - ? 0.toAmountAsRaw(fractionDigits: coin.fractionDigits) - : amount!, + amount ?? 0.toAmountAsRaw(fractionDigits: coin.fractionDigits), ); }); } @@ -369,7 +369,7 @@ class _SendViewState extends ConsumerState { } String? _updateInvalidAddressText(String address) { - if (_data != null && _data!.contactLabel == address) { + if (_data != null && _data.contactLabel == address) { return null; } @@ -695,9 +695,7 @@ class _SendViewState extends ConsumerState { ], feeRateType: ref.read(feeRateTypeStateProvider), satsPerVByte: isCustomFee ? customFeeRate : null, - utxos: (wallet is CoinControlInterface && - coinControlEnabled && - selectedUTXOs.isNotEmpty) + utxos: (coinControlEnabled && selectedUTXOs.isNotEmpty) ? selectedUTXOs : null, ), @@ -714,9 +712,7 @@ class _SendViewState extends ConsumerState { ], feeRateType: ref.read(feeRateTypeStateProvider), satsPerVByte: isCustomFee ? customFeeRate : null, - utxos: (wallet is CoinControlInterface && - coinControlEnabled && - selectedUTXOs.isNotEmpty) + utxos: (coinControlEnabled && selectedUTXOs.isNotEmpty) ? selectedUTXOs : null, ), @@ -828,7 +824,7 @@ class _SendViewState extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); if (mounted) { // pop building dialog Navigator.of(context).pop(); @@ -960,9 +956,9 @@ class _SendViewState extends ConsumerState { baseAmountController.addListener(_baseAmountChanged); if (_data != null) { - if (_data!.amount != null) { + if (_data.amount != null) { final amount = Amount.fromDecimal( - _data!.amount!, + _data.amount!, fractionDigits: coin.fractionDigits, ); @@ -971,8 +967,8 @@ class _SendViewState extends ConsumerState { withUnitName: false, ); } - sendToController.text = _data!.contactLabel; - _address = _data!.address.trim(); + sendToController.text = _data.contactLabel; + _address = _data.address.trim(); _addressToggleFlag = true; WidgetsBinding.instance.addPostFrameCallback((timeStamp) { @@ -1364,7 +1360,23 @@ class _SendViewState extends ConsumerState { selectAll: false, ), onChanged: (newValue) { - _address = newValue.trim(); + final trimmed = newValue.trim(); + + if ((trimmed.length - (_address?.length ?? 0)).abs() > 1) { + final parsed = AddressUtils.parsePaymentUri( + trimmed, + logging: Logging.instance, + ); + if (parsed != null) { + _applyUri(parsed); + } else { + _address = newValue; + sendToController.text = newValue; + } + } else { + _address = newValue; + } + _setValidAddressProviders(_address); setState(() { @@ -1447,19 +1459,28 @@ class _SendViewState extends ConsumerState { content, ); } - sendToController.text = - content.trim(); - _address = content.trim(); - _setValidAddressProviders( - _address, + final trimmed = content.trim(); + final parsed = AddressUtils.parsePaymentUri( + trimmed, + logging: Logging.instance, ); - setState(() { - _addressToggleFlag = - sendToController - .text - .isNotEmpty; - }); + if (parsed != null) { + _applyUri(parsed); + } else { + sendToController.text = + content; + _address = content; + + _setValidAddressProviders(_address,); + + setState(() { + _addressToggleFlag = + sendToController + .text + .isNotEmpty; + }); + } } }, child: sendToController @@ -1602,9 +1623,9 @@ class _SendViewState extends ConsumerState { ) == FiroType.lelantus) { if (_data != null && - _data!.contactLabel == _address) { + _data.contactLabel == _address) { error = SparkInterface.validateSparkAddress( - address: _data!.address, + address: _data.address, isTestNet: coin.network == CryptoCurrencyNetwork.test, ) @@ -1620,7 +1641,7 @@ class _SendViewState extends ConsumerState { } } else { if (_data != null && - _data!.contactLabel == _address) { + _data.contactLabel == _address) { error = null; } else if (!ref.watch(pValidSendToAddress) && !ref.watch(pValidSparkSendToAddress)) { @@ -1631,7 +1652,7 @@ class _SendViewState extends ConsumerState { } } else { if (_data != null && - _data!.contactLabel == _address) { + _data.contactLabel == _address) { error = null; } else if (!ref.watch(pValidSendToAddress)) { error = "Invalid address"; @@ -1815,7 +1836,9 @@ class _SendViewState extends ConsumerState { if (coin is! Ethereum && coin is! Tezos) CustomTextButton( text: _getSendAllTitle( - showCoinControl, selectedUTXOs), + showCoinControl, + selectedUTXOs, + ), onTap: () => _sendAllTapped(showCoinControl), ), ], diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index 0ca0dba47..2586d88ee 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -831,7 +831,7 @@ class _TransactionFeeSelectionSheetState return null; } } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + Logging.instance.w("$e $s", error: e, stackTrace: s,); return null; } } diff --git a/lib/pages/send_view/token_send_view.dart b/lib/pages/send_view/token_send_view.dart index 4837b9773..01ae07406 100644 --- a/lib/pages/send_view/token_send_view.dart +++ b/lib/pages/send_view/token_send_view.dart @@ -159,18 +159,14 @@ class _TokenSendViewState extends ConsumerState { // .state = true, // ); - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info, - ); + Logging.instance.d("qrResult content: ${qrResult.rawContent}"); final paymentData = AddressUtils.parsePaymentUri( qrResult.rawContent, logging: Logging.instance, ); - Logging.instance - .log("qrResult parsed: $paymentData", level: LogLevel.Info); + Logging.instance.d("qrResult parsed: $paymentData"); if (paymentData != null && paymentData.coin?.uriScheme == coin.uriScheme) { @@ -221,9 +217,10 @@ class _TokenSendViewState extends ConsumerState { // .state = true; // here we ignore the exception caused by not giving permission // to use the camera to scan a qr code - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, ); } } @@ -255,10 +252,6 @@ class _TokenSendViewState extends ConsumerState { return; } _cachedAmountToSend = _amountToSend; - Logging.instance.log( - "it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info, - ); _cryptoAmountChangeLock = true; cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( @@ -293,10 +286,6 @@ class _TokenSendViewState extends ConsumerState { return; } _cachedAmountToSend = _amountToSend; - Logging.instance.log( - "it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info, - ); final price = ref .read(priceAnd24hChangeNotifierProvider) @@ -535,7 +524,7 @@ class _TokenSendViewState extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); if (mounted) { // pop building dialog Navigator.of(context).pop(); diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart index 6f04cce80..d86f17526 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart @@ -23,7 +23,7 @@ import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/draggable_switch_button.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../stack_privacy_calls.dart'; -import 'debug_view.dart'; +import 'logging_settings_view.dart'; import 'manage_coin_units/manage_coin_units_view.dart'; import 'manage_explorer_view.dart'; @@ -68,7 +68,8 @@ class AdvancedSettingsView extends StatelessWidget { ), ), onPressed: () { - Navigator.of(context).pushNamed(DebugView.routeName); + Navigator.of(context) + .pushNamed(LoggingSettingsView.routeName); }, child: Padding( padding: const EdgeInsets.symmetric( @@ -78,7 +79,7 @@ class AdvancedSettingsView extends StatelessWidget { child: Row( children: [ Text( - "Debug info", + "Logging", style: STextStyles.titleBold12(context), textAlign: TextAlign.left, ), diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart deleted file mode 100644 index 92e5cc256..000000000 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ /dev/null @@ -1,609 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:event_bus/event_bus.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; -import 'package:package_info_plus/package_info_plus.dart'; - -import '../../../../app_config.dart'; -import '../../../../models/isar/models/log.dart'; -import '../../../../notifications/show_flush_bar.dart'; -import '../../../../providers/global/debug_service_provider.dart'; -import '../../../../themes/stack_colors.dart'; -import '../../../../utilities/assets.dart'; -import '../../../../utilities/clipboard_interface.dart'; -import '../../../../utilities/constants.dart'; -import '../../../../utilities/logger.dart'; -import '../../../../utilities/stack_file_system.dart'; -import '../../../../utilities/text_styles.dart'; -import '../../../../utilities/util.dart'; -import '../../../../widgets/background.dart'; -import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; -import '../../../../widgets/custom_buttons/blue_text_button.dart'; -import '../../../../widgets/custom_loading_overlay.dart'; -import '../../../../widgets/icon_widgets/x_icon.dart'; -import '../../../../widgets/rounded_container.dart'; -import '../../../../widgets/stack_dialog.dart'; -import '../../../../widgets/stack_text_field.dart'; -import '../../../../widgets/textfield_icon_button.dart'; -import '../stack_backup_views/helpers/swb_file_system.dart'; - -class DebugView extends ConsumerStatefulWidget { - const DebugView({super.key}); - - static const String routeName = "/debug"; - - @override - ConsumerState createState() => _DebugViewState(); -} - -class _DebugViewState extends ConsumerState { - final _searchController = TextEditingController(); - final _searchFocusNode = FocusNode(); - - final scrollController = ScrollController(); - - String _searchTerm = ""; - - List filtered(List unfiltered, String filter) { - if (filter == "") { - return unfiltered; - } - return unfiltered - .where( - (e) => (e.toString().toLowerCase().contains(filter.toLowerCase())), - ) - .toList(); - } - - BorderRadius? _borderRadius(int index, int listLength) { - if (index == 0 && listLength == 1) { - return BorderRadius.circular( - Constants.size.circularBorderRadius, - ); - } else if (index == 0) { - return BorderRadius.vertical( - bottom: Radius.circular( - Constants.size.circularBorderRadius, - ), - ); - } else if (index == listLength - 1) { - return BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, - ), - ); - } - return null; - } - - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - _searchController.dispose(); - scrollController.dispose(); - _searchFocusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Background( - child: Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Debug", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("deleteLogsAppBarButtonKey"), - size: 36, - shadows: const [], - color: Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.trash, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () async { - await showDialog( - context: context, - builder: (_) => StackDialog( - title: "Delete logs?", - message: - "You are about to delete all logs permanently. Are you sure?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonStyle(context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12(context), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), - child: Text( - "Delete logs", - style: STextStyles.button(context), - ), - onPressed: () async { - Navigator.of(context).pop(); - - bool shouldPop = false; - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: const CustomLoadingOverlay( - message: "Deleting logs...", - eventBus: null, - ), - ), - ), - ); - - await ref - .read(debugServiceProvider) - .deleteAllLogs(); - - shouldPop = true; - - if (mounted) { - Navigator.pop(context); - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - context: context, - message: 'Logs cleared!', - ), - ); - - setState(() {}); - } - }, - ), - ), - ); - }, - ), - ), - ), - ], - ), - body: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 16, - right: 16, - ), - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: - NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: (newString) { - setState(() => _searchTerm = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: - const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - _searchTerm = ""; - }); - }, - ), - ], - ), - ), - ) - : null, - ), - ), - ), - const SizedBox( - height: 12, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomTextButton( - text: "Save Debug Info to clipboard", - onTap: () async { - try { - final packageInfo = - await PackageInfo.fromPlatform(); - final version = packageInfo.version; - final build = packageInfo.buildNumber; - final signature = - packageInfo.buildSignature; - final appName = packageInfo.appName; - final String firoCommit = - FIRO_VERSIONS.getPluginVersion(); - final String epicCashCommit = - EPIC_VERSIONS.getPluginVersion(); - // final String moneroCommit = - // MONERO_VERSIONS.getPluginVersion(); - final DeviceInfoPlugin deviceInfoPlugin = - DeviceInfoPlugin(); - final deviceInfo = - await deviceInfoPlugin.deviceInfo; - final deviceInfoMap = deviceInfo.toMap(); - deviceInfoMap.remove("systemFeatures"); - - final logs = filtered( - ref.watch( - debugServiceProvider.select( - (value) => value.recentLogs, - ), - ), - _searchTerm, - ).reversed.toList(growable: false); - final List errorLogs = []; - for (final log in logs) { - if (log.logLevel == LogLevel.Error || - log.logLevel == LogLevel.Fatal) { - errorLogs.add( - "${log.logLevel}: ${log.message}", - ); - } - } - - final finalDebugMap = { - "version": version, - "build": build, - "signature": signature, - "appName": appName, - "firoCommit": firoCommit, - "epicCashCommit": epicCashCommit, - // "moneroCommit": moneroCommit, - "deviceInfoMap": deviceInfoMap, - "errorLogs": errorLogs, - }; - Logging.instance.log( - json.encode(finalDebugMap), - level: LogLevel.Info, - printFullLength: true, - ); - const ClipboardInterface clipboard = - ClipboardWrapper(); - await clipboard.setData( - ClipboardData( - text: json.encode(finalDebugMap), - ), - ); - } catch (e, s) { - Logging.instance - .log("$e $s", level: LogLevel.Error); - } - }, - ), - const Spacer(), - CustomTextButton( - text: "Save logs to file", - onTap: () async { - final systemfile = SWBFileSystem(); - await systemfile.prepareStorage(); - Directory rootPath = await StackFileSystem - .applicationRootDirectory(); - - if (Platform.isAndroid) { - rootPath = - Directory("/storage/emulated/0/"); - } - - Directory dir = - Directory('${rootPath.path}/Documents'); - if (Platform.isIOS) { - dir = Directory(rootPath.path); - } - try { - if (!dir.existsSync()) { - dir.createSync(); - } - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); - } - String? path; - if (Platform.isAndroid) { - path = dir.path; - } else { - path = await FilePicker.platform - .getDirectoryPath( - dialogTitle: "Choose Log Save Location", - initialDirectory: - systemfile.startPath!.path, - lockParentWindow: true, - ); - } - - if (path != null) { - final eventBus = EventBus(); - bool shouldPop = false; - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => WillPopScope( - onWillPop: () async { - return shouldPop; - }, - child: CustomLoadingOverlay( - message: - "Generating ${AppConfig.prefix} logs file", - eventBus: eventBus, - ), - ), - ), - ); - - bool logsSaved = true; - String? filename; - try { - filename = await ref - .read(debugServiceProvider) - .exportToFile(path, eventBus); - } catch (e, s) { - logsSaved = false; - Logging.instance - .log("$e $s", level: LogLevel.Error); - } - - shouldPop = true; - - if (mounted) { - Navigator.pop(context); - - if (Platform.isAndroid) { - unawaited( - showDialog( - context: context, - builder: (context) => StackOkDialog( - title: logsSaved - ? "Logs saved to" - : "Error Saving Logs", - message: "${path!}/$filename", - ), - ), - ); - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - context: context, - message: logsSaved - ? 'Logs file saved' - : "Error Saving Logs", - ), - ); - } - } - } - }, - ), - ], - ), - ], - ), - ), - ), - ), - ]; - }, - body: Builder( - builder: (context) { - final logs = filtered( - ref.watch( - debugServiceProvider.select((value) => value.recentLogs), - ), - _searchTerm, - ).reversed.toList(growable: false); - - return CustomScrollView( - reverse: true, - // shrinkWrap: true, - controller: scrollController, - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor( - context, - ), - ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final log = logs[index]; - - return Container( - key: Key( - "log_${log.id}_${log.timestampInMillisUTC}", - ), - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, - borderRadius: _borderRadius(index, logs.length), - ), - child: Padding( - padding: const EdgeInsets.all(4), - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .popupBG, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - " [${log.logLevel.name}]", - style: STextStyles.baseXS(context) - .copyWith( - fontSize: 8, - color: (log.logLevel == - LogLevel.Info - ? Theme.of(context) - .extension()! - .topNavIconGreen - : (log.logLevel == - LogLevel.Warning - ? Theme.of(context) - .extension< - StackColors>()! - .topNavIconYellow - : (log.logLevel == - LogLevel.Error - ? Colors.orange - : Theme.of(context) - .extension< - StackColors>()! - .topNavIconRed))), - ), - ), - Text( - "[${DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC, isUtc: true)}]: ", - style: STextStyles.baseXS(context) - .copyWith( - fontSize: 8, - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - ], - ), - Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const SizedBox( - width: 20, - ), - Flexible( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SelectableText( - log.message, - style: - STextStyles.baseXS(context) - .copyWith(fontSize: 8), - ), - ], - ), - ), - ], - ), - ], - ), - ), - ), - ); - }, - childCount: logs.length, - ), - ), - ], - ); - }, - ), - ), - ), - ), - ); - } -} diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/logging_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/logging_settings_view.dart new file mode 100644 index 000000000..dc6c24013 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/advanced_views/logging_settings_view.dart @@ -0,0 +1,258 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:async'; +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../../app_config.dart'; +import '../../../../providers/global/prefs_provider.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/assets.dart'; +import '../../../../utilities/logger.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../widgets/background.dart'; +import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/log_level_preference_widget.dart'; +import '../../../../widgets/rounded_white_container.dart'; +import '../../../../widgets/stack_dialog.dart'; + +class LoggingSettingsView extends ConsumerStatefulWidget { + const LoggingSettingsView({super.key}); + + static const String routeName = "/loggingView"; + + @override + ConsumerState createState() => + _LoggingSettingsViewState(); +} + +class _LoggingSettingsViewState extends ConsumerState { + late final TextEditingController fileLocationController; + bool _lock = false; + + Future _edit() async { + final currentPath = ref.read(prefsChangeNotifierProvider).logsPath ?? + Logging.instance.logsDirPath; + final newPath = await _pickDir(context, currentPath); + + // test if has permission to write + if (newPath != null) { + final file = File( + "$newPath${Platform.pathSeparator}._test", + ); + if (!file.existsSync()) { + file.createSync(); + file.deleteSync(); + } + } + + // success + ref.read(prefsChangeNotifierProvider).logsPath = newPath; + + if (mounted) { + setState(() { + fileLocationController.text = + ref.read(prefsChangeNotifierProvider).logsPath ?? + Logging.instance.logsDirPath; + }); + } + } + + Future _pickDir(BuildContext context, String currentPath) async { + final String? chosenPath; + // if (Platform.isIOS) { + // chosenPath = currentPath; + // } else { + final String path = + Platform.isWindows ? currentPath.replaceAll("/", "\\") : currentPath; + chosenPath = await FilePicker.platform.getDirectoryPath( + dialogTitle: "Choose Log Save location", + initialDirectory: path, + lockParentWindow: true, + ); + // } + return chosenPath; + } + + @override + void initState() { + super.initState(); + fileLocationController = TextEditingController(); + fileLocationController.text = + ref.read(prefsChangeNotifierProvider).logsPath ?? + Logging.instance.logsDirPath; + } + + @override + void dispose() { + fileLocationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Logging", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 16, + right: 16, + ), + child: Column( + children: [ + Row( + children: [ + Text( + "Log files location", + style: STextStyles.fieldLabel(context), + textAlign: TextAlign.left, + ), + ], + ), + const SizedBox( + height: 16, + ), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Save to...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + key: const Key( + "logsDirPathLocationControllerKey", + ), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) {}, + ), + const SizedBox( + height: 16, + ), + const LogLevelPreferenceWidget(), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + child: Text( + "NOTE: ${AppConfig.appName} must be restarted in order" + " for changes to take effect.", + style: STextStyles.subtitle500(context), + ), + ), + ), + ], + ), + const SizedBox( + height: 16, + ), + const Spacer(), + PrimaryButton( + label: "Select log save location", + onPressed: () async { + if (_lock) { + return; + } + _lock = true; + try { + await _edit(); + } catch (e, s) { + Logging.instance.e( + "Failed to change logs path", + error: e, + stackTrace: s, + ); + if (context.mounted) { + final String err; + if (e + .toString() + .contains("OS Error: Operation not permitted")) { + err = "Cannot use chosen location"; + } else { + err = e.toString(); + } + + unawaited( + showDialog( + context: context, + builder: (context) => StackOkDialog( + title: "Failed to change logs path", + message: err, + ), + ), + ); + } + } finally { + _lock = false; + } + }, + ), + const SizedBox( + height: 16, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart index b95384ba1..5ef5821a2 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart @@ -15,6 +15,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; + import '../../../../../themes/stack_colors.dart'; import '../../../../../themes/theme_service.dart'; import '../../../../../utilities/assets.dart'; @@ -54,10 +55,7 @@ class _InstallThemeFromFileDialogState ]); return true; } catch (e, s) { - Logging.instance.log( - "Failed to install theme: $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("Failed to install theme: ", error: e, stackTrace: s); return false; } } @@ -77,7 +75,7 @@ class _InstallThemeFromFileDialogState }); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); } } diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart index 3ab865653..06cbfe852 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart @@ -64,10 +64,7 @@ class _StackThemeCardState extends ConsumerState { await service.install(themeArchiveData: data); return true; } catch (e, s) { - Logging.instance.log( - "Failed _downloadAndInstall of ${widget.data.id}: $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("Failed _downloadAndInstall of ${widget.data.id}: ", error: e, stackTrace: s); return false; } } diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index b389f31a3..99fb927cb 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -15,7 +15,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../../notifications/show_flush_bar.dart'; -import '../../../providers/global/debug_service_provider.dart'; import '../../../providers/providers.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; @@ -75,6 +74,39 @@ class HiddenSettings extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + Consumer( + builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + ref + .read(prefsChangeNotifierProvider) + .advancedFiroFeatures = + !ref + .read(prefsChangeNotifierProvider) + .advancedFiroFeatures; + }, + child: RoundedWhiteContainer( + child: Text( + ref.watch( + prefsChangeNotifierProvider.select( + (s) => s.advancedFiroFeatures, + ), + ) + ? "Hide advanced Firo features" + : "Show advanced Firo features", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ); + }, + ), + const SizedBox( + height: 12, + ), Consumer( builder: (_, ref, __) { return GestureDetector( @@ -115,6 +147,29 @@ class HiddenSettings extends StatelessWidget { ); }, ), + const SizedBox( + height: 12, + ), + Consumer( + builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + ref.read(prefsChangeNotifierProvider).logsPath = + null; + }, + child: RoundedWhiteContainer( + child: Text( + "Reset log location", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ); + }, + ), // const SizedBox( // height: 12, // ), @@ -143,40 +198,40 @@ class HiddenSettings extends StatelessWidget { // ), // ); // }), - const SizedBox( - height: 12, - ), - Consumer( - builder: (_, ref, __) { - return GestureDetector( - onTap: () async { - await ref - .read(debugServiceProvider) - .deleteAllLogs(); - - if (context.mounted) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.success, - message: "Debug Logs deleted", - context: context, - ), - ); - } - }, - child: RoundedWhiteContainer( - child: Text( - "Delete Debug Logs", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - ); - }, - ), + // const SizedBox( + // height: 12, + // ), + // Consumer( + // builder: (_, ref, __) { + // return GestureDetector( + // onTap: () async { + // await ref + // .read(debugServiceProvider) + // .deleteAllLogs(); + // + // if (context.mounted) { + // unawaited( + // showFloatingFlushBar( + // type: FlushBarType.success, + // message: "Debug Logs deleted", + // context: context, + // ), + // ); + // } + // }, + // child: RoundedWhiteContainer( + // child: Text( + // "Delete Debug Logs", + // style: STextStyles.button(context).copyWith( + // color: Theme.of(context) + // .extension()! + // .accentColorDark, + // ), + // ), + // ), + // ); + // }, + // ), const SizedBox( height: 12, ), diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index ccf4bf2ba..0f00f75db 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -10,6 +10,7 @@ import 'dart:async'; +import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -26,6 +27,8 @@ import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/sync_type_enum.dart'; import '../../../../utilities/flutter_secure_storage_interface.dart'; +import '../../../../utilities/logger.dart'; +import '../../../../utilities/node_uri_util.dart'; import '../../../../utilities/test_node_connection.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/tor_plain_net_option_enum.dart'; @@ -38,7 +41,9 @@ import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/desktop/qr_code_scanner_dialog.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/icon_widgets/qrcode_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/stack_dialog.dart'; import '../../../../widgets/stack_text_field.dart'; @@ -73,6 +78,8 @@ class _AddEditNodeViewState extends ConsumerState { late final String? nodeId; late final bool isDesktop; + (NodeModel, String)? _scannedResult; + late bool saveEnabled; late bool testConnectionEnabled; @@ -330,6 +337,83 @@ class _AddEditNodeViewState extends ConsumerState { } } + bool _scanLock = false; + + void _scanQr() async { + if (_scanLock) return; + _scanLock = true; + try { + if (Util.isDesktop) { + try { + final qrResult = await showDialog( + context: context, + builder: (context) => const QrCodeScannerDialog(), + ); + + if (qrResult == null) { + Logging.instance.d("Qr scanning cancelled"); + } else { + try { + await _processQrData(qrResult); + } catch (e, s) { + Logging.instance.e("Error processing QR code data: ", + error: e, stackTrace: s); + } + } + } catch (e, s) { + Logging.instance.e("Error opening QR code scanner dialog: ", + error: e, stackTrace: s); + } + } else { + try { + final result = await BarcodeScanner.scan(); + await _processQrData(result.rawContent); + } catch (e, s) { + Logging.instance.e( + "$runtimeType._scanQr()", + error: e, + stackTrace: s, + ); + } + } + } finally { + _scanLock = false; + } + } + + Future _processQrData(String data) async { + try { + final nodeQrData = NodeQrUtil.decodeUri(data); + if (mounted) { + setState(() { + _scannedResult = ( + NodeModel( + host: nodeQrData.host, + port: nodeQrData.port, + name: nodeQrData.label ?? "", + id: const Uuid().v1(), + useSSL: nodeQrData.scheme == "https", + enabled: true, + coinName: coin.identifier, + isFailover: true, + isDown: false, + torEnabled: true, + clearnetEnabled: !nodeQrData.host.endsWith(".onion"), + loginName: (nodeQrData as LibMoneroNodeQrData?)?.user, + ), + (nodeQrData as LibMoneroNodeQrData?)?.password ?? "" + ); + }); + } + } catch (e, s) { + Logging.instance.w( + "$e\n$s", + error: e, + stackTrace: s, + ); + } + } + @override void initState() { isDesktop = Util.isDesktop; @@ -390,6 +474,35 @@ class _AddEditNodeViewState extends ConsumerState { style: STextStyles.navBarTitle(context), ), actions: [ + if (viewType == AddEditNodeViewType.add && + coin + is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("qrNodeAppBarButtonKey"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .background, + icon: QrCodeIcon( + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + onPressed: _scanQr, + ), + ), + ), if (viewType == AddEditNodeViewType.edit && ref .watch( @@ -473,19 +586,47 @@ class _AddEditNodeViewState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ + const SizedBox( + height: 8, + ), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const SizedBox( - width: 8, - ), - const AppBarBackButton( - iconSize: 24, - size: 40, - ), - Text( - "Add new node", - style: STextStyles.desktopH3(context), + Row( + children: [ + const SizedBox( + width: 8, + ), + const AppBarBackButton( + iconSize: 24, + size: 40, + ), + Text( + "Add new node", + style: STextStyles.desktopH3(context), + ), + ], ), + if (coin + is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future + Padding( + padding: const EdgeInsets.only(right: 32), + child: AppBarIconButton( + size: 40, + color: isDesktop + ? Theme.of(context) + .extension()! + .textFieldDefaultBG + : Theme.of(context) + .extension()! + .background, + icon: const QrCodeIcon( + width: 21, + height: 21, + ), + onPressed: _scanQr, + ), + ), ], ), Padding( @@ -504,7 +645,9 @@ class _AddEditNodeViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ NodeForm( - node: node, + key: Key((node ?? _scannedResult?.$1)?.id ?? "none"), + node: node ?? _scannedResult?.$1, + scannedPw: _scannedResult?.$2, secureStore: ref.read(secureStoreProvider), readOnly: false, coin: widget.coin, @@ -629,6 +772,7 @@ class NodeForm extends ConsumerStatefulWidget { const NodeForm({ super.key, this.node, + this.scannedPw, required this.secureStore, required this.readOnly, required this.coin, @@ -636,6 +780,7 @@ class NodeForm extends ConsumerStatefulWidget { }); final NodeModel? node; + final String? scannedPw; final SecureStorageInterface secureStore; final bool readOnly; final CryptoCurrency coin; @@ -710,13 +855,10 @@ class _NodeFormState extends ConsumerState { onChanged?.call(canSave, canTestConnection); ref.read(nodeFormDataProvider).name = _nameController.text; ref.read(nodeFormDataProvider).host = _hostController.text; - ref.read(nodeFormDataProvider).login = _usernameController.text.isEmpty ? null : _usernameController.text; - ref.read(nodeFormDataProvider).password = _passwordController.text.isEmpty ? null : _passwordController.text; - ref.read(nodeFormDataProvider).port = port; ref.read(nodeFormDataProvider).useSSL = _useSSL; ref.read(nodeFormDataProvider).isFailover = _isFailover; @@ -738,13 +880,15 @@ class _NodeFormState extends ConsumerState { if (widget.node != null) { final node = widget.node!; if (enableAuthFields) { - node.getPassword(widget.secureStore).then((value) { - if (value is String) { - _passwordController.text = value; - } - }); - - _usernameController.text = node.loginName ?? ""; + if (widget.scannedPw == null) { + node.getPassword(widget.secureStore).then((value) { + if (value is String) { + _passwordController.text = value; + } + }); + } else { + _passwordController.text = widget.scannedPw!; + } } _nameController.text = node.name; @@ -1179,7 +1323,11 @@ class _NodeFormState extends ConsumerState { }); if (widget.readOnly) { ref.read(nodeServiceChangeNotifierProvider).edit( - widget.node!.copyWith(isFailover: _isFailover), + widget.node!.copyWith( + isFailover: _isFailover, + loginName: widget.node!.loginName, + trusted: widget.node!.trusted, + ), null, true, ); @@ -1204,8 +1352,11 @@ class _NodeFormState extends ConsumerState { }); if (widget.readOnly) { ref.read(nodeServiceChangeNotifierProvider).edit( - widget.node! - .copyWith(isFailover: _isFailover), + widget.node!.copyWith( + isFailover: _isFailover, + loginName: widget.node!.loginName, + trusted: widget.node!.trusted, + ), null, true, ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart index 4e1fdea36..c75ac4a83 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart @@ -176,7 +176,7 @@ class _EnableAutoBackupViewState extends ConsumerState { } } catch (e, s) { Logging.instance - .log("$e\n$s", level: LogLevel.Error); + .e("$e\n$s", error: e, stackTrace: s); } }, controller: fileLocationController, @@ -579,7 +579,7 @@ class _EnableAutoBackupViewState extends ConsumerState { final String err = getErrorMessageFromSWBException(e); Logging.instance - .log("$err\n$s", level: LogLevel.Error); + .e("$err\n$s", error: e, stackTrace: s); // pop encryption progress dialog Navigator.of(context).pop(); showFloatingFlushBar( @@ -589,8 +589,11 @@ class _EnableAutoBackupViewState extends ConsumerState { ); return; } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); + Logging.instance.e( + "", + error: e, + stackTrace: s, + ); // pop encryption progress dialog Navigator.of(context).pop(); showFloatingFlushBar( diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 4bf75c0bf..944847a42 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -207,8 +207,11 @@ class _RestoreFromFileViewState extends State { }); } } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); + Logging.instance.e( + "", + error: e, + stackTrace: s, + ); } }, controller: fileLocationController, diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index ae88abd24..53669d9f0 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -159,7 +159,7 @@ class _EditAutoBackupViewState extends ConsumerState { adkVersion = adk.item1; } on Exception catch (e, s) { final String err = getErrorMessageFromSWBException(e); - Logging.instance.log("$err\n$s", level: LogLevel.Error); + Logging.instance.e("$err\n$s", error: e, stackTrace: s); // pop encryption progress dialog Navigator.of(context).pop(); unawaited( @@ -171,7 +171,7 @@ class _EditAutoBackupViewState extends ConsumerState { ); return; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); // pop encryption progress dialog Navigator.of(context).pop(); unawaited( @@ -374,7 +374,7 @@ class _EditAutoBackupViewState extends ConsumerState { }); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); } }, controller: fileLocationController, diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index d20faf66b..b6e872bd4 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -55,6 +55,7 @@ import '../../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../../../wallets/wallet/impl/wownero_wallet.dart'; +import '../../../../../wallets/wallet/impl/xelis_wallet.dart'; import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../../wallets/wallet/wallet.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; @@ -99,12 +100,10 @@ abstract class SWB { _cancelCompleter = null; _cancelCompleter = Completer(); _shouldCancelRestore = true; - Logging.instance - .log("SWB cancel restore requested", level: LogLevel.Info); + Logging.instance.d("SWB cancel restore requested"); } else { - Logging.instance.log( + Logging.instance.d( "SWB cancel restore requested while a cancellation request is currently in progress", - level: LogLevel.Warning, ); } @@ -147,10 +146,10 @@ abstract class SWB { backupFile .writeAsStringSync(Format.uint8listToString(encryptedContent)); } - Logging.instance.log(backupFile.absolute, level: LogLevel.Info); + Logging.instance.d(backupFile.absolute); return true; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); return false; } } @@ -174,10 +173,10 @@ abstract class SWB { backupFile .writeAsStringSync(Format.uint8listToString(encryptedContent)); } - Logging.instance.log(backupFile.absolute, level: LogLevel.Info); + Logging.instance.d(backupFile.absolute); return true; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); return false; } } @@ -194,7 +193,7 @@ abstract class SWB { Tuple2(encryptedText, passphrase), ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); return null; } } @@ -214,7 +213,7 @@ abstract class SWB { final jsonBackup = utf8.decode(decryptedContent); return jsonBackup; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); return null; } } @@ -223,27 +222,23 @@ abstract class SWB { static Future> createStackWalletJSON({ required SecureStorageInterface secureStorage, }) async { - Logging.instance - .log("Starting createStackWalletJSON...", level: LogLevel.Info); + Logging.instance.d("Starting createStackWalletJSON..."); final _wallets = Wallets.sharedInstance; final Map backupJson = {}; final NodeService nodeService = NodeService(secureStorageInterface: secureStorage); final _secureStore = secureStorage; - Logging.instance.log( + Logging.instance.d( "createStackWalletJSON awaiting DB.instance.mutex...", - level: LogLevel.Info, ); // prevent modification of data await DB.instance.mutex.protect(() async { - Logging.instance.log( + Logging.instance.i( "...createStackWalletJSON DB.instance.mutex acquired", - level: LogLevel.Info, ); - Logging.instance.log( + Logging.instance.i( "SWB backing up nodes", - level: LogLevel.Warning, ); try { final primaryNodes = nodeService.primaryNodes.map((e) async { @@ -253,7 +248,11 @@ abstract class SWB { }).toList(); backupJson['primaryNodes'] = await Future.wait(primaryNodes); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + Logging.instance.e( + "", + error: e, + stackTrace: s, + ); } try { final nodesFuture = nodeService.nodes.map((e) async { @@ -264,12 +263,11 @@ abstract class SWB { final nodes = await Future.wait(nodesFuture); backupJson['nodes'] = nodes; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("", error: e, stackTrace: s); } - Logging.instance.log( + Logging.instance.d( "SWB backing up prefs", - level: LogLevel.Warning, ); final Map prefs = {}; @@ -291,9 +289,8 @@ abstract class SWB { backupJson['prefs'] = prefs; - Logging.instance.log( + Logging.instance.d( "SWB backing up addressbook", - level: LogLevel.Warning, ); final AddressBookService addressBookService = AddressBookService(); @@ -301,9 +298,8 @@ abstract class SWB { backupJson['addressBookEntries'] = addresses.map((e) => e.toMap()).toList(); - Logging.instance.log( + Logging.instance.d( "SWB backing up wallets", - level: LogLevel.Warning, ); final List backupWallets = []; @@ -330,7 +326,7 @@ abstract class SWB { final String err = "${wallet.info.coin.identifier} wallet ${wallet.info.name} " "has null keys or config"; - Logging.instance.log(err, level: LogLevel.Fatal); + Logging.instance.e(err); throw Exception(err); } //This case should never actually happen in practice unless the whole @@ -368,9 +364,8 @@ abstract class SWB { } backupJson['wallets'] = backupWallets; - Logging.instance.log( + Logging.instance.d( "SWB backing up trades", - level: LogLevel.Warning, ); // back up trade history @@ -385,9 +380,8 @@ abstract class SWB { tradeTxidLookupDataService.all.map((e) => e.toMap()).toList(); backupJson["tradeTxidLookupData"] = lookupData; - Logging.instance.log( + Logging.instance.d( "SWB backing up trade notes", - level: LogLevel.Warning, ); // back up trade notes @@ -395,9 +389,8 @@ abstract class SWB { final tradeNotes = tradeNotesService.all; backupJson["tradeNotes"] = tradeNotes; }); - Logging.instance.log( + Logging.instance.d( "createStackWalletJSON DB.instance.mutex released", - level: LogLevel.Info, ); // // back up notifications data @@ -407,8 +400,7 @@ abstract class SWB { // .map((e) => e.toMap()) // .toList(growable: false); - Logging.instance - .log("...createStackWalletJSON complete", level: LogLevel.Info); + Logging.instance.d("...createStackWalletJSON complete"); return backupJson; } @@ -458,7 +450,7 @@ abstract class SWB { mnemonic: mnemonic, mnemonicPassphrase: mnemonicPassphrase, ); - + Wallet? wallet; try { String? serializedKeys; String? multisigConfig; @@ -491,7 +483,7 @@ abstract class SWB { }); } - final wallet = await Wallet.create( + wallet = await Wallet.create( walletInfo: info, mainDB: MainDB.instance, secureStorageInterface: secureStorageInterface, @@ -515,6 +507,10 @@ abstract class SWB { case const (WowneroWallet): await (wallet as WowneroWallet).init(isRestore: true); break; + + case const (XelisWallet): + await (wallet as XelisWallet).init(isRestore: true); + break; default: await wallet.init(); @@ -590,9 +586,8 @@ abstract class SWB { await restoringFuture; - Logging.instance.log( + Logging.instance.i( "SWB restored: ${info.walletId} ${info.name} ${info.coin.prettyName}", - level: LogLevel.Info, ); final currentAddress = await wallet.getCurrentReceivingAddress(); @@ -606,7 +601,11 @@ abstract class SWB { mnemonicPassphrase: mnemonicPassphrase, ); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); + Logging.instance.i( + "", + error: e, + stackTrace: s, + ); uiState?.update( walletId: info.walletId, restoringStatus: StackRestoringStatus.failed, @@ -614,6 +613,8 @@ abstract class SWB { mnemonicPassphrase: mnemonicPassphrase, ); return false; + } finally { + await wallet?.exit(); } return true; } @@ -638,18 +639,16 @@ abstract class SWB { uiState?.preferences = StackRestoringStatus.restoring; - Logging.instance.log( + Logging.instance.d( "SWB restoring prefs", - level: LogLevel.Warning, ); await _restorePrefs(prefs); uiState?.preferences = StackRestoringStatus.success; uiState?.addressBook = StackRestoringStatus.restoring; - Logging.instance.log( + Logging.instance.d( "SWB restoring addressbook", - level: LogLevel.Warning, ); if (addressBookEntries != null) { await _restoreAddressBook(addressBookEntries); @@ -658,9 +657,8 @@ abstract class SWB { uiState?.addressBook = StackRestoringStatus.success; uiState?.nodes = StackRestoringStatus.restoring; - Logging.instance.log( + Logging.instance.d( "SWB restoring nodes", - level: LogLevel.Warning, ); await _restoreNodes( nodes, @@ -673,18 +671,16 @@ abstract class SWB { // restore trade history if (trades != null) { - Logging.instance.log( + Logging.instance.d( "SWB restoring trades", - level: LogLevel.Warning, ); await _restoreTrades(trades); } // restore trade history lookup data for trades send from stack wallet if (tradeTxidLookupData != null) { - Logging.instance.log( + Logging.instance.d( "SWB restoring trade look up data", - level: LogLevel.Warning, ); await _restoreTradesLookUpData(tradeTxidLookupData, oldToNewWalletIdMap); } @@ -692,9 +688,8 @@ abstract class SWB { // restore trade notes if (tradeNotes != null) { - Logging.instance.log( + Logging.instance.d( "SWB restoring trade notes", - level: LogLevel.Warning, ); await _restoreTradesNotes(tradeNotes); } @@ -727,15 +722,13 @@ abstract class SWB { ) async { if (!Platform.isLinux) await WakelockPlus.enable(); - Logging.instance.log( + Logging.instance.d( "SWB creating temp backup", - level: LogLevel.Warning, ); final preRestoreJSON = await createStackWalletJSON(secureStorage: secureStorageInterface); - Logging.instance.log( + Logging.instance.d( "SWB temp backup created", - level: LogLevel.Warning, ); final List _currentWalletIds = await MainDB.instance.isar.walletInfo @@ -830,9 +823,10 @@ abstract class SWB { otherData = Map.from(data as Map); } } catch (e, s) { - Logging.instance.log( - "SWB restore walletinfo otherdata error: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "SWB restore walletinfo otherdata error: ", + error: e, + stackTrace: s, ); } @@ -948,7 +942,9 @@ abstract class SWB { return false; } - Logging.instance.log("done with SWB restore", level: LogLevel.Warning); + Logging.instance.d( + "done with SWB restore", + ); await Wallets.sharedInstance .loadAfterStackRestore(_prefs, uiState?.wallets ?? [], Util.isDesktop); @@ -1069,6 +1065,7 @@ abstract class SWB { loginName: nodeData['loginName'] as String?, isFailover: nodeData['isFailover'] as bool, isDown: nodeData['isDown'] as bool, + trusted: nodeData['trusted'] as bool?, ), nodeData['password'] as String?, true, @@ -1097,7 +1094,7 @@ abstract class SWB { node: nodeService.getNodeById(id: node['id'] as String)!, ); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("", error: e, stackTrace: s); } } } @@ -1190,7 +1187,9 @@ abstract class SWB { _cancelCompleter!.complete(); _shouldCancelRestore = false; - Logging.instance.log("Revert SWB complete", level: LogLevel.Info); + Logging.instance.d( + "Revert SWB complete", + ); } static Future _restorePrefs(Map prefs) async { @@ -1307,7 +1306,7 @@ abstract class SWB { node: nodeService.getNodeById(id: node['id'] as String)!, ); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("", error: e, stackTrace: s); } } } @@ -1324,7 +1323,7 @@ abstract class SWB { exTx = ExchangeTransaction.fromJson(trades[i] as Map); } catch (e) { // unneeded log - // Logging.instance.log("$e\n$s", level: LogLevel.Warning); + // Logging.instance.log("$e\n$s", error: e, stackTrace: s,); } Trade trade; diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index c994d6c7c..400e6e08d 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -172,7 +172,7 @@ class _RestoreFromFileViewState extends ConsumerState { }); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); } }, controller: fileLocationController, diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index b160eb2e1..2c7ddd71e 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -169,7 +169,7 @@ class _StackRestoreProgressViewState ref.read(secureStoreProvider), ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s", error: e, stackTrace: s,); } if (finished != null && finished && uiState.done) { diff --git a/lib/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart b/lib/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart index 425a7accc..245937fdf 100644 --- a/lib/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart @@ -604,10 +604,7 @@ Future connectTor(WidgetRef ref, BuildContext context) async { // Toggle the useTor preference on success. ref.read(prefsChangeNotifierProvider).useTor = true; } catch (e, s) { - Logging.instance.log( - "Error starting tor: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error starting tor: ", error: e, stackTrace: s); // TODO: show dialog with error message } } @@ -627,10 +624,7 @@ Future disconnectTor(WidgetRef ref, BuildContext context) async { // Toggle the useTor preference on success. ref.read(prefsChangeNotifierProvider).useTor = false; } catch (e, s) { - Logging.instance.log( - "Error stopping tor: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error stopping tor: ", error: e, stackTrace: s); // TODO: show dialog with error message } } diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart index 7adb5b4c3..afc48647d 100644 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart @@ -148,10 +148,7 @@ class _CompleteReshareConfigViewState ); } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); if (mounted) { await showDialog( context: context, diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart index 14cae2ee0..ffe541b21 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart @@ -156,11 +156,13 @@ class _CNWalletKeysState extends State { SizedBox( height: Util.isDesktop ? 12 : 16, ), - QR( - data: _current(_currentDropDownValue), - size: - Util.isDesktop ? 256 : MediaQuery.of(context).size.width / 1.5, - ), + if (_current(_currentDropDownValue) != "ERROR") + QR( + data: _current(_currentDropDownValue), + size: Util.isDesktop + ? 256 + : MediaQuery.of(context).size.width / 1.5, + ), SizedBox( height: Util.isDesktop ? 12 : 16, ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index 1be577f1d..5b3a2eb07 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -39,6 +39,7 @@ import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../../wallets/wallet/impl/wownero_wallet.dart'; +import '../../../../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import '../../../../widgets/animated_text.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/conditional_parent.dart'; @@ -233,7 +234,12 @@ class _WalletNetworkSettingsViewState _percent = 1; _blocksRemaining = 0; } else { - _percent = 0; + final wallet = ref.read(pWallets).getWallet(widget.walletId); + if (wallet is ElectrumXInterface) { + _percent = wallet.refreshingPercent ?? 0; + } else { + _percent = 0; + } _blocksRemaining = -1; } diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index 4948876bd..18b18d9e1 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -49,7 +49,6 @@ import '../../../widgets/stack_dialog.dart'; import '../../address_book_views/address_book_view.dart'; import '../../home_view/home_view.dart'; import '../../pinpad_views/lock_screen_view.dart'; -import '../global_settings_view/advanced_views/debug_view.dart'; import '../global_settings_view/syncing_preferences_views/syncing_preferences_view.dart'; import '../sub_widgets/settings_list_button.dart'; import 'frost_ms/frost_ms_options_view.dart'; @@ -316,9 +315,10 @@ class _WalletSettingsViewState extends ConsumerState { if (wallet is MnemonicInterface) { if (wallet is ViewOnlyOptionInterface && - !(wallet - as ViewOnlyOptionInterface) + (wallet as ViewOnlyOptionInterface) .isViewOnly) { + // TODO: is something needed here? + } else { mnemonic = await wallet .getMnemonicAsWords(); } @@ -557,17 +557,17 @@ class _WalletSettingsViewState extends ConsumerState { ); }, ), - const SizedBox( - height: 8, - ), - SettingsListButton( - iconAssetName: Assets.svg.ellipsis, - title: "Debug Info", - onPressed: () { - Navigator.of(context) - .pushNamed(DebugView.routeName); - }, - ), + // const SizedBox( + // height: 8, + // ), + // SettingsListButton( + // iconAssetName: Assets.svg.ellipsis, + // title: "Debug Info", + // onPressed: () { + // Navigator.of(context) + // .pushNamed(DebugView.routeName); + // }, + // ), ], ), ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart new file mode 100644 index 000000000..61d497452 --- /dev/null +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart @@ -0,0 +1,238 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../notifications/show_flush_bar.dart'; +import '../../../../providers/db/main_db_provider.dart'; +import '../../../../providers/global/wallets_provider.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/constants.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../utilities/util.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../../widgets/background.dart'; +import '../../../../widgets/conditional_parent.dart'; +import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../../widgets/desktop/desktop_dialog.dart'; +import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/icon_widgets/x_icon.dart'; +import '../../../../widgets/stack_text_field.dart'; +import '../../../../widgets/textfield_icon_button.dart'; + +class EditRefreshHeightView extends ConsumerStatefulWidget { + const EditRefreshHeightView({ + super.key, + required this.walletId, + }); + + static const String routeName = "/editRefreshHeightView"; + + final String walletId; + + @override + ConsumerState createState() => + _EditRefreshHeightViewState(); +} + +class _EditRefreshHeightViewState extends ConsumerState { + late final LibMoneroWallet _wallet; + late final TextEditingController _controller; + final _focusNode = FocusNode(); + + bool _saveLock = false; + + void _save() async { + if (_saveLock) return; + _saveLock = true; + try { + String? errMessage; + try { + final newHeight = int.tryParse(_controller.text); + if (newHeight != null && newHeight >= 0) { + await _wallet.info.updateRestoreHeight( + newRestoreHeight: newHeight, + isar: ref.read(mainDBProvider).isar, + ); + _wallet.libMoneroWallet!.setRefreshFromBlockHeight(newHeight); + } else { + errMessage = "Invalid height: ${_controller.text}"; + } + } catch (e) { + errMessage = e.toString(); + } + + if (mounted) { + if (errMessage == null) { + Navigator.of(context).pop(); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Refresh height updated", + context: context, + ), + ); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: errMessage, + context: context, + ), + ); + } + } + } finally { + _saveLock = false; + } + } + + @override + void initState() { + super.initState(); + _wallet = ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet; + _controller = TextEditingController() + ..text = _wallet.libMoneroWallet!.getRefreshFromBlockHeight().toString(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return DesktopDialog( + maxWidth: 500, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: child, + ), + const SizedBox( + height: 32, + ), + ], + ), + ); + }, + child: ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Restore height", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("restoreHeightFieldKey"), + controller: _controller, + focusNode: _focusNode, + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ) + : STextStyles.field(context), + enableSuggestions: false, + autocorrect: false, + autofocus: true, + onSubmitted: (_) => _save(), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Restore height", + _focusNode, + context, + ).copyWith( + suffixIcon: _controller.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: ConditionalParent( + condition: Util.isDesktop, + builder: (child) => SizedBox( + height: 70, + child: child, + ), + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _controller.text = ""; + }); + }, + ), + ], + ), + ), + ), + ) + : Util.isDesktop + ? const SizedBox( + height: 70, + ) + : null, + ), + ), + ), + Util.isDesktop + ? const SizedBox( + height: 32, + ) + : const Spacer(), + PrimaryButton( + label: "Save", + onPressed: _save, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart index 56e726731..da8c328bf 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart @@ -8,17 +8,27 @@ * */ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; import '../../../../providers/db/main_db_provider.dart'; +import '../../../../providers/global/wallets_provider.dart'; import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/logger.dart'; +import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; +import '../../../../utilities/util.dart'; import '../../../../wallets/isar/models/wallet_info.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/draggable_switch_button.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/stack_dialog.dart'; class LelantusSettingsView extends ConsumerStatefulWidget { const LelantusSettingsView({ @@ -50,12 +60,91 @@ class _LelantusSettingsViewState extends ConsumerState { }, isar: ref.read(mainDBProvider).isar, ); + if (newValue) { + await _doRescanMaybe(); + } } finally { // ensure _isUpdatingLelantusScanning is set to false no matter what _isUpdatingLelantusScanning = false; } } + Future _doRescanMaybe() async { + final shouldRescan = await showDialog( + context: context, + builder: (context) { + return StackDialog( + title: "Rescan may be required", + message: "A blockchain rescan may be required to fully recover all " + "lelantus history. This may take a while.", + leftButton: SecondaryButton( + label: "Rescan now", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + rightButton: PrimaryButton( + label: "Later", + onPressed: () => Navigator.of(context).pop(false), + ), + ); + }, + ); + + if (mounted && shouldRescan == true) { + try { + if (!Platform.isLinux) await WakelockPlus.enable(); + + Exception? e; + if (mounted) { + await showLoading( + whileFuture: ref.read(pWallets).getWallet(widget.walletId).recover( + isRescan: true, + ), + context: context, + message: "Rescanning blockchain", + subMessage: "This may take a while." + "\nPlease do not exit this screen.", + rootNavigator: Util.isDesktop, + onException: (ex) => e = ex, + ); + + if (e != null) { + throw e!; + } + } + } catch (e, s) { + Logging.instance.e("$e\n$s", error: e, stackTrace: s); + if (mounted) { + // show error + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) => StackDialog( + title: "Rescan failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context, rootNavigator: Util.isDesktop).pop(); + }, + ), + ), + ); + } + } finally { + if (!Platform.isLinux) await WakelockPlus.disable(); + } + } + } + @override Widget build(BuildContext context) { return Background( diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart index bf41d6261..c689baf30 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart @@ -20,6 +20,7 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/isar/models/wallet_info.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; @@ -32,6 +33,7 @@ import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; import '../../../pinpad_views/lock_screen_view.dart'; import 'delete_wallet_warning_view.dart'; +import 'edit_refresh_height_view.dart'; import 'lelantus_settings_view.dart'; import 'rbf_settings_view.dart'; import 'rename_wallet_view.dart'; @@ -354,6 +356,42 @@ class _WalletSettingsWalletSettingsViewState ), ), ), + if (wallet is LibMoneroWallet) + const SizedBox( + height: 8, + ), + if (wallet is LibMoneroWallet) + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () { + Navigator.of(context).pushNamed( + EditRefreshHeightView.routeName, + arguments: widget.walletId, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Restore height", + style: STextStyles.titleBold12(context), + ), + ], + ), + ), + ), + ), const SizedBox( height: 8, ), diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 1071ffdae..11ebd1b82 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -219,7 +219,7 @@ class _TransactionDetailsViewState return address; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s", error: e, stackTrace: s,); return address; } } diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart index a1d4bb3fd..8f0852941 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart @@ -42,6 +42,7 @@ import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/models/spark_coin.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../widgets/background.dart'; @@ -57,6 +58,7 @@ import '../../../../widgets/icon_widgets/copy_icon.dart'; import '../../../../widgets/icon_widgets/pencil_icon.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; +import '../../../../widgets/tx_key_widget.dart'; import '../../sub_widgets/tx_icon.dart'; import '../../wallet_view.dart'; import '../dialogs/cancelling_transaction_progress_dialog.dart'; @@ -96,6 +98,7 @@ class _TransactionV2DetailsViewState late final int minConfirms; late final EthContract? ethContract; late final bool supportsRbf; + late final bool hasTxKeyProbably; bool get isTokenTx => ethContract != null; @@ -180,10 +183,16 @@ class _TransactionV2DetailsViewState _transaction = widget.transaction; walletId = widget.walletId; + final wallet = ref.read(pWallets).getWallet(walletId); + + hasTxKeyProbably = wallet is LibMoneroWallet && + (_transaction.type == TransactionType.outgoing || + _transaction.type == TransactionType.sentToSelf); + if (_transaction.type case TransactionType.sentToSelf || TransactionType.outgoing) { supportsRbf = _transaction.subType == TransactionSubType.none && - ref.read(pWallets).getWallet(walletId) is RbfInterface; + wallet is RbfInterface; } else { supportsRbf = false; } @@ -397,7 +406,7 @@ class _TransactionV2DetailsViewState return address; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s", error: e, stackTrace: s,); return address; } } @@ -1357,13 +1366,17 @@ class _TransactionV2DetailsViewState ], ), ), - if (coin is! NanoCurrency) + if (coin is! NanoCurrency && + !(coin is Xelis && _transaction.type == TransactionType.incoming) + ) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (coin is! NanoCurrency) + if (coin is! NanoCurrency && + !(coin is Xelis && _transaction.type == TransactionType.incoming) + ) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -1704,6 +1717,17 @@ class _TransactionV2DetailsViewState ], ), ), + if (hasTxKeyProbably) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (hasTxKeyProbably) + TxKeyWidget( + walletId: walletId, + txid: _transaction.txid, + ), isDesktop ? const _Divider() : const SizedBox( diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index d3a98ef16..41422c7e0 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -22,6 +22,8 @@ import '../../app_config.dart'; import '../../frost_route_generator.dart'; import '../../models/isar/exchange_cache/currency.dart'; import '../../notifications/show_flush_bar.dart'; +import '../../pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart'; +import '../../pages_desktop_specific/spark_coins/spark_coins_view.dart'; import '../../providers/global/active_wallet_provider.dart'; import '../../providers/global/auto_swb_service_provider.dart'; import '../../providers/global/paynym_api_provider.dart'; @@ -50,6 +52,7 @@ import '../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; import '../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../wallets/wallet/impl/firo_wallet.dart'; +import '../../wallets/wallet/impl/namecoin_wallet.dart'; import '../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; @@ -85,6 +88,7 @@ import '../churning/churning_view.dart'; import '../coin_control/coin_control_view.dart'; import '../exchange_view/wallet_initiated_exchange_view.dart'; import '../monkey/monkey_view.dart'; +import '../namecoin_names/namecoin_names_home_view.dart'; import '../notification_views/notifications_view.dart'; import '../ordinals/ordinals_view.dart'; import '../paynym/paynym_claim_view.dart'; @@ -1138,6 +1142,49 @@ class _WalletViewState extends ConsumerState { ); }, ), + if (wallet is FiroWallet && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.advancedFiroFeatures, + ), + )) + WalletNavigationBarItemData( + label: "Lelantus coins", + icon: const CoinControlNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + LelantusCoinsView.routeName, + arguments: widget.walletId, + ); + }, + ), + if (wallet is FiroWallet && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.advancedFiroFeatures, + ), + )) + WalletNavigationBarItemData( + label: "Spark coins", + icon: const CoinControlNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + SparkCoinsView.routeName, + arguments: widget.walletId, + ); + }, + ), + if (wallet is NamecoinWallet) + WalletNavigationBarItemData( + label: "Domains", + icon: const PaynymNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + NamecoinNamesHomeView.routeName, + arguments: widget.walletId, + ); + }, + ), if (!viewOnly && wallet is PaynymInterface) WalletNavigationBarItemData( label: "PayNym", @@ -1165,10 +1212,7 @@ class _WalletViewState extends ConsumerState { .read(paynymAPIProvider) .nym(code.toString()); - Logging.instance.log( - "my nym account: $account", - level: LogLevel.Info, - ); + Logging.instance.d("my nym account: $account"); if (context.mounted) { Navigator.of(context).pop(); diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 26e11624a..d9f730804 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -27,7 +27,7 @@ import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/coins/firo.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart'; -import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../wallets/wallet/intermediate/external_wallet.dart'; import '../../../widgets/coin_card.dart'; import '../../../widgets/conditional_parent.dart'; import '../../wallet_view/wallet_view.dart'; @@ -132,7 +132,7 @@ class _FavoriteCardState extends ConsumerState { } final Future loadFuture; - if (wallet is LibMoneroWallet) { + if (wallet is ExternalWallet) { loadFuture = wallet.init().then((value) async => await (wallet).open()); } else { diff --git a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart index 767770ba9..938baac71 100644 --- a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart +++ b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart @@ -25,7 +25,7 @@ import '../../../utilities/show_node_tor_settings_mismatch.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; -import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../wallets/wallet/intermediate/external_wallet.dart'; import '../../../widgets/dialogs/tor_warning_dialog.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../wallet_view/wallet_view.dart'; @@ -99,7 +99,7 @@ class WalletListItem extends ConsumerWidget { } final Future loadFuture; - if (wallet is LibMoneroWallet) { + if (wallet is ExternalWallet) { loadFuture = wallet.init().then((value) async => await (wallet).open()); } else { diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart index 2567a83cf..95e38b07d 100644 --- a/lib/pages/wallets_view/wallets_overview.dart +++ b/lib/pages/wallets_view/wallets_overview.dart @@ -187,7 +187,7 @@ class _EthWalletsOverviewState extends ConsumerState { updateWallets(); if (AppConfig.isSingleCoinApp) { - GlobalEventBus.instance.on().listen((_) { + GlobalEventBus.instance.on().listen((_) { updateWallets(); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { diff --git a/lib/pages_desktop_specific/coin_control/freeze_button.dart b/lib/pages_desktop_specific/coin_control/freeze_button.dart index dddcc92fa..b012b01ad 100644 --- a/lib/pages_desktop_specific/coin_control/freeze_button.dart +++ b/lib/pages_desktop_specific/coin_control/freeze_button.dart @@ -11,11 +11,12 @@ import 'package:async/async.dart'; import 'package:flutter/material.dart'; import 'package:isar/isar.dart'; + import '../../db/isar/main_db.dart'; import '../../models/isar/models/blockchain_data/utxo.dart'; -import 'utxo_row.dart'; import '../../utilities/logger.dart'; import '../../widgets/desktop/primary_button.dart'; +import 'utxo_row.dart'; class FreezeButton extends StatefulWidget { const FreezeButton({ @@ -78,10 +79,7 @@ class _FreezeButtonState extends State { break; default: - Logging.instance.log( - "Unknown utxo method name found in $runtimeType", - level: LogLevel.Fatal, - ); + Logging.instance.f("Unknown utxo method name found in $runtimeType"); return; } diff --git a/lib/pages_desktop_specific/coin_control/utxo_row.dart b/lib/pages_desktop_specific/coin_control/utxo_row.dart index 26204375c..548e2c05e 100644 --- a/lib/pages_desktop_specific/coin_control/utxo_row.dart +++ b/lib/pages_desktop_specific/coin_control/utxo_row.dart @@ -20,7 +20,9 @@ import '../../themes/stack_colors.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/amount/amount_formatter.dart'; import '../../utilities/text_styles.dart'; +import '../../wallets/crypto_currency/coins/namecoin.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/wallet/impl/namecoin_wallet.dart'; import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; import '../../widgets/desktop/secondary_button.dart'; @@ -135,19 +137,18 @@ class _UtxoRowState extends ConsumerState { ), UTXOStatusIcon( blocked: utxo.isBlocked, - status: utxo.isConfirmed( - ref.watch(pWalletChainHeight(widget.walletId)), - ref - .watch(pWallets) - .getWallet(widget.walletId) - .cryptoCurrency - .minConfirms, - ref - .watch(pWallets) - .getWallet(widget.walletId) - .cryptoCurrency - .minCoinbaseConfirms, - ) + status: (coin is Namecoin + ? (ref.watch(pWallets).getWallet(widget.walletId) + as NamecoinWallet) + .checkUtxoConfirmed( + utxo, + ref.watch(pWalletChainHeight(widget.walletId)), + ) + : utxo.isConfirmed( + ref.watch(pWalletChainHeight(widget.walletId)), + coin.minConfirms, + coin.minCoinbaseConfirms, + )) ? UTXOStatusIconStatus.confirmed : UTXOStatusIconStatus.unconfirmed, background: Theme.of(context).extension()!.popupBG, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index a7f76c7e8..fd7c72a23 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -83,7 +83,7 @@ class _DesktopStep2State extends ConsumerState { ref.read(desktopExchangeModelProvider)!.recipientAddress = info.item2; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); + Logging.instance.i("$e\n$s", error: e, stackTrace: s,); } widget.enableNextChanged.call( @@ -116,7 +116,7 @@ class _DesktopStep2State extends ConsumerState { ref.read(desktopExchangeModelProvider)!.refundAddress = info.item2; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); + Logging.instance.i("$e\n$s", error: e, stackTrace: s,); } widget.enableNextChanged.call( _next(), diff --git a/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart b/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart index 61624a272..c08891aef 100644 --- a/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart +++ b/lib/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart @@ -11,224 +11,122 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:isar/isar.dart'; + import '../../models/isar/models/isar_models.dart'; import '../../providers/db/main_db_provider.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/assets.dart'; import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/desktop/desktop_app_bar.dart'; import '../../widgets/desktop/desktop_scaffold.dart'; -import '../../widgets/rounded_white_container.dart'; +import '../../widgets/isar_collection_watcher_list.dart'; -class LelantusCoinsView extends ConsumerStatefulWidget { +class LelantusCoinsView extends ConsumerWidget { const LelantusCoinsView({ super.key, required this.walletId, }); + static const title = "Lelantus coins"; static const String routeName = "/lelantusCoinsView"; final String walletId; @override - ConsumerState createState() => _LelantusCoinsViewState(); -} - -class _LelantusCoinsViewState extends ConsumerState { - List _coins = []; - - Stream>? lelantusCoinsCollectionWatcher; - - void _onLelantusCoinsCollectionWatcherEvent(List coins) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() { - _coins = coins; - }); - } - }); - } - - @override - void initState() { - lelantusCoinsCollectionWatcher = ref - .read(mainDBProvider) - .isar - .lelantusCoins - .where() - .walletIdEqualTo(widget.walletId) - .sortByMintIndexDesc() - .watch(fireImmediately: true); - lelantusCoinsCollectionWatcher! - .listen((data) => _onLelantusCoinsCollectionWatcherEvent(data)); - - super.initState(); - } - - @override - void dispose() { - lelantusCoinsCollectionWatcher = null; - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return DesktopScaffold( - appBar: DesktopAppBar( - background: Theme.of(context).extension()!.popupBG, - leading: Expanded( - child: Row( - children: [ - const SizedBox( - width: 32, - ), - AppBarIconButton( - size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: Navigator.of(context).pop, - ), - const SizedBox( - width: 12, - ), - Text( - "Lelantus Coins", - style: STextStyles.desktopH3(context), - ), - const Spacer(), - ], - ), - ), - useSpacers: false, - isCompactHeight: true, - ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - children: [ - Expanded( - flex: 9, - child: Text( - "TXID", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 3, - child: Text( - "Value (sats)", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Index", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Is JMint", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Used", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), + Widget build(BuildContext context, WidgetRef ref) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, ), - ], - ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 12, + ), + Text( + title, + style: STextStyles.desktopH3(context), + ), + const Spacer(), + ], ), ), - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: _coins.length, - separatorBuilder: (_, __) => Container( - height: 1, - color: Theme.of(context) - .extension()! - .backgroundAppBar, + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: child, + ), + ); + }, + child: ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + automaticallyImplyLeading: false, + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), ), - itemBuilder: (_, index) => Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 9, - child: SelectableText( - _coins[index].txid, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 3, - child: SelectableText( - _coins[index].value, - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].mintIndex.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].isJMint.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].isUsed.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - ], - ), - ), + title: Text( + title, + style: STextStyles.navBarTitle(context), ), ), + body: SafeArea( + child: child, + ), ), - ], + ); + }, + child: IsarCollectionWatcherList( + itemName: title, + queryBuilder: () => ref + .read(mainDBProvider) + .isar + .lelantusCoins + .where() + .walletIdEqualTo(walletId) + .sortByMintIndexDesc(), + itemBuilder: (LelantusCoin? coin) { + return [ + ("TXID", coin?.txid ?? "", 9), + ("Value (sats)", coin?.value ?? "", 3), + ("Index", coin?.mintIndex.toString() ?? "", 2), + ("Is JMint", coin?.isJMint.toString() ?? "", 2), + ("Used", coin?.isUsed.toString() ?? "", 2), + ]; + }, ), ), ); diff --git a/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart b/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart index 8da907ca3..aaf1d1402 100644 --- a/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart +++ b/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart @@ -21,7 +21,7 @@ import '../../utilities/show_loading.dart'; import '../../utilities/show_node_tor_settings_mismatch.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; -import '../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../wallets/wallet/intermediate/external_wallet.dart'; import '../../widgets/rounded_container.dart'; import '../../widgets/wallet_info_row/wallet_info_row.dart'; import 'wallet_view/desktop_wallet_view.dart'; @@ -101,7 +101,7 @@ class CoinWalletsTable extends ConsumerWidget { } final Future loadFuture; - if (wallet is LibMoneroWallet) { + if (wallet is ExternalWallet) { loadFuture = wallet .init() .then((value) async => await (wallet).open()); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart index 5df82cc0f..066ae9073 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart @@ -25,7 +25,7 @@ import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/isar/providers/all_wallets_info_provider.dart'; -import '../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../wallets/wallet/intermediate/external_wallet.dart'; import '../../widgets/breathing.dart'; import '../../widgets/conditional_parent.dart'; import '../../widgets/desktop/desktop_dialog.dart'; @@ -138,7 +138,7 @@ class _DesktopWalletSummaryRowState } final Future loadFuture; - if (wallet is LibMoneroWallet) { + if (wallet is ExternalWallet) { loadFuture = wallet.init().then((value) async => await (wallet).open()); } else { diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart index 9893a6cae..2cf41f06a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -45,6 +45,7 @@ import '../../../utilities/wallet_tools.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/wallet/impl/banano_wallet.dart'; import '../../../wallets/wallet/impl/firo_wallet.dart'; +import '../../../wallets/wallet/wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/custom_buttons/blue_text_button.dart'; @@ -55,6 +56,7 @@ import '../../../widgets/rounded_white_container.dart'; import '../../coin_control/desktop_coin_control_use_dialog.dart'; import 'sub_widgets/desktop_wallet_features.dart'; import 'sub_widgets/desktop_wallet_summary.dart'; +import 'sub_widgets/firo_desktop_wallet_summary.dart'; import 'sub_widgets/my_wallet.dart'; import 'sub_widgets/network_info_button.dart'; import 'sub_widgets/wallet_keys_button.dart'; @@ -62,11 +64,7 @@ import 'sub_widgets/wallet_options_button.dart'; /// [eventBus] should only be set during testing class DesktopWalletView extends ConsumerStatefulWidget { - const DesktopWalletView({ - super.key, - required this.walletId, - this.eventBus, - }); + const DesktopWalletView({super.key, required this.walletId, this.eventBus}); static const String routeName = "/desktopWalletView"; @@ -138,12 +136,10 @@ class _DesktopWalletViewState extends ConsumerState { eventBus = widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance; - WidgetsBinding.instance.addPostFrameCallback( - (_) { - ref.read(currentWalletIdProvider.notifier).state = wallet.walletId; - ref.read(desktopUseUTXOs.notifier).state = {}; - }, - ); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(currentWalletIdProvider.notifier).state = wallet.walletId; + ref.read(desktopUseUTXOs.notifier).state = {}; + }); if (!wallet.shouldAutoSync) { // // enable auto sync if it wasn't enabled when loading wallet @@ -171,9 +167,10 @@ class _DesktopWalletViewState extends ConsumerState { final monke = wallet is BananoWallet ? wallet.getMonkeyImageBytes() : null; // if the view only wallet watches a single address there are no keys of any kind - final showKeysButton = !(wallet is ViewOnlyOptionInterface && - wallet.isViewOnly && - wallet.viewOnlyType == ViewOnlyWalletType.addressOnly); + final showKeysButton = + !(wallet is ViewOnlyOptionInterface && + wallet.isViewOnly && + wallet.viewOnlyType == ViewOnlyWalletType.addressOnly); return DesktopScaffold( appBar: DesktopAppBar( @@ -181,59 +178,48 @@ class _DesktopWalletViewState extends ConsumerState { leading: Expanded( child: Row( children: [ - const SizedBox( - width: 32, - ), + const SizedBox(width: 32), AppBarIconButton( size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, shadows: const [], icon: SvgPicture.asset( Assets.svg.arrowLeft, width: 18, height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, + color: + Theme.of( + context, + ).extension()!.topNavIconPrimary, ), onPressed: onBackPressed, ), - const SizedBox( - width: 15, - ), + const SizedBox(width: 15), SvgPicture.file( - File( - ref.watch(coinIconProvider(wallet.info.coin)), - ), + File(ref.watch(coinIconProvider(wallet.info.coin))), width: 32, height: 32, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 48, - ), + constraints: const BoxConstraints(minWidth: 48), child: IntrinsicWidth( - child: DesktopWalletNameField( - walletId: widget.walletId, - ), + child: DesktopWalletNameField(walletId: widget.walletId), ), ), if (ref.watch(pWalletInfo(widget.walletId)).isViewOnly) - const SizedBox( - width: 20, - ), + const SizedBox(width: 20), if (ref.watch(pWalletInfo(widget.walletId)).isViewOnly) Text( "(View only)", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, ), ), if (kDebugMode) const Spacer(), @@ -244,12 +230,8 @@ class _DesktopWalletViewState extends ConsumerState { children: [ Row( children: [ - const Text( - "dbgHeight: ", - ), - const SizedBox( - width: 2, - ), + const Text("dbgHeight: "), + const SizedBox(width: 2), Text( ref .watch(pWalletChainHeight(widget.walletId)) @@ -259,12 +241,8 @@ class _DesktopWalletViewState extends ConsumerState { ), Row( children: [ - const Text( - "dbgTxCount: ", - ), - const SizedBox( - width: 2, - ), + const Text("dbgTxCount: "), + const SizedBox(width: 2), Text( wallet.isarTransactionVersion == 2 ? ref @@ -290,16 +268,10 @@ class _DesktopWalletViewState extends ConsumerState { wallet is FiroWallet) Row( children: [ - const Text( - "dbgBal: ", - ), - const SizedBox( - width: 2, - ), + const Text("dbgBal: "), + const SizedBox(width: 2), Text( - WalletDevTools.checkFiroTransactionTally( - widget.walletId, - ), + WalletDevTools.checkFiroTransactionTally(wallet), ), ], ), @@ -307,19 +279,13 @@ class _DesktopWalletViewState extends ConsumerState { wallet is FiroWallet) Row( children: [ - const Text( - "sparkCache: ", - ), - const SizedBox( - width: 2, - ), + const Text("sparkCache: "), + const SizedBox(width: 2), FutureBuilder( future: FiroCacheCoordinator.getSparkCacheSize( wallet.cryptoCurrency.network, ), - builder: (_, snapshot) => Text( - snapshot.data ?? "", - ), + builder: (_, snapshot) => Text(snapshot.data ?? ""), ), ], ), @@ -332,23 +298,12 @@ class _DesktopWalletViewState extends ConsumerState { walletId: widget.walletId, eventBus: eventBus, ), + if (showKeysButton) const SizedBox(width: 2), if (showKeysButton) - const SizedBox( - width: 2, - ), - if (showKeysButton) - WalletKeysButton( - walletId: widget.walletId, - ), - const SizedBox( - width: 2, - ), - WalletOptionsButton( - walletId: widget.walletId, - ), - const SizedBox( - width: 12, - ), + WalletKeysButton(walletId: widget.walletId), + const SizedBox(width: 2), + WalletOptionsButton(walletId: widget.walletId), + const SizedBox(width: 12), ], ), ], @@ -361,43 +316,8 @@ class _DesktopWalletViewState extends ConsumerState { padding: const EdgeInsets.all(24), child: Column( children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - if (monke != null) - SvgPicture.memory( - Uint8List.fromList(monke!), - width: 60, - height: 60, - ), - if (monke == null) - SvgPicture.file( - File( - ref.watch(coinIconProvider(wallet.info.coin)), - ), - width: 40, - height: 40, - ), - const SizedBox( - width: 10, - ), - DesktopWalletSummary( - walletId: widget.walletId, - initialSyncStatus: wallet.refreshMutex.isLocked - ? WalletSyncStatus.syncing - : WalletSyncStatus.synced, - ), - const Spacer(), - DesktopWalletFeatures( - walletId: widget.walletId, - ), - ], - ), - ), - const SizedBox( - height: 24, - ), + DesktopWalletHeaderRow(wallet, monke), + const SizedBox(height: 24), Row( children: [ SizedBox( @@ -405,15 +325,14 @@ class _DesktopWalletViewState extends ConsumerState { child: Text( "My wallet", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, ), ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -422,25 +341,29 @@ class _DesktopWalletViewState extends ConsumerState { wallet.cryptoCurrency.hasTokenSupport ? "Tokens" : "Recent activity", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, ), ), CustomTextButton( - text: wallet.cryptoCurrency.hasTokenSupport - ? "Edit" - : "See all", + text: + wallet.cryptoCurrency.hasTokenSupport + ? "Edit" + : "See all", onTap: () async { if (wallet.cryptoCurrency.hasTokenSupport) { final result = await showDialog( context: context, - builder: (context) => EditWalletTokensView( - walletId: widget.walletId, - isDesktopPopup: true, - ), + builder: + (context) => EditWalletTokensView( + walletId: widget.walletId, + isDesktopPopup: true, + ), ); if (result == 42) { @@ -462,34 +385,23 @@ class _DesktopWalletViewState extends ConsumerState { ), ], ), - const SizedBox( - height: 14, - ), + const SizedBox(height: 14), Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: sendReceiveColumnWidth, - child: MyWallet( - walletId: widget.walletId, - ), - ), - const SizedBox( - width: 16, + child: MyWallet(walletId: widget.walletId), ), + const SizedBox(width: 16), Expanded( - child: wallet.cryptoCurrency.hasTokenSupport - ? MyTokensView( - walletId: widget.walletId, - ) - : wallet.isarTransactionVersion == 2 - ? TransactionsV2List( - walletId: widget.walletId, - ) - : TransactionsList( - walletId: widget.walletId, - ), + child: + wallet.cryptoCurrency.hasTokenSupport + ? MyTokensView(walletId: widget.walletId) + : wallet.isarTransactionVersion == 2 + ? TransactionsV2List(walletId: widget.walletId) + : TransactionsList(walletId: widget.walletId), ), ], ), @@ -500,3 +412,84 @@ class _DesktopWalletViewState extends ConsumerState { ); } } + +class DesktopWalletHeaderRow extends ConsumerWidget { + const DesktopWalletHeaderRow(this.wallet, this.monke, {super.key}); + + final Wallet wallet; + final List? monke; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(20), + child: + wallet is FiroWallet && MediaQuery.of(context).size.width < 1600 + ? Column( + children: [ + Row( + children: [ + SvgPicture.file( + File(ref.watch(coinIconProvider(wallet.info.coin))), + width: 40, + height: 40, + ), + const SizedBox(width: 10), + FiroDesktopWalletSummary( + walletId: wallet.walletId, + initialSyncStatus: + wallet.refreshMutex.isLocked + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, + ), + + const Spacer(), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + DesktopWalletFeatures(walletId: wallet.walletId), + ], + ), + ], + ) + : Row( + children: [ + if (monke != null) + SvgPicture.memory( + Uint8List.fromList(monke!), + width: 60, + height: 60, + ), + if (monke == null) + SvgPicture.file( + File(ref.watch(coinIconProvider(wallet.info.coin))), + width: 40, + height: 40, + ), + const SizedBox(width: 10), + if (wallet is FiroWallet) + FiroDesktopWalletSummary( + walletId: wallet.walletId, + initialSyncStatus: + wallet.refreshMutex.isLocked + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, + ), + + if (wallet is! FiroWallet) + DesktopWalletSummary( + walletId: wallet.walletId, + initialSyncStatus: + wallet.refreshMutex.isLocked + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, + ), + const Spacer(), + DesktopWalletFeatures(walletId: wallet.walletId), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart index abe41055c..5f4dcf574 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart @@ -10,14 +10,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; + import '../../../../providers/wallet/public_private_balance_state_provider.dart'; import '../../../../providers/wallet/wallet_balance_toggle_state_provider.dart'; import '../../../../themes/stack_colors.dart'; -import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/wallet_balance_toggle_state.dart'; import '../../../../utilities/text_styles.dart'; +import '../../../../wallets/isar/providers/wallet_info_provider.dart'; class DesktopBalanceToggleButton extends ConsumerWidget { const DesktopBalanceToggleButton({ @@ -75,37 +75,53 @@ class DesktopBalanceToggleButton extends ConsumerWidget { class DesktopPrivateBalanceToggleButton extends ConsumerWidget { const DesktopPrivateBalanceToggleButton({ super.key, + required this.walletId, this.onPressed, }); + final String walletId; final VoidCallback? onPressed; @override Widget build(BuildContext context, WidgetRef ref) { final currentType = ref.watch(publicPrivateBalanceStateProvider); + final showLelantus = + ref.watch(pWalletBalanceSecondary(walletId)).spendable.raw > + BigInt.zero; + return SizedBox( height: 22, - width: 22, + width: 80, child: MaterialButton( color: Theme.of(context).extension()!.buttonBackSecondary, splashColor: Theme.of(context).extension()!.highlight, onPressed: () { - switch (currentType) { - case FiroType.public: - ref.read(publicPrivateBalanceStateProvider.state).state = - FiroType.lelantus; - break; + if (showLelantus) { + switch (currentType) { + case FiroType.public: + ref.read(publicPrivateBalanceStateProvider.state).state = + FiroType.lelantus; + break; + + case FiroType.lelantus: + ref.read(publicPrivateBalanceStateProvider.state).state = + FiroType.spark; + break; - case FiroType.lelantus: + case FiroType.spark: + ref.read(publicPrivateBalanceStateProvider.state).state = + FiroType.public; + break; + } + } else { + if (currentType != FiroType.spark) { ref.read(publicPrivateBalanceStateProvider.state).state = FiroType.spark; - break; - - case FiroType.spark: + } else { ref.read(publicPrivateBalanceStateProvider.state).state = FiroType.public; - break; + } } onPressed?.call(); }, @@ -120,22 +136,12 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget { ), ), child: Center( - child: currentType == FiroType.spark - ? SvgPicture.asset( - Assets.svg.spark, - width: 16, - // color: Theme.of(context) - // .extension()! - // .accentColorYellow, - ) - : Image( - image: AssetImage( - currentType == FiroType.public - ? Assets.png.glasses - : Assets.png.glassesHidden, - ), - width: 16, - ), + child: FittedBox( + child: Text( + currentType.name.toUpperCase(), + style: STextStyles.w500_10(context), + ), + ), ), ), ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 7fb764cfa..009893e7e 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -145,28 +145,23 @@ class _DesktopSendState extends ConsumerState { Future scanWebcam() async { try { - await showDialog( + final qrResult = await showDialog( context: context, - builder: (context) { - return QrCodeScannerDialog( - onQrCodeDetected: (qrCodeData) { - try { - _processQrCodeData(qrCodeData); - } catch (e, s) { - Logging.instance.log( - "Error processing QR code data: $e\n$s", - level: LogLevel.Error, - ); - } - }, - ); - }, + builder: (context) => const QrCodeScannerDialog(), ); + if (qrResult == null) { + Logging.instance.w("Qr scanning cancelled"); + } else { + try { + _processQrCodeData(qrResult); + } catch (e, s) { + Logging.instance + .e("Error processing QR code data", error: e, stackTrace: s); + } + } } catch (e, s) { - Logging.instance.log( - "Error opening QR code scanner dialog: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("Error opening QR code scanner dialog", error: e, stackTrace: s); } } @@ -511,7 +506,7 @@ class _DesktopSendState extends ConsumerState { ); } } catch (e, s) { - Logging.instance.log("Desktop send: $e\n$s", level: LogLevel.Error); + Logging.instance.e("Desktop send: ", error: e, stackTrace: s); if (mounted) { // pop building dialog Navigator.of( @@ -616,17 +611,14 @@ class _DesktopSendState extends ConsumerState { if (_cachedAmountToSend != null && _cachedAmountToSend == amount) { return; } - Logging.instance.log( - "it changed $amount $_cachedAmountToSend", - level: LogLevel.Info, - ); + _cachedAmountToSend = amount; final price = ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (price > Decimal.zero) { - final String fiatAmountString = (amount!.decimal * price) + final String fiatAmountString = (amount.decimal * price) .toAmount(fractionDigits: 2) .fiatString( locale: ref.read(localeServiceChangeNotifierProvider).locale, @@ -668,33 +660,7 @@ class _DesktopSendState extends ConsumerState { if (paymentData != null && paymentData.coin?.uriScheme == coin.uriScheme) { - // Auto fill address. - _address = paymentData.address.trim(); - sendToController.text = _address!; - - // Amount. - if (paymentData.amount != null) { - final Amount amount = Decimal.parse(paymentData.amount!).toAmount( - fractionDigits: coin.fractionDigits, - ); - cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( - amount, - withUnitName: false, - ); - ref.read(pSendAmount.notifier).state = amount; - } - - // Note/message. - if (paymentData.message != null) { - _note = paymentData.message; - } else if (paymentData.label != null) { - _note = paymentData.label; - } - - _setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = sendToController.text.isNotEmpty; - }); + _applyUri(paymentData); } else { _address = qrCodeData.split("\n").first.trim(); sendToController.text = _address ?? ""; @@ -705,8 +671,11 @@ class _DesktopSendState extends ConsumerState { }); } } catch (e, s) { - Logging.instance - .log("Error processing QR code data: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "Error processing QR code data", + error: e, + stackTrace: s, + ); } } @@ -741,6 +710,40 @@ class _DesktopSendState extends ConsumerState { } } + void _applyUri(PaymentUriData paymentData) { + try { + // auto fill address + _address = paymentData.address; + sendToController.text = _address!; + + // autofill notes field. + if (paymentData.message != null) { + _note = paymentData.message; + } else if (paymentData.label != null) { + _note = paymentData.label; + } + + // autofill amount field + if (paymentData.amount != null) { + final amount = Decimal.parse(paymentData.amount!).toAmount( + fractionDigits: coin.fractionDigits, + ); + cryptoAmountController.text = ref + .read(pAmountFormatter(coin)) + .format(amount, withUnitName: false); + ref.read(pSendAmount.notifier).state = amount; + } + + // Trigger validation after pasting. + _setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } catch (e, s) { + Logging.instance.e("Error applying URI", error: e, stackTrace: s); + } + } + Future pasteAddress() async { final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); if (data?.text != null && data!.text!.isNotEmpty) { @@ -756,33 +759,7 @@ class _DesktopSendState extends ConsumerState { ); if (paymentData != null && paymentData.coin?.uriScheme == coin.uriScheme) { - // auto fill address - _address = paymentData.address; - sendToController.text = _address!; - - // autofill notes field. - if (paymentData.message != null) { - _note = paymentData.message; - } else if (paymentData.label != null) { - _note = paymentData.label; - } - - // autofill amoutn field - if (paymentData.amount != null) { - final amount = Decimal.parse(paymentData.amount!).toAmount( - fractionDigits: coin.fractionDigits, - ); - cryptoAmountController.text = ref - .read(pAmountFormatter(coin)) - .format(amount, withUnitName: false); - ref.read(pSendAmount.notifier).state = amount; - } - - // Trigger validation after pasting. - _setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = sendToController.text.isNotEmpty; - }); + _applyUri(paymentData); } else { content = content.split("\n").first.trim(); if (coin is Epiccash) { @@ -856,11 +833,9 @@ class _DesktopSendState extends ConsumerState { return; } _cachedAmountToSend = amount; - Logging.instance - .log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info); final amountString = ref.read(pAmountFormatter(coin)).format( - amount!, + amount, withUnitName: false, ); @@ -960,11 +935,11 @@ class _DesktopSendState extends ConsumerState { cryptoAmountController.addListener(onCryptoAmountChanged); if (_data != null) { - if (_data!.amount != null) { - cryptoAmountController.text = _data!.amount!.toString(); + if (_data.amount != null) { + cryptoAmountController.text = _data.amount!.toString(); } - sendToController.text = _data!.contactLabel; - _address = _data!.address; + sendToController.text = _data.contactLabel; + _address = _data.address; _addressToggleFlag = true; } @@ -1114,28 +1089,30 @@ class _DesktopSendState extends ConsumerState { ], ), ), - DropdownMenuItem( - value: FiroType.lelantus, - child: Row( - children: [ - Text( - "Lelantus balance", - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - width: 10, - ), - Text( - ref.watch(pAmountFormatter(coin)).format( - ref - .watch(pWalletBalanceSecondary(walletId)) - .spendable, - ), - style: STextStyles.itemSubtitle(context), - ), - ], + if (ref.watch(pWalletBalanceSecondary(walletId)).spendable.raw > + BigInt.zero) + DropdownMenuItem( + value: FiroType.lelantus, + child: Row( + children: [ + Text( + "Lelantus balance", + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + width: 10, + ), + Text( + ref.watch(pAmountFormatter(coin)).format( + ref + .watch(pWalletBalanceSecondary(walletId)) + .spendable, + ), + style: STextStyles.itemSubtitle(context), + ), + ], + ), ), - ), DropdownMenuItem( value: FiroType.public, child: Row( @@ -1444,7 +1421,20 @@ class _DesktopSendState extends ConsumerState { selectAll: false, ), onChanged: (newValue) { - _address = newValue; + final trimmed = newValue; + + if ((trimmed.length - (_address?.length ?? 0)).abs() > 1) { + final parsed = AddressUtils.parsePaymentUri(trimmed, logging: Logging.instance); + if (parsed != null) { + _applyUri(parsed); + } else { + _address = newValue; + sendToController.text = newValue; + } + } else { + _address = newValue; + } + _setValidAddressProviders(_address); setState(() { @@ -1584,9 +1574,9 @@ class _DesktopSendState extends ConsumerState { error = null; } else if (coin is Firo) { if (firoType == FiroType.lelantus) { - if (_data != null && _data!.contactLabel == _address) { + if (_data != null && _data.contactLabel == _address) { error = SparkInterface.validateSparkAddress( - address: _data!.address, + address: _data.address, isTestNet: coin.network.isTestNet, ) ? "Lelantus to Spark not supported" @@ -1599,7 +1589,7 @@ class _DesktopSendState extends ConsumerState { : "Invalid address"; } } else { - if (_data != null && _data!.contactLabel == _address) { + if (_data != null && _data.contactLabel == _address) { error = null; } else if (!ref.watch(pValidSendToAddress) && !ref.watch(pValidSparkSendToAddress)) { @@ -1609,7 +1599,7 @@ class _DesktopSendState extends ConsumerState { } } } else { - if (_data != null && _data!.contactLabel == _address) { + if (_data != null && _data.contactLabel == _address) { error = null; } else if (!ref.watch(pValidSendToAddress)) { error = "Invalid address"; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart index 158beed86..647b1e1cc 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart @@ -406,10 +406,6 @@ class _DesktopTokenSendState extends ConsumerState { _cachedAmountToSend == _amountToSend) { return; } - Logging.instance.log( - "it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info, - ); _cachedAmountToSend = _amountToSend; final price = ref @@ -471,18 +467,14 @@ class _DesktopTokenSendState extends ConsumerState { final qrResult = await scanner.scan(); - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info, - ); + Logging.instance.d("qrResult content: ${qrResult.rawContent}"); final paymentData = AddressUtils.parsePaymentUri( qrResult.rawContent, logging: Logging.instance, ); - Logging.instance - .log("qrResult parsed: $paymentData", level: LogLevel.Info); + Logging.instance.d("qrResult parsed: $paymentData"); if (paymentData != null && paymentData.coin?.uriScheme == coin.uriScheme) { @@ -529,9 +521,10 @@ class _DesktopTokenSendState extends ConsumerState { } on PlatformException catch (e, s) { // here we ignore the exception caused by not giving permission // to use the camera to scan a qr code - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, ); } } @@ -586,10 +579,6 @@ class _DesktopTokenSendState extends ConsumerState { return; } _cachedAmountToSend = _amountToSend; - Logging.instance.log( - "it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info, - ); final amountString = ref.read(pAmountFormatter(coin)).format( _amountToSend!, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart index 2499d4754..78a36d8d7 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart @@ -9,7 +9,6 @@ */ import 'dart:async'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -19,6 +18,7 @@ import 'package:flutter_svg/svg.dart'; import '../../../../app_config.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../pages/monkey/monkey_view.dart'; +import '../../../../pages/namecoin_names/namecoin_names_home_view.dart'; import '../../../../pages/paynym/paynym_claim_view.dart'; import '../../../../pages/paynym/paynym_home_view.dart'; import '../../../../providers/desktop/current_desktop_menu_item.dart'; @@ -26,7 +26,6 @@ import '../../../../providers/global/paynym_api_provider.dart'; import '../../../../providers/providers.dart'; import '../../../../providers/wallet/my_paynym_account_state_provider.dart'; import '../../../../themes/stack_colors.dart'; -import '../../../../themes/theme_providers.dart'; import '../../../../utilities/amount/amount.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; @@ -49,15 +48,14 @@ import '../../../cashfusion/desktop_cashfusion_view.dart'; import '../../../churning/desktop_churning_view.dart'; import '../../../coin_control/desktop_coin_control_view.dart'; import '../../../desktop_menu.dart'; +import '../../../lelantus_coins/lelantus_coins_view.dart'; import '../../../ordinals/desktop_ordinals_view.dart'; +import '../../../spark_coins/spark_coins_view.dart'; import '../desktop_wallet_view.dart'; import 'more_features/more_features_dialog.dart'; class DesktopWalletFeatures extends ConsumerStatefulWidget { - const DesktopWalletFeatures({ - super.key, - required this.walletId, - }); + const DesktopWalletFeatures({super.key, required this.walletId}); final String walletId; @@ -77,6 +75,7 @@ class _DesktopWalletFeaturesState extends ConsumerState { } Future _onBuyPressed() async { + Navigator.of(context, rootNavigator: true).pop(); ref.read(currentDesktopMenuItemProvider.state).state = DesktopMenuItemId.buy; ref.read(prevDesktopMenuItemProvider.state).state = DesktopMenuItemId.buy; @@ -85,17 +84,22 @@ class _DesktopWalletFeaturesState extends ConsumerState { Future _onMorePressed() async { await showDialog( context: context, - builder: (_) => MoreFeaturesDialog( - walletId: widget.walletId, - onPaynymPressed: _onPaynymPressed, - onCoinControlPressed: _onCoinControlPressed, - onAnonymizeAllPressed: _onAnonymizeAllPressed, - onWhirlpoolPressed: _onWhirlpoolPressed, - onOrdinalsPressed: _onOrdinalsPressed, - onMonkeyPressed: _onMonkeyPressed, - onFusionPressed: _onFusionPressed, - onChurnPressed: _onChurnPressed, - ), + builder: + (_) => MoreFeaturesDialog( + walletId: widget.walletId, + onPaynymPressed: _onPaynymPressed, + onBuyPressed: _onBuyPressed, + onCoinControlPressed: _onCoinControlPressed, + onLelantusCoinsPressed: _onLelantusCoinsPressed, + onSparkCoinsPressedPressed: _onSparkCoinsPressed, + // onAnonymizeAllPressed: _onAnonymizeAllPressed, + onWhirlpoolPressed: _onWhirlpoolPressed, + onOrdinalsPressed: _onOrdinalsPressed, + onMonkeyPressed: _onMonkeyPressed, + onFusionPressed: _onFusionPressed, + onChurnPressed: _onChurnPressed, + onNamesPressed: _onNamesPressed, + ), ); } @@ -106,64 +110,74 @@ class _DesktopWalletFeaturesState extends ConsumerState { void _onCoinControlPressed() { Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).pushNamed( - DesktopCoinControlView.routeName, - arguments: widget.walletId, - ); + Navigator.of( + context, + ).pushNamed(DesktopCoinControlView.routeName, arguments: widget.walletId); } - Future _onAnonymizeAllPressed() async { + void _onLelantusCoinsPressed() { Navigator.of(context, rootNavigator: true).pop(); + + Navigator.of( + context, + ).pushNamed(LelantusCoinsView.routeName, arguments: widget.walletId); + } + + void _onSparkCoinsPressed() { + Navigator.of(context, rootNavigator: true).pop(); + + Navigator.of( + context, + ).pushNamed(SparkCoinsView.routeName, arguments: widget.walletId); + } + + Future _onAnonymizeAllPressed() async { await showDialog( context: context, barrierDismissible: false, - builder: (context) => DesktopDialog( - maxWidth: 500, - maxHeight: double.infinity, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), - child: Column( - children: [ - Text( - "Attention!", - style: STextStyles.desktopH2(context), - ), - const SizedBox(height: 16), - Text( - "You're about to anonymize all of your public funds.", - style: STextStyles.desktopTextSmall(context), - ), - const SizedBox(height: 32), - Row( - mainAxisAlignment: MainAxisAlignment.center, + builder: + (context) => DesktopDialog( + maxWidth: 500, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( children: [ - SecondaryButton( - width: 200, - buttonHeight: ButtonHeight.l, - label: "Cancel", - onPressed: () { - Navigator.of(context).pop(); - }, + Text("Attention!", style: STextStyles.desktopH2(context)), + const SizedBox(height: 16), + Text( + "You're about to anonymize all of your public funds.", + style: STextStyles.desktopTextSmall(context), ), - const SizedBox(width: 20), - PrimaryButton( - width: 200, - buttonHeight: ButtonHeight.l, - label: "Continue", - onPressed: () { - Navigator.of(context).pop(); - - unawaited( - _attemptAnonymize(), - ); - }, + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of(context).pop(); + + unawaited(_attemptAnonymize()); + }, + ), + ], ), ], ), - ], + ), ), - ), - ), ); } @@ -172,13 +186,14 @@ class _DesktopWalletFeaturesState extends ConsumerState { unawaited( showDialog( context: context, - builder: (context) => WillPopScope( - child: const CustomLoadingOverlay( - message: "Anonymizing balance", - eventBus: null, - ), - onWillPop: () async => shouldPop, - ), + builder: + (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Anonymizing balance", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), ), ); final firoWallet = @@ -189,9 +204,9 @@ class _DesktopWalletFeaturesState extends ConsumerState { shouldPop = true; if (context.mounted) { Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).popUntil( - ModalRoute.withName(DesktopWalletView.routeName), - ); + Navigator.of( + context, + ).popUntil(ModalRoute.withName(DesktopWalletView.routeName)); unawaited( showFloatingFlushBar( type: FlushBarType.info, @@ -209,9 +224,9 @@ class _DesktopWalletFeaturesState extends ConsumerState { shouldPop = true; if (mounted) { Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).popUntil( - ModalRoute.withName(DesktopWalletView.routeName), - ); + Navigator.of( + context, + ).popUntil(ModalRoute.withName(DesktopWalletView.routeName)); unawaited( showFloatingFlushBar( type: FlushBarType.success, @@ -224,53 +239,51 @@ class _DesktopWalletFeaturesState extends ConsumerState { shouldPop = true; if (mounted) { Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).popUntil( - ModalRoute.withName(DesktopWalletView.routeName), - ); + Navigator.of( + context, + ).popUntil(ModalRoute.withName(DesktopWalletView.routeName)); await showDialog( context: context, - builder: (_) => DesktopDialog( - maxWidth: 400, - maxHeight: 300, - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Anonymize all failed", - style: STextStyles.desktopH3(context), - ), - const Spacer( - flex: 1, - ), - Text( - "Reason: $e", - style: STextStyles.desktopTextSmall(context), - ), - const Spacer( - flex: 2, - ), - Row( + builder: + (_) => DesktopDialog( + maxWidth: 400, + maxHeight: 300, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Spacer(), - const SizedBox( - width: 16, + Text( + "Anonymize all failed", + style: STextStyles.desktopH3(context), + ), + const Spacer(flex: 1), + Text( + "Reason: $e", + style: STextStyles.desktopTextSmall(context), ), - Expanded( - child: PrimaryButton( - label: "Ok", - buttonHeight: ButtonHeight.l, - onPressed: - Navigator.of(context, rootNavigator: true).pop, - ), + const Spacer(flex: 2), + Row( + children: [ + const Spacer(), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: + Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ), + ], ), ], ), - ], + ), ), - ), - ), ); } } @@ -283,9 +296,7 @@ class _DesktopWalletFeaturesState extends ConsumerState { showDialog( context: context, builder: (context) { - return const LoadingIndicator( - width: 100, - ); + return const LoadingIndicator(width: 100); }, ), ); @@ -297,30 +308,25 @@ class _DesktopWalletFeaturesState extends ConsumerState { final account = await ref.read(paynymAPIProvider).nym(code.toString()); - Logging.instance.log( - "my nym account: $account", - level: LogLevel.Info, - ); + Logging.instance.d("my nym account: $account"); if (mounted) { Navigator.of(context, rootNavigator: true).pop(); // check if account exists and for matching code to see if claimed if (account.value != null && account.value!.nonSegwitPaymentCode.claimed - // && - // account.value!.segwit - ) { + // && + // account.value!.segwit + ) { ref.read(myPaynymAccountStateProvider.state).state = account.value!; - await Navigator.of(context).pushNamed( - PaynymHomeView.routeName, - arguments: widget.walletId, - ); + await Navigator.of( + context, + ).pushNamed(PaynymHomeView.routeName, arguments: widget.walletId); } else { - await Navigator.of(context).pushNamed( - PaynymClaimView.routeName, - arguments: widget.walletId, - ); + await Navigator.of( + context, + ).pushNamed(PaynymClaimView.routeName, arguments: widget.walletId); } } } @@ -328,37 +334,41 @@ class _DesktopWalletFeaturesState extends ConsumerState { Future _onMonkeyPressed() async { Navigator.of(context, rootNavigator: true).pop(); - await (Navigator.of(context).pushNamed( - MonkeyView.routeName, - arguments: widget.walletId, - )); + await (Navigator.of( + context, + ).pushNamed(MonkeyView.routeName, arguments: widget.walletId)); } void _onOrdinalsPressed() { Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).pushNamed( - DesktopOrdinalsView.routeName, - arguments: widget.walletId, - ); + Navigator.of( + context, + ).pushNamed(DesktopOrdinalsView.routeName, arguments: widget.walletId); } void _onFusionPressed() { Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).pushNamed( - DesktopCashFusionView.routeName, - arguments: widget.walletId, - ); + Navigator.of( + context, + ).pushNamed(DesktopCashFusionView.routeName, arguments: widget.walletId); } void _onChurnPressed() { Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).pushNamed( - DesktopChurningView.routeName, - arguments: widget.walletId, - ); + Navigator.of( + context, + ).pushNamed(DesktopChurningView.routeName, arguments: widget.walletId); + } + + void _onNamesPressed() { + Navigator.of(context, rootNavigator: true).pop(); + + Navigator.of( + context, + ).pushNamed(NamecoinNamesHomeView.routeName, arguments: widget.walletId); } @override @@ -369,7 +379,8 @@ class _DesktopWalletFeaturesState extends ConsumerState { final prefs = ref.watch(prefsChangeNotifierProvider); final showExchange = prefs.enableExchange; - final showMore = wallet is PaynymInterface || + final showMore = + wallet is PaynymInterface || (wallet is CoinControlInterface && ref.watch( prefsChangeNotifierProvider.select( @@ -385,54 +396,46 @@ class _DesktopWalletFeaturesState extends ConsumerState { final isViewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly; return Row( + mainAxisSize: MainAxisSize.min, children: [ - if (!isViewOnly && - Constants.enableExchange && - AppConfig.hasFeature(AppFeature.swap) && - showExchange) + if (!isViewOnly && wallet.info.coin is Firo) SecondaryButton( - label: "Swap", - width: buttonWidth, + label: "Anonymize funds", + width: buttonWidth * 2, buttonHeight: ButtonHeight.l, icon: SvgPicture.asset( - Assets.svg.arrowRotate, + Assets.svg.recycle, height: 20, width: 20, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + color: + Theme.of( + context, + ).extension()!.buttonTextSecondary, ), - onPressed: () => _onSwapPressed(), - ), - if (Constants.enableExchange && - AppConfig.hasFeature(AppFeature.buy) && - showExchange) - const SizedBox( - width: 16, + onPressed: () => _onAnonymizeAllPressed(), ), - if (Constants.enableExchange && - AppConfig.hasFeature(AppFeature.buy) && + if (!isViewOnly && wallet.info.coin is Firo) const SizedBox(width: 16), + if (!isViewOnly && + Constants.enableExchange && + AppConfig.hasFeature(AppFeature.swap) && showExchange) SecondaryButton( - label: "Buy", + label: "Swap", width: buttonWidth, buttonHeight: ButtonHeight.l, - icon: SvgPicture.file( - File( - ref.watch(themeProvider.select((value) => value.assets.buy)), - ), + icon: SvgPicture.asset( + Assets.svg.arrowRotate, height: 20, width: 20, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + color: + Theme.of( + context, + ).extension()!.buttonTextSecondary, ), - onPressed: () => _onBuyPressed(), - ), - if (showMore) - const SizedBox( - width: 16, + onPressed: () => _onSwapPressed(), ), + + if (showMore) const SizedBox(width: 16), if (showMore) SecondaryButton( label: "More", @@ -442,9 +445,10 @@ class _DesktopWalletFeaturesState extends ConsumerState { Assets.svg.bars, height: 20, width: 20, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + color: + Theme.of( + context, + ).extension()!.buttonTextSecondary, ), onPressed: () => _onMorePressed(), ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index 5f0e0f69b..fdc0f99b7 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -23,6 +23,8 @@ import '../../../../utilities/amount/amount_formatter.dart'; import '../../../../utilities/enums/wallet_balance_toggle_state.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/crypto_currency/coins/firo.dart'; +import '../../../../wallets/crypto_currency/crypto_currency.dart' + show CryptoCurrency; import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../../../wallets/isar/providers/eth/token_balance_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; @@ -48,10 +50,15 @@ class DesktopWalletSummary extends ConsumerStatefulWidget { class _WDesktopWalletSummaryState extends ConsumerState { late final String walletId; + late final CryptoCurrency coin; + late final bool isFiro; + @override void initState() { - walletId = widget.walletId; super.initState(); + walletId = widget.walletId; + coin = ref.read(pWalletCoin(widget.walletId)); + isFiro = coin is Firo; } @override @@ -59,36 +66,40 @@ class _WDesktopWalletSummaryState extends ConsumerState { debugPrint("BUILD: $runtimeType"); final externalCalls = ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), + prefsChangeNotifierProvider.select((value) => value.externalCalls), ); - final coin = ref.watch(pWalletCoin(widget.walletId)); - final isFiro = coin is Firo; + final locale = ref.watch( localeServiceChangeNotifierProvider.select((value) => value.locale), ); - final baseCurrency = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + final baseCurrency = ref.watch( + prefsChangeNotifierProvider.select((value) => value.currency), + ); - final tokenContract = widget.isToken - ? ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract)) - : null; + final tokenContract = + widget.isToken + ? ref.watch( + pCurrentTokenWallet.select((value) => value!.tokenContract), + ) + : null; - final priceTuple = widget.isToken - ? ref.watch( - priceAnd24hChangeNotifierProvider - .select((value) => value.getTokenPrice(tokenContract!.address)), - ) - : ref.watch( - priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin)), - ); + final priceTuple = + widget.isToken + ? ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getTokenPrice(tokenContract!.address), + ), + ) + : ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ); final _showAvailable = ref.watch(walletBalanceToggleStateProvider.state).state == - WalletBalanceToggleState.available; + WalletBalanceToggleState.available; final Amount balanceToShow; if (isFiro) { @@ -109,13 +120,15 @@ class _WDesktopWalletSummaryState extends ConsumerState { break; } } else { - final Balance balance = widget.isToken - ? ref.watch( - pTokenBalance( - (walletId: walletId, contractAddress: tokenContract!.address), - ), - ) - : ref.watch(pWalletBalance(walletId)); + final Balance balance = + widget.isToken + ? ref.watch( + pTokenBalance(( + walletId: walletId, + contractAddress: tokenContract!.address, + )), + ) + : ref.watch(pWalletBalance(walletId)); balanceToShow = _showAvailable ? balance.spendable : balance.total; } @@ -139,38 +152,39 @@ class _WDesktopWalletSummaryState extends ConsumerState { ), if (externalCalls) SelectableText( - "${Amount.fromDecimal( - priceTuple.item1 * balanceToShow.decimal, - fractionDigits: 2, - ).fiatString( - locale: locale, - )} $baseCurrency", + "${Amount.fromDecimal(priceTuple.item1 * balanceToShow.decimal, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), ), + // if (coin is Firo) + // Row( + // children: [ + // DesktopPrivateBalanceToggleButton( + // walletId: walletId, + // ), + // const SizedBox( + // width: 8, + // ), + // const DesktopBalanceToggleButton(), + // ], + // ), ], ), - const SizedBox( - width: 8, - ), + const SizedBox(width: 8), WalletRefreshButton( walletId: walletId, initialSyncStatus: widget.initialSyncStatus, - tokenContractAddress: widget.isToken - ? ref.watch(pCurrentTokenWallet)!.tokenContract.address - : null, - ), - if (coin is Firo) - const SizedBox( - width: 8, - ), - if (coin is Firo) const DesktopPrivateBalanceToggleButton(), - const SizedBox( - width: 8, + tokenContractAddress: + widget.isToken + ? ref.watch(pCurrentTokenWallet)!.tokenContract.address + : null, ), + + const SizedBox(width: 8), const DesktopBalanceToggleButton(), ], ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart new file mode 100644 index 000000000..73f384f82 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart @@ -0,0 +1,245 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../pages/wallet_view/sub_widgets/wallet_refresh_button.dart'; +import '../../../../providers/providers.dart'; +import '../../../../providers/wallet/public_private_balance_state_provider.dart'; +import '../../../../providers/wallet/wallet_balance_toggle_state_provider.dart'; +import '../../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/amount/amount.dart'; +import '../../../../utilities/amount/amount_formatter.dart'; +import '../../../../utilities/assets.dart'; +import '../../../../utilities/enums/wallet_balance_toggle_state.dart'; +import '../../../../utilities/extensions/extensions.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../wallets/crypto_currency/coins/firo.dart'; +import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import 'desktop_balance_toggle_button.dart'; + +class FiroDesktopWalletSummary extends ConsumerStatefulWidget { + const FiroDesktopWalletSummary({ + super.key, + required this.walletId, + required this.initialSyncStatus, + }); + + final String walletId; + final WalletSyncStatus initialSyncStatus; + + @override + ConsumerState createState() => + _WFiroDesktopWalletSummaryState(); +} + +class _WFiroDesktopWalletSummaryState + extends ConsumerState { + late final String walletId; + + late final Firo coin; + late final bool isFiro; + + @override + void initState() { + super.initState(); + walletId = widget.walletId; + coin = ref.read(pWalletCoin(widget.walletId)) as Firo; + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + Decimal? price; + if (ref.watch( + prefsChangeNotifierProvider.select((value) => value.externalCalls), + )) { + final priceTuple = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ); + price = priceTuple.item1; + } + + final _showAvailable = + ref.watch(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available; + + final balance0 = ref.watch(pWalletBalanceTertiary(walletId)); + final balanceToShowSpark = + _showAvailable ? balance0.spendable : balance0.total; + + final balance1 = ref.watch(pWalletBalanceSecondary(walletId)); + final balanceToShowLelantus = + _showAvailable ? balance1.spendable : balance1.total; + + final balance2 = ref.watch(pWalletBalance(walletId)); + final balanceToShowPublic = + _showAvailable ? balance2.spendable : balance2.total; + + return Consumer( + builder: (context, ref, __) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Table( + columnWidths: { + 0: const IntrinsicColumnWidth(), + 1: const IntrinsicColumnWidth(), + if (price != null) 2: const IntrinsicColumnWidth(), + }, + children: [ + TableRow( + children: [ + const _Prefix(type: FiroType.spark), + _Balance(coin: coin, amount: balanceToShowSpark), + if (price != null) + _Price( + coin: coin, + amount: balanceToShowSpark, + price: price, + ), + ], + ), + if (balanceToShowLelantus.raw > BigInt.zero) + TableRow( + children: [ + const _Prefix(type: FiroType.lelantus), + _Balance(coin: coin, amount: balanceToShowLelantus), + if (price != null) + _Price( + coin: coin, + amount: balanceToShowLelantus, + price: price, + ), + ], + ), + TableRow( + children: [ + const _Prefix(type: FiroType.public), + _Balance(coin: coin, amount: balanceToShowPublic), + if (price != null) + _Price( + coin: coin, + amount: balanceToShowPublic, + price: price, + ), + ], + ), + ], + ), + + const SizedBox(width: 8), + WalletRefreshButton( + walletId: walletId, + initialSyncStatus: widget.initialSyncStatus, + ), + const SizedBox(width: 8), + const DesktopBalanceToggleButton(), + ], + ); + }, + ); + } +} + +class _Prefix extends StatelessWidget { + const _Prefix({super.key, required this.type}); + + final FiroType type; + + String get asset { + switch (type) { + case FiroType.public: + return Assets.png.glasses; + case FiroType.lelantus: + return Assets.png.glasses; + case FiroType.spark: + return Assets.svg.spark; + } + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox( + width: 20, + height: 20, + child: + asset.endsWith(".png") + ? Image(image: AssetImage(asset)) + : SvgPicture.asset(asset), + ), + const SizedBox(width: 6), + + SelectableText( + type.name.capitalize(), + style: STextStyles.w500_24(context), + ), + ], + ), + ); + } +} + +class _Balance extends ConsumerWidget { + const _Balance({super.key, required this.coin, required this.amount}); + + final Firo coin; + final Amount amount; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SelectableText( + ref.watch(pAmountFormatter(coin)).format(amount, ethContract: null), + style: STextStyles.desktopH3(context), + textAlign: TextAlign.end, + ); + } +} + +class _Price extends ConsumerWidget { + const _Price({ + super.key, + required this.coin, + required this.amount, + required this.price, + }); + + final Firo coin; + final Amount amount; + final Decimal price; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.only(left: 16), + child: SelectableText( + "${Amount.fromDecimal(price * amount.decimal, fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} " + "${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ), + + textAlign: TextAlign.end, + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart index 0869b0b50..1ee0447b3 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart @@ -8,9 +8,12 @@ * */ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; import '../../../../../app_config.dart'; import '../../../../../db/sqlite/firo_cache.dart'; @@ -19,11 +22,18 @@ import '../../../../../providers/db/main_db_provider.dart'; import '../../../../../providers/global/prefs_provider.dart'; import '../../../../../providers/global/wallets_provider.dart'; import '../../../../../themes/stack_colors.dart'; +import '../../../../../themes/theme_providers.dart'; import '../../../../../utilities/assets.dart'; +import '../../../../../utilities/constants.dart'; +import '../../../../../utilities/logger.dart'; +import '../../../../../utilities/show_loading.dart'; import '../../../../../utilities/text_styles.dart'; +import '../../../../../utilities/util.dart'; import '../../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../../wallets/wallet/impl/firo_wallet.dart'; +import '../../../../../wallets/wallet/impl/namecoin_wallet.dart'; import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; @@ -39,30 +49,39 @@ import '../../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../../widgets/desktop/primary_button.dart'; import '../../../../../widgets/desktop/secondary_button.dart'; import '../../../../../widgets/rounded_container.dart'; +import '../../../../../widgets/stack_dialog.dart'; class MoreFeaturesDialog extends ConsumerStatefulWidget { const MoreFeaturesDialog({ super.key, required this.walletId, required this.onPaynymPressed, + required this.onBuyPressed, required this.onCoinControlPressed, - required this.onAnonymizeAllPressed, + required this.onLelantusCoinsPressed, + required this.onSparkCoinsPressedPressed, + // required this.onAnonymizeAllPressed, required this.onWhirlpoolPressed, required this.onOrdinalsPressed, required this.onMonkeyPressed, required this.onFusionPressed, required this.onChurnPressed, + required this.onNamesPressed, }); final String walletId; final VoidCallback? onPaynymPressed; + final VoidCallback? onBuyPressed; final VoidCallback? onCoinControlPressed; - final VoidCallback? onAnonymizeAllPressed; + final VoidCallback? onLelantusCoinsPressed; + final VoidCallback? onSparkCoinsPressedPressed; + // final VoidCallback? onAnonymizeAllPressed; final VoidCallback? onWhirlpoolPressed; final VoidCallback? onOrdinalsPressed; final VoidCallback? onMonkeyPressed; final VoidCallback? onFusionPressed; final VoidCallback? onChurnPressed; + final VoidCallback? onNamesPressed; @override ConsumerState createState() => _MoreFeaturesDialogState(); @@ -77,12 +96,16 @@ class _MoreFeaturesDialogState extends ConsumerState { try { // Toggle enableLelantusScanning in wallet info. - await ref.read(pWalletInfo(widget.walletId)).updateOtherData( - newEntries: { - WalletInfoKeys.enableLelantusScanning: newValue, - }, - isar: ref.read(mainDBProvider).isar, - ); + await ref + .read(pWalletInfo(widget.walletId)) + .updateOtherData( + newEntries: {WalletInfoKeys.enableLelantusScanning: newValue}, + isar: ref.read(mainDBProvider).isar, + ); + + if (newValue) { + await _doRescanMaybe(); + } } finally { // ensure _isUpdatingLelantusScanning is set to false no matter what _isUpdatingLelantusScanning = false; @@ -98,18 +121,131 @@ class _MoreFeaturesDialogState extends ConsumerState { try { // Toggle enableOptInRbf in wallet info. - await ref.read(pWalletInfo(widget.walletId)).updateOtherData( - newEntries: { - WalletInfoKeys.enableOptInRbf: newValue, - }, - isar: ref.read(mainDBProvider).isar, - ); + await ref + .read(pWalletInfo(widget.walletId)) + .updateOtherData( + newEntries: {WalletInfoKeys.enableOptInRbf: newValue}, + isar: ref.read(mainDBProvider).isar, + ); } finally { // ensure _switchRbfToggledLock is set to false no matter what _switchRbfToggledLock = false; } } + Future _doRescanMaybe() async { + final shouldRescan = await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 700, + child: Column( + children: [ + const DesktopDialogCloseButton(), + const SizedBox(height: 5), + Text( + "Rescan may be required", + style: STextStyles.desktopH2(context), + textAlign: TextAlign.left, + ), + const SizedBox(height: 16), + const Spacer(), + Text( + "A blockchain rescan may be required to fully recover all lelantus history." + "\nThis may take a while.", + style: STextStyles.desktopTextMedium(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + textAlign: TextAlign.center, + ), + const Spacer(), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Rescan now", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Later", + onPressed: () => Navigator.of(context).pop(false), + ), + ), + ], + ), + ), + ], + ), + ); + }, + ); + + if (mounted && shouldRescan == true) { + try { + if (!Platform.isLinux) await WakelockPlus.enable(); + + Exception? e; + if (mounted) { + await showLoading( + whileFuture: ref + .read(pWallets) + .getWallet(widget.walletId) + .recover(isRescan: true), + context: context, + message: "Rescanning blockchain", + subMessage: + "This may take a while.\nPlease do not exit this screen.", + rootNavigator: Util.isDesktop, + onException: (ex) => e = ex, + ); + + if (e != null) { + throw e!; + } + } + } catch (e, s) { + Logging.instance.e("$e\n$s", error: e, stackTrace: s); + if (mounted) { + // show error + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: + (context) => StackDialog( + title: "Rescan failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of( + context, + rootNavigator: Util.isDesktop, + ).pop(); + }, + ), + ), + ); + } + } finally { + if (!Platform.isLinux) await WakelockPlus.disable(); + } + } + } + late final DSBController _switchController; bool _switchReuseAddressToggledLock = false; // Mutex. @@ -156,9 +292,7 @@ class _MoreFeaturesDialogState extends ConsumerState { "Reusing addresses reduces your privacy and security. Are you sure you want to reuse addresses by default?", style: STextStyles.desktopTextSmall(context), ), - const SizedBox( - height: 43, - ), + const SizedBox(height: 43), Row( children: [ Expanded( @@ -170,9 +304,7 @@ class _MoreFeaturesDialogState extends ConsumerState { label: "Cancel", ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: PrimaryButton( buttonHeight: ButtonHeight.l, @@ -206,12 +338,12 @@ class _MoreFeaturesDialogState extends ConsumerState { } Future _updateAddressReuse(bool shouldReuse) async { - await ref.read(pWalletInfo(widget.walletId)).updateOtherData( - newEntries: { - WalletInfoKeys.reuseAddress: shouldReuse, - }, - isar: ref.read(mainDBProvider).isar, - ); + await ref + .read(pWalletInfo(widget.walletId)) + .updateOtherData( + newEntries: {WalletInfoKeys.reuseAddress: shouldReuse}, + isar: ref.read(mainDBProvider).isar, + ); if (_switchController.isOn != null) { if (_switchController.isOn!.call() != shouldReuse) { @@ -229,19 +361,16 @@ class _MoreFeaturesDialogState extends ConsumerState { @override Widget build(BuildContext context) { final wallet = ref.watch( - pWallets.select( - (value) => value.getWallet(widget.walletId), - ), + pWallets.select((value) => value.getWallet(widget.walletId)), ); final coinControlPrefEnabled = ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.enableCoinControl, - ), + prefsChangeNotifierProvider.select((value) => value.enableCoinControl), ); final isViewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly; - final isViewOnlyNoAddressGen = wallet is ViewOnlyOptionInterface && + final isViewOnlyNoAddressGen = + wallet is ViewOnlyOptionInterface && wallet.isViewOnly && wallet.viewOnlyType == ViewOnlyWalletType.addressOnly; @@ -254,9 +383,7 @@ class _MoreFeaturesDialogState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: const EdgeInsets.only( - left: 32, - ), + padding: const EdgeInsets.only(left: 32), child: Text( "More features", style: STextStyles.desktopH3(context), @@ -265,13 +392,25 @@ class _MoreFeaturesDialogState extends ConsumerState { const DesktopDialogCloseButton(), ], ), - if (!isViewOnly && wallet.info.coin is Firo) + if (Constants.enableExchange && + AppConfig.hasFeature(AppFeature.buy) && + ref.watch(prefsChangeNotifierProvider).enableExchange) _MoreFeaturesItem( - label: "Anonymize funds", - detail: "Anonymize funds", - iconAsset: Assets.svg.recycle, - onPressed: () async => widget.onAnonymizeAllPressed?.call(), + label: "Buy", + detail: "Buy cryptocurrency", + isSvgFile: true, + iconAsset: ref.watch( + themeProvider.select((value) => value.assets.buy), + ), + onPressed: () async => widget.onBuyPressed?.call(), ), + // if (!isViewOnly && wallet.info.coin is Firo) + // _MoreFeaturesItem( + // label: "Anonymize funds", + // detail: "Anonymize funds", + // iconAsset: Assets.svg.recycle, + // onPressed: () async => widget.onAnonymizeAllPressed?.call(), + // ), // TODO: [prio=med] // if (manager.hasWhirlpoolSupport) // _MoreFeaturesItem( @@ -287,6 +426,30 @@ class _MoreFeaturesDialogState extends ConsumerState { iconAsset: Assets.svg.coinControl.gamePad, onPressed: () async => widget.onCoinControlPressed?.call(), ), + if (wallet is FiroWallet && + ref.watch( + prefsChangeNotifierProvider.select( + (s) => s.advancedFiroFeatures, + ), + )) + _MoreFeaturesItem( + label: "Lelantus Coins", + detail: "View wallet lelantus coins", + iconAsset: Assets.svg.coinControl.gamePad, + onPressed: () async => widget.onLelantusCoinsPressed?.call(), + ), + if (wallet is FiroWallet && + ref.watch( + prefsChangeNotifierProvider.select( + (s) => s.advancedFiroFeatures, + ), + )) + _MoreFeaturesItem( + label: "Spark Coins", + detail: "View wallet spark coins", + iconAsset: Assets.svg.coinControl.gamePad, + onPressed: () async => widget.onSparkCoinsPressedPressed?.call(), + ), if (!isViewOnly && wallet is PaynymInterface) _MoreFeaturesItem( label: "PayNym", @@ -322,6 +485,13 @@ class _MoreFeaturesDialogState extends ConsumerState { iconAsset: Assets.svg.churn, onPressed: () async => widget.onChurnPressed?.call(), ), + if (wallet is NamecoinWallet) + _MoreFeaturesItem( + label: "Domains", + detail: "Namecoin DNS", + iconAsset: Assets.svg.robotHead, + onPressed: () async => widget.onNamesPressed?.call(), + ), if (wallet is SparkInterface && !isViewOnly) _MoreFeaturesClearSparkCacheItem( cryptoCurrency: wallet.cryptoCurrency, @@ -335,17 +505,18 @@ class _MoreFeaturesDialogState extends ConsumerState { height: 20, width: 40, child: DraggableSwitchButton( - isOn: ref.watch( - pWalletInfo(widget.walletId) - .select((value) => value.otherData), - )[WalletInfoKeys.enableLelantusScanning] as bool? ?? + isOn: + ref.watch( + pWalletInfo( + widget.walletId, + ).select((value) => value.otherData), + )[WalletInfoKeys.enableLelantusScanning] + as bool? ?? false, onValueChanged: _switchToggled, ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -367,17 +538,18 @@ class _MoreFeaturesDialogState extends ConsumerState { height: 20, width: 40, child: DraggableSwitchButton( - isOn: ref.watch( - pWalletInfo(widget.walletId) - .select((value) => value.otherData), - )[WalletInfoKeys.enableOptInRbf] as bool? ?? + isOn: + ref.watch( + pWalletInfo( + widget.walletId, + ).select((value) => value.otherData), + )[WalletInfoKeys.enableOptInRbf] + as bool? ?? false, onValueChanged: _switchRbfToggled, ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -402,18 +574,19 @@ class _MoreFeaturesDialogState extends ConsumerState { width: 40, child: IgnorePointer( child: DraggableSwitchButton( - isOn: ref.watch( - pWalletInfo(widget.walletId) - .select((value) => value.otherData), - )[WalletInfoKeys.reuseAddress] as bool? ?? + isOn: + ref.watch( + pWalletInfo( + widget.walletId, + ).select((value) => value.otherData), + )[WalletInfoKeys.reuseAddress] + as bool? ?? false, controller: _switchController, ), ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -426,9 +599,7 @@ class _MoreFeaturesDialogState extends ConsumerState { ], ), ), - const SizedBox( - height: 28, - ), + const SizedBox(height: 28), ], ), ); @@ -441,6 +612,7 @@ class _MoreFeaturesItem extends StatefulWidget { required this.label, required this.detail, required this.iconAsset, + this.isSvgFile = false, this.onPressed, }); @@ -450,6 +622,7 @@ class _MoreFeaturesItem extends StatefulWidget { final String label; final String detail; final String iconAsset; + final bool isSvgFile; final Future Function()? onPressed; @override @@ -482,26 +655,33 @@ class _MoreFeaturesItemState extends State<_MoreFeaturesItem> { height: _MoreFeaturesItem.iconSizeBG, radiusMultiplier: _MoreFeaturesItem.iconSizeBG, child: Center( - child: SvgPicture.asset( - widget.iconAsset, - width: _MoreFeaturesItem.iconSize, - height: _MoreFeaturesItem.iconSize, - color: Theme.of(context) - .extension()! - .settingsIconIcon, - ), + child: + widget.isSvgFile + ? SvgPicture.file( + File(widget.iconAsset), + width: _MoreFeaturesItem.iconSize, + height: _MoreFeaturesItem.iconSize, + color: + Theme.of( + context, + ).extension()!.settingsIconIcon, + ) + : SvgPicture.asset( + widget.iconAsset, + width: _MoreFeaturesItem.iconSize, + height: _MoreFeaturesItem.iconSize, + color: + Theme.of( + context, + ).extension()!.settingsIconIcon, + ), ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - widget.label, - style: STextStyles.w600_20(context), - ), + Text(widget.label, style: STextStyles.w600_20(context)), Text( widget.detail, style: STextStyles.desktopTextExtraExtraSmall(context), @@ -515,11 +695,7 @@ class _MoreFeaturesItemState extends State<_MoreFeaturesItem> { } class _MoreFeaturesItemBase extends StatelessWidget { - const _MoreFeaturesItemBase({ - super.key, - required this.child, - this.onPressed, - }); + const _MoreFeaturesItemBase({super.key, required this.child, this.onPressed}); final Widget child; final VoidCallback? onPressed; @@ -527,10 +703,7 @@ class _MoreFeaturesItemBase extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.symmetric( - vertical: 6, - horizontal: 32, - ), + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 32), child: RoundedContainer( color: Colors.transparent, borderColor: @@ -593,22 +766,18 @@ class _MoreFeaturesClearSparkCacheItemState Assets.svg.x, width: _MoreFeaturesItem.iconSize, height: _MoreFeaturesItem.iconSize, - color: Theme.of(context) - .extension()! - .settingsIconIcon, + color: + Theme.of( + context, + ).extension()!.settingsIconIcon, ), ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - label, - style: STextStyles.w600_20(context), - ), + Text(label, style: STextStyles.w600_20(context)), FutureBuilder( future: FiroCacheCoordinator.getSparkCacheSize( widget.cryptoCurrency.network, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart index f2357448c..938e9568a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart @@ -313,108 +313,7 @@ class _UnlockWalletKeysDesktopState child: PrimaryButton( label: "Continue", enabled: continueEnabled, - onPressed: continueEnabled - ? () async { - unawaited( - showDialog( - context: context, - builder: (context) => const Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LoadingIndicator( - width: 200, - height: 200, - ), - ], - ), - ), - ); - - await Future.delayed( - const Duration(seconds: 1), - ); - - final verified = await ref - .read(storageCryptoHandlerProvider) - .verifyPassphrase(passwordController.text); - - if (verified) { - if (context.mounted) { - Navigator.of(context, rootNavigator: true) - .pop(); - } - - ({String keys, String config})? frostData; - List? words; - - final wallet = - ref.read(pWallets).getWallet(widget.walletId); - - // TODO: [prio=low] handle wallets that don't have a mnemonic - // All wallets currently are mnemonic based - if (wallet is! MnemonicInterface) { - if (wallet is BitcoinFrostWallet) { - frostData = ( - keys: (await wallet.getSerializedKeys())!, - config: (await wallet.getMultisigConfig())!, - ); - } else { - throw Exception("FIXME ~= see todo in code"); - } - } else { - if (wallet is ViewOnlyOptionInterface && - (wallet as ViewOnlyOptionInterface) - .isViewOnly) { - // TODO: is something needed here? - } else { - words = await wallet.getMnemonicAsWords(); - } - } - - KeyDataInterface? keyData; - if (wallet is ViewOnlyOptionInterface && - wallet.isViewOnly) { - keyData = await wallet.getViewOnlyWalletData(); - } else if (wallet is ExtendedKeysInterface) { - keyData = await wallet.getXPrivs(); - } else if (wallet is LibMoneroWallet) { - keyData = await wallet.getKeys(); - } - - if (context.mounted) { - await Navigator.of(context) - .pushReplacementNamed( - WalletKeysDesktopPopup.routeName, - arguments: ( - mnemonic: words ?? [], - walletId: widget.walletId, - frostData: frostData, - keyData: keyData, - ), - ); - } - } else { - if (context.mounted) { - Navigator.of(context, rootNavigator: true) - .pop(); - } - - await Future.delayed( - const Duration(milliseconds: 300), - ); - if (context.mounted) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Invalid passphrase!", - context: context, - ), - ); - } - } - } - : null, + onPressed: continueEnabled ? enterPassphrase : null, ), ), ], diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart index e0da6d5e9..47b590a61 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart @@ -10,13 +10,13 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import '../../../../pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart'; import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart'; +import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart'; import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart'; import '../../../../providers/global/wallets_provider.dart'; import '../../../../route_generator.dart'; @@ -26,15 +26,13 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; -import '../../../../wallets/crypto_currency/coins/firo.dart'; import '../../../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../addresses/desktop_wallet_addresses_view.dart'; -import '../../../lelantus_coins/lelantus_coins_view.dart'; -import '../../../spark_coins/spark_coins_view.dart'; import 'desktop_delete_wallet_dialog.dart'; enum _WalletOptions { @@ -42,9 +40,8 @@ enum _WalletOptions { deleteWallet, changeRepresentative, showXpub, - lelantusCoins, - sparkCoins, - frostOptions; + frostOptions, + refreshFromHeight; String get prettyName { switch (this) { @@ -56,12 +53,10 @@ enum _WalletOptions { return "Change representative"; case _WalletOptions.showXpub: return "Show xPub"; - case _WalletOptions.lelantusCoins: - return "Lelantus Coins"; - case _WalletOptions.sparkCoins: - return "Spark Coins"; case _WalletOptions.frostOptions: return "FROST settings"; + case _WalletOptions.refreshFromHeight: + return "Refresh height"; } } } @@ -102,15 +97,12 @@ class WalletOptionsButton extends ConsumerWidget { onShowXpubPressed: () async { Navigator.of(context).pop(_WalletOptions.showXpub); }, - onFiroShowLelantusCoins: () async { - Navigator.of(context).pop(_WalletOptions.lelantusCoins); - }, - onFiroShowSparkCoins: () async { - Navigator.of(context).pop(_WalletOptions.sparkCoins); - }, onFrostMSWalletOptionsPressed: () async { Navigator.of(context).pop(_WalletOptions.frostOptions); }, + onRefreshHeightPressed: () async { + Navigator.of(context).pop(_WalletOptions.refreshFromHeight); + }, walletId: walletId, ); }, @@ -217,24 +209,6 @@ class WalletOptionsButton extends ConsumerWidget { } break; - case _WalletOptions.lelantusCoins: - unawaited( - Navigator.of(context).pushNamed( - LelantusCoinsView.routeName, - arguments: walletId, - ), - ); - break; - - case _WalletOptions.sparkCoins: - unawaited( - Navigator.of(context).pushNamed( - SparkCoinsView.routeName, - arguments: walletId, - ), - ); - break; - case _WalletOptions.frostOptions: unawaited( Navigator.of(context).pushNamed( @@ -243,6 +217,26 @@ class WalletOptionsButton extends ConsumerWidget { ), ); break; + + case _WalletOptions.refreshFromHeight: + if (Util.isDesktop) { + unawaited( + showDialog( + context: context, + builder: (context) => EditRefreshHeightView( + walletId: walletId, + ), + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + EditRefreshHeightView.routeName, + arguments: walletId, + ), + ); + } + break; } } }, @@ -275,9 +269,8 @@ class WalletOptionsPopupMenu extends ConsumerWidget { required this.onAddressListPressed, required this.onShowXpubPressed, required this.onChangeRepPressed, - required this.onFiroShowLelantusCoins, - required this.onFiroShowSparkCoins, required this.onFrostMSWalletOptionsPressed, + required this.onRefreshHeightPressed, required this.walletId, }); @@ -285,28 +278,25 @@ class WalletOptionsPopupMenu extends ConsumerWidget { final VoidCallback onAddressListPressed; final VoidCallback onShowXpubPressed; final VoidCallback onChangeRepPressed; - final VoidCallback onFiroShowLelantusCoins; - final VoidCallback onFiroShowSparkCoins; final VoidCallback onFrostMSWalletOptionsPressed; + final VoidCallback onRefreshHeightPressed; final String walletId; @override Widget build(BuildContext context, WidgetRef ref) { final coin = ref.watch(pWalletCoin(walletId)); - bool firoDebug = kDebugMode && (coin is Firo); - final wallet = ref.watch(pWallets).getWallet(walletId); bool xpubEnabled = wallet is ExtendedKeysInterface; if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { xpubEnabled = false; - firoDebug = false; } final bool canChangeRep = coin is NanoCurrency; final bool isFrost = coin is FrostCurrency; + final bool isMoneroWow = wallet is LibMoneroWallet; return Stack( children: [ @@ -398,57 +388,20 @@ class WalletOptionsPopupMenu extends ConsumerWidget { ), ), ), - if (firoDebug) - const SizedBox( - height: 8, - ), - if (firoDebug) - TransparentButton( - onPressed: onFiroShowLelantusCoins, - child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SvgPicture.asset( - Assets.svg.eye, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, - ), - const SizedBox(width: 14), - Expanded( - child: Text( - _WalletOptions.lelantusCoins.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ), - ], - ), - ), - ), - if (firoDebug) + if (isFrost) const SizedBox( height: 8, ), - if (firoDebug) + if (isFrost) TransparentButton( - onPressed: onFiroShowSparkCoins, + onPressed: onFrostMSWalletOptionsPressed, child: Padding( padding: const EdgeInsets.all(8), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ SvgPicture.asset( - Assets.svg.eye, + Assets.svg.addressBookDesktop, width: 20, height: 20, color: Theme.of(context) @@ -458,7 +411,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { const SizedBox(width: 14), Expanded( child: Text( - _WalletOptions.sparkCoins.prettyName, + _WalletOptions.frostOptions.prettyName, style: STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( @@ -472,13 +425,13 @@ class WalletOptionsPopupMenu extends ConsumerWidget { ), ), ), - if (isFrost) + if (isMoneroWow) const SizedBox( height: 8, ), - if (isFrost) + if (isMoneroWow) TransparentButton( - onPressed: onFrostMSWalletOptionsPressed, + onPressed: onRefreshHeightPressed, child: Padding( padding: const EdgeInsets.all(8), child: Row( @@ -495,7 +448,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { const SizedBox(width: 14), Expanded( child: Text( - _WalletOptions.frostOptions.prettyName, + _WalletOptions.refreshFromHeight.prettyName, style: STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( diff --git a/lib/pages_desktop_specific/password/delete_password_warning_view.dart b/lib/pages_desktop_specific/password/delete_password_warning_view.dart index e8a4c5256..1eb1331a7 100644 --- a/lib/pages_desktop_specific/password/delete_password_warning_view.dart +++ b/lib/pages_desktop_specific/password/delete_password_warning_view.dart @@ -87,10 +87,7 @@ class _ForgotPasswordDesktopViewState await DB.instance.init(); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("$e\n$s", error: e, stackTrace: s,); return false; } diff --git a/lib/pages_desktop_specific/password/desktop_login_view.dart b/lib/pages_desktop_specific/password/desktop_login_view.dart index 89447ee57..15b795278 100644 --- a/lib/pages_desktop_specific/password/desktop_login_view.dart +++ b/lib/pages_desktop_specific/password/desktop_login_view.dart @@ -75,10 +75,10 @@ class _DesktopLoginViewState extends ConsumerState { secureStore: ref.read(secureStoreProvider), ); } catch (e, s) { - Logging.instance.log( - "Cannot migrate desktop database\n$e $s", - level: LogLevel.Error, - printFullLength: true, + Logging.instance.f( + "Cannot migrate desktop database", + error: e, + stackTrace: s, ); } } diff --git a/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart index a20660779..161459e43 100644 --- a/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart +++ b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart @@ -271,7 +271,7 @@ class _ForgottenPassphraseRestoreFromSWBState }); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); } }, child: MouseRegion( diff --git a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart index e96a5288c..60a164b8f 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart @@ -363,7 +363,7 @@ class _AdvancedSettings extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "Debug info", + "Logging", style: STextStyles.desktopTextExtraSmall(context) .copyWith( color: Theme.of(context) @@ -374,7 +374,7 @@ class _AdvancedSettings extends ConsumerState { ), PrimaryButton( buttonHeight: ButtonHeight.xs, - label: "Show logs", + label: "View", width: 101, onPressed: () async { await showDialog( @@ -382,7 +382,7 @@ class _AdvancedSettings extends ConsumerState { useSafeArea: false, barrierDismissible: true, builder: (context) { - return const DebugInfoDialog(); + return const DesktopLoggingDialog(); }, ); }, diff --git a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart index dedfe999d..14048b7d9 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart @@ -1,6 +1,6 @@ -/* +/* * This file is part of Stack Wallet. - * + * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. @@ -9,129 +9,101 @@ */ import 'dart:async'; +import 'dart:io'; -import 'package:event_bus/event_bus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import '../../../../models/isar/models/log.dart'; -import '../../../../notifications/show_flush_bar.dart'; -import '../../../../providers/global/debug_service_provider.dart'; +import '../../../../app_config.dart'; +import '../../../../providers/global/prefs_provider.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/assets.dart'; -import '../../../../utilities/constants.dart'; import '../../../../utilities/logger.dart'; -import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; -import '../../../../utilities/util.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/primary_button.dart'; -import '../../../../widgets/desktop/secondary_button.dart'; -import '../../../../widgets/icon_widgets/x_icon.dart'; -import '../../../../widgets/rounded_container.dart'; +import '../../../../widgets/log_level_preference_widget.dart'; +import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; -import '../../../../widgets/stack_text_field.dart'; -import '../../../../widgets/textfield_icon_button.dart'; -class DebugInfoDialog extends ConsumerStatefulWidget { - const DebugInfoDialog({super.key}); +class DesktopLoggingDialog extends ConsumerStatefulWidget { + const DesktopLoggingDialog({super.key}); @override - ConsumerState createState() => _DebugInfoDialog(); + ConsumerState createState() => _DebugInfoDialog(); } -class _DebugInfoDialog extends ConsumerState { - late final TextEditingController searchDebugController; - late final FocusNode searchDebugFocusNode; - - final scrollController = ScrollController(); +class _DebugInfoDialog extends ConsumerState { + late final TextEditingController fileLocationController; + bool _lock = false; - String _searchTerm = ""; + Future _edit() async { + final currentPath = ref.read(prefsChangeNotifierProvider).logsPath ?? + Logging.instance.logsDirPath; + final newPath = await _pickDir(context, currentPath); - List filtered(List unfiltered, String filter) { - if (filter == "") { - return unfiltered; - } - return unfiltered - .where( - (e) => (e.toString().toLowerCase().contains(filter.toLowerCase())), - ) - .toList(); - } - - BorderRadius? _borderRadius(int index, int listLength) { - if (index == 0 && listLength == 1) { - return BorderRadius.circular( - Constants.size.circularBorderRadius, - ); - } else if (index == 0) { - return BorderRadius.vertical( - bottom: Radius.circular( - Constants.size.circularBorderRadius, - ), - ); - } else if (index == listLength - 1) { - return BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, - ), + // test if has permission to write + if (newPath != null) { + final file = File( + "$newPath${Platform.pathSeparator}._test", ); + if (!file.existsSync()) { + file.createSync(); + file.deleteSync(); + } } - return null; - } - bool _lock = false; - Future<(String?, bool)?> _saveFile() async { - final path = await FilePicker.platform.getDirectoryPath( - dialogTitle: "Choose Log Save Location", - lockParentWindow: true, - ); + // success + ref.read(prefsChangeNotifierProvider).logsPath = newPath; - if (path == null) { - return null; + if (mounted) { + setState(() { + fileLocationController.text = + ref.read(prefsChangeNotifierProvider).logsPath ?? + Logging.instance.logsDirPath; + }); } + } - bool logsSaved = true; - String? filename; - try { - filename = - await ref.read(debugServiceProvider).exportToFile(path, EventBus()); - } catch (e, s) { - logsSaved = false; - Logging.instance.log("$e $s", level: LogLevel.Error); + Future _pickDir(BuildContext context, String currentPath) async { + final String? chosenPath; + if (Platform.isIOS) { + chosenPath = currentPath; + } else { + final String path = + Platform.isWindows ? currentPath.replaceAll("/", "\\") : currentPath; + chosenPath = await FilePicker.platform.getDirectoryPath( + dialogTitle: "Choose Log Save location", + initialDirectory: path, + lockParentWindow: true, + ); } - return (filename, logsSaved); + return chosenPath; } @override void initState() { - searchDebugController = TextEditingController(); - searchDebugFocusNode = FocusNode(); - super.initState(); + fileLocationController = TextEditingController(); + fileLocationController.text = + ref.read(prefsChangeNotifierProvider).logsPath ?? + Logging.instance.logsDirPath; } @override void dispose() { - searchDebugFocusNode.dispose(); - searchDebugController.dispose(); - - scrollController.dispose(); - + fileLocationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return DesktopDialog( - // Max height of 850 unless the screen is smaller than that. - maxHeight: MediaQuery.of(context).size.height < 850 - ? MediaQuery.of(context).size.height - : 850, - maxWidth: 600, + maxHeight: double.infinity, + maxWidth: 640, child: Column( children: [ Row( @@ -140,7 +112,7 @@ class _DebugInfoDialog extends ConsumerState { Padding( padding: const EdgeInsets.all(32), child: Text( - "Debug info", + "Logging", style: STextStyles.desktopH3(context), textAlign: TextAlign.center, ), @@ -150,287 +122,143 @@ class _DebugInfoDialog extends ConsumerState { ), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: searchDebugController, - focusNode: searchDebugFocusNode, - onChanged: (newString) { - setState(() => _searchTerm = newString); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - searchDebugFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: searchDebugController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - searchDebugController.text = ""; - _searchTerm = ""; - }); - }, - ), - ], - ), - ), - ) - : null, + child: Row( + children: [ + Text( + "Log files location", + style: STextStyles.desktopTextFieldLabel(context), + textAlign: TextAlign.left, ), - ), + ], ), ), - Expanded( - // flex: 24, - child: NestedScrollView( - // floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor( - context, - ), - sliver: const SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.symmetric( - vertical: 16, - horizontal: 32, - ), - child: Column( - children: [], - ), + const SizedBox( + height: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Save to...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, ), - ), - ), - ]; - }, - body: Builder( - builder: (context) { - final logs = filtered( - ref.watch( - debugServiceProvider.select((value) => value.recentLogs), - ), - _searchTerm, - ).reversed.toList(growable: false); - return CustomScrollView( - reverse: true, - // shrinkWrap: true, - controller: scrollController, - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor( - context, - ), + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 16, + height: 16, ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final log = logs[index]; - - return Container( - key: Key( - "log_${log.id}_${log.timestampInMillisUTC}", - ), - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, - borderRadius: _borderRadius(index, logs.length), - ), - child: Padding( - padding: const EdgeInsets.all(4), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - ), - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .popupBG, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - " [${log.logLevel.name}]", - style: STextStyles.baseXS(context) - .copyWith( - fontSize: 8, - color: (log.logLevel == - LogLevel.Info - ? Theme.of(context) - .extension< - StackColors>()! - .topNavIconGreen - : (log.logLevel == - LogLevel.Warning - ? Theme.of(context) - .extension< - StackColors>()! - .topNavIconYellow - : (log.logLevel == - LogLevel.Error - ? Colors.orange - : Theme.of(context) - .extension< - StackColors>()! - .topNavIconRed))), - ), - ), - Text( - "[${DateTime.fromMillisecondsSinceEpoch(log.timestampInMillisUTC, isUtc: true)}]: ", - style: STextStyles.baseXS(context) - .copyWith( - fontSize: 12, - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - ], - ), - Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const SizedBox( - width: 20, - ), - Flexible( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SelectableText( - log.message, - style: STextStyles.baseXS( - context, - ).copyWith( - fontSize: 11.5, - ), - ), - ], - ), - ), - ], - ), - ], - ), - ), - ), - ), - ); - }, - childCount: logs.length, - ), + const SizedBox( + width: 12, ), ], - ); - }, + ), + ), + ), + key: const Key( + "logsDirPathLocationControllerKey", ), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) {}, ), ), - // const Spacer(), - Padding( - padding: const EdgeInsets.all(32), - child: Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Clear logs", - onPressed: () async { - await ref.read(debugServiceProvider).deleteAllLogs(); - setState(() {}); - - if (context.mounted) { - Navigator.pop(context); - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - context: context, - message: 'Logs cleared!', - ), - ); - } - }, + const SizedBox( + height: 8, + ), + const Padding( + padding: EdgeInsets.all(32), + child: LogLevelPreferenceWidget(), + ), + if (!Platform.isMacOS) + const SizedBox( + height: 8, + ), + if (!Platform.isMacOS) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + borderColor: Theme.of(context) + .extension()! + .textSubtitle6, + child: Text( + "NOTE: ${AppConfig.appName} must be restarted in order" + " for changes to take effect.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: PrimaryButton( - label: "Save logs to file", - onPressed: () async { - if (_lock) { - return; - } - _lock = true; - try { - final results = await showLoading<(String?, bool)?>( - whileFuture: _saveFile(), - context: context, - message: "Generating logs file...", - ); - - if (results != null) { - if (results.$2) { - unawaited( - showDialog( - context: context, - builder: (context) => StackOkDialog( - title: "Logs saved to", - message: results.$1, - ), - ), - ); - } else { + ], + ), + ), + if (!Platform.isMacOS) + const SizedBox( + height: 16, + ), + if (!Platform.isMacOS) + Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Select log save location", + onPressed: () async { + if (_lock) { + return; + } + _lock = true; + try { + await _edit(); + } catch (e, s) { + Logging.instance.e( + "Failed to change logs path", + error: e, + stackTrace: s, + ); + if (context.mounted) { unawaited( showDialog( context: context, builder: (context) => StackOkDialog( - title: "Error Saving Logs", - message: results.$1, + title: "Failed to change logs path", + message: e.toString(), ), ), ); } + } finally { + _lock = false; } - } finally { - _lock = false; - } - }, + }, + ), ), - ), - ], + ], + ), ), - ), ], ), ); diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart index 081540c3b..94ce0da9d 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:share_plus/share_plus.dart'; + import '../../../../../themes/stack_colors.dart'; import '../../../../../themes/theme_service.dart'; import '../../../../../utilities/assets.dart'; @@ -56,10 +57,7 @@ class _DesktopInstallThemeState extends ConsumerState { ]); return true; } catch (e, s) { - Logging.instance.log( - "Failed to install theme: $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("Failed to install theme: ", error: e, stackTrace: s); return false; } } @@ -81,7 +79,7 @@ class _DesktopInstallThemeState extends ConsumerState { } } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); } } diff --git a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart index bf5f3e009..3ffea6508 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart @@ -201,8 +201,11 @@ class _CreateAutoBackup extends ConsumerState { }); } } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); + Logging.instance.e( + "", + error: e, + stackTrace: s, + ); } }, controller: fileLocationController, @@ -699,8 +702,11 @@ class _CreateAutoBackup extends ConsumerState { } on Exception catch (e, s) { final String err = getErrorMessageFromSWBException(e); - Logging.instance - .log("$err\n$s", level: LogLevel.Error); + Logging.instance.e( + err, + error: e, + stackTrace: s, + ); // pop encryption progress dialog Navigator.of(context).pop(); unawaited( @@ -712,8 +718,11 @@ class _CreateAutoBackup extends ConsumerState { ); return; } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Error); + Logging.instance.e( + "", + error: e, + stackTrace: s, + ); // pop encryption progress dialog Navigator.of(context).pop(); unawaited( diff --git a/lib/pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart index d01a5d678..162ed9860 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart @@ -71,10 +71,7 @@ class _TorSettingsState extends ConsumerState { // Toggle the useTor preference on success. ref.read(prefsChangeNotifierProvider).useTor = true; } catch (e, s) { - Logging.instance.log( - "Error starting tor: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error starting tor: ", error: e, stackTrace: s); // TODO: show dialog with error message } }, @@ -101,10 +98,7 @@ class _TorSettingsState extends ConsumerState { // Toggle the useTor preference on success. ref.read(prefsChangeNotifierProvider).useTor = false; } catch (e, s) { - Logging.instance.log( - "Error stopping tor: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error stopping tor: ", error: e, stackTrace: s); } }, ); diff --git a/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart b/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart index 43ba62a6a..8a937fab3 100644 --- a/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart +++ b/lib/pages_desktop_specific/spark_coins/spark_coins_view.dart @@ -11,285 +11,126 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:isar/isar.dart'; + import '../../providers/db/main_db_provider.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/assets.dart'; import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; import '../../wallets/isar/models/spark_coin.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/desktop/desktop_app_bar.dart'; import '../../widgets/desktop/desktop_scaffold.dart'; -import '../../widgets/rounded_white_container.dart'; +import '../../widgets/isar_collection_watcher_list.dart'; -class SparkCoinsView extends ConsumerStatefulWidget { +class SparkCoinsView extends ConsumerWidget { const SparkCoinsView({ super.key, required this.walletId, }); + static const title = "Spark coins"; static const String routeName = "/sparkCoinsView"; final String walletId; @override - ConsumerState createState() => _SparkCoinsViewState(); -} - -class _SparkCoinsViewState extends ConsumerState { - List _coins = []; - - Stream>? sparkCoinsCollectionWatcher; - - void _onSparkCoinsCollectionWatcherEvent(List coins) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() { - _coins = coins; - }); - } - }); - } - - @override - void initState() { - sparkCoinsCollectionWatcher = ref - .read(mainDBProvider) - .isar - .sparkCoins - .where() - .walletIdEqualToAnyLTagHash(widget.walletId) - .sortByHeightDesc() - .watch(fireImmediately: true); - sparkCoinsCollectionWatcher! - .listen((data) => _onSparkCoinsCollectionWatcherEvent(data)); - - super.initState(); - } - - @override - void dispose() { - sparkCoinsCollectionWatcher = null; - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return DesktopScaffold( - appBar: DesktopAppBar( - background: Theme.of(context).extension()!.popupBG, - leading: Expanded( - child: Row( - children: [ - const SizedBox( - width: 32, - ), - AppBarIconButton( - size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: Navigator.of(context).pop, - ), - const SizedBox( - width: 12, - ), - Text( - "Spark Coins", - style: STextStyles.desktopH3(context), - ), - const Spacer(), - ], - ), - ), - useSpacers: false, - isCompactHeight: true, - ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - children: [ - Expanded( - flex: 9, - child: Text( - "TXID", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 9, - child: Text( - "LTag Hash", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 9, - child: Text( - "Address", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 4, - child: Text( - "Memo", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ), - ), - Expanded( - flex: 3, - child: Text( - "Value (sats)", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Height", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Group Id", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Type", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "Used", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.right, - ), + Widget build(BuildContext context, WidgetRef ref) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) { + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, ), - ], - ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 12, + ), + Text( + title, + style: STextStyles.desktopH3(context), + ), + const Spacer(), + ], ), ), - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: _coins.length, - separatorBuilder: (_, __) => Container( - height: 1, - color: Theme.of(context) - .extension()! - .backgroundAppBar, + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: child, + ), + ); + }, + child: ConditionalParent( + condition: !Util.isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + automaticallyImplyLeading: false, + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), ), - itemBuilder: (_, index) => Padding( - padding: const EdgeInsets.all(4), - child: RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 9, - child: SelectableText( - _coins[index].txHash, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 9, - child: SelectableText( - _coins[index].lTagHash, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 9, - child: SelectableText( - _coins[index].address, - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 4, - child: SelectableText( - _coins[index].memo ?? "", - style: STextStyles.itemSubtitle12(context), - ), - ), - Expanded( - flex: 3, - child: SelectableText( - _coins[index].value.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].height.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].groupId.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].type.name, - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: SelectableText( - _coins[index].isUsed.toString(), - style: STextStyles.itemSubtitle12(context), - textAlign: TextAlign.right, - ), - ), - ], - ), - ), + title: Text( + title, + style: STextStyles.navBarTitle(context), ), ), + body: SafeArea( + child: child, + ), ), - ], + ); + }, + child: IsarCollectionWatcherList( + itemName: title, + queryBuilder: () => ref + .read(mainDBProvider) + .isar + .sparkCoins + .where() + .walletIdEqualToAnyLTagHash(walletId) + .sortByHeightDesc(), + itemBuilder: (SparkCoin? coin) { + return [ + ("TXID", coin?.txHash ?? "", 9), + ("LTag Hash", coin?.lTagHash ?? "", 9), + ("Address", coin?.address ?? "", 9), + ("Memo", coin?.memo ?? "", 4), + ("Value (sats)", coin?.value.toString() ?? "", 3), + ("Height", coin?.height.toString() ?? "", 2), + ("Group ID", coin?.groupId.toString() ?? "", 2), + ("Type", coin?.type.name ?? "", 2), + ("Used", coin?.isUsed.toString() ?? "", 2), + ]; + }, ), ), ); diff --git a/lib/providers/global/debug_service_provider.dart b/lib/providers/global/debug_service_provider.dart deleted file mode 100644 index 4d666cdcf..000000000 --- a/lib/providers/global/debug_service_provider.dart +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../services/debug_service.dart'; - -final debugServiceProvider = - ChangeNotifierProvider((ref) => DebugService.instance); diff --git a/lib/providers/progress_report/xelis_table_progress_provider.dart b/lib/providers/progress_report/xelis_table_progress_provider.dart new file mode 100644 index 000000000..cbebe8224 --- /dev/null +++ b/lib/providers/progress_report/xelis_table_progress_provider.dart @@ -0,0 +1,78 @@ +import 'package:xelis_flutter/src/api/api.dart' as xelis_api; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:flutter/foundation.dart'; +import 'dart:math' as math; + +enum XelisTableGenerationStep { + t1PointsGeneration, + t1CuckooSetup, + t2Table, + unknown; + + factory XelisTableGenerationStep.fromString(String step) { + return switch (step) { + "T1PointsGeneration" => XelisTableGenerationStep.t1PointsGeneration, + "T1CuckooSetup" => XelisTableGenerationStep.t1CuckooSetup, + "T2Table" => XelisTableGenerationStep.t2Table, + _ => XelisTableGenerationStep.unknown, + }; + } + + String get displayName => switch (this) { + t1PointsGeneration => "Generating T1 Points", + t1CuckooSetup => "Setting up T1 Cuckoo", + t2Table => "Generating T2 Table", + unknown => "Processing", + }; +} + +class XelisTableProgressState { + final double? tableProgress; + final XelisTableGenerationStep currentStep; + + const XelisTableProgressState({ + this.tableProgress, + this.currentStep = XelisTableGenerationStep.unknown, + }); + + XelisTableProgressState copyWith({ + double? tableProgress, + XelisTableGenerationStep? currentStep, + }) { + return XelisTableProgressState( + tableProgress: tableProgress ?? this.tableProgress, + currentStep: currentStep ?? this.currentStep, + ); + } +} + +final xelisTableProgressProvider = StreamProvider((ref) { + double lastPrintedProgress = 0.0; + return xelis_api.createProgressReportStream().map((report) { + return report.when( + tableGeneration: (progress, step, _) { + final currentStep = XelisTableGenerationStep.fromString(step); + final stepIndex = switch(currentStep) { + XelisTableGenerationStep.t1PointsGeneration => 0, + XelisTableGenerationStep.t1CuckooSetup => 1, + XelisTableGenerationStep.t2Table => 2, + XelisTableGenerationStep.unknown => 0, + }; + + if ((progress - lastPrintedProgress).abs() >= 0.05 || + currentStep != XelisTableGenerationStep.fromString(step) || + progress >= 0.99) { + debugPrint("Xelis Table Generation: $step - ${progress*100.0}%"); + lastPrintedProgress = progress; + } + + return XelisTableProgressState( + tableProgress: progress, + currentStep: currentStep, + ); + }, + misc: (_) => const XelisTableProgressState(), + ); + }); +}); \ No newline at end of file diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index a96a9869c..e6e9ef20b 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -32,3 +32,4 @@ export './ui/verify_recovery_phrase/correct_word_provider.dart'; export './ui/verify_recovery_phrase/random_index_provider.dart'; export './ui/verify_recovery_phrase/selected_word_provider.dart'; export './wallet/transaction_note_provider.dart'; +export './progress_report/xelis_table_progress_provider.dart'; diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 52454fb29..6695a53b8 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -72,6 +72,11 @@ import 'pages/home_view/home_view.dart'; import 'pages/intro_view.dart'; import 'pages/manage_favorites_view/manage_favorites_view.dart'; import 'pages/monkey/monkey_view.dart'; +import 'pages/namecoin_names/buy_domain_view.dart'; +import 'pages/namecoin_names/confirm_name_transaction_view.dart'; +import 'pages/namecoin_names/manage_domain_view.dart'; +import 'pages/namecoin_names/namecoin_names_home_view.dart'; +import 'pages/namecoin_names/sub_widgets/name_details.dart'; import 'pages/notification_views/notifications_view.dart'; import 'pages/ordinals/ordinal_details_view.dart'; import 'pages/ordinals/ordinals_filter_view.dart'; @@ -91,7 +96,7 @@ import 'pages/send_view/send_view.dart'; import 'pages/send_view/token_send_view.dart'; import 'pages/settings_views/global_settings_view/about_view.dart'; import 'pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart'; -import 'pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; +import 'pages/settings_views/global_settings_view/advanced_views/logging_settings_view.dart'; import 'pages/settings_views/global_settings_view/advanced_views/manage_coin_units/edit_coin_units_view.dart'; import 'pages/settings_views/global_settings_view/advanced_views/manage_coin_units/manage_coin_units_view.dart'; import 'pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart'; @@ -135,6 +140,7 @@ import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_setting import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_view_only_wallet_keys_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart'; +import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rbf_settings_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart'; @@ -715,6 +721,21 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case NameDetailsView.routeName: + if (args is (Id, String)) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => NameDetailsView( + walletId: args.$2, + utxoId: args.$1, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case PaynymClaimView.routeName: if (args is String) { return getRoute( @@ -771,6 +792,35 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case NamecoinNamesHomeView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => NamecoinNamesHomeView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ManageDomainView.routeName: + if (args is ({String walletId, UTXO utxo})) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ManageDomainView( + walletId: args.walletId, + utxo: args.utxo, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case FusionProgressView.routeName: if (args is String) { return getRoute( @@ -950,10 +1000,10 @@ class RouteGenerator { settings: RouteSettings(name: settings.name), ); - case DebugView.routeName: + case LoggingSettingsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => const DebugView(), + builder: (_) => const LoggingSettingsView(), settings: RouteSettings(name: settings.name), ); @@ -1842,6 +1892,21 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case ConfirmNameTransactionView.routeName: + if (args is (TxData, String)) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ConfirmNameTransactionView( + txData: args.$1, + walletId: args.$2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case WalletInitiatedExchangeView.routeName: if (args is Tuple2) { return getRoute( @@ -2140,6 +2205,35 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case EditRefreshHeightView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => EditRefreshHeightView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case BuyDomainView.routeName: + if (args is ({String walletId, String domainName})) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => BuyDomainView( + walletId: args.walletId, + domainName: args.domainName, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + // == Desktop specific routes ============================================ case CreatePasswordView.routeName: if (args is bool) { diff --git a/lib/services/auto_swb_service.dart b/lib/services/auto_swb_service.dart index 1113b30f1..24f58d0f7 100644 --- a/lib/services/auto_swb_service.dart +++ b/lib/services/auto_swb_service.dart @@ -42,14 +42,12 @@ class AutoSWBService extends ChangeNotifier { /// Attempt a backup. Future doBackup() async { if (_status == AutoSWBStatus.backingUp) { - Logging.instance.log( + Logging.instance.w( "AutoSWBService attempted to run doBackup() while a backup is in progress!", - level: LogLevel.Warning, ); return; } - Logging.instance - .log("AutoSWBService.doBackup() started...", level: LogLevel.Info); + Logging.instance.d("AutoSWBService.doBackup() started..."); // set running backup status and notify listeners _status = AutoSWBStatus.backingUp; @@ -62,9 +60,8 @@ class AutoSWBService extends ChangeNotifier { final autoBackupDirectoryPath = Prefs.instance.autoBackupLocation; if (autoBackupDirectoryPath == null) { - Logging.instance.log( + Logging.instance.e( "AutoSWBService attempted to run doBackup() when no auto backup directory was set!", - level: LogLevel.Error, ); // set error backup status and notify listeners _status = AutoSWBStatus.error; @@ -104,17 +101,16 @@ class AutoSWBService extends ChangeNotifier { // delete all but the latest 3 auto backups trimBackups(autoBackupDirectoryPath, 3); - Logging.instance - .log("AutoSWBService.doBackup() succeeded", level: LogLevel.Info); + Logging.instance.d("AutoSWBService.doBackup() succeeded"); } on Exception catch (e, s) { final String err = getErrorMessageFromSWBException(e); - Logging.instance.log("$err\n$s", level: LogLevel.Error); + Logging.instance.e("$err\n$s", error: e, stackTrace: s); // set error backup status and notify listeners _status = AutoSWBStatus.error; notifyListeners(); return; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); // set error backup status and notify listeners _status = AutoSWBStatus.error; notifyListeners(); diff --git a/lib/services/buy/simplex/simplex_api.dart b/lib/services/buy/simplex/simplex_api.dart index 5bbb82c1c..eed33ea1b 100644 --- a/lib/services/buy/simplex/simplex_api.dart +++ b/lib/services/buy/simplex/simplex_api.dart @@ -72,9 +72,10 @@ class SimplexAPI { return _parseSupportedCryptos(jsonArray); } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableCurrencies exception: ", + error: e, + stackTrace: s, ); return BuyResponse( exception: BuyException( @@ -106,8 +107,11 @@ class SimplexAPI { return BuyResponse(value: cryptos); } catch (e, s) { - Logging.instance - .log("_parseSupported exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "_parseSupported exception", + error: e, + stackTrace: s, + ); return BuyResponse( exception: BuyException( e.toString(), @@ -143,10 +147,8 @@ class SimplexAPI { return _parseSupportedFiats(jsonArray); } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("getAvailableCurrencies exception: ", error: e, stackTrace: s); return BuyResponse( exception: BuyException( e.toString(), @@ -179,8 +181,11 @@ class SimplexAPI { return BuyResponse(value: fiats); } catch (e, s) { - Logging.instance - .log("_parseSupported exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "_parseSupported exception", + error: e, + stackTrace: s, + ); return BuyResponse( exception: BuyException( e.toString(), @@ -236,7 +241,11 @@ class SimplexAPI { return _parseQuote(jsonArray); } catch (e, s) { - Logging.instance.log("getQuote exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getQuote exception", + error: e, + stackTrace: s, + ); return BuyResponse( exception: BuyException( e.toString(), @@ -280,8 +289,11 @@ class SimplexAPI { return BuyResponse(value: _quote); } catch (e, s) { - Logging.instance - .log("_parseQuote exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "_parseQuote exception", + error: e, + stackTrace: s, + ); return BuyResponse( exception: BuyException( e.toString(), @@ -350,7 +362,11 @@ class SimplexAPI { return BuyResponse(value: _order); } catch (e, s) { - Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "newOrder exception", + error: e, + stackTrace: s, + ); return BuyResponse( exception: BuyException( e.toString(), @@ -378,7 +394,11 @@ class SimplexAPI { return BuyResponse(value: status); } catch (e, s) { - Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "newOrder exception", + error: e, + stackTrace: s, + ); return BuyResponse( exception: BuyException( e.toString(), diff --git a/lib/services/coins/tezos/api/tezos_api.dart b/lib/services/coins/tezos/api/tezos_api.dart index e646019ee..6c2193577 100644 --- a/lib/services/coins/tezos/api/tezos_api.dart +++ b/lib/services/coins/tezos/api/tezos_api.dart @@ -25,10 +25,7 @@ abstract final class TezosAPI { final result = jsonDecode(response.body); return result as int; } catch (e, s) { - Logging.instance.log( - "Error occurred in TezosAPI while getting counter for $address: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in TezosAPI while getting counter for $address: ", error: e, stackTrace: s); rethrow; } } @@ -53,10 +50,7 @@ abstract final class TezosAPI { return account; } catch (e, s) { - Logging.instance.log( - "Error occurred in TezosAPI while getting account for $address: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in TezosAPI while getting account for $address: ", error: e, stackTrace: s); rethrow; } } @@ -109,10 +103,7 @@ abstract final class TezosAPI { } return txs; } catch (e, s) { - Logging.instance.log( - "Error occurred in TezosAPI while getting transactions for $address: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in TezosAPI while getting transactions for $address: ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/services/coins/tezos/api/tezos_rpc_api.dart b/lib/services/coins/tezos/api/tezos_rpc_api.dart index 59ca98045..688d9f68e 100644 --- a/lib/services/coins/tezos/api/tezos_rpc_api.dart +++ b/lib/services/coins/tezos/api/tezos_rpc_api.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import '../../../../networking/http.dart'; -import '../../../tor_service.dart'; import '../../../../utilities/logger.dart'; import '../../../../utilities/prefs.dart'; +import '../../../tor_service.dart'; abstract final class TezosRpcAPI { static final HTTP _client = HTTP(); @@ -27,10 +27,11 @@ abstract final class TezosRpcAPI { final balance = BigInt.parse(response.body.substring(1, response.body.length - 2)); return balance; - } catch (e) { - Logging.instance.log( - "Error occurred in tezos_rpc_api.dart while getting balance for $address: $e", - level: LogLevel.Error, + } catch (e, s) { + Logging.instance.e( + "Error occurred in tezos_rpc_api.dart while getting balance for $address", + error: e, + stackTrace: s, ); } return null; @@ -53,10 +54,11 @@ abstract final class TezosRpcAPI { final jsonParsedResponse = jsonDecode(response.body); return int.parse(jsonParsedResponse["level"].toString()); - } catch (e) { - Logging.instance.log( - "Error occurred in tezos_rpc_api.dart while getting chain height for tezos: $e", - level: LogLevel.Error, + } catch (e, s) { + Logging.instance.e( + "Error occurred in tezos_rpc_api.dart while getting chain height for tezos", + error: e, + stackTrace: s, ); } return null; diff --git a/lib/services/debug_service.dart b/lib/services/debug_service.dart deleted file mode 100644 index 4d6e51553..000000000 --- a/lib/services/debug_service.dart +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'dart:async'; -import 'dart:io'; - -import 'package:event_bus/event_bus.dart'; -import 'package:flutter/material.dart'; -import 'package:isar/isar.dart'; - -import '../app_config.dart'; -import '../models/isar/models/log.dart'; -import '../utilities/logger.dart'; - -class DebugService extends ChangeNotifier { - DebugService._(); - static final DebugService _instance = DebugService._(); - static DebugService get instance => _instance; - - late final Isar isar; - // late final Stream logsChanged; - - // bool _shouldPause = false; - // - // void togglePauseUiUpdates() { - // _shouldPause = !_shouldPause; - // notifyListeners(); - // } - - // bool get isPaused => _shouldPause; - - Future init(Isar isar) async { - this.isar = isar; - // logsChanged = this.isar.logs.watchLazy(); - // logsChanged.listen((_) { - // if (!_shouldPause) { - // updateRecentLogs(); - // } - // }); - } - - List get recentLogs => isar.logs - .where() - .sortByTimestampInMillisUTCDesc() - .limit(100) - .findAllSync(); - - // Future updateRecentLogs() async { - // int totalCount = await isar.logs.count(); - // int offset = totalCount - numberOfRecentLogsToLoad; - // if (offset < 0) { - // offset = 0; - // } - // - // _recentLogs = (await isar.logs - // .where() - // .anyTimestampInMillisUTC() - // .offset(offset) - // .limit(numberOfRecentLogsToLoad) - // .findAll()); - // notifyListeners(); - // } - - Future deleteAllLogs() async { - try { - await isar.writeTxn(() async => await isar.logs.clear()); - notifyListeners(); - return true; - } catch (_) { - return false; - } - } - - Future deleteLogsOlderThan({ - Duration timeframe = const Duration(days: 30), - }) async { - final cutoffDate = DateTime.now().subtract(timeframe).toUtc(); - await isar.writeTxn(() async { - await isar.logs - .where() - .timestampInMillisUTCLessThan(cutoffDate.millisecondsSinceEpoch) - .deleteAll(); - }); - - Logging.instance.log( - "Logs older than $cutoffDate cleared!", - level: LogLevel.Info, - ); - } - - /// returns the filename of the saved logs file - Future exportToFile(String directory, EventBus eventBus) async { - final now = DateTime.now(); - final filename = - "${AppConfig.prefix}_Wallet_logs_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.txt"; - final filepath = "$directory/$filename"; - final File file = await File(filepath).create(); - - final sink = file.openWrite(); - final logs = await isar.logs.where().anyTimestampInMillisUTC().findAll(); - final count = logs.length; - int counter = 0; - - for (final log in logs) { - sink.writeln(log); - await sink.flush(); - counter++; - final exportPercent = (counter / count).clamp(0.0, 1.0); - eventBus.fire(exportPercent); - } - - await sink.flush(); - await sink.close(); - - eventBus.fire(1.0); - return filename; - } -} diff --git a/lib/services/ethereum/cached_eth_token_balance.dart b/lib/services/ethereum/cached_eth_token_balance.dart index c241f7231..ce6efe1b4 100644 --- a/lib/services/ethereum/cached_eth_token_balance.dart +++ b/lib/services/ethereum/cached_eth_token_balance.dart @@ -9,13 +9,14 @@ */ import 'package:isar/isar.dart'; + import '../../db/isar/main_db.dart'; import '../../models/balance.dart'; import '../../models/isar/models/ethereum/eth_contract.dart'; -import 'ethereum_api.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/logger.dart'; import '../../wallets/isar/models/token_wallet_info.dart'; +import 'ethereum_api.dart'; class CachedEthTokenBalance { final String walletId; @@ -54,9 +55,8 @@ class CachedEthTokenBalance { isar: mainDB.isar, ); } else { - Logging.instance.log( + Logging.instance.w( "CachedEthTokenBalance.fetchAndUpdateCachedBalance failed: ${response.exception}", - level: LogLevel.Warning, ); } } diff --git a/lib/services/ethereum/ethereum_api.dart b/lib/services/ethereum/ethereum_api.dart index 6f5f2313e..fccce0ceb 100644 --- a/lib/services/ethereum/ethereum_api.dart +++ b/lib/services/ethereum/ethereum_api.dart @@ -104,9 +104,11 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getEthTransactions($address): $e\n$s", - level: LogLevel.Error, + Logging.instance.e("getEthTransactions()", error: e); + Logging.instance.d( + "getEthTransactions($address)", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -170,9 +172,14 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getEthTransactionByHash($txid): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getEthTransactionByHash()", + error: e, + ); + Logging.instance.d( + "getEthTransactionByHash($txid)", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -233,9 +240,14 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getEthTransactionNonces($txns): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getEthTransactionNonces()", + error: e, + ); + Logging.instance.d( + "getEthTransactionNonces($txns)", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -291,9 +303,14 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getEthTokenTransactionsByTxids($txids): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getEthTokenTransactionsByTxids()", + error: e, + ); + Logging.instance.d( + "getEthTokenTransactionsByTxids($txids)", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -352,9 +369,14 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getTokenTransactions($address, $tokenContractAddress): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getTokenTransactions()", + error: e, + ); + Logging.instance.d( + "getTokenTransactions($address, $tokenContractAddress)", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -478,9 +500,10 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getWalletTokenBalance(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getWalletTokenBalance()", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -529,9 +552,10 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getAddressNonce(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAddressNonce()", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -585,9 +609,10 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getGasOracle(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getGasOracle()", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -643,17 +668,15 @@ abstract class EthereumAPI { if (json["data"] is List) { if ((json["data"] as List).isEmpty) { if (autoNameOnEmpty) { - Logging.instance.log( + Logging.instance.d( "getTokenByContractAddress(): Adding token data to server", - level: LogLevel.Debug, ); // this will add the missing data to server await _addContractInfoToServer(contractAddress); - Logging.instance.log( + Logging.instance.d( "getTokenByContractAddress(): Adding to server threw so now" "we try a normal fetch again", - level: LogLevel.Debug, ); // now try again @@ -709,9 +732,10 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getTokenByContractAddress(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getTokenByContractAddress()", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -753,9 +777,10 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getTokenAbi($name, $contractAddress): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getTokenAbi($name, $contractAddress)", + error: e, + stackTrace: s, ); return EthereumResponse( null, @@ -798,9 +823,10 @@ abstract class EthereumAPI { e, ); } catch (e, s) { - Logging.instance.log( - "getProxyTokenImplementationAddress($contractAddress) : $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getProxyTokenImplementationAddress($contractAddress)", + error: e, + stackTrace: s, ); return EthereumResponse( null, diff --git a/lib/services/event_bus/events/global/balance_refreshed_event.dart b/lib/services/event_bus/events/global/balance_refreshed_event.dart index 5b7a5028a..90e81a0e5 100644 --- a/lib/services/event_bus/events/global/balance_refreshed_event.dart +++ b/lib/services/event_bus/events/global/balance_refreshed_event.dart @@ -14,9 +14,6 @@ class BalanceRefreshedEvent { final String walletId; BalanceRefreshedEvent(this.walletId) { - Logging.instance.log( - "BalanceRefreshedEvent fired on $walletId", - level: LogLevel.Info, - ); + Logging.instance.d("BalanceRefreshedEvent fired on $walletId"); } } diff --git a/lib/services/event_bus/events/global/blocks_remaining_event.dart b/lib/services/event_bus/events/global/blocks_remaining_event.dart index 008d39440..ec8b968f0 100644 --- a/lib/services/event_bus/events/global/blocks_remaining_event.dart +++ b/lib/services/event_bus/events/global/blocks_remaining_event.dart @@ -15,9 +15,7 @@ class BlocksRemainingEvent { String walletId; BlocksRemainingEvent(this.blocksRemaining, this.walletId) { - Logging.instance.log( - "RefreshPercentChangedEvent fired on $walletId with blocks remaining = $blocksRemaining", - level: LogLevel.Info, - ); + Logging.instance.d( + "RefreshPercentChangedEvent fired on $walletId with blocks remaining = $blocksRemaining"); } } diff --git a/lib/services/event_bus/events/global/node_connection_status_changed_event.dart b/lib/services/event_bus/events/global/node_connection_status_changed_event.dart index 7d3ca2fca..202c2287d 100644 --- a/lib/services/event_bus/events/global/node_connection_status_changed_event.dart +++ b/lib/services/event_bus/events/global/node_connection_status_changed_event.dart @@ -19,9 +19,8 @@ class NodeConnectionStatusChangedEvent { CryptoCurrency coin; NodeConnectionStatusChangedEvent(this.newStatus, this.walletId, this.coin) { - Logging.instance.log( + Logging.instance.d( "NodeConnectionStatusChangedEvent fired in $walletId with arg newStatus = $newStatus", - level: LogLevel.Info, ); } } diff --git a/lib/services/event_bus/events/global/refresh_percent_changed_event.dart b/lib/services/event_bus/events/global/refresh_percent_changed_event.dart index 7429b828e..2bd9ffe27 100644 --- a/lib/services/event_bus/events/global/refresh_percent_changed_event.dart +++ b/lib/services/event_bus/events/global/refresh_percent_changed_event.dart @@ -15,9 +15,7 @@ class RefreshPercentChangedEvent { String walletId; RefreshPercentChangedEvent(this.percent, this.walletId) { - Logging.instance.log( - "RefreshPercentChangedEvent fired on $walletId with percent (range of 0.0-1.0)= $percent", - level: LogLevel.Info, - ); + Logging.instance.d( + "RefreshPercentChangedEvent fired on $walletId with percent (range of 0.0-1.0)= $percent"); } } diff --git a/lib/services/event_bus/events/global/tor_connection_status_changed_event.dart b/lib/services/event_bus/events/global/tor_connection_status_changed_event.dart index 9087d37e2..6d7818c32 100644 --- a/lib/services/event_bus/events/global/tor_connection_status_changed_event.dart +++ b/lib/services/event_bus/events/global/tor_connection_status_changed_event.dart @@ -16,9 +16,7 @@ class TorConnectionStatusChangedEvent { String message = ""; TorConnectionStatusChangedEvent(this.newStatus, this.message) { - Logging.instance.log( - "TorSyncStatusChangedEvent fired with arg newStatus = $newStatus ($message)", - level: LogLevel.Info, - ); + Logging.instance.d( + "TorSyncStatusChangedEvent fired with arg newStatus = $newStatus ($message)"); } } diff --git a/lib/services/event_bus/events/global/tor_status_changed_event.dart b/lib/services/event_bus/events/global/tor_status_changed_event.dart index 140e01607..55147f0b1 100644 --- a/lib/services/event_bus/events/global/tor_status_changed_event.dart +++ b/lib/services/event_bus/events/global/tor_status_changed_event.dart @@ -20,9 +20,7 @@ class TorPreferenceChangedEvent { required this.status, this.message, }) { - Logging.instance.log( - "TorStatusChangedEvent changed to \"$status\" with message: $message", - level: LogLevel.Warning, - ); + Logging.instance.w( + "TorStatusChangedEvent changed to \"$status\" with message: $message"); } } diff --git a/lib/services/event_bus/events/global/updated_in_background_event.dart b/lib/services/event_bus/events/global/updated_in_background_event.dart index 36a23360f..88089e810 100644 --- a/lib/services/event_bus/events/global/updated_in_background_event.dart +++ b/lib/services/event_bus/events/global/updated_in_background_event.dart @@ -8,8 +8,6 @@ * */ -import 'package:flutter/foundation.dart'; - import '../../../../utilities/logger.dart'; class UpdatedInBackgroundEvent { @@ -17,11 +15,8 @@ class UpdatedInBackgroundEvent { String walletId; UpdatedInBackgroundEvent(this.message, this.walletId) { - if (kDebugMode) { - Logging.instance.log( - "UpdatedInBackgroundEvent fired with message: $message", - level: LogLevel.Info, - ); - } + Logging.instance.d( + "UpdatedInBackgroundEvent fired with message: $message", + ); } } diff --git a/lib/services/event_bus/events/global/wallet_sync_status_changed_event.dart b/lib/services/event_bus/events/global/wallet_sync_status_changed_event.dart index 0f92fb272..0fc07a993 100644 --- a/lib/services/event_bus/events/global/wallet_sync_status_changed_event.dart +++ b/lib/services/event_bus/events/global/wallet_sync_status_changed_event.dart @@ -19,9 +19,7 @@ class WalletSyncStatusChangedEvent { CryptoCurrency coin; WalletSyncStatusChangedEvent(this.newStatus, this.walletId, this.coin) { - Logging.instance.log( - "WalletSyncStatusChangedEvent fired in $walletId with arg newStatus = $newStatus", - level: LogLevel.Info, - ); + Logging.instance.d( + "WalletSyncStatusChangedEvent fired in $walletId with arg newStatus = $newStatus"); } } diff --git a/lib/services/event_bus/events/wallet_added_event.dart b/lib/services/event_bus/events/wallet_added_event.dart index 0407946fc..97b334169 100644 --- a/lib/services/event_bus/events/wallet_added_event.dart +++ b/lib/services/event_bus/events/wallet_added_event.dart @@ -1 +1 @@ -class WalletAddedEvent {} +class WalletsChangedEvent {} diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index 2882f9d4d..d7cf255d5 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -78,8 +78,11 @@ class ChangeNowAPI { }; } } catch (e, s) { - Logging.instance - .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "_makeRequest($uri) threw", + error: e, + stackTrace: s, + ); rethrow; } } @@ -102,8 +105,11 @@ class ChangeNowAPI { return parsed; } catch (e, s) { - Logging.instance - .log("_makeRequestV2($uri) threw: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "_makeRequestV2($uri) threw", + error: e, + stackTrace: s, + ); rethrow; } } @@ -128,14 +134,20 @@ class ChangeNowAPI { final parsed = jsonDecode(data); return parsed; - } catch (_) { - Logging.instance - .log("ChangeNOW api failed to parse: $data", level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e( + "ChangeNOW api failed to parse: $data", + error: e, + stackTrace: s, + ); rethrow; } } catch (e, s) { - Logging.instance - .log("_makePostRequest($uri) threw: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "_makePostRequest($uri) threw", + error: e, + stackTrace: s, + ); rethrow; } } @@ -173,9 +185,10 @@ class ChangeNowAPI { ); return result; } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableCurrencies exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -185,9 +198,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableCurrencies exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -263,9 +277,10 @@ class ChangeNowAPI { ); return result; } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableCurrencies exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -275,9 +290,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableCurrencies exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -381,9 +397,10 @@ class ChangeNowAPI { } } } catch (e, s) { - Logging.instance.log( - "getPairedCurrencies exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getPairedCurrencies exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -394,8 +411,11 @@ class ChangeNowAPI { } return ExchangeResponse(value: currencies); } catch (e, s) { - Logging.instance - .log("getPairedCurrencies exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getPairedCurrencies exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -435,9 +455,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getMinimalExchangeAmount exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getMinimalExchangeAmount exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -478,9 +499,10 @@ class ChangeNowAPI { ), ); } catch (e, s) { - Logging.instance.log( - "getRange exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getRange exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -551,9 +573,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getEstimatedExchangeAmount exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getEstimatedExchangeAmount exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -637,9 +660,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getEstimatedExchangeAmount exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getEstimatedExchangeAmount exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -763,9 +787,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getEstimatedExchangeAmountV2 exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getEstimatedExchangeAmountV2 exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -797,9 +822,10 @@ class ChangeNowAPI { await compute(_parseFixedRateMarketsJson, jsonArray as List); return result; } catch (e, s) { - Logging.instance.log( - "getAvailableFixedRateMarkets exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableFixedRateMarkets exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -809,9 +835,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getAvailableFixedRateMarkets exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableFixedRateMarkets exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -898,9 +925,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "createStandardExchangeTransaction exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "createStandardExchangeTransaction exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -973,9 +1001,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "createFixedRateExchangeTransaction exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "createFixedRateExchangeTransaction exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -1011,8 +1040,11 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance - .log("getTransactionStatus exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getTransactionStatus exception: ", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -1041,9 +1073,10 @@ class ChangeNowAPI { ); return result; } catch (e, s) { - Logging.instance.log( - "getAvailableFloatingRatePairs exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableFloatingRatePairs exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -1053,9 +1086,10 @@ class ChangeNowAPI { ); } } catch (e, s) { - Logging.instance.log( - "getAvailableFloatingRatePairs exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableFloatingRatePairs exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart index 811949d6e..1cfaee23b 100644 --- a/lib/services/exchange/exchange_data_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -145,9 +145,8 @@ class ExchangeDataLoadingService { if (_isar == null) { await initDB(); } - Logging.instance.log( + Logging.instance.d( "ExchangeDataLoadingService.loadAll starting...", - level: LogLevel.Info, ); final start = DateTime.now(); try { @@ -185,16 +184,16 @@ class ExchangeDataLoadingService { // wait for all loading futures to complete await Future.wait(futures); - Logging.instance.log( + Logging.instance.d( "ExchangeDataLoadingService.loadAll finished in ${DateTime.now().difference(start).inSeconds} seconds", - level: LogLevel.Info, ); onLoadingComplete?.call(); await _updateCurrentCacheVersion(cacheVersion); } catch (e, s) { - Logging.instance.log( - "ExchangeDataLoadingService.loadAll failed after ${DateTime.now().difference(start).inSeconds} seconds: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "ExchangeDataLoadingService.loadAll failed after ${DateTime.now().difference(start).inSeconds} seconds: ", + error: e, + stackTrace: s, ); onLoadingError?.call(); } @@ -219,9 +218,8 @@ class ExchangeDataLoadingService { await isar.currencies.putAll(responseCurrencies.value!); }); } else { - Logging.instance.log( + Logging.instance.w( "Failed to load changeNOW currencies: ${responseCurrencies.exception?.message}", - level: LogLevel.Error, ); return; } @@ -353,9 +351,8 @@ class ExchangeDataLoadingService { await isar.currencies.putAll(responseCurrencies.value!); }); } else { - Logging.instance.log( + Logging.instance.w( "loadMajesticBankCurrencies: $responseCurrencies", - level: LogLevel.Warning, ); } } @@ -378,9 +375,8 @@ class ExchangeDataLoadingService { await isar.currencies.putAll(responseCurrencies.value!); }); } else { - Logging.instance.log( + Logging.instance.w( "loadTrocadorCurrencies: $responseCurrencies", - level: LogLevel.Warning, ); } } @@ -403,9 +399,8 @@ class ExchangeDataLoadingService { await isar.currencies.putAll(responseCurrencies.value!); }); } else { - Logging.instance.log( + Logging.instance.w( "loadNanswapCurrencies: $responseCurrencies", - level: LogLevel.Warning, ); } } diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index 0dc46343b..210be8af4 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -11,6 +11,7 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; + import '../../../exceptions/exchange/exchange_exception.dart'; import '../../../exceptions/exchange/majestic_bank/mb_exception.dart'; import '../../../exceptions/exchange/pair_unavailable_exception.dart'; @@ -20,10 +21,10 @@ import '../../../models/exchange/majestic_bank/mb_order_calculation.dart'; import '../../../models/exchange/majestic_bank/mb_order_status.dart'; import '../../../models/exchange/majestic_bank/mb_rate.dart'; import '../../../networking/http.dart'; -import '../exchange_response.dart'; -import '../../tor_service.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/prefs.dart'; +import '../../tor_service.dart'; +import '../exchange_response.dart'; class MajesticBankAPI { static const String scheme = "https"; @@ -60,10 +61,8 @@ class MajesticBankAPI { return parsed; } catch (e, s) { - Logging.instance.log( - "_makeRequest($uri) HTTP:$code threw: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("_makeRequest($uri) HTTP:$code threw: ", error: e, stackTrace: s); rethrow; } } @@ -91,7 +90,11 @@ class MajesticBankAPI { } return ExchangeResponse(value: rates); } catch (e, s) { - Logging.instance.log("getRates exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getRates exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -124,8 +127,11 @@ class MajesticBankAPI { return ExchangeResponse(value: limit); } catch (e, s) { - Logging.instance - .log("getLimits exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getLimits exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -157,8 +163,11 @@ class MajesticBankAPI { return ExchangeResponse(value: limits); } catch (e, s) { - Logging.instance - .log("getLimits exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getLimits exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -226,9 +235,10 @@ class MajesticBankAPI { return ExchangeResponse(value: result); } catch (e, s) { - Logging.instance.log( - "calculateOrder $fromCurrency-$receiveCurrency exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "calculateOrder $fromCurrency-$receiveCurrency exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -274,8 +284,11 @@ class MajesticBankAPI { return ExchangeResponse(value: order); } catch (e, s) { - Logging.instance - .log("createOrder exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "createOrder exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -330,7 +343,7 @@ class MajesticBankAPI { return ExchangeResponse(value: order); } catch (e, s) { Logging.instance - .log("createFixedRateOrder exception: $e\n$s", level: LogLevel.Error); + .e("createFixedRateOrder exception: ", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -377,9 +390,10 @@ class MajesticBankAPI { return ExchangeResponse(value: status); } catch (e, s) { - Logging.instance.log( - "trackOrder exception when trying to parse $json: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "trackOrder exception when trying to parse $json: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( diff --git a/lib/services/exchange/nanswap/api_response_models/n_currency.dart b/lib/services/exchange/nanswap/api_response_models/n_currency.dart index 87f42de12..35b01e87e 100644 --- a/lib/services/exchange/nanswap/api_response_models/n_currency.dart +++ b/lib/services/exchange/nanswap/api_response_models/n_currency.dart @@ -4,7 +4,7 @@ class NCurrency { final String name; final String image; final String network; - final bool hasExternalId; + final bool? hasExternalId; final bool feeLess; NCurrency({ @@ -24,7 +24,7 @@ class NCurrency { name: json['name'] as String, image: json['image'] as String, network: json['network'] as String, - hasExternalId: json['hasExternalId'] as bool, + hasExternalId: json['hasExternalId'] as bool?, feeLess: json['feeless'] as bool, ); } diff --git a/lib/services/exchange/nanswap/nanswap_api.dart b/lib/services/exchange/nanswap/nanswap_api.dart index 41f06f19a..6e2848586 100644 --- a/lib/services/exchange/nanswap/nanswap_api.dart +++ b/lib/services/exchange/nanswap/nanswap_api.dart @@ -33,12 +33,11 @@ class NanswapAPI { try { final response = await _client.get( url: uri, - headers: { - 'Accept': 'application/json', - }, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + headers: {'Accept': 'application/json'}, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); code = response.code; @@ -47,18 +46,16 @@ class NanswapAPI { return parsed; } catch (e, s) { - Logging.instance.log( - "NanswapAPI._makeRequest($uri) HTTP:$code threw: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "NanswapAPI._makeRequest($uri) HTTP:$code threw: ", + error: e, + stackTrace: s, ); rethrow; } } - Future _makePostRequest( - Uri uri, - Map body, - ) async { + Future _makePostRequest(Uri uri, Map body) async { int code = -1; try { final response = await _client.post( @@ -69,9 +66,10 @@ class NanswapAPI { 'Accept': 'application/json', }, body: jsonEncode(body), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); code = response.code; @@ -81,9 +79,10 @@ class NanswapAPI { return parsed; } catch (e, s) { - Logging.instance.log( - "NanswapAPI._makePostRequest($uri) HTTP:$code threw: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "NanswapAPI._makePostRequest($uri) HTTP:$code threw: ", + error: e, + stackTrace: s, ); rethrow; } @@ -115,9 +114,7 @@ class NanswapAPI { // // application/json Future>> getSupportedCurrencies() async { - final uri = _buildUri( - endpoint: "all-currencies", - ); + final uri = _buildUri(endpoint: "all-currencies"); try { final json = await _makeGetRequest(uri); @@ -126,18 +123,15 @@ class NanswapAPI { for (final key in (json as Map).keys) { final _map = json[key] as Map; _map["id"] = key; - result.add( - NCurrency.fromJson( - Map.from(_map), - ), - ); + result.add(NCurrency.fromJson(Map.from(_map))); } return ExchangeResponse(value: result); } catch (e, s) { - Logging.instance.log( - "Nanswap.getSupportedCurrencies() exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Nanswap.getSupportedCurrencies() exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -193,22 +187,20 @@ class NanswapAPI { map["to"] ??= to.toUpperCase(); map["from"] ??= from.toUpperCase(); - return ExchangeResponse( - value: NEstimate.fromJson( - map, - ), - ); - } catch (_) { - Logging.instance.log( + return ExchangeResponse(value: NEstimate.fromJson(map)); + } catch (e, s) { + Logging.instance.e( "Nanswap.getEstimate() response was: $json", - level: LogLevel.Error, + error: e, + stackTrace: s, ); rethrow; } } catch (e, s) { - Logging.instance.log( - "Nanswap.getEstimate() exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Nanswap.getEstimate() exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -265,15 +257,12 @@ class NanswapAPI { map["to"] ??= to.toUpperCase(); map["from"] ??= from.toUpperCase(); - return ExchangeResponse( - value: NEstimate.fromJson( - map, - ), - ); + return ExchangeResponse(value: NEstimate.fromJson(map)); } catch (e, s) { - Logging.instance.log( - "Nanswap.getEstimateReverse() exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Nanswap.getEstimateReverse() exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -306,25 +295,20 @@ class NanswapAPI { }) async { final uri = _buildUri( endpoint: "get-limits", - params: { - "to": to.toUpperCase(), - "from": from.toUpperCase(), - }, + params: {"to": to.toUpperCase(), "from": from.toUpperCase()}, ); try { final json = await _makeGetRequest(uri); return ExchangeResponse( - value: ( - minFrom: json["min"] as num, - maxFrom: json["max"] as num, - ), + value: (minFrom: json["min"] as num, maxFrom: json["max"] as num), ); } catch (e, s) { - Logging.instance.log( - "Nanswap.getOrderLimits() exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Nanswap.getOrderLimits() exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -390,9 +374,7 @@ class NanswapAPI { required String toAddress, String? extraIdOrMemo, }) async { - final uri = _buildUri( - endpoint: "create-order", - ); + final uri = _buildUri(endpoint: "create-order"); final body = { "from": from.toUpperCase(), @@ -410,18 +392,17 @@ class NanswapAPI { try { return ExchangeResponse( - value: NTrade.fromJson( - Map.from(json as Map), - ), + value: NTrade.fromJson(Map.from(json as Map)), ); } catch (_) { debugPrint(json.toString()); rethrow; } } catch (e, s) { - Logging.instance.log( - "Nanswap.createOrder() exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Nanswap.createOrder() exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -481,30 +462,24 @@ class NanswapAPI { // // The order id Future> getOrder({required String id}) async { - final uri = _buildUri( - endpoint: "get-order", - params: { - "id": id, - }, - ); + final uri = _buildUri(endpoint: "get-order", params: {"id": id}); try { final json = await _makeGetRequest(uri); try { return ExchangeResponse( - value: NTrade.fromJson( - Map.from(json as Map), - ), + value: NTrade.fromJson(Map.from(json as Map)), ); } catch (_) { debugPrint(json.toString()); rethrow; } } catch (e, s) { - Logging.instance.log( - "Nanswap.getOrder($id) exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Nanswap.getOrder($id) exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( diff --git a/lib/services/exchange/nanswap/nanswap_exchange.dart b/lib/services/exchange/nanswap/nanswap_exchange.dart index a2de87c33..de89b886e 100644 --- a/lib/services/exchange/nanswap/nanswap_exchange.dart +++ b/lib/services/exchange/nanswap/nanswap_exchange.dart @@ -80,7 +80,6 @@ class NanswapExchange extends Exchange { } final t = response.value!; - print(t); return ExchangeResponse( value: Trade( diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 0653ff7c6..0a532822d 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -59,10 +59,8 @@ class SimpleSwapAPI { return parsed; } catch (e, s) { - Logging.instance.log( - "_makeRequest($uri) HTTP:$code threw: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("_makeRequest($uri) HTTP:$code threw: ", error: e, stackTrace: s); rethrow; } } @@ -88,8 +86,11 @@ class SimpleSwapAPI { throw Exception("response: ${response.body}"); } catch (e, s) { - Logging.instance - .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "_makeRequest($uri) threw", + error: e, + stackTrace: s, + ); rethrow; } } @@ -149,10 +150,8 @@ class SimpleSwapAPI { ); return ExchangeResponse(value: trade, exception: null); } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("getAvailableCurrencies exception: ", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -177,10 +176,8 @@ class SimpleSwapAPI { return await compute(_parseAvailableCurrenciesJson, jsonArray as List); } catch (e, s) { - Logging.instance.log( - "getAvailableCurrencies exception: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("getAvailableCurrencies exception: ", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -212,9 +209,10 @@ class SimpleSwapAPI { return ExchangeResponse(value: currencies); } catch (e, s) { - Logging.instance.log( - "_parseAvailableCurrenciesJson exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "_parseAvailableCurrenciesJson exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -246,8 +244,11 @@ class SimpleSwapAPI { ), ); } catch (e, s) { - Logging.instance - .log("getCurrency exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getCurrency exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -279,8 +280,11 @@ class SimpleSwapAPI { ); return result; } catch (e, s) { - Logging.instance - .log("getAllPairs exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getAllPairs exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -321,9 +325,10 @@ class SimpleSwapAPI { return ExchangeResponse(value: pairs); } catch (e, s) { - Logging.instance.log( - "_parseAvailableCurrenciesJson exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "_parseAvailableCurrenciesJson exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -358,8 +363,11 @@ class SimpleSwapAPI { return ExchangeResponse(value: jsonObject as String); } catch (e, s) { - Logging.instance - .log("getEstimated exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getEstimated exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -415,8 +423,11 @@ class SimpleSwapAPI { return ExchangeResponse(value: trade); } catch (e, s) { - Logging.instance - .log("getExchange exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getExchange exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -454,7 +465,11 @@ class SimpleSwapAPI { ), ); } catch (e, s) { - Logging.instance.log("getRange exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getRange exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -488,9 +503,10 @@ class SimpleSwapAPI { ); return result; } catch (e, s) { - Logging.instance.log( - "getAvailableFixedRateMarkets exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableFixedRateMarkets exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -500,9 +516,10 @@ class SimpleSwapAPI { ); } } catch (e, s) { - Logging.instance.log( - "getAvailableFixedRateMarkets exception: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getAvailableFixedRateMarkets exception: ", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( diff --git a/lib/services/exchange/trocador/response_objects/trocador_quote.dart b/lib/services/exchange/trocador/response_objects/trocador_quote.dart index cb478a25e..74c55a477 100644 --- a/lib/services/exchange/trocador/response_objects/trocador_quote.dart +++ b/lib/services/exchange/trocador/response_objects/trocador_quote.dart @@ -12,6 +12,7 @@ import 'package:decimal/decimal.dart'; class TrocadorQuote { final String provider; + final String? providerLogo; final String kycRating; final int insurance; final bool fixed; @@ -21,6 +22,7 @@ class TrocadorQuote { TrocadorQuote({ required this.provider, + this.providerLogo, required this.kycRating, required this.insurance, required this.fixed, @@ -32,6 +34,7 @@ class TrocadorQuote { factory TrocadorQuote.fromMap(Map map) { return TrocadorQuote( provider: map['provider'] as String, + providerLogo: map['provider_logo'] as String?, kycRating: map['kycrating'] as String, insurance: map['insurance'] as int, // wtf trocador? diff --git a/lib/services/exchange/trocador/trocador_api.dart b/lib/services/exchange/trocador/trocador_api.dart index 9b497a685..9aff6d872 100644 --- a/lib/services/exchange/trocador/trocador_api.dart +++ b/lib/services/exchange/trocador/trocador_api.dart @@ -28,7 +28,7 @@ const kTrocadorApiKey = "8rFqf7QLxX1mUBiNPEMaLUpV2biz6n"; const kTrocadorRefCode = "9eHm9BkQfS"; abstract class TrocadorAPI { - static const String authority = "trocador.app"; + static const String authority = "api.trocador.app"; static const String onionAuthority = "trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion"; @@ -42,8 +42,8 @@ abstract class TrocadorAPI { Map? params, }) { return isOnion - ? Uri.http(onionAuthority, "api/$method", params) - : Uri.https(authority, "api/$method", params); + ? Uri.http(onionAuthority, method, params) + : Uri.https(authority, method, params); } static Future _makeGetRequest(Uri uri) async { @@ -52,7 +52,10 @@ abstract class TrocadorAPI { debugPrint("URI: $uri"); final response = await client.get( url: uri, - headers: {'Content-Type': 'application/json'}, + headers: { + "Content-Type": "application/json", + "API-KEY": kTrocadorApiKey, + }, proxyInfo: Prefs.instance.useTor ? TorService.sharedInstance.getProxyInfo() : null, @@ -67,10 +70,8 @@ abstract class TrocadorAPI { return json; } catch (e, s) { - Logging.instance.log( - "_makeRequest($uri) HTTP:$code threw: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("_makeRequest($uri) HTTP:$code threw: ", error: e, stackTrace: s); rethrow; } } @@ -83,7 +84,6 @@ abstract class TrocadorAPI { isOnion: isOnion, method: "coins", params: { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, }, ); @@ -104,7 +104,11 @@ abstract class TrocadorAPI { throw Exception("unexpected json: $json"); } } catch (e, s) { - Logging.instance.log("getCoins exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getCoins exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -123,7 +127,6 @@ abstract class TrocadorAPI { isOnion: isOnion, method: "trade", params: { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "id": tradeId, }, @@ -135,7 +138,11 @@ abstract class TrocadorAPI { return ExchangeResponse(value: TrocadorTrade.fromMap(map)); } catch (e, s) { - Logging.instance.log("getTrade exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getTrade exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -155,7 +162,6 @@ abstract class TrocadorAPI { required String fromAmount, }) async { final params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, @@ -180,7 +186,6 @@ abstract class TrocadorAPI { required String toAmount, }) async { final params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, @@ -211,8 +216,11 @@ abstract class TrocadorAPI { return ExchangeResponse(value: TrocadorRate.fromMap(map)); } catch (e, s) { - Logging.instance - .log("getNewRate exception: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "getNewRate exception", + error: e, + stackTrace: s, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), @@ -239,7 +247,6 @@ abstract class TrocadorAPI { required bool isFixedRate, }) async { final Map params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, @@ -280,7 +287,6 @@ abstract class TrocadorAPI { required bool isFixedRate, }) async { final params = { - "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, @@ -329,9 +335,10 @@ abstract class TrocadorAPI { "This trade couldn't be completed. Please select another provider."; } - Logging.instance.log( - "_getNewTrade failed to parse response: $json\n$e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "_getNewTrade failed to parse response: $json\n", + error: e, + stackTrace: s, ); return ExchangeResponse( exception: ExchangeException( @@ -341,10 +348,7 @@ abstract class TrocadorAPI { ); } } catch (e, s) { - Logging.instance.log( - "_getNewTrade exception: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("_getNewTrade exception: ", error: e, stackTrace: s); return ExchangeResponse( exception: ExchangeException( e.toString(), diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart index 701bca7cb..cf02cc206 100644 --- a/lib/services/exchange/trocador/trocador_exchange.dart +++ b/lib/services/exchange/trocador/trocador_exchange.dart @@ -236,6 +236,7 @@ class TrocadorExchange extends Exchange { fixedRate: quote.fixed, reversed: reversed, exchangeProvider: quote.provider, + exchangeProviderLogo: quote.providerLogo, rateId: response.value!.tradeId, kycRating: quote.kycRating, ), @@ -256,6 +257,7 @@ class TrocadorExchange extends Exchange { fixedRate: quote.fixed, reversed: reversed, exchangeProvider: quote.provider, + exchangeProviderLogo: quote.providerLogo, rateId: response.value!.tradeId, kycRating: quote.kycRating, ), diff --git a/lib/services/frost.dart b/lib/services/frost.dart index a3e2e030f..76c0b5db9 100644 --- a/lib/services/frost.dart +++ b/lib/services/frost.dart @@ -35,10 +35,7 @@ abstract class Frost { return participants; } catch (e, s) { - Logging.instance.log( - "getParticipants failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("getParticipants failed: ", error: e, stackTrace: s); rethrow; } } @@ -48,10 +45,7 @@ abstract class Frost { decodeMultisigConfig(multisigConfig: encodedConfig); return true; } catch (e, s) { - Logging.instance.log( - "validateEncodedMultisigConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("validateEncodedMultisigConfig failed: ", error: e, stackTrace: s); return false; } } @@ -66,10 +60,7 @@ abstract class Frost { return threshold; } catch (e, s) { - Logging.instance.log( - "getThreshold failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("getThreshold failed: ", error: e, stackTrace: s); rethrow; } } @@ -144,10 +135,7 @@ abstract class Frost { inputs: outputs, ); } catch (e, s) { - Logging.instance.log( - "extractDataFromSignConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("extractDataFromSignConfig failed: ", error: e, stackTrace: s); rethrow; } } @@ -168,10 +156,7 @@ abstract class Frost { return config; } catch (e, s) { - Logging.instance.log( - "createMultisigConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("createMultisigConfig failed: ", error: e, stackTrace: s); rethrow; } } @@ -204,10 +189,7 @@ abstract class Frost { secretShareMachineWrapperPtr: machinePtr, ); } catch (e, s) { - Logging.instance.log( - "startKeyGeneration failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("startKeyGeneration failed: ", error: e, stackTrace: s); rethrow; } } @@ -234,10 +216,7 @@ abstract class Frost { return (share: share, secretSharesResPtr: secretSharesResPtr); } catch (e, s) { - Logging.instance.log( - "generateSecretShares failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("generateSecretShares failed: ", error: e, stackTrace: s); rethrow; } } @@ -275,10 +254,7 @@ abstract class Frost { serializedKeys: serializedKeys, ); } catch (e, s) { - Logging.instance.log( - "completeKeyGeneration failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("completeKeyGeneration failed: ", error: e, stackTrace: s); rethrow; } } @@ -322,10 +298,7 @@ abstract class Frost { return signConfig; } catch (e, s) { - Logging.instance.log( - "createSignConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("createSignConfig failed: ", error: e, stackTrace: s); rethrow; } } @@ -352,10 +325,7 @@ abstract class Frost { machinePtr: attemptSignRes.ref.machine, ); } catch (e, s) { - Logging.instance.log( - "attemptSignConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("attemptSignConfig failed: ", error: e, stackTrace: s); rethrow; } } @@ -378,10 +348,7 @@ abstract class Frost { machinePtr: continueSignRes.ref.machine, ); } catch (e, s) { - Logging.instance.log( - "continueSigning failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("continueSigning failed: ", error: e, stackTrace: s); rethrow; } } @@ -398,10 +365,7 @@ abstract class Frost { return rawTransaction; } catch (e, s) { - Logging.instance.log( - "completeSigning failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("completeSigning failed: ", error: e, stackTrace: s); rethrow; } } @@ -419,10 +383,7 @@ abstract class Frost { ); return configPtr; } catch (e, s) { - Logging.instance.log( - "decodedSignConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("decodedSignConfig failed: ", error: e, stackTrace: s); rethrow; } } @@ -443,10 +404,7 @@ abstract class Frost { return config; } catch (e, s) { - Logging.instance.log( - "createResharerConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("createResharerConfig failed: ", error: e, stackTrace: s); rethrow; } } @@ -469,10 +427,7 @@ abstract class Frost { machine: result.machine, ); } catch (e, s) { - Logging.instance.log( - "beginResharer failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("beginResharer failed: ", error: e, stackTrace: s); rethrow; } } @@ -498,10 +453,7 @@ abstract class Frost { prior: result.machine, ); } catch (e, s) { - Logging.instance.log( - "beginReshared failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("beginReshared failed: ", error: e, stackTrace: s); rethrow; } } @@ -518,10 +470,7 @@ abstract class Frost { ); return result; } catch (e, s) { - Logging.instance.log( - "finishResharer failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("finishResharer failed: ", error: e, stackTrace: s); rethrow; } } @@ -542,10 +491,7 @@ abstract class Frost { ); return result; } catch (e, s) { - Logging.instance.log( - "finishReshared failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("finishReshared failed: ", error: e, stackTrace: s); rethrow; } } @@ -558,10 +504,7 @@ abstract class Frost { return config; } catch (e, s) { - Logging.instance.log( - "decodedResharerConfig failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("decodedResharerConfig failed: ", error: e, stackTrace: s); rethrow; } } @@ -631,10 +574,7 @@ abstract class Frost { newParticipants: newParticipants, ); } catch (e, s) { - Logging.instance.log( - "extractResharerConfigData failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("extractResharerConfigData failed: ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/services/fusion_tor_service.dart b/lib/services/fusion_tor_service.dart index 0d5b29f59..caa0ec2ee 100644 --- a/lib/services/fusion_tor_service.dart +++ b/lib/services/fusion_tor_service.dart @@ -1,8 +1,9 @@ import 'dart:io'; -import '../utilities/logger.dart'; import 'package:tor_ffi_plugin/tor_ffi_plugin.dart'; +import '../utilities/logger.dart'; + class FusionTorService { Tor? _tor; String? _torDataDirPath; @@ -61,10 +62,7 @@ class FusionTorService { try { await _tor!.start(torDataDirPath: _torDataDirPath!); } catch (e, s) { - Logging.instance.log( - "FusionTorService.start failed: $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("FusionTorService.start failed: ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/services/monkey_service.dart b/lib/services/monkey_service.dart index f921135c1..0ae745d34 100644 --- a/lib/services/monkey_service.dart +++ b/lib/services/monkey_service.dart @@ -1,10 +1,11 @@ import 'dart:typed_data'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../networking/http.dart'; -import 'tor_service.dart'; import '../utilities/logger.dart'; import '../utilities/prefs.dart'; +import 'tor_service.dart'; final pMonKeyService = Provider((ref) => MonKeyService()); @@ -38,10 +39,7 @@ class MonKeyService { ); } } catch (e, s) { - Logging.instance.log( - "Failed fetchMonKey($address): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Failed fetchMonKey($address): ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/services/node_service.dart b/lib/services/node_service.dart index e55e2218b..35f595050 100644 --- a/lib/services/node_service.dart +++ b/lib/services/node_service.dart @@ -32,6 +32,49 @@ class NodeService extends ChangeNotifier { }); Future updateDefaults() async { + // hack + if (AppConfig.coins.where((e) => e.identifier == "firo").isNotEmpty) { + final others = [ + "electrumx01.firo.org", + "electrumx02.firo.org", + "electrumx03.firo.org", + "electrumx.firo.org", + ]; + const port = 50002; + const idPrefix = "not_a_real_default_but_temp"; + + for (final host in others) { + final _id = "${idPrefix}_$host"; + + NodeModel? node = DB.instance.get( + boxName: DB.boxNameNodeModels, + key: _id, + ); + + if (node == null) { + node = NodeModel( + host: host, + port: port, + name: host, + id: _id, + useSSL: true, + enabled: true, + coinName: "firo", + isFailover: true, + isDown: false, + torEnabled: true, + clearnetEnabled: true, + ); + + await DB.instance.put( + boxName: DB.boxNameNodeModels, + key: _id, + value: node, + ); + } + } + } + for (final defaultNode in AppConfig.coins.map( (e) => e.defaultNode, )) { @@ -61,6 +104,7 @@ class NodeService extends ChangeNotifier { trusted: savedNode.trusted, torEnabled: savedNode.torEnabled, clearnetEnabled: savedNode.clearnetEnabled, + loginName: savedNode.loginName, ), ); } @@ -79,6 +123,7 @@ class NodeService extends ChangeNotifier { trusted: primaryNode.trusted, torEnabled: primaryNode.torEnabled, clearnetEnabled: primaryNode.clearnetEnabled, + loginName: primaryNode.loginName, ), ); } @@ -170,6 +215,8 @@ class NodeService extends ChangeNotifier { key: "${node.id}_nodePW", value: password, ); + } else { + await secureStorageInterface.delete(key: "${node.id}_nodePW"); } if (shouldNotifyListeners) { notifyListeners(); @@ -197,7 +244,11 @@ class NodeService extends ChangeNotifier { await DB.instance.put( boxName: DB.boxNameNodeModels, key: model.id, - value: model.copyWith(enabled: enabled), + value: model.copyWith( + enabled: enabled, + loginName: model.loginName, + trusted: model.trusted, + ), ); if (shouldNotifyListeners) { notifyListeners(); @@ -242,7 +293,7 @@ class NodeService extends ChangeNotifier { final json = jsonDecode(response.body) as Map; final result = jsonDecode(json['result'] as String); final map = jsonDecode(result as String); - Logging.instance.log(map, level: LogLevel.Info); + Logging.instance.d(map); for (final coin in AppConfig.coins) { final nodeList = List>.from( @@ -271,14 +322,19 @@ class NodeService extends ChangeNotifier { useSSL: node.useSSL, coinName: node.coinName, isDown: node.isDown, + loginName: node.loginName, + trusted: node.trusted, ); } await add(node, null, false); } } } catch (e, s) { - Logging.instance - .log("updateCommunityNodes() failed: $e\n$s", level: LogLevel.Error); + Logging.instance.e( + "updateCommunityNodes() failed", + error: e, + stackTrace: s, + ); } } } diff --git a/lib/services/notifications_service.dart b/lib/services/notifications_service.dart index 065285257..93e09aceb 100644 --- a/lib/services/notifications_service.dart +++ b/lib/services/notifications_service.dart @@ -105,8 +105,7 @@ class NotificationsService extends ChangeNotifier { stopCheckingWatchedTransactions(); _timer = Timer.periodic(notificationRefreshInterval, (_) { - Logging.instance - .log("Periodic notifications update check", level: LogLevel.Info); + Logging.instance.d("Periodic notifications update check"); if (prefs.externalCalls) { _checkTrades(); } @@ -222,7 +221,7 @@ class NotificationsService extends ChangeNotifier { } on NoSuchTransactionException catch (e, s) { await _deleteWatchedTxNotification(notification); } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("$e $s", error: e, stackTrace: s); } } } diff --git a/lib/services/price.dart b/lib/services/price.dart index 5a97e43cb..801e720e2 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -20,6 +20,7 @@ import '../db/hive/db.dart'; import '../networking/http.dart'; import '../utilities/logger.dart'; import '../utilities/prefs.dart'; +import '../utilities/util.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; import 'tor_service.dart'; @@ -47,6 +48,7 @@ class PriceAPI { Namecoin: "namecoin", Nano: "nano", Banano: "banano", + Xelis: "xelis", }; static const refreshInterval = 60; @@ -128,11 +130,10 @@ class PriceAPI { } final externalCalls = Prefs.instance.externalCalls; - if ((!Logger.isTestEnv && !externalCalls) || + if ((!Util.isTestEnv && !externalCalls) || !(await Prefs.instance.isExternalCallsSet())) { - Logging.instance.log( + Logging.instance.i( "User does not want to use external calls", - level: LogLevel.Info, ); return _cachedPrices; } @@ -171,10 +172,8 @@ class PriceAPI { return _cachedPrices; } catch (e, s) { - Logging.instance.log( - "getPricesAnd24hChange($baseCurrency): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("getPricesAnd24hChange($baseCurrency): ", error: e, stackTrace: s); // return previous cached values return _cachedPrices; } @@ -184,11 +183,10 @@ class PriceAPI { final externalCalls = Prefs.instance.externalCalls; final HTTP client = HTTP(); - if ((!Logger.isTestEnv && !externalCalls) || + if ((!Util.isTestEnv && !externalCalls) || !(await Prefs.instance.isExternalCallsSet())) { - Logging.instance.log( + Logging.instance.i( "User does not want to use external calls", - level: LogLevel.Info, ); return null; } @@ -207,9 +205,10 @@ class PriceAPI { final json = jsonDecode(response.body) as List; return List.from(json); } catch (e, s) { - Logging.instance.log( - "availableBaseCurrencies() using $uriString: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "availableBaseCurrencies() using $uriString: ", + error: e, + stackTrace: s, ); return null; } @@ -226,11 +225,10 @@ class PriceAPI { contractAddresses.isEmpty) return tokenPrices; final externalCalls = Prefs.instance.externalCalls; - if ((!Logger.isTestEnv && !externalCalls) || + if ((!Util.isTestEnv && !externalCalls) || !(await Prefs.instance.isExternalCallsSet())) { - Logging.instance.log( + Logging.instance.i( "User does not want to use external calls", - level: LogLevel.Info, ); return tokenPrices; } @@ -272,9 +270,10 @@ class PriceAPI { return tokenPrices; } catch (e, s) { - Logging.instance.log( - "getPricesAnd24hChangeForEthTokens($baseCurrency,$contractAddresses): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "getPricesAnd24hChangeForEthTokens($baseCurrency,$contractAddresses): ", + error: e, + stackTrace: s, ); // return previous cached values return tokenPrices; diff --git a/lib/services/tor_service.dart b/lib/services/tor_service.dart index 6904f8789..542032df7 100644 --- a/lib/services/tor_service.dart +++ b/lib/services/tor_service.dart @@ -85,10 +85,7 @@ class TorService { // Complete the future. return; } catch (e, s) { - Logging.instance.log( - "TorService.start failed: $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("TorService.start failed: ", error: e, stackTrace: s); // _enabled should already be false // Fire a TorConnectionStatusChangedEvent on the event bus. diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index c606a75cf..07856e6ee 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -62,7 +62,9 @@ class Wallets { ); } _wallets[wallet.walletId] = wallet; - GlobalEventBus.instance.fire(WalletAddedEvent()); + if (AppConfig.isSingleCoinApp) { + GlobalEventBus.instance.fire(WalletsChangedEvent()); + } } Future deleteWallet( @@ -70,10 +72,7 @@ class Wallets { SecureStorageInterface secureStorage, ) async { final walletId = info.walletId; - Logging.instance.log( - "deleteWallet called with walletId=$walletId", - level: LogLevel.Warning, - ); + Logging.instance.d("deleteWallet called with walletId=$walletId"); final wallet = _wallets[walletId]; _wallets.remove(walletId); @@ -94,24 +93,21 @@ class Wallets { type: lib_monero_compat.WalletType.wownero, appRoot: await StackFileSystem.applicationRootDirectory(), ); - Logging.instance - .log("monero wallet: $walletId deleted", level: LogLevel.Info); + Logging.instance.d("monero wallet: $walletId deleted"); } else if (info.coin is Monero) { await lib_monero_compat.deleteWalletFiles( name: walletId, type: lib_monero_compat.WalletType.monero, appRoot: await StackFileSystem.applicationRootDirectory(), ); - Logging.instance - .log("monero wallet: $walletId deleted", level: LogLevel.Info); + Logging.instance.d("monero wallet: $walletId deleted"); } else if (info.coin is Epiccash) { final deleteResult = await deleteEpicWallet( walletId: walletId, secureStore: secureStorage, ); - Logging.instance.log( + Logging.instance.d( "epic wallet: $walletId deleted with result: $deleteResult", - level: LogLevel.Info, ); } @@ -150,6 +146,10 @@ class Wallets { await mainDB.isar.writeTxn(() async { await mainDB.isar.walletInfo.deleteByWalletId(walletId); }); + + if (AppConfig.isSingleCoinApp) { + GlobalEventBus.instance.fire(WalletsChangedEvent()); + } } Future load(Prefs prefs, MainDB mainDB) async { @@ -204,11 +204,9 @@ class Wallets { for (final walletInfo in walletInfoList) { try { final isVerified = await walletInfo.isMnemonicVerified(mainDB.isar); - Logging.instance.log( - "LOADING WALLET: ${walletInfo.name}:${walletInfo.walletId} " - "IS VERIFIED: $isVerified", - level: LogLevel.Info, - ); + Logging.instance + .d("LOADING WALLET: ${walletInfo.name}:${walletInfo.walletId} " + "IS VERIFIED: $isVerified"); if (isVerified) { // TODO: integrate this into the new wallets somehow? @@ -246,7 +244,7 @@ class Wallets { // await walletsService.deleteWallet(walletInfo.name, false); } } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Fatal); + Logging.instance.w("", error: e, stackTrace: s); continue; } } @@ -311,11 +309,9 @@ class Wallets { for (final walletInfo in walletInfoList) { try { final isVerified = await walletInfo.isMnemonicVerified(mainDB.isar); - Logging.instance.log( - "LOADING WALLET: ${walletInfo.name}:${walletInfo.walletId} " - "IS VERIFIED: $isVerified", - level: LogLevel.Info, - ); + Logging.instance + .d("LOADING WALLET: ${walletInfo.name}:${walletInfo.walletId} " + "IS VERIFIED: $isVerified"); if (isVerified) { // TODO: integrate this into the new wallets somehow? @@ -349,7 +345,11 @@ class Wallets { deleteFutures.add(_deleteWallet(walletInfo.walletId)); } } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Fatal); + Logging.instance.w( + "$e $s", + error: e, + stackTrace: s, + ); continue; } } @@ -441,11 +441,9 @@ class Wallets { for (final walletInfo in walletInfoList) { try { final isVerified = await walletInfo.isMnemonicVerified(mainDB.isar); - Logging.instance.log( - "LOADING WALLET: ${walletInfo.name}:${walletInfo.walletId} " - "IS VERIFIED: $isVerified", - level: LogLevel.Info, - ); + Logging.instance + .d("LOADING WALLET: ${walletInfo.name}:${walletInfo.walletId} " + "IS VERIFIED: $isVerified"); if (isVerified) { // TODO: integrate this into the new wallets somehow? @@ -479,7 +477,11 @@ class Wallets { deleteFutures.add(_deleteWallet(walletInfo.walletId)); } } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Fatal); + Logging.instance.w( + "$e $s", + error: e, + stackTrace: s, + ); continue; } } @@ -503,9 +505,8 @@ class Wallets { Future _refreshFutures(List idsToRefresh) async { final start = DateTime.now(); - Logging.instance.log( + Logging.instance.d( "Initial refresh start: ${start.toUtc()}", - level: LogLevel.Warning, ); const groupCount = 3; for (int i = 0; i < idsToRefresh.length; i += groupCount) { @@ -520,10 +521,8 @@ class Wallets { } await Future.wait(futures); } - Logging.instance.log( - "Initial refresh duration: ${DateTime.now().difference(start)}", - level: LogLevel.Warning, - ); + Logging.instance + .d("Initial refresh duration: ${DateTime.now().difference(start)}"); } if (walletInitFutures.isNotEmpty && walletsToInitLinearly.isNotEmpty) { @@ -574,9 +573,8 @@ class Wallets { for (final wallet in wallets) { final isVerified = await wallet.info.isMnemonicVerified(mainDB.isar); - Logging.instance.log( + Logging.instance.d( "LOADING WALLET: ${wallet.info.name}:${wallet.walletId} IS VERIFIED: $isVerified", - level: LogLevel.Info, ); if (isVerified) { diff --git a/lib/services/wallets_service.dart b/lib/services/wallets_service.dart index 744b327d1..25d768fe8 100644 --- a/lib/services/wallets_service.dart +++ b/lib/services/wallets_service.dart @@ -11,6 +11,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; + import '../app_config.dart'; import '../db/hive/db.dart'; import '../utilities/logger.dart'; @@ -74,9 +75,8 @@ class WalletsService extends ChangeNotifier { final names = DB.instance .get(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?; if (names == null) { - Logging.instance.log( + Logging.instance.e( "Fetched wallet 'names' returned null. Setting initializing 'names'", - level: LogLevel.Info, ); await DB.instance.put( boxName: DB.boxNameAllWalletsData, @@ -85,7 +85,7 @@ class WalletsService extends ChangeNotifier { ); return {}; } - Logging.instance.log("Fetched wallet names: $names", level: LogLevel.Info); + Logging.instance.d("Fetched wallet names: $names"); final mapped = Map.from(names); mapped.removeWhere((name, dyn) { final jsonObject = Map.from(dyn as Map); @@ -93,9 +93,10 @@ class WalletsService extends ChangeNotifier { AppConfig.getCryptoCurrencyFor(jsonObject["coin"] as String); return false; } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Error, ${jsonObject["coin"]} does not exist", - level: LogLevel.Error, + error: e, + stackTrace: s, ); return true; } diff --git a/lib/themes/theme_service.dart b/lib/themes/theme_service.dart index 19050029c..542c6375e 100644 --- a/lib/themes/theme_service.dart +++ b/lib/themes/theme_service.dart @@ -31,9 +31,7 @@ final pThemeService = Provider((ref) { }); class ThemeService { - // dumb quick conditional based on name. Should really be done better - static const _currentDefaultThemeVersion = - AppConfig.appName == "Campfire" ? 17 : 16; + static const _currentDefaultThemeVersion = 17; ThemeService._(); static ThemeService? _instance; static ThemeService get instance => _instance ??= ThemeService._(); @@ -61,9 +59,7 @@ class ThemeService { final jsonString = utf8.decode(themeJsonFiles.first.content as List); final json = jsonDecode(jsonString) as Map; - final theme = StackTheme.fromJson( - json: Map.from(json), - ); + final theme = StackTheme.fromJson(json: Map.from(json)); try { theme.assets; @@ -77,9 +73,9 @@ class ThemeService { if (file.isFile) { // TODO more sanitation? if (file.name.contains("..")) { - Logging.instance.log( + Logging.instance.e( "Bad theme asset file path: ${file.name}", - level: LogLevel.Error, + stackTrace: StackTrace.current, ); } else { final os = OutputFileStream("$assetsPath/${file.name}"); @@ -96,11 +92,12 @@ class ThemeService { Future remove({required String themeId}) async { final themesDir = StackFileSystem.themesDir!; - final isarId = await db.isar.stackThemes - .where() - .themeIdEqualTo(themeId) - .idProperty() - .findFirst(); + final isarId = + await db.isar.stackThemes + .where() + .themeIdEqualTo(themeId) + .idProperty() + .findFirst(); if (isarId != null) { await db.isar.writeTxn(() async { await db.isar.stackThemes.delete(isarId); @@ -110,9 +107,9 @@ class ThemeService { await dir.delete(recursive: true); } } else { - Logging.instance.log( + Logging.instance.w( "Failed to delete theme $themeId", - level: LogLevel.Warning, + stackTrace: StackTrace.current, ); } } @@ -142,18 +139,12 @@ class ThemeService { } Future _updateDefaultTheme(String name) async { - Logging.instance.log( - "Updating default $name theme...", - level: LogLevel.Info, - ); + Logging.instance.w("Updating default $name theme..."); final zip = await rootBundle.load("assets/default_themes/$name.zip"); await ThemeService.instance.install( themeArchiveData: zip.buffer.asUint8List(), ); - Logging.instance.log( - "Updating default $name theme... finished", - level: LogLevel.Info, - ); + Logging.instance.w("Updating default $name theme... finished"); } // TODO more thorough check/verification of theme @@ -174,9 +165,9 @@ class ThemeService { await Directory("${themesDir.path}/$themeId/assets").exists(); if (!jsonFileExists || !assetsDirExists) { - Logging.instance.log( + Logging.instance.w( "Theme $themeId found in DB but is missing files", - level: LogLevel.Warning, + stackTrace: StackTrace.current, ); } @@ -190,23 +181,26 @@ class ThemeService { try { final response = await client.get( url: Uri.parse("$baseServerUrl/themes"), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); final jsonList = jsonDecode(response.body) as List; - final result = List>.from(jsonList) - .map((e) => StackThemeMetaData.fromMap(e)) - .where((e) => e.id != "light" && e.id != "dark") - .toList(); + final result = + List>.from(jsonList) + .map((e) => StackThemeMetaData.fromMap(e)) + .where((e) => e.id != "light" && e.id != "dark") + .toList(); return result; } catch (e, s) { - Logging.instance.log( - "Failed to fetch themes list: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to fetch themes list: ", + error: e, + stackTrace: s, ); rethrow; } @@ -218,9 +212,10 @@ class ThemeService { try { final response = await client.get( url: Uri.parse("$baseServerUrl/theme/${themeMetaData.id}"), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); final bytes = Uint8List.fromList(response.bodyBytes); @@ -236,9 +231,10 @@ class ThemeService { ); } } catch (e, s) { - Logging.instance.log( - "Failed to fetch themes list: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to fetch themes list: ", + error: e, + stackTrace: s, ); rethrow; } @@ -279,9 +275,10 @@ class StackThemeMetaData { previewImageUrl: map["previewImageUrl"] as String, ); } catch (e, s) { - Logging.instance.log( - "Failed to create instance of StackThemeMetaData using $map: \n$e\n$s", - level: LogLevel.Fatal, + Logging.instance.f( + "Failed to create instance of StackThemeMetaData using $map", + error: e, + stackTrace: s, ); rethrow; } diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 2e6b241b8..171dc09b2 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -72,8 +72,12 @@ class AddressUtils { result["tx_description"] = Uri.decodeComponent(u.fragment); } } - } catch (e) { - print("Exception caught in parseUri($uri): $e"); + } catch (e, s) { + Logging.instance.d( + "Exception caught in parseUri($uri): $e", + error: e, + stackTrace: s, + ); } return result; } @@ -151,7 +155,7 @@ class AddressUtils { additionalParams: filteredParams, ); } catch (e, s) { - logging?.log("$e\n$s", level: LogLevel.Error); + logging?.e("", error: e, stackTrace: s); return null; } } @@ -164,7 +168,14 @@ class AddressUtils { ) { // Filter unrecognized parameters. final filteredParams = _filterParams(params); - String uriString = "$scheme:$address"; + String uriString; + + // cashaddrs strike again + if (address.startsWith("$scheme:")) { + uriString = address; + } else { + uriString = "$scheme:$address"; + } if (scheme.toLowerCase() == "monero") { // Handle Monero-specific formatting. @@ -192,8 +203,12 @@ class AddressUtils { Map result = {}; try { result = Map.from(jsonDecode(data) as Map); - } catch (e) { - print("Exception caught in parseQRSeedData($data): $e"); + } catch (e, s) { + Logging.instance.d( + "Exception caught in parseQRSeedData($data)", + error: e, + stackTrace: s, + ); } return result; } diff --git a/lib/utilities/biometrics.dart b/lib/utilities/biometrics.dart index cf9d8c27d..9a30d5fe3 100644 --- a/lib/utilities/biometrics.dart +++ b/lib/utilities/biometrics.dart @@ -27,16 +27,14 @@ class Biometrics { required String title, }) async { if (!(Platform.isIOS || Platform.isAndroid)) { - Logging.instance.log( + Logging.instance.w( "Tried to use Biometrics.authenticate() on a platform that is not Android or iOS! ...returning false.", - level: LogLevel.Error, ); return false; } if (integrationTestFlag) { - Logging.instance.log( + Logging.instance.w( "Tried to use Biometrics.authenticate() during integration testing. Returning false.", - level: LogLevel.Warning, ); return false; } @@ -50,12 +48,12 @@ class Biometrics { // debugPrint("isDeviceSupported: $isDeviceSupported"); if (canCheckBiometrics && isDeviceSupported) { - List availableSystems = + final List availableSystems = await localAuth.getAvailableBiometrics(); - Logging.instance.log( + Logging.instance.w( "Bio availableSystems: $availableSystems", - level: LogLevel.Info, + stackTrace: StackTrace.current, ); //TODO properly handle caught exceptions @@ -78,10 +76,11 @@ class Biometrics { if (didAuthenticate) { return true; } - } catch (e) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.e( "local_auth exception caught in Biometrics.authenticate(), e: $e", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } } diff --git a/lib/utilities/connection_check/electrum_connection_check.dart b/lib/utilities/connection_check/electrum_connection_check.dart index 85ff83511..325cfa059 100644 --- a/lib/utilities/connection_check/electrum_connection_check.dart +++ b/lib/utilities/connection_check/electrum_connection_check.dart @@ -27,9 +27,8 @@ Future checkElectrumServer({ // And the killswitch isn't set... if (!_prefs.torKillSwitch) { // Then we'll just proceed and connect to ElectrumX through clearnet at the bottom of this function. - Logging.instance.log( + Logging.instance.w( "Tor preference set but Tor is not enabled, killswitch not set, connecting to Electrum adapter through clearnet", - level: LogLevel.Warning, ); } else { // ... But if the killswitch is set, then we throw an exception. @@ -61,7 +60,12 @@ Future checkElectrumServer({ .timeout(Duration(seconds: (proxyInfo == null ? 5 : 30))); return true; - } catch (_) { + } catch (e, s) { + Logging.instance.e( + "$e\n$s", + error: e, + stackTrace: s, + ); return false; } } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 6b64d4a73..1c3b908a1 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -35,12 +35,12 @@ abstract class Constants { // // true; // true for development, static const int notificationsMax = 0xFFFFFFFF; - static const Duration networkAliveTimerDuration = Duration(seconds: 10); + static const Duration networkAliveTimerDuration = Duration(seconds: 30); // Enable Logger.print statements static const bool disableLogger = false; - static const int currentDataVersion = 13; + static const int currentDataVersion = 14; static const int rescanV1 = 1; diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart index 3852a8695..39243962b 100644 --- a/lib/utilities/desktop_password_service.dart +++ b/lib/utilities/desktop_password_service.dart @@ -68,10 +68,7 @@ class DPS { await _put(key: _kKeyBlobKey, value: await _handler!.getKeyBlob()); await _updateStoredKeyBlobVersion(kLatestBlobVersion); } catch (e, s) { - Logging.instance.log( - "${_getMessageFromException(e)}\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("${_getMessageFromException(e)}\n$s", error: e, stackTrace: s); rethrow; } } @@ -104,10 +101,7 @@ class DPS { await _updateStoredKeyBlobVersion(kLatestBlobVersion); } } catch (e, s) { - Logging.instance.log( - "${_getMessageFromException(e)}\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("${_getMessageFromException(e)}\n$s", error: e, stackTrace: s); throw Exception(_getMessageFromException(e)); } } @@ -125,10 +119,7 @@ class DPS { // existing passphrase matches key blob return true; } catch (e, s) { - Logging.instance.log( - "${_getMessageFromException(e)}\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("${_getMessageFromException(e)}\n$s", error: e, stackTrace: s,); // password is wrong or some other error return false; } @@ -161,10 +152,7 @@ class DPS { // successfully updated passphrase return true; } catch (e, s) { - Logging.instance.log( - "${_getMessageFromException(e)}\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("${_getMessageFromException(e)}\n$s", error: e, stackTrace: s,); return false; } } @@ -189,10 +177,7 @@ class DPS { box = await DB.instance.hive.openBox(kBoxNameDesktopData); await box.put(key, value); } catch (e, s) { - Logging.instance.log( - "DPS failed put($key): $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("DPS failed put($key): ", error: e, stackTrace: s); } finally { await box?.close(); } @@ -205,10 +190,7 @@ class DPS { box = await DB.instance.hive.openBox(kBoxNameDesktopData); value = box.get(key); } catch (e, s) { - Logging.instance.log( - "DPS failed get($key): $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance.f("DPS failed get($key): ", error: e, stackTrace: s); } finally { await box?.close(); } diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index 3d64fb45a..e50ed2387 100644 --- a/lib/utilities/enums/derive_path_type_enum.dart +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -19,7 +19,8 @@ enum DerivePathType { eCash44, solana, bip86, - cardanoShelley; + cardanoShelley, + xelis; AddressType getAddressType() { switch (this) { @@ -45,6 +46,9 @@ enum DerivePathType { case DerivePathType.cardanoShelley: return AddressType.cardanoShelley; + + case DerivePathType.xelis: + return AddressType.xelis; } } } diff --git a/lib/utilities/enums/log_level_enum.dart b/lib/utilities/enums/log_level_enum.dart index 5fb7f9e52..c2c1de557 100644 --- a/lib/utilities/enums/log_level_enum.dart +++ b/lib/utilities/enums/log_level_enum.dart @@ -10,10 +10,28 @@ // Used in Isar db and stored there as int indexes so adding/removing values // in this definition should be done extremely carefully in production +import 'package:logger/logger.dart'; + +@Deprecated("Use Level instead, combined with the new logging features") enum LogLevel { Info, Warning, Error, Fatal, Debug; + + Level getLoggerLevel() { + switch (this) { + case LogLevel.Info: + return Level.info; + case LogLevel.Warning: + return Level.warning; + case LogLevel.Error: + return Level.error; + case LogLevel.Fatal: + return Level.fatal; + case LogLevel.Debug: + return Level.debug; + } + } } diff --git a/lib/utilities/extensions/impl/contract_abi.dart b/lib/utilities/extensions/impl/contract_abi.dart index b067c552e..74f252a69 100644 --- a/lib/utilities/extensions/impl/contract_abi.dart +++ b/lib/utilities/extensions/impl/contract_abi.dart @@ -68,10 +68,7 @@ extension ContractAbiExtensions on ContractAbi { return ContractAbi(name, functions, events); } catch (e, s) { - Logging.instance.log( - "Failed to parse ABI for $name: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Failed to parse ABI for $name: ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/utilities/git_status.dart b/lib/utilities/git_status.dart index 093bc39af..0a3f081a6 100644 --- a/lib/utilities/git_status.dart +++ b/lib/utilities/git_status.dart @@ -138,7 +138,7 @@ abstract class GitStatus { String project, String commit, ) async { - Logging.instance.log("doesCommitExist", level: LogLevel.Info); + Logging.instance.d("doesCommitExist"); final Client client = Client(); try { final uri = Uri.parse( @@ -151,21 +151,17 @@ abstract class GitStatus { ); final response = jsonDecode(commitQuery.body.toString()); - Logging.instance.log( - "doesCommitExist $project $commit $response", - level: LogLevel.Info, - ); + Logging.instance.d("doesCommitExist $project $commit $response"); bool isThereCommit; try { isThereCommit = response['sha'] == commit; - Logging.instance - .log("isThereCommit $isThereCommit", level: LogLevel.Info); + Logging.instance.d("isThereCommit $isThereCommit"); return isThereCommit; } catch (e, s) { return false; } } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("$e $s", error: e, stackTrace: s); return false; } } @@ -176,7 +172,7 @@ abstract class GitStatus { String branch, String commit, ) async { - Logging.instance.log("doesCommitExist", level: LogLevel.Info); + Logging.instance.d("doesCommitExist"); final Client client = Client(); try { final uri = Uri.parse( @@ -189,20 +185,17 @@ abstract class GitStatus { ); final response = jsonDecode(commitQuery.body.toString()); - Logging.instance.log( - "isHeadCommit $project $commit $branch $response", - level: LogLevel.Info, - ); + Logging.instance.d("isHeadCommit $project $commit $branch $response"); bool isHead; try { isHead = response['sha'] == commit; - Logging.instance.log("isHead $isHead", level: LogLevel.Info); + Logging.instance.d("isHead $isHead"); return isHead; - } catch (e, s) { - return false; + } catch (e) { + rethrow; } } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("", error: e, stackTrace: s); return false; } } diff --git a/lib/utilities/logger.dart b/lib/utilities/logger.dart index 83949ec5b..f62b1ea4e 100644 --- a/lib/utilities/logger.dart +++ b/lib/utilities/logger.dart @@ -8,161 +8,251 @@ * */ +import 'dart:convert'; import 'dart:core' as core; import 'dart:core'; -import 'dart:io'; +import 'dart:isolate'; +import 'dart:ui'; -import 'package:flutter/foundation.dart'; -import 'package:isar/isar.dart'; +import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart' as spark; +import 'package:logger/logger.dart'; -import '../models/isar/models/log.dart'; -import 'constants.dart'; -import 'enums/log_level_enum.dart'; +import 'util.dart'; export 'enums/log_level_enum.dart'; +const _kLoggerPortName = "logger_port"; + +// convenience conversion for spark +extension LoggingLevelExt on spark.LoggingLevel { + Level getLoggerLevel() { + switch (this) { + case spark.LoggingLevel.info: + return Level.info; + case spark.LoggingLevel.warning: + return Level.warning; + case spark.LoggingLevel.error: + return Level.error; + case spark.LoggingLevel.fatal: + return Level.fatal; + case spark.LoggingLevel.debug: + return Level.debug; + case spark.LoggingLevel.trace: + return Level.trace; + } + } +} + class Logging { - static const isArmLinux = bool.fromEnvironment("IS_ARM"); - static final isTestEnv = Platform.environment["FLUTTER_TEST"] == "true"; Logging._(); static final Logging _instance = Logging._(); static Logging get instance => _instance; - static const core.int defaultPrintLength = 1020; + late final String logsDirPath; - late final Isar? isar; - late final _AsyncLogWriterQueue _queue; + SendPort get _sendPort { + final port = IsolateNameServer.lookupPortByName(_kLoggerPortName); + if (port == null) { + throw Exception( + "Did you forget to call Logging.initialize()?", + ); + } + return port; + } - Future init(Isar isar) async { - _queue = _AsyncLogWriterQueue(); - this.isar = isar; + Future initialize(String logsPath, {required Level level}) async { + if (Isolate.current.debugName != "main") { + throw Exception( + "Logging.initialize() must be called on the main isolate.", + ); + } + if (IsolateNameServer.lookupPortByName(_kLoggerPortName) != null) { + throw Exception( + "Logging was already initialized", + ); + } + + logsDirPath = logsPath; + + final receivePort = ReceivePort(); + await Isolate.spawn( + (sendPort) { + final ReceivePort receivePort = ReceivePort(); + sendPort.send(receivePort.sendPort); + + PrettyPrinter prettyPrinter(bool toFile) => PrettyPrinter( + printEmojis: false, + methodCount: 0, + dateTimeFormat: + toFile ? DateTimeFormat.none : DateTimeFormat.dateAndTime, + colors: !toFile, + noBoxingByDefault: toFile, + ); + + final consoleLogger = Logger( + printer: PrefixPrinter(prettyPrinter(false)), + filter: ProductionFilter(), + level: level, + ); + + final fileLogger = Logger( + printer: PrefixPrinter(prettyPrinter(true)), + filter: ProductionFilter(), + level: level, + output: AdvancedFileOutput( + path: logsDirPath, + overrideExisting: false, + latestFileName: "latest.txt", + writeImmediately: [ + Level.error, + Level.fatal, + Level.warning, + Level.trace, // mainly for spark debugging. TODO: Remove later + ], + ), + ); + + receivePort.listen((message) { + final event = (message as (LogEvent, bool)).$1; + consoleLogger.log( + event.level, + event.message, + stackTrace: event.stackTrace, + error: event.error, + time: event.time.toUtc(), + ); + if (message.$2) { + fileLogger.log( + event.level, + "${event.time.toUtc().toIso8601String()} ${event.message}", + stackTrace: event.stackTrace, + error: event.error, + time: event.time, + ); + } + }); + }, + receivePort.sendPort, + ); + final loggerPort = await receivePort.first as SendPort; + IsolateNameServer.registerPortWithName(loggerPort, _kLoggerPortName); } + String _stringifyMessage(dynamic message) => + !(message is Map || message is Iterable) + ? message.toString() + : JsonEncoder.withIndent(' ', (o) => o.toString()).convert(message); + void log( - core.Object? object, { - required LogLevel level, - core.bool printToConsole = true, - core.bool printFullLength = false, + Level level, + dynamic message, { + DateTime? time, + Object? error, + StackTrace? stackTrace, + bool toFile = true, // false will print to console only }) { + if (Util.isTestEnv || Util.isArmLinux) { + toFile = false; + } try { - if (isTestEnv || isArmLinux) { - Logger.print(object, normalLength: !printFullLength); - return; - } - String message = object.toString(); - - // random value to check max size of message before storing in db - if (message.length > 20000) { - message = "${message.substring(0, 20000)}..."; - } - - final now = core.DateTime.now().toUtc(); - final log = Log() - ..message = message - ..logLevel = level - ..timestampInMillisUTC = now.millisecondsSinceEpoch; - if (level == LogLevel.Error || level == LogLevel.Fatal) { - printFullLength = true; - } - - _queue.add( - () async => isar!.writeTxn( - () async => await isar!.logs.put(log), + _sendPort.send( + ( + LogEvent( + level, + _stringifyMessage(message), + time: time, + error: error, + stackTrace: stackTrace, + ), + toFile ), ); - - if (printToConsole) { - final core.String logStr = "Log: ${log.toString()}"; - final core.int logLength = logStr.length; - - if (!printFullLength || logLength <= defaultPrintLength) { - debugPrint(logStr); - } else { - core.int start = 0; - core.int endIndex = defaultPrintLength; - core.int tmpLogLength = logLength; - while (endIndex < logLength) { - debugPrint(logStr.substring(start, endIndex)); - endIndex += defaultPrintLength; - start += defaultPrintLength; - tmpLogLength -= defaultPrintLength; - } - if (tmpLogLength > 0) { - debugPrint(logStr.substring(start, logLength)); - } - } - } } catch (e, s) { - print("problem trying to log"); - print("$e $s"); - Logger.print(object); + t("Isolates suck", error: e, stackTrace: s); } } -} -abstract class Logger { - static final isTestEnv = Platform.environment["FLUTTER_TEST"] == "true"; + void t( + dynamic message, { + DateTime? time, + Object? error, + StackTrace? stackTrace, + }) => + log( + Level.trace, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); - static void print( - core.Object? object, { - core.bool withTimeStamp = true, - core.bool normalLength = true, - }) async { - if (Constants.disableLogger && !isTestEnv) { - return; - } - final utcTime = withTimeStamp ? "${core.DateTime.now().toUtc()}: " : ""; - final core.int defaultPrintLength = 1020 - utcTime.length; - if (normalLength) { - debugPrint("$utcTime$object"); - } else if (object == null || - object.toString().length <= defaultPrintLength) { - debugPrint("$utcTime$object"); - } else { - final core.String log = object.toString(); - core.int start = 0; - core.int endIndex = defaultPrintLength; - final core.int logLength = log.length; - core.int tmpLogLength = log.length; - while (endIndex < logLength) { - debugPrint(utcTime + log.substring(start, endIndex)); - endIndex += defaultPrintLength; - start += defaultPrintLength; - tmpLogLength -= defaultPrintLength; - } - if (tmpLogLength > 0) { - debugPrint(utcTime + log.substring(start, logLength)); - } - } - } -} + void d( + dynamic message, { + DateTime? time, + Object? error, + StackTrace? stackTrace, + }) => + log( + Level.debug, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); -/// basic async queue for writing logs in the [Logging] to isar -class _AsyncLogWriterQueue { - final List Function()> _queue = []; - bool _runningLock = false; + void i( + dynamic message, { + DateTime? time, + Object? error, + StackTrace? stackTrace, + }) => + log( + Level.info, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); - void add(Future Function() futureFunction) { - _queue.add(futureFunction); - _run(); - } + void w( + dynamic message, { + DateTime? time, + Object? error, + StackTrace? stackTrace, + }) => + log( + Level.warning, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); - void _run() async { - if (_runningLock) { - return; - } - _runningLock = true; - try { - while (_queue.isNotEmpty) { - final futureFunction = _queue.removeAt(0); - try { - await futureFunction.call(); - } catch (e, s) { - debugPrint("$e\n$s"); - } - } - } finally { - _runningLock = false; - } - } + void e( + dynamic message, { + DateTime? time, + Object? error, + StackTrace? stackTrace, + }) => + log( + Level.error, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); + + void f( + dynamic message, { + DateTime? time, + Object? error, + StackTrace? stackTrace, + }) => + log( + Level.fatal, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); } diff --git a/lib/utilities/node_uri_util.dart b/lib/utilities/node_uri_util.dart new file mode 100644 index 000000000..73876949e --- /dev/null +++ b/lib/utilities/node_uri_util.dart @@ -0,0 +1,132 @@ +abstract interface class NodeQrData { + final String host; + final int port; + final String? label; + + NodeQrData({required this.host, required this.port, this.label}); + + String encode(); + String get scheme; +} + +abstract class LibMoneroNodeQrData extends NodeQrData { + final String user; + final String password; + + LibMoneroNodeQrData({ + required super.host, + required super.port, + super.label, + required this.user, + required this.password, + }); + + @override + String encode() { + String? userInfo; + if (user.isNotEmpty) { + userInfo = user; + if (password.isNotEmpty) { + userInfo += ":$password"; + } + } + + final uri = Uri( + scheme: scheme, + userInfo: userInfo, + port: port, + host: host, + queryParameters: {"label": label}, + ); + + return uri.toString(); + } + + @override + String toString() { + return "$runtimeType {" + "scheme: $scheme, " + "host: $host, " + "port: $port, " + "user: $user, " + "password: $password, " + "label: $label" + "}"; + } +} + +class MoneroNodeQrData extends LibMoneroNodeQrData { + MoneroNodeQrData({ + required super.host, + required super.port, + required super.user, + required super.password, + super.label, + }); + + @override + String get scheme => "xmrrpc"; +} + +class WowneroNodeQrData extends LibMoneroNodeQrData { + WowneroNodeQrData({ + required super.host, + required super.port, + required super.user, + required super.password, + super.label, + }); + + @override + String get scheme => "wowrpc"; +} + +abstract final class NodeQrUtil { + static ({String? user, String? password}) _parseUserInfo(String? userInfo) { + if (userInfo == null || userInfo.isEmpty) { + return (user: null, password: null); + } + + final splitIndex = userInfo.indexOf(":"); + if (splitIndex == -1) { + return (user: userInfo, password: null); + } + + return ( + user: userInfo.substring(0, splitIndex), + password: userInfo.substring(splitIndex + 1), + ); + } + + static NodeQrData decodeUri(String uriString) { + final uri = Uri.tryParse(uriString); + if (uri == null) throw Exception("Invalid uri string."); + if (!uri.hasAuthority) throw Exception("Uri has no authority."); + + final userInfo = _parseUserInfo(uri.userInfo); + + final query = uri.queryParameters; + + switch (uri.scheme) { + case "xmrrpc": + return MoneroNodeQrData( + host: uri.host, + port: uri.port, + user: userInfo.user ?? "", + password: userInfo.password ?? "", + label: query["label"], + ); + case "wowrpc": + return WowneroNodeQrData( + host: uri.host, + port: uri.port, + user: userInfo.user ?? "", + password: userInfo.password ?? "", + label: query["label"], + ); + + default: + throw Exception("Unknown node uri scheme \"${uri.scheme}\" found."); + } + } +} diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 5ffc7a059..395e5ecd6 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -11,6 +11,7 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; +import 'package:logger/logger.dart'; import 'package:uuid/uuid.dart'; import '../app_config.dart'; @@ -72,6 +73,9 @@ class Prefs extends ChangeNotifier { _fusionServerInfo = await _getFusionServerInfo(); _autoPin = await _getAutoPin(); _enableExchange = await _getEnableExchange(); + _advancedFiroFeatures = await _getAdvancedFiroFeatures(); + _logsPath = await _getLogsPath(); + _logLevel = await _getLogLevel(); _initialized = true; } @@ -1158,4 +1162,78 @@ class Prefs extends ChangeNotifier { ) as bool? ?? true; } + + // Show/hide lelantus and spark coins. Defaults to false + bool _advancedFiroFeatures = false; + bool get advancedFiroFeatures => _advancedFiroFeatures; + set advancedFiroFeatures(bool advancedFiroFeatures) { + if (_advancedFiroFeatures != advancedFiroFeatures) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "advancedFiroFeatures", + value: advancedFiroFeatures, + ); + _advancedFiroFeatures = advancedFiroFeatures; + notifyListeners(); + } + } + + Future _getAdvancedFiroFeatures() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "advancedFiroFeatures", + ) as bool? ?? + false; + } + + // Logs path. Null defaults to default as defined in stack_file_system.dart + String? _logsPath; + String? get logsPath => _logsPath; + set logsPath(String? logsPath) { + if (_logsPath != logsPath) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "logsPath", + value: logsPath, + ); + _logsPath = logsPath; + notifyListeners(); + } + } + + Future _getLogsPath() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "logsPath", + ) as String?; + } + + // log level pref + Level _logLevel = Level.warning; + Level get logLevel => _logLevel; + set logLevel(Level logLevel) { + if (_logLevel != logLevel) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "logLevel", + value: logLevel.value, + ); + _logLevel = logLevel; + notifyListeners(); + } + } + + Future _getLogLevel() async { + final value = await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "logLevel", + ) as int?; + + try { + return Level.values.firstWhere((e) => e.value == value); + } catch (_) { + // default to warning + return Level.warning; + } + } } diff --git a/lib/utilities/show_loading.dart b/lib/utilities/show_loading.dart index 371d67d1d..040bc2303 100644 --- a/lib/utilities/show_loading.dart +++ b/lib/utilities/show_loading.dart @@ -71,10 +71,7 @@ Future showLoading({ result = await whileFuture; } } catch (e, s) { - Logging.instance.log( - "showLoading caught: $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("showLoading caught: ", error: e, stackTrace: s); ex = e is Exception ? e : Exception(e.toString()); } diff --git a/lib/utilities/stack_file_system.dart b/lib/utilities/stack_file_system.dart index 53e382ff0..795ea9720 100644 --- a/lib/utilities/stack_file_system.dart +++ b/lib/utilities/stack_file_system.dart @@ -11,9 +11,10 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; import '../app_config.dart'; -import 'logger.dart'; +import 'prefs.dart'; import 'util.dart'; abstract class StackFileSystem { @@ -36,10 +37,11 @@ abstract class StackFileSystem { Directory appDirectory; // todo: can merge and do same as regular linux home dir? - if (Logging.isArmLinux) { + if (Util.isArmLinux) { appDirectory = await getApplicationDocumentsDirectory(); - appDirectory = - Directory("${appDirectory.path}/.${AppConfig.appDefaultDataDirName}"); + appDirectory = Directory( + "${appDirectory.path}/.${AppConfig.appDefaultDataDirName}", + ); } else if (Platform.isLinux) { if (_overrideDesktopDirPath != null) { appDirectory = Directory(_overrideDesktopDirPath!); @@ -147,6 +149,24 @@ abstract class StackFileSystem { } } + static Future applicationXelisDirectory() async { + final root = await applicationRootDirectory(); + final dir = Directory("${root.path}${Platform.pathSeparator}xelis"); + if (!dir.existsSync()) { + await dir.create(); + } + return dir; + } + + static Future applicationXelisTableDirectory() async { + final xelis = await applicationXelisDirectory(); + final dir = Directory("${xelis.path}${Platform.pathSeparator}table"); + if (!dir.existsSync()) { + await dir.create(); + } + return dir; + } + static Future initThemesDir() async { final root = await applicationRootDirectory(); @@ -158,4 +178,36 @@ abstract class StackFileSystem { } static Directory? themesDir; + + static Future applicationLogsDirectory(Prefs prefs) async { + // if prefs logs path is set, use that + if (prefs.logsPath != null) { + final dir = Directory(prefs.logsPath!); + if (await dir.exists()) { + return dir; + } + } + + final appDocsDir = await getApplicationDocumentsDirectory(); + const logsDirName = "${AppConfig.prefix}_Logs"; + final Directory logsDir; + + if (Platform.isIOS) { + logsDir = Directory("${appDocsDir.path}/logs"); + } else if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) { + // TODO check this is correct for macos + logsDir = Directory("${appDocsDir.path}/$logsDirName"); + } else if (Platform.isAndroid) { + await Permission.storage.request(); + logsDir = Directory("/storage/emulated/0/Documents/$logsDirName"); + } else { + throw Exception("Unsupported Platform"); + } + + if (!logsDir.existsSync()) { + await logsDir.create(recursive: true); + } + + return logsDir; + } } diff --git a/lib/utilities/test_epic_box_connection.dart b/lib/utilities/test_epic_box_connection.dart index 7fff18369..1c39390e2 100644 --- a/lib/utilities/test_epic_box_connection.dart +++ b/lib/utilities/test_epic_box_connection.dart @@ -41,7 +41,7 @@ Future _testEpicBoxNodeConnection(Uri uri) async { return false; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s", error: e, stackTrace: s,); return false; } } @@ -87,7 +87,7 @@ Future testEpicNodeConnection(NodeFormData data) async { return null; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s", error: e, stackTrace: s,); return null; } } diff --git a/lib/utilities/test_monero_node_connection.dart b/lib/utilities/test_monero_node_connection.dart index aef26ba9a..7eba0ee30 100644 --- a/lib/utilities/test_monero_node_connection.dart +++ b/lib/utilities/test_monero_node_connection.dart @@ -11,7 +11,10 @@ import 'dart:convert'; import 'dart:io'; +import 'package:digest_auth/digest_auth.dart'; import 'package:flutter/material.dart'; +import 'package:http/io_client.dart'; +import 'package:monero_rpc/monero_rpc.dart'; import 'package:socks5_proxy/socks.dart'; import 'package:tor_ffi_plugin/socks_socket.dart'; @@ -27,11 +30,18 @@ class MoneroNodeConnectionResponse { final int? port; final bool success; - MoneroNodeConnectionResponse(this.cert, this.url, this.port, this.success); + MoneroNodeConnectionResponse( + this.cert, + this.url, + this.port, + this.success, + ); } Future testMoneroNodeConnection( Uri uri, + String? username, + String? password, bool allowBadX509Certificate, { required ({ InternetAddress host, @@ -59,40 +69,41 @@ Future testMoneroNodeConnection( await socket.connect(); await socket.connectTo(uri.host, uri.port); - final body = jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - "method": "get_info", - }); - - final request = 'POST /json_rpc HTTP/1.1\r\n' - 'Host: ${uri.host}\r\n' - 'Content-Type: application/json\r\n' - 'Content-Length: ${body.length}\r\n' - '\r\n' - '$body'; - - socket.write(request); - print("Request sent: $request"); - - final buffer = StringBuffer(); - await for (var response in socket.inputStream) { - buffer.write(utf8.decode(response)); - if (buffer.toString().contains("\r\n\r\n")) { - break; + final rawRequest = DaemonRpc.rawRequestRpc(uri, 'get_info', {}); + var response = await socket.send(rawRequest); + // check if we need authentication + String? authenticateHeaderValue; + for (final line in response.split('\r\n')) { + if (line.contains('WWW-authenticate: ')) { + // both the password and username needs to be + if (username == null || password == null) { + // node asking us for authentication, but we don't have any crendentials. + return MoneroNodeConnectionResponse(null, null, null, false); + } + authenticateHeaderValue = + line.replaceFirst('WWW-authenticate: ', '').trim(); } } - - final result = buffer.toString(); - print("Response received: $result"); + // header to authenticate was present, we need to remake the request with digest + if (authenticateHeaderValue != null) { + final digestAuth = DigestAuth(username!, password!); + digestAuth.initFromAuthorizationHeader(authenticateHeaderValue); + + // generate the Authorization header for the second request. + final authHeader = digestAuth.getAuthString('POST', uri.path); + final rawRequestAuthenticated = + DaemonRpc.rawRequestRpc(uri, 'get_info', {}, authHeader); + // resend with an authenticated request + response = await socket.send(rawRequestAuthenticated); + } // Check if the response contains "results" and does not contain "error" final success = - result.contains('"result":') && !result.contains('"error"'); + response.contains('"result":') && !response.contains('"error"'); return MoneroNodeConnectionResponse(null, null, null, success); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s", error: e, stackTrace: s,); return MoneroNodeConnectionResponse(null, null, null, false); } finally { await socket?.close(); @@ -124,43 +135,22 @@ Future testMoneroNodeConnection( return false; }; - - final request = await httpClient.postUrl(uri); - - final body = utf8.encode( - jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - "method": "get_info", - }), - ); - - request.headers.add( - 'Content-Length', - body.length.toString(), - preserveHeaderCase: true, - ); - request.headers.set( - 'Content-Type', - 'application/json', - preserveHeaderCase: true, + final daemonRpc = DaemonRpc( + IOClient(httpClient), + '$uri', + username: username, + password: password, ); + final result = await daemonRpc.call('get_info', {}); - request.add(body); - - final response = await request.close(); - final result = await response.transform(utf8.decoder).join(); - // print("HTTP Response: $result"); - - final success = - result.contains('"result":') && !result.contains('"error"'); + final success = result.containsKey('status') && result['status'] == 'OK'; return MoneroNodeConnectionResponse(null, null, null, success); } catch (e, s) { if (badCertResponse != null) { return badCertResponse!; } else { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s", error: e, stackTrace: s,); return MoneroNodeConnectionResponse(null, null, null, false); } } finally { @@ -210,3 +200,18 @@ Future showBadX509CertificateDialog( return result ?? false; } + +extension on SOCKSSocket { + /// write the raw request to the socket and return the response as String + Future send(String rawRequest) async { + write(rawRequest); + final buffer = StringBuffer(); + await for (final response in inputStream) { + buffer.write(utf8.decode(response)); + if (buffer.toString().contains("\r\n\r\n")) { + break; + } + } + return buffer.toString(); + } +} diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index 127702b14..7e8616ac2 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -26,6 +26,8 @@ import 'test_monero_node_connection.dart'; import 'test_stellar_node_connection.dart'; import 'tor_plain_net_option_enum.dart'; +import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk; + Future _xmrHelper( NodeFormData nodeFormData, BuildContext context, @@ -38,6 +40,8 @@ Future _xmrHelper( final data = nodeFormData; final url = data.host!; final port = data.port; + final username = data.login; + final password = data.password; final uri = Uri.parse(url); @@ -51,6 +55,8 @@ Future _xmrHelper( final response = await testMoneroNodeConnection( Uri.parse(uriString), + username, + password, false, proxyInfo: proxyInfo, ).timeout(Duration(seconds: proxyInfo != null ? 30 : 10)); @@ -67,6 +73,8 @@ Future _xmrHelper( if (shouldAllowBadCert) { final response = await testMoneroNodeConnection( Uri.parse(uriString), + username, + password, true, proxyInfo: proxyInfo, ); @@ -94,17 +102,15 @@ Future testNodeConnection({ if (ref.read(prefsChangeNotifierProvider).useTor) { if (formData.netOption! == TorPlainNetworkOption.clear) { - Logging.instance.log( + Logging.instance.w( "This node is configured for non-TOR only but TOR is enabled", - level: LogLevel.Warning, ); return false; } } else { if (formData.netOption! == TorPlainNetworkOption.tor) { - Logging.instance.log( + Logging.instance.w( "This node is configured for TOR only but TOR is disabled", - level: LogLevel.Warning, ); return false; } @@ -122,7 +128,11 @@ Future testNodeConnection({ onSuccess?.call(data); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w( + "$e\n$s", + error: e, + stackTrace: s, + ); } break; @@ -169,7 +179,11 @@ Future testNodeConnection({ } } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w( + "$e\n$s", + error: e, + stackTrace: s, + ); } break; @@ -245,9 +259,8 @@ Future testNodeConnection({ ); final health = await rpcClient.getHealth(); - Logging.instance.log( + Logging.instance.i( "Solana testNodeConnection \"health=$health\"", - level: LogLevel.Info, ); return true; } catch (_) { @@ -277,9 +290,8 @@ Future testNodeConnection({ BlockfrostRequestBackendHealthStatus(), ); - Logging.instance.log( + Logging.instance.i( "Cardano testNodeConnection \"health=$health\"", - level: LogLevel.Info, ); return health; @@ -287,6 +299,28 @@ Future testNodeConnection({ testPassed = false; } break; + + case Xelis(): + try { + final daemon = xelis_sdk.DaemonClient( + endPoint: "${formData.host!}:${formData.port!}", + secureWebSocket: formData.useSSL ?? false, + timeout: 5000 + ); + daemon.connect(); + + final xelis_sdk.GetInfoResult networkInfo = await daemon.getInfo(); + testPassed = networkInfo.height != null; + + daemon.disconnect(); + + Logging.instance.i( + "Xelis testNodeConnection result: \"${networkInfo.toString()}\"", + ); + } catch (e, s) { + testPassed = false; + } + break; } return testPassed; diff --git a/lib/utilities/text_formatters.dart b/lib/utilities/text_formatters.dart new file mode 100644 index 000000000..a7cf76d4f --- /dev/null +++ b/lib/utilities/text_formatters.dart @@ -0,0 +1,87 @@ +import 'dart:convert'; +import 'dart:math' as math; + +import 'package:flutter/services.dart'; + +class Utf8ByteLengthLimitingTextInputFormatter extends TextInputFormatter { + Utf8ByteLengthLimitingTextInputFormatter( + this.maxBytes, { + this.tryMinifyJson = false, + }) : assert(maxBytes == -1 || maxBytes > 0); + + final int maxBytes; + final bool tryMinifyJson; + + static String _maybeTryMinify(String text, bool tryMinifyJson) { + if (tryMinifyJson) { + try { + final json = jsonDecode(text); + final minified = jsonEncode(json); + return minified; + } catch (_) {} + } + + return text; + } + + static TextEditingValue truncate( + TextEditingValue value, + int maxBytes, + bool tryMinifyJson, + ) { + final String text = _maybeTryMinify(value.text, tryMinifyJson); + final encoded = utf8.encode(text); + + if (encoded.length <= maxBytes) { + return value; + } + + int validLength = maxBytes; + while (validLength > 0 && (encoded[validLength] & 0xC0) == 0x80) { + validLength--; + } + + final truncated = utf8.decode(encoded.sublist(0, validLength)); + + return TextEditingValue( + text: truncated, + selection: value.selection.copyWith( + baseOffset: math.min(value.selection.start, truncated.length), + extentOffset: math.min(value.selection.end, truncated.length), + ), + composing: !value.composing.isCollapsed && + truncated.length > value.composing.start + ? TextRange( + start: value.composing.start, + end: math.min(value.composing.end, truncated.length), + ) + : TextRange.empty, + ); + } + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + if (maxBytes == -1 || + utf8 + .encode(_maybeTryMinify(newValue.text, tryMinifyJson)) + .lengthInBytes <= + maxBytes) { + return newValue; + } + + assert(maxBytes > 0); + + if (utf8 + .encode(_maybeTryMinify(oldValue.text, tryMinifyJson)) + .lengthInBytes == + maxBytes && + oldValue.selection.isCollapsed) { + return oldValue; + } + + return truncate(newValue, maxBytes, tryMinifyJson); + } +} diff --git a/lib/utilities/util.dart b/lib/utilities/util.dart index d573d9b90..5480e0804 100644 --- a/lib/utilities/util.dart +++ b/lib/utilities/util.dart @@ -18,6 +18,9 @@ import 'package:intl/number_symbols.dart'; import 'package:intl/number_symbols_data.dart'; abstract class Util { + static const isArmLinux = bool.fromEnvironment("IS_ARM"); + static final isTestEnv = Platform.environment["FLUTTER_TEST"] == "true"; + static Directory? libraryPath; static double? screenWidth; diff --git a/lib/utilities/wallet_tools.dart b/lib/utilities/wallet_tools.dart index cbc3c616c..8d869ea63 100644 --- a/lib/utilities/wallet_tools.dart +++ b/lib/utilities/wallet_tools.dart @@ -2,13 +2,17 @@ import 'package:isar/isar.dart'; import '../db/isar/main_db.dart'; import '../models/isar/models/blockchain_data/v2/transaction_v2.dart'; +import '../models/isar/models/firo_specific/lelantus_coin.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; +import '../wallets/isar/models/spark_coin.dart'; +import '../wallets/wallet/impl/firo_wallet.dart'; import 'amount/amount.dart'; import 'amount/amount_formatter.dart'; import 'amount/amount_unit.dart'; +import 'logger.dart'; abstract class WalletDevTools { - static String checkFiroTransactionTally(String walletId) { + static String checkFiroTransactionTally(FiroWallet wallet) { final amtFmt = AmountFormatter( unit: AmountUnit.normal, locale: "en_US", @@ -18,7 +22,7 @@ abstract class WalletDevTools { final all = MainDB.instance.isar.transactionV2s .where() - .walletIdEqualTo(walletId) + .walletIdEqualTo(wallet.walletId) .findAllSync(); final totalCount = all.length; @@ -43,12 +47,27 @@ abstract class WalletDevTools { fractionDigits: 8, ); - print("======== $walletId ============="); - print("totalTxCount: $totalCount"); - print( + final lelantusCoinsCount = MainDB.instance.isar.lelantusCoins + .where() + .walletIdEqualTo(wallet.walletId) + .countSync(); + final sparkCoinsCount = MainDB.instance.isar.sparkCoins + .where() + .walletIdEqualToAnyLTagHash(wallet.walletId) + .countSync(); + + final buffer = StringBuffer(); + buffer.writeln("============= ${wallet.info.name} ============="); + buffer.writeln("wallet id: ${wallet.walletId}"); + buffer.writeln("totalTxCount: $totalCount"); + buffer.writeln( "balanceAccordingToTxns: ${amtFmt.format(balanceAccordingToTxHistory)}", ); - print("=================================================="); + buffer.writeln("lelantusCoinsCount: $lelantusCoinsCount"); + buffer.writeln("sparkCoinsCount: $sparkCoinsCount"); + buffer.writeln("=================================================="); + + Logging.instance.d(buffer); return amtFmt.format(balanceAccordingToTxHistory); } diff --git a/lib/wallets/api/lelantus_ffi_wrapper.dart b/lib/wallets/api/lelantus_ffi_wrapper.dart index 44e446507..3ee60d994 100644 --- a/lib/wallets/api/lelantus_ffi_wrapper.dart +++ b/lib/wallets/api/lelantus_ffi_wrapper.dart @@ -48,10 +48,7 @@ abstract final class LelantusFfiWrapper { try { return await compute(_restore, args); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _restore(): $e\n$s", - level: LogLevel.Info, - ); + Logging.instance.i("Exception rethrown from _restore(): ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/wallets/api/tezos/tezos_api.dart b/lib/wallets/api/tezos/tezos_api.dart index abe126ac3..49065433e 100644 --- a/lib/wallets/api/tezos/tezos_api.dart +++ b/lib/wallets/api/tezos/tezos_api.dart @@ -25,10 +25,7 @@ abstract final class TezosAPI { final result = jsonDecode(response.body); return result as int; } catch (e, s) { - Logging.instance.log( - "Error occurred in TezosAPI while getting counter for $address: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in TezosAPI while getting counter for $address: ", error: e, stackTrace: s); rethrow; } } @@ -51,14 +48,9 @@ abstract final class TezosAPI { final account = TezosAccount.fromMap(Map.from(result)); - print("Get account =================== $account"); - return account; } catch (e, s) { - Logging.instance.log( - "Error occurred in TezosAPI while getting account for $address: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in TezosAPI while getting account for $address: ", error: e, stackTrace: s); rethrow; } } @@ -111,10 +103,7 @@ abstract final class TezosAPI { } return txs; } catch (e, s) { - Logging.instance.log( - "Error occurred in TezosAPI while getting transactions for $address: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in TezosAPI while getting transactions for $address: ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/wallets/api/tezos/tezos_rpc_api.dart b/lib/wallets/api/tezos/tezos_rpc_api.dart index 1497a46c0..4f9c71fe8 100644 --- a/lib/wallets/api/tezos/tezos_rpc_api.dart +++ b/lib/wallets/api/tezos/tezos_rpc_api.dart @@ -27,10 +27,11 @@ abstract final class TezosRpcAPI { final balance = BigInt.parse(response.body.substring(1, response.body.length - 2)); return balance; - } catch (e) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.e( "Error occurred in tezos_rpc_api.dart while getting balance for $address: $e", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } return null; @@ -53,10 +54,11 @@ abstract final class TezosRpcAPI { final jsonParsedResponse = jsonDecode(response.body); return int.parse(jsonParsedResponse["level"].toString()); - } catch (e) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.e( "Error occurred in tezos_rpc_api.dart while getting chain height for tezos: $e", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } return null; diff --git a/lib/wallets/crypto_currency/coins/monero.dart b/lib/wallets/crypto_currency/coins/monero.dart index 7e0347775..4cbeeeb68 100644 --- a/lib/wallets/crypto_currency/coins/monero.dart +++ b/lib/wallets/crypto_currency/coins/monero.dart @@ -49,6 +49,9 @@ class Monero extends CryptonoteCurrency { @override bool validateAddress(String address) { + if (address.contains("111")) { + return false; + } switch (network) { case CryptoCurrencyNetwork.main: return xmr_wallet_ffi.validateAddress(address, 0); diff --git a/lib/wallets/crypto_currency/coins/namecoin.dart b/lib/wallets/crypto_currency/coins/namecoin.dart index e390d599e..77940784e 100644 --- a/lib/wallets/crypto_currency/coins/namecoin.dart +++ b/lib/wallets/crypto_currency/coins/namecoin.dart @@ -229,7 +229,7 @@ class Namecoin extends Bip39HDCurrency with ElectrumXCurrencyInterface { bool get hasMnemonicPassphraseSupport => true; @override - List get possibleMnemonicLengths => [defaultSeedPhraseLength, 12]; + List get possibleMnemonicLengths => [defaultSeedPhraseLength, 24]; @override AddressType get defaultAddressType => defaultDerivePathType.getAddressType(); diff --git a/lib/wallets/crypto_currency/coins/particl.dart b/lib/wallets/crypto_currency/coins/particl.dart index 067aae72d..8788b6114 100644 --- a/lib/wallets/crypto_currency/coins/particl.dart +++ b/lib/wallets/crypto_currency/coins/particl.dart @@ -219,7 +219,7 @@ class Particl extends Bip39HDCurrency with ElectrumXCurrencyInterface { int get targetBlockTimeSeconds => 600; @override - DerivePathType get defaultDerivePathType => DerivePathType.bip84; + DerivePathType get defaultDerivePathType => DerivePathType.bip44; @override Uri defaultBlockExplorer(String txid) { diff --git a/lib/wallets/crypto_currency/coins/wownero.dart b/lib/wallets/crypto_currency/coins/wownero.dart index e043fdd7b..2aea90aa8 100644 --- a/lib/wallets/crypto_currency/coins/wownero.dart +++ b/lib/wallets/crypto_currency/coins/wownero.dart @@ -49,6 +49,9 @@ class Wownero extends CryptonoteCurrency { @override bool validateAddress(String address) { + if (address.contains("111")) { + return false; + } switch (network) { case CryptoCurrencyNetwork.main: return wow_wallet_ffi.validateAddress(address, 0); @@ -82,7 +85,7 @@ class Wownero extends CryptonoteCurrency { } @override - int get defaultSeedPhraseLength => 14; + int get defaultSeedPhraseLength => 16; //14; @override int get fractionDigits => 11; @@ -94,7 +97,7 @@ class Wownero extends CryptonoteCurrency { bool get hasMnemonicPassphraseSupport => false; @override - List get possibleMnemonicLengths => [defaultSeedPhraseLength, 16, 25]; + List get possibleMnemonicLengths => [defaultSeedPhraseLength, 25]; @override BigInt get satsPerCoin => BigInt.from(100000000000); diff --git a/lib/wallets/crypto_currency/coins/xelis.dart b/lib/wallets/crypto_currency/coins/xelis.dart new file mode 100644 index 000000000..b05fce216 --- /dev/null +++ b/lib/wallets/crypto_currency/coins/xelis.dart @@ -0,0 +1,142 @@ +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/node_model.dart'; +import '../../../utilities/default_nodes.dart'; +import '../../../utilities/enums/derive_path_type_enum.dart'; +import '../crypto_currency.dart'; +import '../intermediate/electrum_currency.dart'; + +import 'package:xelis_flutter/src/api/utils.dart' as x_utils; + +class Xelis extends ElectrumCurrency { + Xelis(super.network) { + _idMain = "xelis"; + _uriScheme = "xelis"; + switch (network) { + case CryptoCurrencyNetwork.main: + _id = _idMain; + _name = "Xelis"; + _ticker = "XEL"; + case CryptoCurrencyNetwork.test: + _id = "xelisTestNet"; + _name = "tXelis"; + _ticker = "XET"; + default: + throw Exception("Unsupported network: $network"); + } + } + + late final String _id; + @override + String get identifier => _id; + + late final String _idMain; + @override + String get mainNetId => _idMain; + + late final String _name; + @override + String get prettyName => _name; + + late final String _uriScheme; + @override + String get uriScheme => _uriScheme; + + late final String _ticker; + @override + String get ticker => _ticker; + + @override + NodeModel get defaultNode { + switch (network) { + case CryptoCurrencyNetwork.main: + return NodeModel( + host: "us-node.xelis.io", + port: 443, + name: DefaultNodes.defaultName, + id: DefaultNodes.buildId(this), + useSSL: true, + enabled: true, + coinName: identifier, + isFailover: true, + isDown: false, + torEnabled: false, + clearnetEnabled: true, + ); + + case CryptoCurrencyNetwork.test: + return NodeModel( + host: "testnet-node.xelis.io", + port: 443, + name: DefaultNodes.defaultName, + id: DefaultNodes.buildId(this), + useSSL: true, + enabled: true, + coinName: identifier, + isFailover: true, + isDown: false, + torEnabled: false, + clearnetEnabled: true, + ); + + default: + throw Exception("Unsupported network: $network"); + } + } + + @override + int get minConfirms => 1; + + @override + bool get torSupport => false; + + @override + bool validateAddress(String address) { + try { + return x_utils.isAddressValid(strAddress: address); + } catch (_) { + return false; + } + } + + @override + String get genesisHash => throw UnimplementedError(); + + @override + int get defaultSeedPhraseLength => 25; + + @override + int get fractionDigits => 8; + + @override + bool get hasBuySupport => false; + + @override + bool get hasMnemonicPassphraseSupport => false; + + @override + List get possibleMnemonicLengths => [defaultSeedPhraseLength]; + + @override + AddressType get defaultAddressType => defaultDerivePathType.getAddressType(); + + @override + BigInt get satsPerCoin => BigInt.from(1000000000); + + @override + int get targetBlockTimeSeconds => 15; + + @override + DerivePathType get defaultDerivePathType => DerivePathType.xelis; + + @override + Uri defaultBlockExplorer(String txid) { + switch (network) { + case CryptoCurrencyNetwork.main: + return Uri.parse("https://explorer.xelis.io/txs/$txid"); + default: + throw Exception( + "Unsupported network for defaultBlockExplorer(): $network", + ); + } + } +} diff --git a/lib/wallets/crypto_currency/crypto_currency.dart b/lib/wallets/crypto_currency/crypto_currency.dart index 066675d28..d5553ceca 100644 --- a/lib/wallets/crypto_currency/crypto_currency.dart +++ b/lib/wallets/crypto_currency/crypto_currency.dart @@ -23,6 +23,7 @@ export 'coins/solana.dart'; export 'coins/stellar.dart'; export 'coins/tezos.dart'; export 'coins/wownero.dart'; +export 'coins/xelis.dart'; enum CryptoCurrencyNetwork { main, diff --git a/lib/wallets/crypto_currency/intermediate/electrum_currency.dart b/lib/wallets/crypto_currency/intermediate/electrum_currency.dart new file mode 100644 index 000000000..4d8378f16 --- /dev/null +++ b/lib/wallets/crypto_currency/intermediate/electrum_currency.dart @@ -0,0 +1,5 @@ +import '../crypto_currency.dart'; + +abstract class ElectrumCurrency extends CryptoCurrency { + ElectrumCurrency(super.network); +} \ No newline at end of file diff --git a/lib/wallets/isar/models/spark_coin.dart b/lib/wallets/isar/models/spark_coin.dart index d3ef6825c..9501bf06d 100644 --- a/lib/wallets/isar/models/spark_coin.dart +++ b/lib/wallets/isar/models/spark_coin.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:isar/isar.dart'; part 'spark_coin.g.dart'; @@ -59,6 +61,19 @@ class SparkCoin { @ignore BigInt get diversifier => BigInt.parse(diversifierIntString); + int getConfirmations(int currentChainHeight) { + if (height == null || height! <= 0) return 0; + return max(0, currentChainHeight - (height! - 1)); + } + + bool isConfirmed( + int currentChainHeight, + int minimumConfirms, + ) { + final confirmations = getConfirmations(currentChainHeight); + return confirmations >= minimumConfirms; + } + SparkCoin({ required this.walletId, required this.type, diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index eb6a4e9ea..779f1dd78 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -517,11 +517,12 @@ abstract class WalletInfoKeys { static const String epiccashData = "epiccashDataKey"; static const String bananoMonkeyImageBytes = "monkeyImageBytesKey"; static const String tezosDerivationPath = "tezosDerivationPathKey"; + static const String xelisDerivationPath = "xelisDerivationPathKey"; static const String lelantusCoinIsarRescanRequired = "lelantusCoinIsarRescanRequired"; static const String enableLelantusScanning = "enableLelantusScanningKey"; - static const String firoSparkCacheSetTimestampCache = - "firoSparkCacheSetTimestampCacheKey"; + static const String firoSparkCacheSetBlockHashCache = + "firoSparkCacheSetBlockHashCacheKey"; static const String enableOptInRbf = "enableOptInRbfKey"; static const String reuseAddress = "reuseAddressKey"; static const String isViewOnlyKey = "isViewOnlyKey"; diff --git a/lib/wallets/isar/models/wallet_info.g.dart b/lib/wallets/isar/models/wallet_info.g.dart index 89c5511fa..5e93564c0 100644 --- a/lib/wallets/isar/models/wallet_info.g.dart +++ b/lib/wallets/isar/models/wallet_info.g.dart @@ -268,6 +268,8 @@ const _WalletInfomainAddressTypeEnumValueMap = { 'frostMS': 13, 'p2tr': 14, 'solana': 15, + 'cardanoShelley': 16, + 'xelis': 17, }; const _WalletInfomainAddressTypeValueEnumMap = { 0: AddressType.p2pkh, @@ -286,6 +288,8 @@ const _WalletInfomainAddressTypeValueEnumMap = { 13: AddressType.frostMS, 14: AddressType.p2tr, 15: AddressType.solana, + 16: AddressType.cardanoShelley, + 17: AddressType.xelis, }; Id _walletInfoGetId(WalletInfo object) { diff --git a/lib/wallets/models/name_op_state.dart b/lib/wallets/models/name_op_state.dart new file mode 100644 index 000000000..5390db018 --- /dev/null +++ b/lib/wallets/models/name_op_state.dart @@ -0,0 +1,59 @@ +import 'package:namecoin/namecoin.dart'; + +import '../../models/isar/models/blockchain_data/utxo.dart'; + +class NameOpState { + final String name; + final OpName type; + final String saltHex; + final String commitment; + final String value; + final String nameScriptHex; + final int outputPosition; + final UTXO? output; + + NameOpState({ + required this.name, + required this.type, + required this.saltHex, + required this.commitment, + required this.value, + required this.nameScriptHex, + required this.outputPosition, + this.output, + }); + + NameOpState copyWith({ + String? name, + OpName? type, + String? saltHex, + String? commitment, + String? value, + String? nameScriptHex, + int? outputPosition, + }) { + return NameOpState( + name: name ?? this.name, + type: type ?? this.type, + saltHex: saltHex ?? this.saltHex, + commitment: commitment ?? this.commitment, + value: value ?? this.value, + nameScriptHex: nameScriptHex ?? this.nameScriptHex, + outputPosition: outputPosition ?? this.outputPosition, + output: output, + ); + } + + @override + String toString() { + return "NameOpState(" + "name: $name, " + "type: ${type.name}, " + "saltHex: $saltHex, " + "commitment: $commitment, " + "value: $value, " + "nameScriptHex: $nameScriptHex, " + "outputPosition: $outputPosition, " + "output: $output)"; + } +} diff --git a/lib/wallets/models/tx_data.dart b/lib/wallets/models/tx_data.dart index 652a5605f..94474e390 100644 --- a/lib/wallets/models/tx_data.dart +++ b/lib/wallets/models/tx_data.dart @@ -8,6 +8,7 @@ import '../../models/paynym/paynym_account_lite.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/enums/fee_rate_type_enum.dart'; import '../isar/models/spark_coin.dart'; +import 'name_op_state.dart'; typedef TxRecipient = ({String address, Amount amount, bool isChange}); @@ -73,10 +74,16 @@ class TxData { final List? sparkMints; final List? usedSparkCoins; + // xelis specific + final String? otherData; + final TransactionV2? tempTx; final bool ignoreCachedBalanceChecks; + // Namecoin Name related + final NameOpState? opNameState; + TxData({ this.feeRateType, this.feeRateAmount, @@ -109,10 +116,12 @@ class TxData { this.mintsMapLelantus, this.tezosOperationsList, this.sparkRecipients, + this.otherData, this.sparkMints, this.usedSparkCoins, this.tempTx, this.ignoreCachedBalanceChecks = false, + this.opNameState, }); Amount? get amount { @@ -208,6 +217,7 @@ class TxData { String? note, String? noteOnChain, String? memo, + String? otherData, Set? utxos, List? usedUTXOs, List? recipients, @@ -239,6 +249,7 @@ class TxData { List? usedSparkCoins, TransactionV2? tempTx, bool? ignoreCachedBalanceChecks, + NameOpState? opNameState, }) { return TxData( feeRateType: feeRateType ?? this.feeRateType, @@ -252,6 +263,7 @@ class TxData { note: note ?? this.note, noteOnChain: noteOnChain ?? this.noteOnChain, memo: memo ?? this.memo, + otherData: otherData ?? this.otherData, utxos: utxos ?? this.utxos, usedUTXOs: usedUTXOs ?? this.usedUTXOs, recipients: recipients ?? this.recipients, @@ -277,6 +289,7 @@ class TxData { tempTx: tempTx ?? this.tempTx, ignoreCachedBalanceChecks: ignoreCachedBalanceChecks ?? this.ignoreCachedBalanceChecks, + opNameState: opNameState ?? this.opNameState, ); } @@ -314,7 +327,9 @@ class TxData { 'sparkRecipients: $sparkRecipients, ' 'sparkMints: $sparkMints, ' 'usedSparkCoins: $usedSparkCoins, ' + 'otherData: $otherData, ' 'tempTx: $tempTx, ' 'ignoreCachedBalanceChecks: $ignoreCachedBalanceChecks, ' + 'opNameState: $opNameState, ' '}'; } diff --git a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart index 313f1afb6..3beaa0e8d 100644 --- a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart @@ -56,10 +56,7 @@ class BitcoinFrostWallet extends Wallet required List participants, required int threshold, }) async { - Logging.instance.log( - "Generating new FROST wallet.", - level: LogLevel.Info, - ); + Logging.instance.i("Generating new FROST wallet."); try { final salt = frost @@ -85,18 +82,33 @@ class BitcoinFrostWallet extends Wallet await mainDB.isar.frostWalletInfo.put(frostWalletInfo); }); - final address = await _generateAddress( - change: 0, - index: kFrostSecureStartingIndex, - serializedKeys: serializedKeys, - secure: true, - ); + Address? address; + int index = kFrostSecureStartingIndex; + while (address == null) { + try { + address = await _generateAddress( + change: 0, + index: index, + serializedKeys: serializedKeys, + secure: true, + ); + } on FrostdartException catch (e) { + if (e.errorCode == 72) { + // rust doesn't like the addressDerivationData + index++; + continue; + } else { + rethrow; + } + } + } await mainDB.putAddresses([address]); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from initializeNewFrost(): $e\n$s", - level: LogLevel.Fatal, + Logging.instance.f( + "Exception rethrown from initializeNewFrost(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -612,10 +624,7 @@ class BitcoinFrostWallet extends Wallet // TODO: [prio=none] Check for special Bitcoin outputs like ordinals. } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it): $txData"); continue; } @@ -669,13 +678,13 @@ class BitcoinFrostWallet extends Wallet if (index >= someSaneMaximum) { throw Exception( - "index < kFrostSecureStartingIndex hit someSaneMaximum"); + "index < kFrostSecureStartingIndex hit someSaneMaximum", + ); } } else { - Logging.instance.log( + Logging.instance.f( "$runtimeType.checkSaveInitialReceivingAddress() failed due" " to missing serialized keys", - level: LogLevel.Fatal, ); } } @@ -684,12 +693,14 @@ class BitcoinFrostWallet extends Wallet @override Future confirmSend({required TxData txData}) async { try { - Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + Logging.instance.d("confirmSend txData: $txData"); final hex = txData.raw!; final txHash = await electrumXClient.broadcastTransaction(rawTx: hex); - Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + Logging.instance.d( + "Sent txHash: $txHash", + ); // mark utxos as used final usedUTXOs = txData.utxos!.map((e) => e.copyWith(used: true)); @@ -703,9 +714,10 @@ class BitcoinFrostWallet extends Wallet return txData; } catch (e, s) { - Logging.instance.log( - "Exception rethrown from confirmSend(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown from confirmSend(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -789,11 +801,14 @@ class BitcoinFrostWallet extends Wallet ).raw.toInt(), ); - Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); + Logging.instance.i("fetched fees: $feeObject"); return feeObject; - } catch (e) { - Logging.instance - .log("Exception rethrown from _getFees(): $e", level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e( + "Exception rethrown from _getFees(): $e", + error: e, + stackTrace: s, + ); rethrow; } } @@ -816,7 +831,7 @@ class BitcoinFrostWallet extends Wallet } if (serializedKeys == null || multisigConfig == null) { final err = "${info.coinName} wallet ${info.walletId} had null keys/cfg"; - Logging.instance.log(err, level: LogLevel.Fatal); + Logging.instance.f(err, stackTrace: StackTrace.current); throw Exception(err); // TODO [prio=low]: handle null keys or config. This should not happen. } @@ -943,10 +958,8 @@ class BitcoinFrostWallet extends Wallet unawaited(refresh()); } catch (e, s) { - Logging.instance.log( - "recoverFromSerializedKeys failed: $e\n$s", - level: LogLevel.Fatal, - ); + Logging.instance + .f("recoverFromSerializedKeys failed: ", error: e, stackTrace: s); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( WalletSyncStatus.unableToSync, @@ -1150,10 +1163,8 @@ class BitcoinFrostWallet extends Wallet return await mainDB.updateUTXOs(walletId, outputArray); } catch (e, s) { - Logging.instance.log( - "Output fetch unsuccessful: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("Output fetch unsuccessful: ", error: e, stackTrace: s); return false; } } @@ -1363,13 +1374,14 @@ class BitcoinFrostWallet extends Wallet final newNode = await _getCurrentElectrumXNode(); try { await electrumXClient.closeAdapter(); - } catch (e) { + } catch (e, s) { if (e.toString().contains("initialized")) { // Ignore. This should happen every first time the wallet is opened. } else { - Logging.instance.log( - "Error closing electrumXClient: $e", - level: LogLevel.Error, + Logging.instance.e( + "Error closing electrumXClient", + error: e, + stackTrace: s, ); } } @@ -1464,11 +1476,9 @@ class BitcoinFrostWallet extends Wallet await checkChangeAddressForTransactions(); } } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkChangeAddressForTransactions" - "($cryptoCurrency): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .i("Exception rethrown from _checkChangeAddressForTransactions" + "($cryptoCurrency): $e\n$s"); rethrow; } } @@ -1479,9 +1489,9 @@ class BitcoinFrostWallet extends Wallet try { throw Exception(); } catch (_, s) { - Logging.instance.log( + Logging.instance.e( "checkReceivingAddressForTransactions called but reuse address flag set: $s", - level: LogLevel.Error, + stackTrace: s, ); } } @@ -1511,10 +1521,11 @@ class BitcoinFrostWallet extends Wallet } } } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Exception rethrown from _checkReceivingAddressForTransactions" - "($cryptoCurrency): $e\n$s", - level: LogLevel.Error, + "($cryptoCurrency)", + error: e, + stackTrace: s, ); rethrow; } @@ -1734,9 +1745,8 @@ class BitcoinFrostWallet extends Wallet int gapCounter = 0; int index = secure ? kFrostSecureStartingIndex : 0; for (; gapCounter < 20; index++) { - Logging.instance.log( + Logging.instance.d( "Frost index: $index, \t GapCounter chain=$chain: $gapCounter", - level: LogLevel.Info, ); Address? address; @@ -1829,10 +1839,8 @@ class BitcoinFrostWallet extends Wallet return allTxHashes; } catch (e, s) { - Logging.instance.log( - "$runtimeType._fetchHistory: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("$runtimeType._fetchHistory: ", error: e, stackTrace: s); rethrow; } } diff --git a/lib/wallets/wallet/impl/bitcoincash_wallet.dart b/lib/wallets/wallet/impl/bitcoincash_wallet.dart index 3c8fb58bc..666d5fd6b 100644 --- a/lib/wallets/wallet/impl/bitcoincash_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoincash_wallet.dart @@ -198,9 +198,10 @@ class BitcoincashWallet valueStringSats = prevOut.valueStringSats; addresses.addAll(prevOut.addresses); } catch (e, s) { - Logging.instance.log( + Logging.instance.w( "Error getting prevOutJson: $e\nStack trace: $s", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); } } @@ -293,9 +294,11 @@ class BitcoincashWallet // only found outputs owned by this wallet type = TransactionType.incoming; } else { - Logging.instance.log( + Logging.instance.e( + "Unexpected tx found (ignoring it)", + ); + Logging.instance.d( "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, ); continue; } @@ -345,10 +348,17 @@ class BitcoincashWallet } } catch (e, s) { // Probably doesn't contain a cash token so just log failure - Logging.instance.log( + Logging.instance.w( + "Script pub key cash token" + " parsing check failed", + error: e, + stackTrace: s, + ); + Logging.instance.d( "Script pub key \"$scriptPubKeyHex\" cash token" " parsing check failed: $e\n$s", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); } diff --git a/lib/wallets/wallet/impl/cardano_wallet.dart b/lib/wallets/wallet/impl/cardano_wallet.dart index 2b9a8b9bb..3beafe5f5 100644 --- a/lib/wallets/wallet/impl/cardano_wallet.dart +++ b/lib/wallets/wallet/impl/cardano_wallet.dart @@ -76,10 +76,7 @@ class CardanoWallet extends Bip39Wallet { await mainDB.updateOrPutAddresses([address]); } } catch (e, s) { - Logging.instance.log( - "$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType checkSaveInitialReceivingAddress() failed: ", error: e, stackTrace: s); } } @@ -94,10 +91,7 @@ class CardanoWallet extends Bip39Wallet { return Future.value(health); } catch (e, s) { - Logging.instance.log( - "Error ping checking in cardano_wallet.dart: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error ping checking in cardano_wallet.dart: ", error: e, stackTrace: s); return Future.value(false); } } @@ -146,10 +140,7 @@ class CardanoWallet extends Bip39Wallet { slow: fee, ); } catch (e, s) { - Logging.instance.log( - "Error getting fees in cardano_wallet.dart: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error getting fees in cardano_wallet.dart: ", error: e, stackTrace: s); rethrow; } } @@ -264,10 +255,7 @@ class CardanoWallet extends Bip39Wallet { ); } } catch (e, s) { - Logging.instance.log( - "$runtimeType Cardano prepareSend failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType Cardano prepareSend failed: ", error: e, stackTrace: s); rethrow; } } @@ -355,10 +343,7 @@ class CardanoWallet extends Bip39Wallet { txid: sentTx, ); } catch (e, s) { - Logging.instance.log( - "$runtimeType Cardano confirmSend failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType Cardano confirmSend failed: ", error: e, stackTrace: s); rethrow; } } @@ -425,10 +410,7 @@ class CardanoWallet extends Bip39Wallet { await info.updateBalance(newBalance: balance, isar: mainDB.isar); } catch (e, s) { - Logging.instance.log( - "Error getting balance in cardano_wallet.dart: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error getting balance in cardano_wallet.dart: ", error: e, stackTrace: s); } } @@ -446,10 +428,7 @@ class CardanoWallet extends Bip39Wallet { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( - "Error updating transactions in cardano_wallet.dart: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error updating transactions in cardano_wallet.dart: ", error: e, stackTrace: s); } } @@ -581,10 +560,7 @@ class CardanoWallet extends Bip39Wallet { } on NodeTorMismatchConfigException { rethrow; } catch (e, s) { - Logging.instance.log( - "Error updating transactions in cardano_wallet.dart: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error updating transactions in cardano_wallet.dart: ", error: e, stackTrace: s); } } diff --git a/lib/wallets/wallet/impl/dash_wallet.dart b/lib/wallets/wallet/impl/dash_wallet.dart index bf1e91b9a..59fdaa037 100644 --- a/lib/wallets/wallet/impl/dash_wallet.dart +++ b/lib/wallets/wallet/impl/dash_wallet.dart @@ -212,7 +212,7 @@ class DashWallet extends Bip39HDWallet .fold(BigInt.zero, (value, element) => value + element); TransactionType type; - final TransactionSubType subType = TransactionSubType.none; + const TransactionSubType subType = TransactionSubType.none; // At least one input was owned by this wallet. if (wasSentFromThisWallet) { @@ -234,10 +234,8 @@ class DashWallet extends Bip39HDWallet // Only found outputs owned by this wallet. type = TransactionType.incoming; } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } diff --git a/lib/wallets/wallet/impl/dogecoin_wallet.dart b/lib/wallets/wallet/impl/dogecoin_wallet.dart index 8d2d2f029..e9046f959 100644 --- a/lib/wallets/wallet/impl/dogecoin_wallet.dart +++ b/lib/wallets/wallet/impl/dogecoin_wallet.dart @@ -214,7 +214,7 @@ class DogecoinWallet .fold(BigInt.zero, (value, element) => value + element); TransactionType type; - final TransactionSubType subType = TransactionSubType.none; + const TransactionSubType subType = TransactionSubType.none; // At least one input was owned by this wallet. if (wasSentFromThisWallet) { @@ -237,10 +237,8 @@ class DogecoinWallet // Only found outputs owned by this wallet. type = TransactionType.incoming; } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } diff --git a/lib/wallets/wallet/impl/ecash_wallet.dart b/lib/wallets/wallet/impl/ecash_wallet.dart index 968ea72cc..a8741ba21 100644 --- a/lib/wallets/wallet/impl/ecash_wallet.dart +++ b/lib/wallets/wallet/impl/ecash_wallet.dart @@ -276,10 +276,8 @@ class EcashWallet extends Bip39HDWallet // only found outputs owned by this wallet type = TransactionType.incoming; } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } @@ -332,10 +330,11 @@ class EcashWallet extends Bip39HDWallet } } catch (e, s) { // Probably doesn't contain a cash token so just log failure - Logging.instance.log( + Logging.instance.w( "Script pub key \"$scriptPubKeyHex\" cash token" " parsing check failed: $e\n$s", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); } diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 96d6b112e..10238cef6 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:decimal/decimal.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_libepiccash/lib.dart' as epiccash; import 'package:flutter_libepiccash/models/transaction.dart' as epic_models; import 'package:isar/isar.dart'; @@ -93,13 +92,12 @@ class EpiccashWallet extends Bip39Wallet { wallet: wallet, transactionId: txSlateId, ); - Logging.instance.log( + Logging.instance.d( "cancel $txSlateId result: $result", - level: LogLevel.Info, ); return result; } catch (e, s) { - Logging.instance.log("$e, $s", level: LogLevel.Error); + Logging.instance.e("", error: e, stackTrace: s); return e.toString(); } } @@ -192,18 +190,18 @@ class EpiccashWallet extends Bip39Wallet { (Decimal.parse(transactionFees.fee.toString())).toBigInt().toInt(); } catch (e, s) { //todo: come back to this - debugPrint("$e $s"); + Logging.instance.e("Error getting fees", error: e, stackTrace: s); } return realFee; } catch (e, s) { - Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); + Logging.instance.e("Error getting fees $e - $s", error: e, stackTrace: s); rethrow; } } Future _startSync() async { _hackedCheckTorNodePrefs(); - Logging.instance.log("request start sync", level: LogLevel.Info); + Logging.instance.d("request start sync"); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); const int refreshFromNode = 1; if (!syncMutex.isLocked) { @@ -216,7 +214,7 @@ class EpiccashWallet extends Bip39Wallet { ); }); } else { - Logging.instance.log("request start sync denied", level: LogLevel.Info); + Logging.instance.d("request start sync denied"); } } @@ -256,10 +254,11 @@ class EpiccashWallet extends Bip39Wallet { ); return response is String && response.contains("Challenge"); - } catch (_) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.w( "_testEpicBoxConnection failed on \"$host:$port\"", - level: LogLevel.Info, + error: e, + stackTrace: s, ); return false; } finally { @@ -288,8 +287,7 @@ class EpiccashWallet extends Bip39Wallet { ); return true; } catch (e, s) { - Logging.instance - .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); + Logging.instance.e("ERROR STORING ADDRESS", error: e, stackTrace: s); return false; } } @@ -301,7 +299,7 @@ class EpiccashWallet extends Bip39Wallet { // of the last one that has not been processed, or the index after the one most recently processed; return receivingIndex; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("$e $s", error: e, stackTrace: s); return 0; } } @@ -340,9 +338,8 @@ class EpiccashWallet extends Bip39Wallet { epicboxConfig: epicboxConfig.toString(), ); - Logging.instance.log( + Logging.instance.d( "WALLET_ADDRESS_IS $walletAddress", - level: LogLevel.Info, ); final address = Address( @@ -378,9 +375,8 @@ class EpiccashWallet extends Bip39Wallet { // loop while scanning in chain in chunks (of blocks?) while (lastScannedBlock < chainHeight) { - Logging.instance.log( + Logging.instance.d( "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", - level: LogLevel.Info, ); final int nextScannedBlock = await epiccash.LibEpiccash.scanOutputs( @@ -405,23 +401,17 @@ class EpiccashWallet extends Bip39Wallet { lastScannedBlock = nextScannedBlock; } - Logging.instance.log( - "_startScans successfully at the tip", - level: LogLevel.Info, - ); + Logging.instance.d("_startScans successfully at the tip"); //Once scanner completes restart listener await _listenToEpicbox(); } catch (e, s) { - Logging.instance.log( - "_startScans failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("_startScans failed: ", error: e, stackTrace: s); rethrow; } } Future _listenToEpicbox() async { - Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); + Logging.instance.d("STARTING WALLET LISTENER ...."); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); epiccash.LibEpiccash.startEpicboxListener( @@ -548,9 +538,8 @@ class EpiccashWallet extends Bip39Wallet { ); } else { try { - Logging.instance.log( + Logging.instance.d( "initializeExisting() ${cryptoCurrency.prettyName} wallet", - level: LogLevel.Info, ); final config = await _getRealConfig(); @@ -569,10 +558,8 @@ class EpiccashWallet extends Bip39Wallet { await updateNode(); } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance.log( - "$runtimeType init() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .w("$runtimeType init() failed: ", error: e, stackTrace: s); } } } @@ -635,10 +622,7 @@ class EpiccashWallet extends Bip39Wallet { txid: transaction.slateId, ); } catch (e, s) { - Logging.instance.log( - "Epic cash confirmSend: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Epic cash confirmSend: ", error: e, stackTrace: s); rethrow; } } @@ -679,8 +663,7 @@ class EpiccashWallet extends Bip39Wallet { fee: feeAmount, ); } catch (e, s) { - Logging.instance - .log("Epic cash prepareSend: $e\n$s", level: LogLevel.Error); + Logging.instance.e("Epic cash prepareSend", error: e, stackTrace: s); rethrow; } } @@ -701,7 +684,7 @@ class EpiccashWallet extends Bip39Wallet { isar: mainDB.isar, ); - unawaited(_startScans()); + unawaited(refresh(doScan: true)); } else { await updateNode(); final String password = generatePassword(); @@ -759,13 +742,13 @@ class EpiccashWallet extends Bip39Wallet { epicData.receivingIndex, ); } + unawaited(refresh(doScan: false)); }); - - unawaited(refresh()); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from electrumx_mixin recover(): $e\n$s", - level: LogLevel.Info, + Logging.instance.e( + "Exception rethrown from electrumx_mixin recover(): ", + error: e, + stackTrace: s, ); rethrow; @@ -773,7 +756,7 @@ class EpiccashWallet extends Bip39Wallet { } @override - Future refresh() async { + Future refresh({bool doScan = true}) async { // Awaiting this lock could be dangerous. // Since refresh is periodic (generally) if (refreshMutex.isLocked) { @@ -803,9 +786,11 @@ class EpiccashWallet extends Bip39Wallet { final int curAdd = await _getCurrentIndex(); await _generateAndStoreReceivingAddressForIndex(curAdd); - await _startScans(); + if (doScan) { + await _startScans(); - unawaited(_startSync()); + unawaited(_startSync()); + } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); await updateChainHeight(); @@ -864,7 +849,7 @@ class EpiccashWallet extends Bip39Wallet { // } }); } - } catch (error, strace) { + } catch (e, s) { GlobalEventBus.instance.fire( NodeConnectionStatusChangedEvent( NodeConnectionStatus.disconnected, @@ -879,9 +864,10 @@ class EpiccashWallet extends Bip39Wallet { cryptoCurrency, ), ); - Logging.instance.log( - "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Error, + Logging.instance.e( + "Caught exception in refreshWalletData()", + error: e, + stackTrace: s, ); } finally { refreshMutex.release(); @@ -917,9 +903,10 @@ class EpiccashWallet extends Bip39Wallet { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( - "Epic cash wallet failed to update balance: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Epic cash wallet failed to update balance: ", + error: e, + stackTrace: s, ); } } @@ -1057,10 +1044,11 @@ class EpiccashWallet extends Bip39Wallet { await mainDB.isar.transactionV2s.putAll(txns); }); } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "${cryptoCurrency.runtimeType} ${cryptoCurrency.network} net wallet" - " \"${info.name}\"_${info.walletId} updateTransactions() failed: $e\n$s", - level: LogLevel.Warning, + " \"${info.name}\"_${info.walletId} updateTransactions() failed", + error: e, + stackTrace: s, ); } } @@ -1104,7 +1092,11 @@ class EpiccashWallet extends Bip39Wallet { ) != null; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); + Logging.instance.e( + "", + error: e, + stackTrace: s, + ); return false; } } @@ -1157,10 +1149,11 @@ class EpiccashWallet extends Bip39Wallet { @override Future exit() async { + epiccash.LibEpiccash.stopEpicboxListener(); timer?.cancel(); timer = null; await super.exit(); - Logging.instance.log("EpicCash_wallet exit finished", level: LogLevel.Info); + Logging.instance.d("EpicCash_wallet exit finished"); } void _hackedCheckTorNodePrefs() { @@ -1214,7 +1207,7 @@ Future deleteEpicWallet({ config: config!, ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s", error: e, stackTrace: s); return "deleteEpicWallet($walletId) failed..."; } } diff --git a/lib/wallets/wallet/impl/ethereum_wallet.dart b/lib/wallets/wallet/impl/ethereum_wallet.dart index beb377229..e61da1df6 100644 --- a/lib/wallets/wallet/impl/ethereum_wallet.dart +++ b/lib/wallets/wallet/impl/ethereum_wallet.dart @@ -240,9 +240,10 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( - "$runtimeType wallet failed to update balance: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "$runtimeType wallet failed to update balance: ", + error: e, + stackTrace: s, ); } } @@ -258,9 +259,10 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( - "$runtimeType Exception caught in chainHeight: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "$runtimeType Exception caught in chainHeight: ", + error: e, + stackTrace: s, ); } } @@ -297,10 +299,9 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { ); if (response.value == null) { - Logging.instance.log( + Logging.instance.w( "Failed to refresh transactions for ${cryptoCurrency.prettyName} ${info.name} " "$walletId: ${response.exception}", - level: LogLevel.Warning, ); return; } @@ -407,10 +408,9 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { } await mainDB.updateOrPutTransactionV2s(txns); } else { - Logging.instance.log( + Logging.instance.w( "Failed to refresh transactions with nonces for ${cryptoCurrency.prettyName} " "${info.name} $walletId: ${txsResponse.exception}", - level: LogLevel.Warning, ); } } diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart index 1ff9999fe..aed72a33a 100644 --- a/lib/wallets/wallet/impl/firo_wallet.dart +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -60,9 +60,8 @@ class FiroWallet extends Bip39HDWallet if (txData.tempTx != null) { await mainDB.updateOrPutTransactionV2s([txData.tempTx!]); _unconfirmedTxids.add(txData.tempTx!.txid); - Logging.instance.log( + Logging.instance.d( "Added firo unconfirmed: ${txData.tempTx!.txid}", - level: LogLevel.Info, ); } return txData; @@ -93,33 +92,13 @@ class FiroWallet extends Bip39HDWallet .walletIdEqualToAnyLTagHash(walletId) .findAll(); - final Set sparkTxids = {}; - - for (final coin in sparkCoins) { - sparkTxids.add(coin.txHash); - // check for duplicates before adding to list - if (allTxHashes.indexWhere((e) => e["tx_hash"] == coin.txHash) == -1) { - final info = { - "tx_hash": coin.txHash, - "height": coin.height, - }; - allTxHashes.add(info); - } - } - - final missing = await getMissingSparkSpendTransactionIds(); - for (final txid in missing.map((e) => e.txid).toSet()) { - allTxHashes.add({ - "tx_hash": txid, - }); - } - final List> allTransactions = []; // some lelantus transactions aren't fetched via wallet addresses so they // will never show as confirmed in the gui. - final unconfirmedTransactions = await mainDB - .getTransactions(walletId) + final unconfirmedTransactions = await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) .filter() .heightIsNull() .findAll(); @@ -137,21 +116,54 @@ class FiroWallet extends Bip39HDWallet final info = { "tx_hash": tx.txid, "height": height, - "address": tx.address.value?.value, }; allTxHashes.add(info); } } - for (final txHash in allTxHashes) { - // final storedTx = await db - // .getTransactions(walletId) - // .filter() - // .txidEqualTo(txHash["tx_hash"] as String) - // .findFirst(); + final Set sparkTxids = {}; + for (final coin in sparkCoins) { + sparkTxids.add(coin.txHash); + // check for duplicates before adding to list + if (allTxHashes.indexWhere((e) => e["tx_hash"] == coin.txHash) == -1) { + final info = { + "tx_hash": coin.txHash, + "height": coin.height, + }; + allTxHashes.add(info); + } + } - // if (storedTx == null || - // !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final missing = await getSparkSpendTransactionIds(); + for (final txid in missing.map((e) => e.txid).toSet()) { + // check for duplicates before adding to list + if (allTxHashes.indexWhere((e) => e["tx_hash"] == txid) == -1) { + final info = { + "tx_hash": txid, + }; + allTxHashes.add(info); + } + } + + final currentHeight = await chainHeight; + + for (final txHash in allTxHashes) { + final storedTx = await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx?.isConfirmed( + currentHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + ) == + true) { + // tx already confirmed, no need to process it again + continue; + } // firod/electrumx seem to take forever to process spark txns so we'll // just ignore null errors and check again on next refresh. @@ -174,7 +186,6 @@ class FiroWallet extends Bip39HDWallet tx["height"] ??= txHash["height"]; allTransactions.add(tx); } - // } } final List txns = []; @@ -193,7 +204,6 @@ class FiroWallet extends Bip39HDWallet bool isMint = false; bool isJMint = false; bool isSparkMint = false; - final bool isMasterNodePayment = false; final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3; final bool isMySpark = sparkTxids.contains(txData["txid"] as String); final bool isMySpentSpark = @@ -210,17 +220,15 @@ class FiroWallet extends Bip39HDWallet ); if (isMySpark && sparkCoinsInvolvedReceived.isEmpty && !isMySpentSpark) { - Logging.instance.log( + Logging.instance.e( "sparkCoinsInvolvedReceived is empty and should not be! (ignoring tx parsing)", - level: LogLevel.Error, ); continue; } if (isMySpentSpark && sparkCoinsInvolvedSpent.isEmpty && !isMySpark) { - Logging.instance.log( + Logging.instance.e( "sparkCoinsInvolvedSpent is empty and should not be! (ignoring tx parsing)", - level: LogLevel.Error, ); continue; } @@ -237,15 +245,13 @@ class FiroWallet extends Bip39HDWallet } else if (asm.startsWith("OP_LELANTUSMINT")) { isMint = true; } else { - Logging.instance.log( + Logging.instance.d( "Unknown mint op code found for lelantusmint tx: ${txData["txid"]}", - level: LogLevel.Error, ); } } else { - Logging.instance.log( + Logging.instance.d( "ASM for lelantusmint tx: ${txData["txid"]} is null!", - level: LogLevel.Error, ); } } @@ -257,15 +263,13 @@ class FiroWallet extends Bip39HDWallet asm.startsWith("OP_SPARKSMINT")) { isSparkMint = true; } else { - Logging.instance.log( + Logging.instance.d( "Unknown mint op code found for sparkmint tx: ${txData["txid"]}", - level: LogLevel.Error, ); } } else { - Logging.instance.log( + Logging.instance.d( "ASM for sparkmint tx: ${txData["txid"]} is null!", - level: LogLevel.Error, ); } } @@ -559,10 +563,8 @@ class FiroWallet extends Bip39HDWallet // only found outputs owned by this wallet type = TransactionType.incoming; } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } @@ -671,7 +673,7 @@ class FiroWallet extends Bip39HDWallet // reset last checked values await info.updateOtherData( newEntries: { - WalletInfoKeys.firoSparkCacheSetTimestampCache: {}, + WalletInfoKeys.firoSparkCacheSetBlockHashCache: {}, }, isar: mainDB.isar, ); @@ -725,6 +727,7 @@ class FiroWallet extends Bip39HDWallet i, electrumXClient, cryptoCurrency.network, + null, ), ); } @@ -735,10 +738,7 @@ class FiroWallet extends Bip39HDWallet ); // receiving addresses - Logging.instance.log( - "checking receiving addresses...", - level: LogLevel.Info, - ); + Logging.instance.i("checking receiving addresses..."); final canBatch = await serverCanBatch; @@ -760,10 +760,7 @@ class FiroWallet extends Bip39HDWallet } // change addresses - Logging.instance.log( - "checking change addresses...", - level: LogLevel.Info, - ); + Logging.instance.d("checking change addresses..."); for (final type in cryptoCurrency.supportedDerivationPathTypes) { changeFutures.add( canBatch @@ -887,15 +884,15 @@ class FiroWallet extends Bip39HDWallet }); unawaited(refresh()); - Logging.instance.log( + Logging.instance.i( "Firo recover for " "${info.name}: ${DateTime.now().difference(start)}", - level: LogLevel.Info, ); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from electrumx_mixin recover(): $e\n$s", - level: LogLevel.Info, + Logging.instance.e( + "Exception rethrown from electrumx_mixin recover(): ", + error: e, + stackTrace: s, ); rethrow; diff --git a/lib/wallets/wallet/impl/litecoin_wallet.dart b/lib/wallets/wallet/impl/litecoin_wallet.dart index d301d0608..034056817 100644 --- a/lib/wallets/wallet/impl/litecoin_wallet.dart +++ b/lib/wallets/wallet/impl/litecoin_wallet.dart @@ -287,10 +287,8 @@ class LitecoinWallet // } } } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } diff --git a/lib/wallets/wallet/impl/monero_wallet.dart b/lib/wallets/wallet/impl/monero_wallet.dart index bcec63851..f1de036aa 100644 --- a/lib/wallets/wallet/impl/monero_wallet.dart +++ b/lib/wallets/wallet/impl/monero_wallet.dart @@ -61,11 +61,11 @@ class MoneroWallet extends LibMoneroWallet { bool walletExists(String path) => lib_monero.MoneroWallet.isWalletExist(path); @override - void loadWallet({ + Future loadWallet({ required String path, required String password, - }) { - libMoneroWallet = lib_monero.MoneroWallet.loadWallet( + }) async { + libMoneroWallet = await lib_monero.MoneroWallet.loadWallet( path: path, password: password, ); @@ -75,13 +75,28 @@ class MoneroWallet extends LibMoneroWallet { Future getCreatedWallet({ required String path, required String password, - }) async => - await lib_monero.MoneroWallet.create( - path: path, - password: password, - seedType: lib_monero.MoneroSeedType - .sixteen, // TODO: check we want to actually use 16 here - ); + required int wordCount, + }) async { + final lib_monero.MoneroSeedType type; + switch (wordCount) { + case 16: + type = lib_monero.MoneroSeedType.sixteen; + break; + + case 25: + type = lib_monero.MoneroSeedType.twentyFive; + break; + + default: + throw Exception("Invalid mnemonic word count: $wordCount"); + } + + return await lib_monero.MoneroWallet.create( + path: path, + password: password, + seedType: type, + ); + } @override Future getRestoredWallet({ diff --git a/lib/wallets/wallet/impl/namecoin_wallet.dart b/lib/wallets/wallet/impl/namecoin_wallet.dart index a401d6395..bf6f43e41 100644 --- a/lib/wallets/wallet/impl/namecoin_wallet.dart +++ b/lib/wallets/wallet/impl/namecoin_wallet.dart @@ -1,17 +1,56 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib; import 'package:isar/isar.dart'; +import 'package:namecoin/namecoin.dart'; -import '../../../models/isar/models/blockchain_data/address.dart'; -import '../../../models/isar/models/blockchain_data/transaction.dart'; import '../../../models/isar/models/blockchain_data/v2/input_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; +import '../../../models/isar/models/isar_models.dart'; +import '../../../models/signing_data.dart'; import '../../../utilities/amount/amount.dart'; +import '../../../utilities/enums/derive_path_type_enum.dart'; +import '../../../utilities/enums/fee_rate_type_enum.dart'; +import '../../../utilities/extensions/extensions.dart'; import '../../../utilities/logger.dart'; import '../../crypto_currency/crypto_currency.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; +import '../../models/name_op_state.dart'; +import '../../models/tx_data.dart'; import '../intermediate/bip39_hd_wallet.dart'; import '../wallet_mixin_interfaces/coin_control_interface.dart'; +import '../wallet_mixin_interfaces/cpfp_interface.dart'; import '../wallet_mixin_interfaces/electrumx_interface.dart'; +import '../wallet_mixin_interfaces/rbf_interface.dart'; + +const kNameWaitBlocks = blocksMinToRenewName; +const kNameTxVersion = 0x7100; +const kNameTxDefaultFeeRate = FeeRateType.slow; + +const kNameNewAmountSats = 150_0000; +const kNameAmountSats = 100_0000; + +String nameSaltKeyBuilder(String txid, String walletId, int txPos) { + if (txPos.isNegative) { + throw Exception("Invalid vout index"); + } + + return "${walletId}_${txid}_${txPos}nameSaltData"; +} + +String encodeNameSaltData(String name, String salt, String value) => + jsonEncode({"name": name, "salt": salt, "value": value}); + +({String salt, String name, String value}) decodeNameSaltData(String value) { + try { + final map = (jsonDecode(value) as Map).cast(); + return (salt: map["salt"]!, name: map["name"]!, value: map["value"]!); + } catch (_) { + throw Exception("Bad name salt data"); + } +} class NamecoinWallet extends Bip39HDWallet @@ -33,36 +72,131 @@ class NamecoinWallet @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = await mainDB - .getAddresses(walletId) - .filter() - .not() - .group( - (q) => q - .typeEqualTo(AddressType.nonWallet) - .or() - .subTypeEqualTo(AddressSubType.nonWallet), - ) - .findAll(); + final allAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(AddressType.nonWallet) + .or() + .subTypeEqualTo(AddressSubType.nonWallet), + ) + .findAll(); return allAddresses; } -// =========================================================================== + // =========================================================================== @override - Future< - ({ - bool blocked, - String? blockedReason, - String? utxoLabel, - })> checkBlockUTXO( + Future<({String? blockedReason, bool blocked, String? utxoLabel})> + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map jsonTX, String? utxoOwnerAddress, - ) async { - // Namecoin doesn't have special outputs like tokens, ordinals, etc. - return (blocked: false, blockedReason: null, utxoLabel: null); + ) { + throw UnsupportedError( + "Namecoin does not used the checkBlockUTXO() function. " + "Due to tight integration with names, output freezing is handled directly" + " in the overridden parseUTXO() function.", + ); + } + + @override + Future parseUTXO({required Map jsonUTXO}) async { + final txn = await electrumXCachedClient.getTransaction( + txHash: jsonUTXO["tx_hash"] as String, + verbose: true, + cryptoCurrency: cryptoCurrency, + ); + + final inputs = txn["vin"] as List? ?? []; + final isCoinbase = inputs.any((e) => (e as Map?)?["coinbase"] != null); + + final vout = jsonUTXO["tx_pos"] as int; + + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + + bool shouldBlock = false; + String? blockReason; + String? label; + String? otherDataString; + + for (final output in outputs) { + // find matching output + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + + // check for nameOp + if (output["scriptPubKey"]?["nameOp"] != null) { + // block/freeze regardless of whether parsing the raw data succeeds + shouldBlock = true; + blockReason = "Contains name"; + + try { + final rawNameOP = + (output["scriptPubKey"]["nameOp"] as Map) + .cast(); + + otherDataString = jsonEncode({ + UTXOOtherDataKeys.nameOpData: jsonEncode(rawNameOP), + }); + final nameOp = OpNameData(rawNameOP, jsonUTXO["height"] as int); + Logging.instance.i("nameOp:\n$nameOp"); + + switch (nameOp.op) { + case OpName.nameNew: + label = "Name New"; + break; + case OpName.nameFirstUpdate: + label = "Name First Update: ${nameOp.fullname}"; + break; + case OpName.nameUpdate: + label = "Name Update: ${nameOp.fullname}"; + break; + } + } catch (e, s) { + Logging.instance.w( + "Namecoin OpNameData failed to parse" + " \"${output["scriptPubKey"]?["nameOp"]}\"", + error: e, + stackTrace: s, + ); + label = "Failed to parse raw nameOp data"; + } + } + + break; + } + } + + final utxo = UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: label ?? "", + isBlocked: shouldBlock, + blockedReason: blockReason, + isCoinbase: + txn["is_coinbase"] as bool? ?? + txn["is-coinbase"] as bool? ?? + txn["iscoinbase"] as bool? ?? + isCoinbase, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + otherData: otherDataString, + ); + + return utxo; } @override @@ -89,30 +223,34 @@ class NamecoinWallet await fetchAddressesForElectrumXScan(); // Separate receiving and change addresses. - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => e.value) - .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => e.value) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => e.value) + .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => e.value) + .toSet(); // Remove duplicates. final allAddressesSet = {...receivingAddresses, ...changeAddresses}; // Fetch history from ElectrumX. - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); // Only parse new txs (not in db yet). final List> allTransactions = []; for (final txHash in allTxHashes) { // Check for duplicates by searching for tx by tx_hash in db. - final storedTx = await mainDB.isar.transactionV2s - .where() - .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) - .findFirst(); + final storedTx = + await mainDB.isar.transactionV2s + .where() + .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) + .findFirst(); if (storedTx == null || storedTx.height == null || @@ -125,8 +263,9 @@ class NamecoinWallet ); // Only tx to list once. - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] = txHash["height"]; allTransactions.add(tx); @@ -242,7 +381,7 @@ class NamecoinWallet .fold(BigInt.zero, (value, element) => value + element); TransactionType type; - final TransactionSubType subType = TransactionSubType.none; + const TransactionSubType subType = TransactionSubType.none; // At least one input was owned by this wallet. if (wasSentFromThisWallet) { @@ -264,10 +403,8 @@ class NamecoinWallet // Only found outputs owned by this wallet. type = TransactionType.incoming; } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } @@ -278,7 +415,8 @@ class NamecoinWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -292,4 +430,931 @@ class NamecoinWallet await mainDB.updateOrPutTransactionV2s(txns); } + + // namecoin names ============================================================ + + Future<({OpNameData? data, NameState nameState})> lookupName( + String name, + ) async { + // first check own utxos. Should only need to check NAME NEW here. + // NAME UPDATE and NAME FIRST UPDATE will appear readable from electrumx + final utxos = + await mainDB.getUTXOs(walletId).filter().otherDataIsNotNull().findAll(); + for (final utxo in utxos) { + final nameOp = getOpNameDataFrom(utxo); + if (nameOp?.op == OpName.nameNew) { + final sKey = nameSaltKeyBuilder(utxo.txid, walletId, utxo.vout); + + final encoded = await secureStorageInterface.read(key: sKey); + if (encoded == null) { + // seems this NAME NEW was created elsewhere + continue; + } + + final data = decodeNameSaltData(encoded); + + if (data.name == name) { + return (data: null, nameState: NameState.unavailable); + } + } + } + + bool available = false; + + final nameScriptHash = nameIdentifierToScriptHash(name); + + final historyWithName = await electrumXClient.getHistory( + scripthash: nameScriptHash, + ); + OpNameData? opNameData; + if (historyWithName.isNotEmpty) { + final txHeight = historyWithName.last["height"] as int; + final txHash = historyWithName.last["tx_hash"] as String; + + final txMap = await electrumXCachedClient.getTransaction( + txHash: txHash, + cryptoCurrency: cryptoCurrency, + ); + + try { + opNameData = OpNameData.fromTx(txMap, txHeight); + final isExpired = opNameData.expired(await chainHeight); + + Logging.instance.i("Name $opNameData \nis expired = $isExpired"); + available = isExpired; + } catch (_) { + available = false; // probably + } + } else { + Logging.instance.i("Name \"$name\" not found."); + available = true; + } + + return ( + data: opNameData, + nameState: available ? NameState.available : NameState.unavailable, + ); + } + + // TODO: handle this differently? + final Set<(int, String)> _unknownNameNewOutputs = {}; + + /// Must be called in refresh() AFTER the wallet's UTXOs have been updated! + Future checkAutoRegisterNameNewOutputs() async { + Logging.instance.t("$walletId checkAutoRegisterNameNewOutputs()"); + try { + final currentHeight = await chainHeight; + // not ideal filtering + final utxos = + await mainDB + .getUTXOs(walletId) + .filter() + .otherDataIsNotNull() + .and() + .blockHeightIsNotNull() + .and() + .blockHeightGreaterThan(0) + .and() + .blockHeightLessThan(currentHeight - kNameWaitBlocks) + .findAll(); + + Logging.instance.t( + "_unknownNameNewOutputs(count=${_unknownNameNewOutputs.length})" + ":\n$_unknownNameNewOutputs", + ); + + // check cache and remove known auto unspendable name new outputs + utxos.removeWhere( + (e) => _unknownNameNewOutputs.contains((e.vout, e.txid)), + ); + + for (final utxo in utxos) { + final nameOp = getOpNameDataFrom(utxo); + if (nameOp != null) { + Logging.instance.t("Found OpName: $nameOp\n\nIN UTXO: $utxo"); + + if (nameOp.op == OpName.nameNew) { + // at this point we should have an unspent UTXO that is at least + // 12 blocks old which we can now do nameFirstUpdate on + + //TODO: Should check if name was registered by someone else here + + final sKey = nameSaltKeyBuilder(utxo.txid, walletId, utxo.vout); + + final encoded = await secureStorageInterface.read(key: sKey); + if (encoded == null) { + Logging.instance.d( + "Found OpName NAME NEW utxo without local matching data." + "\nUTXO: $utxo" + "\nUnable to auto register.", + ); + _unknownNameNewOutputs.add((utxo.vout, utxo.txid)); + continue; + } + + final data = decodeNameSaltData(encoded); + + // verify cached matches + final myAddress = await mainDB.getAddress(walletId, utxo.address!); + final pk = await getPrivateKey(myAddress!); + final generatedSalt = scriptNameNew(data.name, pk.data).$2; + + // TODO replace assert with proper error + assert(generatedSalt == data.salt); + + final nameScriptHex = scriptNameFirstUpdate( + data.name, + data.value, + data.salt, + ); + + String noteName = + data.name.startsWith("d/") ? data.name.substring(2) : data.name; + if (!noteName.endsWith(".bit")) { + noteName += ".bit"; + } + + TxData txData = TxData( + utxos: {utxo}, + opNameState: NameOpState( + name: data.name, + saltHex: data.salt, + commitment: "n/a", + value: data.value, + nameScriptHex: nameScriptHex, + type: OpName.nameFirstUpdate, + output: utxo, + outputPosition: -1, //currently unknown, updated later + ), + note: "Purchase $noteName", + feeRateType: kNameTxDefaultFeeRate, // TODO: make configurable? + recipients: [ + ( + address: (await getCurrentReceivingAddress())!.value, + isChange: false, + amount: Amount( + rawValue: BigInt.from(kNameAmountSats), + fractionDigits: cryptoCurrency.fractionDigits, + ), + ), + ], + ); + + // generate tx + txData = await prepareNameSend(txData: txData); + + // broadcast tx + txData = await confirmSend(txData: txData); + + // clear out value from local secure storage on successful registration + await secureStorageInterface.delete(key: sKey); + } + } + } + } catch (e, s) { + Logging.instance.e( + "checkAutoRegisterNameNewOutputs() failed", + error: e, + stackTrace: s, + ); + } + } + + /// Builds and signs a transaction + Future _createNameTx({ + required TxData txData, + required List utxoSigningData, + required bool isForFeeCalcPurposesOnly, + }) async { + Logging.instance.d("Starting _createNameTx ----------"); + + assert(txData.recipients!.where((e) => !e.isChange).length == 1); + + if (!isForFeeCalcPurposesOnly) { + final nameAmount = + txData.recipients!.where((e) => !e.isChange).first.amount; + + switch (txData.opNameState!.type) { + case OpName.nameNew: + assert(nameAmount.raw == BigInt.from(kNameNewAmountSats)); + break; + case OpName.nameFirstUpdate || OpName.nameUpdate: + assert(nameAmount.raw == BigInt.from(kNameAmountSats)); + break; + } + } + + // temp tx data to show in gui while waiting for real data from server + final List tempInputs = []; + final List tempOutputs = []; + + final List prevOuts = []; + + coinlib.Transaction clTx = coinlib.Transaction( + version: kNameTxVersion, + inputs: [], + outputs: [], + ); + + // TODO: [prio=high]: check this opt in rbf + final sequence = + this is RbfInterface && (this as RbfInterface).flagOptInRBF + ? 0xffffffff - 10 + : 0xffffffff - 1; + + // Add transaction inputs + for (int i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + + final hash = Uint8List.fromList( + txid.toUint8ListFromHex.reversed.toList(), + ); + + final prevOutpoint = coinlib.OutPoint(hash, utxoSigningData[i].utxo.vout); + + final prevOutput = coinlib.Output.fromAddress( + BigInt.from(utxoSigningData[i].utxo.value), + coinlib.Address.fromString( + utxoSigningData[i].utxo.address!, + cryptoCurrency.networkParams, + ), + ); + + prevOuts.add(prevOutput); + + final coinlib.Input input; + + switch (utxoSigningData[i].derivePathType) { + case DerivePathType.bip44: + input = coinlib.P2PKHInput( + prevOut: prevOutpoint, + publicKey: utxoSigningData[i].keyPair!.publicKey, + sequence: sequence, + ); + + // TODO: fix this as it is (probably) wrong! + case DerivePathType.bip49: + throw Exception("TODO p2sh"); + // input = coinlib.P2SHMultisigInput( + // prevOut: prevOutpoint, + // program: coinlib.MultisigProgram.decompile( + // utxoSigningData[i].redeemScript!, + // ), + // sequence: sequence, + // ); + + case DerivePathType.bip84: + input = coinlib.P2WPKHInput( + prevOut: prevOutpoint, + publicKey: utxoSigningData[i].keyPair!.publicKey, + sequence: sequence, + ); + + case DerivePathType.bip86: + input = coinlib.TaprootKeyInput(prevOut: prevOutpoint); + + default: + throw UnsupportedError( + "Unknown derivation path type found: ${utxoSigningData[i].derivePathType}", + ); + } + + clTx = clTx.addInput(input); + + tempInputs.add( + InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: input.scriptSig.toHex, + scriptSigAsm: null, + sequence: sequence, + outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor( + txid: utxoSigningData[i].utxo.txid, + vout: utxoSigningData[i].utxo.vout, + ), + addresses: + utxoSigningData[i].utxo.address == null + ? [] + : [utxoSigningData[i].utxo.address!], + valueStringSats: utxoSigningData[i].utxo.value.toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ), + ); + } + + int? nameOpVoutIndex; + + int nonChangeCount = 0; // sanity check counter. Should only hit 1. + // Add transaction outputs + for (int i = 0; i < txData.recipients!.length; i++) { + final address = coinlib.Address.fromString( + normalizeAddress(txData.recipients![i].address), + cryptoCurrency.networkParams, + ); + + final coinlib.Output output; + + // there should only be 1 name output + if (!txData.recipients![i].isChange) { + nonChangeCount++; + if (nonChangeCount > 1) { + Logging.instance.d("Oddly formatted Name txData: $txData"); + throw Exception("Oddly formatted Name tx"); + } + final scriptPubKey = address.program.script.compiled; + output = coinlib.Output.fromScriptBytes( + txData.recipients![i].amount.raw, // should be 0.015 or 0.01 + Uint8List.fromList( + txData.opNameState!.nameScriptHex.toUint8ListFromHex + scriptPubKey, + ), + ); + // redundant sanity check + if (nameOpVoutIndex != null) { + throw Exception("More than one NAME OP output detected!"); + } + nameOpVoutIndex = i; + } else { + // change output + output = coinlib.Output.fromAddress( + txData.recipients![i].amount.raw, + address, + ); + } + + clTx = clTx.addOutput(output); + + tempOutputs.add( + OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "000000", + valueStringSats: txData.recipients![i].amount.raw.toString(), + addresses: [txData.recipients![i].address.toString()], + walletOwns: + (await mainDB.isar.addresses + .where() + .walletIdEqualTo(walletId) + .filter() + .valueEqualTo(txData.recipients![i].address) + .valueProperty() + .findFirst()) != + null, + ), + ); + } + + try { + // Sign the transaction accordingly + for (int i = 0; i < utxoSigningData.length; i++) { + final value = BigInt.from(utxoSigningData[i].utxo.value); + final key = utxoSigningData[i].keyPair!.privateKey; + + if (clTx.inputs[i] is coinlib.TaprootKeyInput) { + final taproot = coinlib.Taproot( + internalKey: utxoSigningData[i].keyPair!.publicKey, + ); + + clTx = clTx.signTaproot( + inputN: i, + key: taproot.tweakPrivateKey(key), + prevOuts: prevOuts, + ); + } else if (clTx.inputs[i] is coinlib.LegacyWitnessInput) { + clTx = clTx.signLegacyWitness(inputN: i, key: key, value: value); + } else if (clTx.inputs[i] is coinlib.LegacyInput) { + clTx = clTx.signLegacy(inputN: i, key: key); + } else if (clTx.inputs[i] is coinlib.TaprootSingleScriptSigInput) { + clTx = clTx.signTaprootSingleScriptSig( + inputN: i, + key: key, + prevOuts: prevOuts, + ); + } else { + throw Exception( + "Unable to sign input of type ${clTx.inputs[i].runtimeType}", + ); + } + } + } catch (e, s) { + Logging.instance.e( + "Caught exception while signing transaction: ", + error: e, + stackTrace: s, + ); + rethrow; + } + + if (nameOpVoutIndex == null) { + throw Exception("No NAME OP output detected!"); + } + + return txData.copyWith( + raw: clTx.toHex(), + vSize: clTx.vSize(), + opNameState: txData.opNameState!.copyWith( + outputPosition: nameOpVoutIndex, + ), + tempTx: TransactionV2( + walletId: walletId, + blockHash: null, + hash: clTx.hashHex, + txid: clTx.txid, + height: null, + timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, + inputs: List.unmodifiable(tempInputs), + outputs: List.unmodifiable(tempOutputs), + version: clTx.version, + type: + tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e) && + txData.paynymAccountLite == null + ? TransactionType.sentToSelf + : TransactionType.outgoing, + subType: TransactionSubType.none, + otherData: null, + ), + ); + } + + Future prepareNameSend({required TxData txData}) async { + try { + if (txData.amount == null) { + throw Exception("No recipients in attempted transaction!"); + } + + Logging.instance.t("prepareNameSend called with TxData:\n\n$txData"); + + final feeRateType = txData.feeRateType; + final customSatsPerVByte = txData.satsPerVByte; + final feeRateAmount = txData.feeRateAmount; + final utxos = txData.utxos; + + if (txData.note == null) { + txData = txData.copyWith( + note: "Name transaction ${txData.opNameState!.type.name}", + ); + } + + final bool coinControl = utxos != null; + + if (customSatsPerVByte != null) { + final result = await coinSelectionName( + txData: txData.copyWith(feeRateAmount: -1), + utxos: utxos?.toList(), + coinControl: coinControl, + ); + + Logging.instance.d("PREPARE NAME SEND RESULT: $result"); + + if (result.fee!.raw.toInt() < result.vSize!) { + throw Exception( + "Error in fee calculation: Transaction fee cannot be less than vSize", + ); + } + + return result; + } else if (feeRateType is FeeRateType || feeRateAmount is int) { + late final int rate; + if (feeRateType is FeeRateType) { + int fee = 0; + final feeObject = await fees; + switch (feeRateType) { + case FeeRateType.fast: + fee = feeObject.fast; + break; + case FeeRateType.average: + fee = feeObject.medium; + break; + case FeeRateType.slow: + fee = feeObject.slow; + break; + default: + throw ArgumentError("Invalid use of custom fee"); + } + rate = fee; + } else { + rate = feeRateAmount as int; + } + + final result = await coinSelectionName( + txData: txData.copyWith(feeRateAmount: rate), + utxos: utxos?.toList(), + coinControl: coinControl, + ); + + Logging.instance.d("prepare send: $result"); + if (result.fee!.raw.toInt() < result.vSize!) { + throw Exception( + "Error in fee calculation: Transaction fee (${result.fee!.raw.toInt()}) cannot " + "be less than vSize (${result.vSize})", + ); + } + + return result; + } else { + throw ArgumentError("Invalid fee rate argument provided!"); + } + } catch (e, s) { + Logging.instance.e( + "Exception rethrown from prepareNameSend(): ", + error: e, + stackTrace: s, + ); + rethrow; + } + } + + Future coinSelectionName({ + required TxData txData, + required bool coinControl, + int additionalOutputs = 0, + List? utxos, + }) async { + Logging.instance.d("Starting coinSelectionName ----------"); + + assert(txData.recipients!.length == 1); + + if (coinControl && utxos == null) { + throw Exception("Coin control used where utxos is null!"); + } + + if ((txData.opNameState!.type == OpName.nameFirstUpdate || + txData.opNameState!.type == OpName.nameUpdate) && + txData.opNameState!.output == null) { + throw Exception("Missing name output to update"); + } + + final recipientAddress = txData.recipients!.first.address; + final satoshiAmountToSend = txData.amount!.raw; + final int? satsPerVByte = txData.satsPerVByte; + final selectedTxFeeRate = txData.feeRateAmount!; + + final int expectedSatsValue; + switch (txData.opNameState!.type) { + case OpName.nameNew: + expectedSatsValue = kNameNewAmountSats; + break; + case OpName.nameFirstUpdate || OpName.nameUpdate: + expectedSatsValue = kNameAmountSats; + break; + } + + if (satoshiAmountToSend != BigInt.from(expectedSatsValue)) { + throw Exception( + "Invalid Name amount for ${txData.opNameState!.type}: ${txData.amount}", + ); + } + + final List availableOutputs = + utxos ?? await mainDB.getUTXOs(walletId).findAll(); + + if (txData.opNameState!.type == OpName.nameUpdate || + txData.opNameState!.type == OpName.nameFirstUpdate) { + // name output is added later + availableOutputs.removeWhere((e) => e == txData.opNameState!.output!); + } + + final currentChainHeight = await chainHeight; + + final canCPFP = this is CpfpInterface && coinControl; + + final spendableOutputs = + availableOutputs + .where( + (e) => + !e.isBlocked && + (e.used != true) && + (canCPFP || + e.isConfirmed( + currentChainHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + )), + ) + .toList(); + + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + // don't care about sorting if using all utxos + } else { + // sort spendable by age (oldest first) + spendableOutputs.sort( + (a, b) => (b.blockTime ?? currentChainHeight).compareTo( + (a.blockTime ?? currentChainHeight), + ), + ); + } + + // add name output to modify + if (txData.opNameState!.type == OpName.nameUpdate || + txData.opNameState!.type == OpName.nameFirstUpdate) { + spendableOutputs.insert(0, txData.opNameState!.output!); + } + + final spendableSatoshiValue = spendableOutputs.fold( + BigInt.zero, + (p, e) => p + BigInt.from(e.value), + ); + + if (spendableSatoshiValue < satoshiAmountToSend) { + throw Exception("Insufficient balance"); + } else if (spendableSatoshiValue == satoshiAmountToSend) { + throw Exception("Insufficient balance to pay transaction fee"); + } + Logging.instance.d( + "spendableOutputs.length: ${spendableOutputs.length}" + "\navailableOutputs.length: ${availableOutputs.length}" + "\nspendableOutputs: $spendableOutputs" + "\nspendableSatoshiValue: $spendableSatoshiValue" + "\nsatoshiAmountToSend: $satoshiAmountToSend", + ); + + BigInt satoshisBeingUsed = BigInt.zero; + int inputsBeingConsumed = 0; + final List utxoObjectsToUse = []; + + if (!coinControl) { + for ( + int i = 0; + satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; + i++ + ) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += BigInt.from(spendableOutputs[i].value); + inputsBeingConsumed += 1; + } + for ( + int i = 0; + i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; + i++ + ) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += BigInt.from( + spendableOutputs[inputsBeingConsumed].value, + ); + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse.addAll(spendableOutputs); + inputsBeingConsumed = spendableOutputs.length; + } + + Logging.instance.d( + "satoshisBeingUsed: $satoshisBeingUsed" + "\ninputsBeingConsumed: $inputsBeingConsumed" + "\nutxoObjectsToUse: $utxoObjectsToUse", + ); + + // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray + final List recipientsArray = [recipientAddress]; + final List recipientsAmtArray = [satoshiAmountToSend]; + + // gather required signing data + final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse); + + final int vSizeForOneOutput; + try { + vSizeForOneOutput = + (await _createNameTx( + utxoSigningData: utxoSigningData, + isForFeeCalcPurposesOnly: true, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + [recipientAddress], + [satoshisBeingUsed], + ), + ), + )).vSize!; + } catch (e, s) { + Logging.instance.e("vSizeForOneOutput: $e", error: e, stackTrace: s); + rethrow; + } + + final int vSizeForTwoOutPuts; + + BigInt maxBI(BigInt a, BigInt b) => a > b ? a : b; + + try { + vSizeForTwoOutPuts = + (await _createNameTx( + utxoSigningData: utxoSigningData, + isForFeeCalcPurposesOnly: true, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + [recipientAddress, (await getCurrentChangeAddress())!.value], + [ + satoshiAmountToSend, + maxBI(BigInt.zero, satoshisBeingUsed - satoshiAmountToSend), + ], + ), + ), + )).vSize!; + } catch (e, s) { + Logging.instance.e("vSizeForTwoOutPuts: $e", error: e, stackTrace: s); + rethrow; + } + + // Assume 1 output, only for recipient and no change + final feeForOneOutput = BigInt.from( + satsPerVByte != null + ? (satsPerVByte * vSizeForOneOutput) + : estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ), + ); + // Assume 2 outputs, one for recipient and one for change + final feeForTwoOutputs = BigInt.from( + satsPerVByte != null + ? (satsPerVByte * vSizeForTwoOutPuts) + : estimateTxFee( + vSize: vSizeForTwoOutPuts, + feeRatePerKB: selectedTxFeeRate, + ), + ); + + Logging.instance.d( + "feeForTwoOutputs: $feeForTwoOutputs" + "\nfeeForOneOutput: $feeForOneOutput", + ); + + final difference = satoshisBeingUsed - satoshiAmountToSend; + + Future _singleOutputTxn() async { + Logging.instance.d( + 'Input size: $satoshisBeingUsed' + '\nRecipient output size: $satoshiAmountToSend' + '\nFee being paid: $difference sats' + '\nEstimated fee: $feeForOneOutput', + ); + final txnData = await _createNameTx( + isForFeeCalcPurposesOnly: false, + utxoSigningData: utxoSigningData, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + recipientsArray, + recipientsAmtArray, + ), + ), + ); + return txnData.copyWith( + fee: Amount( + rawValue: feeForOneOutput, + fractionDigits: cryptoCurrency.fractionDigits, + ), + usedUTXOs: utxoSigningData.map((e) => e.utxo).toList(), + ); + } + + // no change output required + if (difference == feeForOneOutput) { + Logging.instance.d('1 output in tx'); + return await _singleOutputTxn(); + } else if (difference < feeForOneOutput) { + Logging.instance.w( + 'Cannot pay tx fee - checking for more outputs and trying again', + ); + // try adding more outputs + if (spendableOutputs.length > inputsBeingConsumed) { + return coinSelectionName( + txData: txData, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); + } + throw Exception("Insufficient balance to pay transaction fee"); + } else { + if (difference > (feeForOneOutput + cryptoCurrency.dustLimit.raw)) { + final changeOutputSize = difference - feeForTwoOutputs; + // check if possible to add the change output + if (changeOutputSize > cryptoCurrency.dustLimit.raw && + difference - changeOutputSize == feeForTwoOutputs) { + // generate new change address if current change address has been used + await checkChangeAddressForTransactions(); + final String newChangeAddress = + (await getCurrentChangeAddress())!.value; + + BigInt feeBeingPaid = difference - changeOutputSize; + + // add change output + recipientsArray.add(newChangeAddress); + recipientsAmtArray.add(changeOutputSize); + + Logging.instance.d( + '2 outputs in tx' + '\nInput size: $satoshisBeingUsed' + '\nRecipient output size: $satoshiAmountToSend' + '\nChange Output Size: $changeOutputSize' + '\nDifference (fee being paid): $feeBeingPaid sats' + '\nEstimated fee: $feeForTwoOutputs', + ); + + TxData txnData = await _createNameTx( + utxoSigningData: utxoSigningData, + isForFeeCalcPurposesOnly: false, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + recipientsArray, + recipientsAmtArray, + ), + ), + ); + + // make sure minimum fee is accurate if that is being used + if (BigInt.from(txnData.vSize!) - feeBeingPaid == BigInt.one) { + final changeOutputSize = difference - BigInt.from(txnData.vSize!); + feeBeingPaid = difference - changeOutputSize; + recipientsAmtArray.removeLast(); + recipientsAmtArray.add(changeOutputSize); + + Logging.instance.d( + '\nAdjusted Input size: $satoshisBeingUsed' + '\nAdjusted Recipient output size: $satoshiAmountToSend' + '\nAdjusted Change Output Size: $changeOutputSize' + '\nAdjusted Difference (fee being paid): $feeBeingPaid sats' + '\nAdjusted Estimated fee: $feeForTwoOutputs', + ); + + txnData = await _createNameTx( + utxoSigningData: utxoSigningData, + isForFeeCalcPurposesOnly: false, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + recipientsArray, + recipientsAmtArray, + ), + ), + ); + } + + return txnData.copyWith( + fee: Amount( + rawValue: feeBeingPaid, + fractionDigits: cryptoCurrency.fractionDigits, + ), + usedUTXOs: utxoSigningData.map((e) => e.utxo).toList(), + ); + } else { + // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize + // is smaller than or equal to cryptoCurrency.dustLimit. Revert to single output transaction. + Logging.instance.d('Reverting to 1 output in tx'); + + return await _singleOutputTxn(); + } + } + } + + return txData; + } + + @override + bool ignoreUtxoInBalance(UTXO utxo) { + if (getOpNameDataFrom(utxo) != null) { + // ignore name outputs in balance calculation + return true; + } + return false; + } + + /// return null if utxo does not contain name op + OpNameData? getOpNameDataFrom(UTXO utxo) { + if (utxo.otherData == null) { + return null; + } + final otherData = jsonDecode(utxo.otherData!) as Map; + if (otherData[UTXOOtherDataKeys.nameOpData] != null) { + try { + final nameOp = OpNameData( + (jsonDecode(otherData[UTXOOtherDataKeys.nameOpData] as String) as Map) + .cast(), + utxo.blockHeight!, + ); + return nameOp; + } catch (e, s) { + Logging.instance.d( + "getOpNameDataFrom($utxo) failed", + error: e, + stackTrace: s, + ); + return null; + } + } + return null; + } + + bool checkUtxoConfirmed(UTXO utxo, int currentChainHeight) { + final isNameOpOutput = getOpNameDataFrom(utxo) != null; + + final confirmedStatus = utxo.isConfirmed( + currentChainHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + overrideMinConfirms: isNameOpOutput ? kNameWaitBlocks : null, + ); + return confirmedStatus; + } } + +enum NameState { available, unavailable } diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart index 567700e6f..80db6e2ef 100644 --- a/lib/wallets/wallet/impl/particl_wallet.dart +++ b/lib/wallets/wallet/impl/particl_wallet.dart @@ -291,7 +291,7 @@ class ParticlWallet .fold(BigInt.zero, (value, element) => value + element); TransactionType type; - final TransactionSubType subType = TransactionSubType.none; + const TransactionSubType subType = TransactionSubType.none; // Particl has special outputs like confidential amounts. We can check // for them here. They're also checked in checkBlockUTXO. @@ -313,10 +313,8 @@ class ParticlWallet // Only found outputs owned by this wallet. type = TransactionType.incoming; } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } @@ -348,10 +346,7 @@ class ParticlWallet required TxData txData, required List utxoSigningData, }) async { - Logging.instance.log( - "Starting Particl buildTransaction ----------", - level: LogLevel.Info, - ); + Logging.instance.d("Starting Particl buildTransaction ----------"); // TODO: use coinlib (For this we need coinlib to support particl) @@ -523,10 +518,8 @@ class ParticlWallet ); } } catch (e, s) { - Logging.instance.log( - "Caught exception while signing transaction: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Caught exception while signing transaction: ", + error: e, stackTrace: s); rethrow; } @@ -540,9 +533,9 @@ class ParticlWallet String hexString = builtTx.toHex(isParticl: true).toString(); if (hexString.length % 2 != 0) { // Ensure the string has an even length. - Logging.instance.log( + Logging.instance.e( "Hex string has odd length, which is unexpected.", - level: LogLevel.Error, + stackTrace: StackTrace.current, ); throw Exception("Invalid hex string length."); } diff --git a/lib/wallets/wallet/impl/peercoin_wallet.dart b/lib/wallets/wallet/impl/peercoin_wallet.dart index e1d993584..153131415 100644 --- a/lib/wallets/wallet/impl/peercoin_wallet.dart +++ b/lib/wallets/wallet/impl/peercoin_wallet.dart @@ -272,10 +272,8 @@ class PeercoinWallet // Only found outputs owned by this wallet. type = TransactionType.incoming; } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } diff --git a/lib/wallets/wallet/impl/solana_wallet.dart b/lib/wallets/wallet/impl/solana_wallet.dart index 8c7f7e43b..8ae0db348 100644 --- a/lib/wallets/wallet/impl/solana_wallet.dart +++ b/lib/wallets/wallet/impl/solana_wallet.dart @@ -99,10 +99,7 @@ class SolanaWallet extends Bip39Wallet { await mainDB.updateOrPutAddresses([address]); } } catch (e, s) { - Logging.instance.log( - "$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType checkSaveInitialReceivingAddress() failed: ", error: e, stackTrace: s); } } @@ -157,10 +154,7 @@ class SolanaWallet extends Bip39Wallet { ), ); } catch (e, s) { - Logging.instance.log( - "$runtimeType Solana prepareSend failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType Solana prepareSend failed: ", error: e, stackTrace: s); rethrow; } } @@ -197,10 +191,7 @@ class SolanaWallet extends Bip39Wallet { txid: txid, ); } catch (e, s) { - Logging.instance.log( - "$runtimeType Solana confirmSend failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType Solana confirmSend failed: ", error: e, stackTrace: s); rethrow; } } @@ -259,10 +250,7 @@ class SolanaWallet extends Bip39Wallet { health = await _rpcClient?.getHealth(); return health != null; } catch (e, s) { - Logging.instance.log( - "$runtimeType Solana pingCheck failed \"health response=$health\": $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType Solana pingCheck failed \"health response=$health\": $e\n$s"); return Future.value(false); } } @@ -334,10 +322,7 @@ class SolanaWallet extends Bip39Wallet { await info.updateBalance(newBalance: newBalance, isar: mainDB.isar); } catch (e, s) { - Logging.instance.log( - "Error getting balance in solana_wallet.dart: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error getting balance in solana_wallet.dart: ", error: e, stackTrace: s); } } @@ -354,11 +339,8 @@ class SolanaWallet extends Bip39Wallet { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( - "Error occurred in solana_wallet.dart while getting" - " chain height for solana: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in solana_wallet.dart while getting" + " chain height for solana: $e\n$s"); } } @@ -458,11 +440,8 @@ class SolanaWallet extends Bip39Wallet { } on NodeTorMismatchConfigException { rethrow; } catch (e, s) { - Logging.instance.log( - "Error occurred in solana_wallet.dart while getting" - " transactions for solana: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Error occurred in solana_wallet.dart while getting" + " transactions for solana: $e\n$s"); } } diff --git a/lib/wallets/wallet/impl/stellar_wallet.dart b/lib/wallets/wallet/impl/stellar_wallet.dart index 8932a30fb..811858546 100644 --- a/lib/wallets/wallet/impl/stellar_wallet.dart +++ b/lib/wallets/wallet/impl/stellar_wallet.dart @@ -172,10 +172,10 @@ class StellarWallet extends Bip39Wallet { exists = true; } } catch (e, s) { - Logging.instance.log( - "Error getting account ${e.toString()} - ${s.toString()}", - level: LogLevel.Error, - ); + Logging.instance.e( + "Error getting account ${e.toString()} - ${s.toString()}", + error: e, + stackTrace: s); } return exists; } @@ -230,10 +230,10 @@ class StellarWallet extends Bip39Wallet { } } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance.log( - "$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e( + "$runtimeType checkSaveInitialReceivingAddress() failed: ", + error: e, + stackTrace: s); } } @@ -266,10 +266,8 @@ class StellarWallet extends Bip39Wallet { ), ); } catch (e, s) { - Logging.instance.log( - "$runtimeType prepareSend() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("$runtimeType prepareSend() failed: ", error: e, stackTrace: s); rethrow; } } @@ -327,7 +325,7 @@ class StellarWallet extends Bip39Wallet { txid: response.hash!, ); } catch (e, s) { - Logging.instance.log("Error sending TX $e - $s", level: LogLevel.Error); + Logging.instance.e("Error sending TX $e - $s", error: e, stackTrace: s); rethrow; } } @@ -394,10 +392,9 @@ class StellarWallet extends Bip39Wallet { // probably just doesn't have any history yet or whatever stellar needs return; } else { - Logging.instance.log( + Logging.instance.w( "$runtimeType ${info.name} $walletId " "failed to fetch account to updateBalance", - level: LogLevel.Warning, ); rethrow; } @@ -428,10 +425,11 @@ class StellarWallet extends Bip39Wallet { } } } catch (e, s) { - Logging.instance.log( + Logging.instance.w( "$runtimeType ${info.name} $walletId " "updateBalance() failed: $e\n$s", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); rethrow; } @@ -448,10 +446,8 @@ class StellarWallet extends Bip39Wallet { .then((value) => value.records!.first.sequence); await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar); } catch (e, s) { - Logging.instance.log( - "$runtimeType updateChainHeight() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType updateChainHeight() failed: ", + error: e, stackTrace: s); rethrow; } @@ -485,9 +481,8 @@ class StellarWallet extends Bip39Wallet { // probably just doesn't have any history yet or whatever stellar needs return; } else { - Logging.instance.log( + Logging.instance.w( "Stellar ${info.name} $walletId failed to fetch transactions", - level: LogLevel.Warning, ); rethrow; } @@ -676,10 +671,8 @@ class StellarWallet extends Bip39Wallet { await mainDB.updateOrPutTransactionV2s(transactionList); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from updateTransactions(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Exception rethrown from updateTransactions(): ", + error: e, stackTrace: s); rethrow; } } diff --git a/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart b/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart index 99d831f4b..845b0282b 100644 --- a/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart +++ b/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart @@ -153,10 +153,8 @@ class EthTokenWallet extends Wallet { usingContractAddress: contractAddress.hex, ); } catch (e, s) { - Logging.instance.log( - "$runtimeType _updateTokenABI(): $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance + .w("$runtimeType _updateTokenABI(): ", error: e, stackTrace: s); } try { @@ -200,10 +198,8 @@ class EthTokenWallet extends Wallet { _sendFunction = _deployedContract.function('transfer'); } catch (e, s) { - Logging.instance.log( - "$runtimeType wallet failed init(): $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance + .w("$runtimeType wallet failed init(): ", error: e, stackTrace: s); } } @@ -311,9 +307,9 @@ class EthTokenWallet extends Wallet { try { throw Exception(); } catch (_, s) { - Logging.instance.log( - "Eth token wallet recover called. This should not happen. Stacktrace: $s", - level: LogLevel.Warning, + Logging.instance.w( + "Eth token wallet recover called. This should not happen.", + stackTrace: s, ); } } @@ -347,15 +343,15 @@ class EthTokenWallet extends Wallet { isar: mainDB.isar, ); } else { - Logging.instance.log( + Logging.instance.w( "CachedEthTokenBalance.fetchAndUpdateCachedBalance failed: ${response.exception}", - level: LogLevel.Warning, ); } } catch (e, s) { - Logging.instance.log( - "$runtimeType wallet failed to update balance: $e\n$s", - level: LogLevel.Warning, + Logging.instance.e( + "$runtimeType wallet failed to update balance: ", + error: e, + stackTrace: s, ); } } @@ -380,9 +376,8 @@ class EthTokenWallet extends Wallet { if (response.exception != null && response.exception!.message .contains("response is empty but status code is 200")) { - Logging.instance.log( + Logging.instance.d( "No ${tokenContract.name} transfers found for $addressString", - level: LogLevel.Info, ); return; } @@ -415,13 +410,19 @@ class EthTokenWallet extends Wallet { extra: txExtra, ), ); - } catch (_) { + } catch (e, s) { // Server indexing failed for some reason. Instead of hard crashing or // showing no transactions we just skip it here. Not ideal but better // than nothing showing up - Logging.instance.log( + Logging.instance.e( + "Server error: Transaction hash not found.", + error: e, + stackTrace: s, + ); + Logging.instance.d( "Server error: Transaction ${tokenDto.transactionHash} not found.", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } } @@ -523,9 +524,10 @@ class EthTokenWallet extends Wallet { } await mainDB.updateOrPutTransactionV2s(txns); } catch (e, s) { - Logging.instance.log( - "$runtimeType wallet failed to update transactions: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "$runtimeType wallet failed to update transactions: ", + error: e, + stackTrace: s, ); } } diff --git a/lib/wallets/wallet/impl/tezos_wallet.dart b/lib/wallets/wallet/impl/tezos_wallet.dart index ab1448319..992a900ae 100644 --- a/lib/wallets/wallet/impl/tezos_wallet.dart +++ b/lib/wallets/wallet/impl/tezos_wallet.dart @@ -60,9 +60,10 @@ class TezosWallet extends Bip39Wallet { return Tezos.standardDerivationPath; } catch (e, s) { - Logging.instance.log( - "Error in _scanPossiblePaths() in tezos_wallet.dart: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Error in _scanPossiblePaths() in tezos_wallet.dart: ", + error: e, + stackTrace: s, ); rethrow; } @@ -144,9 +145,10 @@ class TezosWallet extends Bip39Wallet { return opList; } catch (e, s) { - Logging.instance.log( - "Error in _buildSendTransaction() in tezos_wallet.dart: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Error in _buildSendTransaction() in tezos_wallet.dart: ", + error: e, + stackTrace: s, ); rethrow; } @@ -164,9 +166,10 @@ class TezosWallet extends Bip39Wallet { } } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance.log( - "$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "$runtimeType checkSaveInitialReceivingAddress() failed: ", + error: e, + stackTrace: s, ); } } @@ -271,9 +274,10 @@ class TezosWallet extends Bip39Wallet { tezosOperationsList: opList, ); } catch (e, s) { - Logging.instance.log( - "Error in prepareSend() in tezos_wallet.dart: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Error in prepareSend() in tezos_wallet.dart: ", + error: e, + stackTrace: s, ); if (e @@ -338,16 +342,16 @@ class TezosWallet extends Bip39Wallet { } catch (e, s) { if (_estCount > 3) { _estCount = 0; - Logging.instance.log( - " Error in _estimate in tezos_wallet.dart: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + " Error in _estimate in tezos_wallet.dart: ", + error: e, + stackTrace: s, ); rethrow; } else { _estCount++; - Logging.instance.log( + Logging.instance.e( "_estimate() retry _estCount=$_estCount", - level: LogLevel.Warning, ); return await _estimate( account, @@ -386,9 +390,10 @@ class TezosWallet extends Bip39Wallet { return fee; } catch (e, s) { - Logging.instance.log( - " Error in estimateFeeFor() in tezos_wallet.dart: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + " Error in estimateFeeFor() in tezos_wallet.dart: ", + error: e, + stackTrace: s, ); rethrow; } @@ -499,9 +504,10 @@ class TezosWallet extends Bip39Wallet { await info.updateBalance(newBalance: newBalance, isar: mainDB.isar); } catch (e, s) { - Logging.instance.log( - "Error getting balance in tezos_wallet.dart: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Error getting balance in tezos_wallet.dart: ", + error: e, + stackTrace: s, ); } } @@ -523,10 +529,11 @@ class TezosWallet extends Bip39Wallet { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Error occurred in tezos_wallet.dart while getting" - " chain height for tezos: $e\n$s", - level: LogLevel.Error, + " chain height for tezos", + error: e, + stackTrace: s, ); } } diff --git a/lib/wallets/wallet/impl/wownero_wallet.dart b/lib/wallets/wallet/impl/wownero_wallet.dart index a33bd2da7..81407f837 100644 --- a/lib/wallets/wallet/impl/wownero_wallet.dart +++ b/lib/wallets/wallet/impl/wownero_wallet.dart @@ -97,11 +97,11 @@ class WowneroWallet extends LibMoneroWallet { lib_monero.WowneroWallet.isWalletExist(path); @override - void loadWallet({ + Future loadWallet({ required String path, required String password, - }) { - libMoneroWallet = lib_monero.WowneroWallet.loadWallet( + }) async { + libMoneroWallet = await lib_monero.WowneroWallet.loadWallet( path: path, password: password, ); @@ -111,14 +111,29 @@ class WowneroWallet extends LibMoneroWallet { Future getCreatedWallet({ required String path, required String password, - }) async => - await lib_monero.WowneroWallet.create( - path: path, - password: password, - seedType: lib_monero.WowneroSeedType - .fourteen, // TODO: check we want to actually use 14 here - overrideDeprecated14WordSeedException: true, - ); + required int wordCount, + }) async { + final lib_monero.WowneroSeedType type; + switch (wordCount) { + case 16: + type = lib_monero.WowneroSeedType.sixteen; + break; + + case 25: + type = lib_monero.WowneroSeedType.twentyFive; + break; + + default: + throw Exception("Invalid mnemonic word count: $wordCount"); + } + + return await lib_monero.WowneroWallet.create( + path: path, + password: password, + seedType: type, + overrideDeprecated14WordSeedException: true, + ); + } @override Future getRestoredWallet({ @@ -152,7 +167,7 @@ class WowneroWallet extends LibMoneroWallet { @override void invalidSeedLengthCheck(int length) { - if (!(length == 14 || length == 16 || length == 25)) { + if (!(length == 16 || length == 25)) { throw Exception("Invalid wownero mnemonic length found: $length"); } } diff --git a/lib/wallets/wallet/impl/xelis_wallet.dart b/lib/wallets/wallet/impl/xelis_wallet.dart new file mode 100644 index 000000000..352b076f7 --- /dev/null +++ b/lib/wallets/wallet/impl/xelis_wallet.dart @@ -0,0 +1,1012 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; + +import 'package:isar/isar.dart'; +import 'package:mutex/mutex.dart'; +import 'package:stack_wallet_backup/generate_password.dart'; +import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk; +import 'package:xelis_flutter/src/api/wallet.dart' as x_wallet; + +import '../../../models/balance.dart'; +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/isar/models/blockchain_data/transaction.dart'; +import '../../../models/isar/models/blockchain_data/v2/input_v2.dart'; +import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; +import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; +import '../../../models/paymint/fee_object_model.dart'; +import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import '../../../services/event_bus/global_event_bus.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/stack_file_system.dart'; +import '../../crypto_currency/crypto_currency.dart'; +import '../../models/tx_data.dart'; +import '../intermediate/lib_xelis_wallet.dart'; +import '../wallet.dart'; + +class XelisWallet extends LibXelisWallet { + Completer? _initCompleter; + + XelisWallet(CryptoCurrencyNetwork network) : super(Xelis(network)); + // ==================== Overrides ============================================ + + @override + int get isarTransactionVersion => 2; + + Future _restoreWallet() async { + final tablePath = await getPrecomputedTablesPath(); + final tableState = await getTableState(); + final xelisDir = await StackFileSystem.applicationXelisDirectory(); + final String name = walletId; + final String directory = xelisDir.path; + final password = await secureStorageInterface.read( + key: Wallet.mnemonicPassphraseKey(walletId: info.walletId), + ); + + final mnemonic = await getMnemonic(); + final seedLength = mnemonic.trim().split(" ").length; + + invalidSeedLengthCheck(seedLength); + + Logging.instance.i("Xelis: recovering wallet"); + final wallet = await x_wallet.createXelisWallet( + name: name, + directory: directory, + password: password!, + seed: mnemonic.trim(), + network: cryptoCurrency.network.xelisNetwork, + precomputedTablesPath: tablePath, + l1Low: tableState.currentSize.isLow, + ); + + await secureStorageInterface.write( + key: Wallet.mnemonicKey(walletId: walletId), + value: mnemonic.trim(), + ); + + libXelisWallet = wallet; + + await _finishInit(); + } + + Future _createNewWallet() async { + final tablePath = await getPrecomputedTablesPath(); + final tableState = await getTableState(); + final xelisDir = await StackFileSystem.applicationXelisDirectory(); + final String name = walletId; + final String directory = xelisDir.path; + final String password = generatePassword(); + + Logging.instance.d("Xelis: storing password"); + await secureStorageInterface.write( + key: Wallet.mnemonicPassphraseKey(walletId: info.walletId), + value: password, + ); + + final wallet = await x_wallet.createXelisWallet( + name: name, + directory: directory, + password: password, + network: cryptoCurrency.network.xelisNetwork, + precomputedTablesPath: tablePath, + l1Low: tableState.currentSize.isLow, + ); + + final mnemonic = await wallet.getSeed(); + await secureStorageInterface.write( + key: Wallet.mnemonicKey(walletId: walletId), + value: mnemonic.trim(), + ); + + libXelisWallet = wallet; + + await _finishInit(); + } + + Future _existingWallet() async { + Logging.instance.i("Xelis: opening existing wallet"); + final tablePath = await getPrecomputedTablesPath(); + final tableState = await getTableState(); + final xelisDir = await StackFileSystem.applicationXelisDirectory(); + final String name = walletId; + final String directory = xelisDir.path; + final password = await secureStorageInterface.read( + key: Wallet.mnemonicPassphraseKey(walletId: info.walletId), + ); + + libXelisWallet = await x_wallet.openXelisWallet( + name: name, + directory: directory, + password: password!, + network: cryptoCurrency.network.xelisNetwork, + precomputedTablesPath: tablePath, + l1Low: tableState.currentSize.isLow, + ); + + await _finishInit(); + } + + Future _finishInit() async { + if (await isTableUpgradeAvailable()) { + unawaited(updateTablesToDesiredSize()); + } + + final newReceivingAddress = + await getCurrentReceivingAddress() ?? + Address( + walletId: walletId, + derivationIndex: 0, + derivationPath: null, + value: libXelisWallet!.getAddressStr(), + publicKey: [], + type: AddressType.xelis, + subType: AddressSubType.receiving, + ); + + await mainDB.updateOrPutAddresses([newReceivingAddress]); + + if (info.cachedReceivingAddress != newReceivingAddress.value) { + await info.updateReceivingAddress( + newAddress: newReceivingAddress.value, + isar: mainDB.isar, + ); + } + } + + @override + Future init({bool? isRestore}) async { + Logging.instance.d("Xelis: init"); + + if (_initCompleter != null) { + await _initCompleter!.future; + return super.init(); + } + + _initCompleter = Completer(); + + try { + final bool walletExists = await LibXelisWallet.checkWalletExists( + walletId, + ); + + if (libXelisWallet == null) { + if (isRestore == true) { + await _restoreWallet(); + } else { + if (!walletExists) { + await _createNewWallet(); + } else { + await _existingWallet(); + } + } + } + _initCompleter!.complete(); + } catch (e, s) { + _initCompleter!.completeError(e); + Logging.instance.e( + "Xelis init() rethrowing error", + error: e, + stackTrace: s, + ); + rethrow; + } + + return super.init(); + } + + @override + Future recover({required bool isRescan}) async { + if (isRescan) { + await refreshMutex.protect(() async { + await mainDB.deleteWalletBlockchainData(walletId); + await updateTransactions(isRescan: true, topoheight: 0); + }); + return; + } + + // Borrowed from libmonero for now, need to refactor for Xelis view keys + // if (isViewOnly) { + // await recoverViewOnly(); + // return; + // } + + try { + await open(); + } catch (e, s) { + Logging.instance.e( + "Error rethrown from $runtimeType recover(isRescan: $isRescan)", + error: e, + stackTrace: s, + ); + rethrow; + } + } + + @override + Future pingCheck() async { + try { + await libXelisWallet!.getDaemonInfo(); + await handleOnline(); + return true; + } catch (_) { + await handleOffline(); + return false; + } + } + + final _balanceUpdateMutex = Mutex(); + + @override + Future updateBalance({int? newBalance}) async { + await _balanceUpdateMutex.protect(() async { + try { + if (await libXelisWallet!.hasXelisBalance()) { + final BigInt xelBalance = + newBalance != null + ? BigInt.from(newBalance) + : await libXelisWallet! + .getXelisBalanceRaw(); // in the future, use getAssetBalances and handle each + final balance = Balance( + total: Amount( + rawValue: xelBalance, + fractionDigits: cryptoCurrency.fractionDigits, + ), + spendable: Amount( + rawValue: xelBalance, + fractionDigits: cryptoCurrency.fractionDigits, + ), + blockedTotal: Amount.zeroWith( + fractionDigits: cryptoCurrency.fractionDigits, + ), + pendingSpendable: Amount.zeroWith( + fractionDigits: cryptoCurrency.fractionDigits, + ), + ); + await info.updateBalance(newBalance: balance, isar: mainDB.isar); + } + } catch (e, s) { + Logging.instance.e( + "Error in $runtimeType updateBalance()", + error: e, + stackTrace: s, + ); + } + }); + } + + Future _fetchChainHeight() async { + final infoString = await libXelisWallet!.getDaemonInfo(); + final Map nodeInfo = + (json.decode(infoString) as Map).cast(); + + pruningHeight = + int.tryParse(nodeInfo['pruned_topoheight']?.toString() ?? '0') ?? 0; + return int.parse(nodeInfo['topoheight'].toString()); + } + + @override + Future updateChainHeight({int? topoheight}) async { + try { + final height = topoheight ?? await _fetchChainHeight(); + + await info.updateCachedChainHeight( + newHeight: height.toInt(), + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.e( + "Error in $runtimeType updateChainHeight()", + error: e, + stackTrace: s, + ); + } + } + + @override + Future updateNode() async { + try { + final bool online = await libXelisWallet!.isOnline(); + if (online == true) { + await libXelisWallet!.offlineMode(); + } + await super.connect(); + } catch (e, s) { + Logging.instance.e( + "Error rethrown from $runtimeType updateNode()", + error: e, + stackTrace: s, + ); + rethrow; + } + } + + @override + Future> updateTransactions({ + bool isRescan = false, + List? objTransactions, + int? topoheight, + }) async { + checkInitialized(); + + final newReceivingAddress = + await getCurrentReceivingAddress() ?? + Address( + walletId: walletId, + derivationIndex: 0, + derivationPath: null, + value: libXelisWallet!.getAddressStr(), + publicKey: [], + type: AddressType.xelis, + subType: AddressSubType.receiving, + ); + + final thisAddress = newReceivingAddress.value; + + int firstBlock = 0; + if (!isRescan) { + firstBlock = + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .heightProperty() + .max() ?? + 0; + + if (firstBlock > 10) { + // add some buffer + firstBlock -= 10; + } + } else { + await libXelisWallet!.rescan(topoheight: BigInt.from(pruningHeight)); + } + + xelis_sdk.TransactionEntry _checkDecodeJsonStringTxEntry( + String jsonString, + ) { + final json = jsonDecode(jsonString); + if (json is Map) { + return xelis_sdk.TransactionEntry.fromJson(json.cast()); + } + + throw Exception("Not a Map on jsonDecode($jsonString)"); + } + + final txList = + objTransactions ?? + (await libXelisWallet!.allHistory()) + .map(_checkDecodeJsonStringTxEntry) + .toList(); + + final List txns = []; + + for (final transactionEntry in txList) { + try { + // Check for duplicates + final storedTx = + await mainDB.isar.transactionV2s + .where() + .txidWalletIdEqualTo(transactionEntry.hash, walletId) + .findFirst(); + + if (storedTx != null && + storedTx.height != null && + storedTx.height! > 0) { + continue; // Skip already processed transactions + } + + final List outputs = []; + final List inputs = []; + TransactionType? txType; + const TransactionSubType txSubType = TransactionSubType.none; + int? nonce; + Amount fee = Amount( + rawValue: BigInt.zero, + fractionDigits: cryptoCurrency.fractionDigits, + ); + final Map otherData = {}; + + final entryType = transactionEntry.txEntryType; + + if (entryType is xelis_sdk.CoinbaseEntry) { + final coinbase = entryType; + txType = TransactionType.incoming; + + final int decimals = await libXelisWallet!.getAssetDecimals( + asset: xelis_sdk.xelisAsset, + ); + + fee = Amount(rawValue: BigInt.zero, fractionDigits: cryptoCurrency.fractionDigits); + + outputs.add( + OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "", + valueStringSats: coinbase.reward.toString(), + addresses: [thisAddress], + walletOwns: true, + ), + ); + otherData['overrideFee'] = fee.toJsonString(); + } else if (entryType is xelis_sdk.BurnEntry) { + final burn = entryType; + txType = TransactionType.outgoing; + + final int decimals = await libXelisWallet!.getAssetDecimals( + asset: burn.asset, + ); + + fee = Amount( + rawValue: BigInt.from(burn.fee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + + inputs.add( + InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigAsm: null, + scriptSigHex: null, + sequence: null, + outpoint: null, + valueStringSats: burn.amount.toString(), + addresses: [thisAddress], + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ), + ); + + outputs.add( + OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "", + valueStringSats: burn.amount.toString(), + addresses: ['burn'], + walletOwns: false, + ), + ); + + otherData['burnAsset'] = burn.asset; + } else if (entryType is xelis_sdk.IncomingEntry) { + final incoming = entryType; + txType = + incoming.from == thisAddress + ? TransactionType.sentToSelf + : TransactionType.incoming; + + for (final transfer in incoming.transfers) { + final int decimals = await libXelisWallet!.getAssetDecimals( + asset: transfer.asset, + ); + + fee = Amount(rawValue: BigInt.zero, fractionDigits: cryptoCurrency.fractionDigits); + + outputs.add( + OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "", + valueStringSats: transfer.amount.toString(), + addresses: [thisAddress], + walletOwns: true, + ), + ); + + otherData['asset_${transfer.asset}'] = transfer.amount.toString(); + if (transfer.extraData != null) { + otherData['extraData_${transfer.asset}'] = + transfer.extraData!.toJson(); + } + otherData['overrideFee'] = fee.toJsonString(); + } + } else if (entryType is xelis_sdk.OutgoingEntry) { + final outgoing = entryType; + txType = TransactionType.outgoing; + nonce = outgoing.nonce; + + fee = Amount( + rawValue: BigInt.from(outgoing.fee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + + inputs.add( + InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + scriptSigAsm: null, + sequence: null, + outpoint: null, + addresses: [thisAddress], + valueStringSats: (outgoing.fee).toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ), + ); + + for (final transfer in outgoing.transfers) { + inputs.add( + InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + scriptSigAsm: null, + sequence: null, + outpoint: null, + addresses: [thisAddress], + valueStringSats: (transfer.amount).toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ), + ); + + outputs.add( + OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "", + valueStringSats: transfer.amount.toString(), + addresses: [transfer.destination], + walletOwns: false, + ), + ); + + otherData['asset_${transfer.asset}_amount'] = + transfer.amount.toString(); + if (transfer.extraData != null) { + otherData['extraData_${transfer.asset}'] = + transfer.extraData!.toJson(); + } + } + } else { + // Skip unknown entry types + continue; + } + + final txn = TransactionV2( + walletId: walletId, + blockHash: "", // Not provided in Xelis data + hash: transactionEntry.hash, + txid: transactionEntry.hash, + timestamp: + (transactionEntry.timestamp?.millisecondsSinceEpoch ?? 0) ~/ 1000, + height: transactionEntry.topoheight, + inputs: List.unmodifiable(inputs), + outputs: List.unmodifiable(outputs), + version: -1, // Version not provided + type: txType, + subType: txSubType, + otherData: jsonEncode({ + ...otherData, + if (nonce != null) 'nonce': nonce, + }), + ); + + // Logging.instance.log( + // "Entry done ${entryType.toString()}", + // level: LogLevel.Debug, + // ); + + txns.add(txn); + } catch (e, s) { + Logging.instance.w( + "Error in $runtimeType handling transaction: $transactionEntry", + error: e, + stackTrace: s, + ); + } + } + await updateBalance(); + + await mainDB.updateOrPutTransactionV2s(txns); + return txns.map((e) => e.txid).toList(); + } + + @override + Future updateUTXOs() async { + // not used in xel + return false; + } + + @override + Future checkSaveInitialReceivingAddress() async { + // do nothing + } + + @override + FilterOperation? get changeAddressFilterOperation => + throw UnimplementedError("Not used for $runtimeType"); + + @override + FilterOperation? get receivingAddressFilterOperation => + FilterGroup.and(standardReceivingAddressFilters); + + @override + Future get fees async { + // TODO: implement _getFees... maybe + return FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 10, + numberOfBlocksSlow: 10, + fast: 1, + medium: 1, + slow: 1, + ); + } + + @override + Future prepareSend({required TxData txData, String? assetId}) async { + try { + checkInitialized(); + + final recipients = + txData.recipients?.isNotEmpty == true + ? txData.recipients! + : throw ArgumentError( + 'Address cannot be empty.', + ); // in the future, support for multiple recipients will work. + + final asset = assetId ?? xelis_sdk.xelisAsset; + + // Calculate total send amount + final totalSendAmount = recipients.fold( + Amount( + rawValue: BigInt.zero, + fractionDigits: cryptoCurrency.fractionDigits, + ), + (sum, recipient) => sum + recipient.amount, + ); + + // Check balance using raw method + final xelBalance = await libXelisWallet!.getXelisBalanceRaw(); + final balance = Amount( + rawValue: xelBalance, + fractionDigits: cryptoCurrency.fractionDigits, + ); + + // Estimate fee using the shared method + final boostedFee = await estimateFeeFor( + totalSendAmount, + 1, + feeMultiplier: 1.0, + recipients: recipients, + assetId: asset, + ); + + // Check if we have enough for both transfers and fee + if (totalSendAmount + boostedFee > balance) { + final requiredAmt = await libXelisWallet!.formatCoin( + atomicAmount: (totalSendAmount + boostedFee).raw, + assetHash: asset, + ); + + final availableAmt = await libXelisWallet!.formatCoin( + atomicAmount: xelBalance, + assetHash: asset, + ); + + throw Exception( + "Insufficient balance to cover transfers and fees. " + "Required: $requiredAmt, Available: $availableAmt", + ); + } + + return txData.copyWith( + fee: boostedFee, + otherData: jsonEncode({'asset': asset}), + ); + } catch (_) { + // Logging.instance.log( + // "Exception rethrown from prepareSend(): $e\n$s", + // level: LogLevel.Error, + // ); + rethrow; + } + } + + @override + Future estimateFeeFor( + Amount amount, + int feeRate, { + double? feeMultiplier, + List recipients = const [], + String? assetId, + }) async { + try { + checkInitialized(); + final asset = assetId ?? xelis_sdk.xelisAsset; + + // Default values for a new wallet or when estimation fails + final defaultDecimals = cryptoCurrency.fractionDigits; + final defaultFee = BigInt.from(0); + + // Use default address if recipients list is empty to ensure basic fee estimates are readily available + final effectiveRecipients = + recipients.isNotEmpty + ? recipients + : [ + ( + address: + 'xel:xz9574c80c4xegnvurazpmxhw5dlg2n0g9qm60uwgt75uqyx3pcsqzzra9m', + amount: amount, + isChange: false, + ), + ]; + + try { + final transfers = await Future.wait( + effectiveRecipients.map((recipient) async { + try { + final amt = double.parse( + await libXelisWallet!.formatCoin( + atomicAmount: recipient.amount.raw, + assetHash: asset, + ), + ); + return x_wallet.Transfer( + floatAmount: amt, + strAddress: recipient.address, + assetHash: asset, + extraData: null, + ); + } catch (e, s) { + // Handle formatCoin error - use default conversion + Logging.instance.d( + "formatCoin failed, using fallback conversion", + error: e, + stackTrace: s, + ); + final rawAmount = recipient.amount.raw; + final floatAmount = + rawAmount / BigInt.from(10).pow(defaultDecimals); + return x_wallet.Transfer( + floatAmount: floatAmount.toDouble(), + strAddress: recipient.address, + assetHash: asset, + extraData: null, + ); + } + }), + ); + + final decimals = await libXelisWallet!.getAssetDecimals(asset: asset); + final estimatedFee = double.parse( + await libXelisWallet!.estimateFees(transfers: transfers), + ); + final rawFee = (estimatedFee * pow(10, decimals)).round(); + return Amount( + rawValue: BigInt.from(rawFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + } catch (e, s) { + Logging.instance.d( + "Fee estimation failed. Using fallback fee: $defaultFee", + error: e, + stackTrace: s, + ); + return Amount( + rawValue: defaultFee, + fractionDigits: cryptoCurrency.fractionDigits, + ); + } + } catch (_) { + // Logging.instance.log( + // "Exception rethrown from estimateFeeFor(): $e\n$s", + // level: LogLevel.Error, + // ); + rethrow; + } + } + + @override + Future confirmSend({required TxData txData}) async { + try { + checkInitialized(); + + // Validate recipients + if (txData.recipients == null || txData.recipients!.length != 1) { + throw Exception("$runtimeType confirmSend requires 1 recipient"); + } + + final recipient = txData.recipients!.first; + final Amount sendAmount = recipient.amount; + + final asset = + (txData.otherData != null + ? jsonDecode(txData.otherData!) + : null)?['asset'] + as String? ?? + xelis_sdk.xelisAsset; + + final amt = double.parse( + await libXelisWallet!.formatCoin( + atomicAmount: sendAmount.raw, + assetHash: asset, + ), + ); + + // Create a transfer transaction + final txJson = await libXelisWallet!.createTransfersTransaction( + transfers: [ + x_wallet.Transfer( + floatAmount: amt, + strAddress: recipient.address, + assetHash: asset, + extraData: null, // Add extra data if needed + ), + ], + ); + + final txMap = jsonDecode(txJson); + final txHash = txMap['hash'] as String; + + // Broadcast the transaction + await libXelisWallet!.broadcastTransaction(txHash: txHash); + + return await updateSentCachedTxData( + txData: txData.copyWith(txid: txHash), + ); + } catch (_) { + // Logging.instance.log( + // "Exception rethrown from confirmSend(): $e\n$s", + // level: LogLevel.Error, + // ); + rethrow; + } + } + + @override + Future handleEvent(Event event) async { + try { + switch (event) { + case NewTopoheight(:final height): + await handleNewTopoHeight(height); + case NewAsset(:final asset): + await handleNewAsset(asset); + case NewTransaction(:final transaction): + await handleNewTransaction(transaction); + case BalanceChanged(:final event): + await handleBalanceChanged(event); + case Rescan(:final startTopoheight): + await handleRescan(startTopoheight); + case Online(): + await handleOnline(); + case Offline(): + await handleOffline(); + case HistorySynced(:final topoheight): + await handleHistorySynced(topoheight); + } + } catch (e, s) { + Logging.instance.e( + "Error in $runtimeType handleEvent($event)", + error: e, + stackTrace: s, + ); + } + } + + @override + Future handleNewTopoHeight(int height) async { + await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar); + } + + @override + Future handleNewTransaction(xelis_sdk.TransactionEntry tx) async { + try { + final txList = [tx]; + final newTxIds = await updateTransactions( + isRescan: false, + objTransactions: txList, + ); + + await updateBalance(); + + // Logging.instance.log( + // "New transaction processed: ${newTxIds.first}", + // level: LogLevel.Info, + // ); + } catch (e, s) { + Logging.instance.e( + "Error in $runtimeType handleNewTransaction($tx)", + error: e, + stackTrace: s, + ); + } + } + + @override + Future handleBalanceChanged(xelis_sdk.BalanceChangedEvent event) async { + try { + final asset = event.assetHash; + if (asset == xelis_sdk.xelisAsset) { + await updateBalance(newBalance: event.balance); + } + + // TODO: Update asset balances if needed + } catch (e, s) { + Logging.instance.e( + "Error in $runtimeType handleBalanceChanged($event)", + error: e, + stackTrace: s, + ); + } + } + + @override + Future handleRescan(int startTopoheight) async { + await refreshMutex.protect(() async { + await mainDB.deleteWalletBlockchainData(walletId); + await updateTransactions(isRescan: true, topoheight: startTopoheight); + await updateBalance(); + }); + } + + @override + Future handleOnline() async { + await updateChainHeight(); + await updateBalance(); + await updateTransactions(); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + info.coin, + ), + ); + unawaited(refresh()); + } + + @override + Future handleOffline() async { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + info.coin, + ), + ); + } + + @override + Future handleHistorySynced(int topoheight) async { + await updateChainHeight(); + await updateBalance(); + await updateTransactions(); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + info.coin, + ), + ); + } + + @override + Future handleNewAsset(xelis_sdk.AssetData asset) async { + // TODO: Store asset information if needed + // TODO: Update UI/state for new asset + Logging.instance.d("New xelis asset detected: $asset"); + } + + @override + Future refresh({int? topoheight}) async { + await refreshMutex.protect(() async { + try { + final bool online = await libXelisWallet!.isOnline(); + if (online == true) { + await updateChainHeight(topoheight: topoheight); + await updateBalance(); + await updateTransactions(); + } else { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + info.coin, + ), + ); + } + } catch (e, s) { + Logging.instance.e( + "Error in $runtimeType refresh()", + error: e, + stackTrace: s, + ); + } + }); + } +} diff --git a/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart b/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart index 832ce3e13..777dda064 100644 --- a/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart +++ b/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart @@ -6,6 +6,7 @@ import 'package:isar/isar.dart'; import '../../../models/balance.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/isar/models/blockchain_data/utxo.dart'; import '../../../models/keys/view_only_wallet_data.dart'; import '../../../utilities/amount/amount.dart'; import '../../../utilities/enums/derive_path_type_enum.dart'; @@ -33,14 +34,19 @@ abstract class Bip39HDWallet extends Bip39Wallet return coinlib.HDPrivateKey.fromSeed(seed); } + Future getPrivateKey(Address address) async { + return (await getRootHDNode()) + .derivePath(address.derivationPath!.value) + .privateKey; + } + Future getPrivateKeyWIF(Address address) async { - final keys = - (await getRootHDNode()).derivePath(address.derivationPath!.value); + final privateKey = await getPrivateKey(address); final List data = [ cryptoCurrency.networkParams.wifPrefix, - ...keys.privateKey.data, - if (keys.privateKey.compressed) 1, + ...privateKey.data, + if (privateKey.compressed) 1, ]; final checksum = coinlib.sha256DoubleHash(Uint8List.fromList(data)).sublist(0, 4); @@ -189,6 +195,9 @@ abstract class Bip39HDWallet extends Bip39Wallet return address; } + /// If this function returns true, the UTXO will be ignored in displayed balance + bool ignoreUtxoInBalance(UTXO utxo) => false; + // ========== Private ======================================================== Future _viewOnlyPathHelper() async { @@ -324,6 +333,8 @@ abstract class Bip39HDWallet extends Bip39Wallet ); for (final utxo in utxos) { + if (ignoreUtxoInBalance(utxo)) continue; + final utxoAmount = Amount( rawValue: BigInt.from(utxo.value), fractionDigits: cryptoCurrency.fractionDigits, diff --git a/lib/wallets/wallet/intermediate/cryptonote_wallet.dart b/lib/wallets/wallet/intermediate/cryptonote_wallet.dart index 131bf8f04..0d32e07a3 100644 --- a/lib/wallets/wallet/intermediate/cryptonote_wallet.dart +++ b/lib/wallets/wallet/intermediate/cryptonote_wallet.dart @@ -2,8 +2,9 @@ import '../../crypto_currency/intermediate/cryptonote_currency.dart'; import '../wallet.dart'; import '../wallet_mixin_interfaces/coin_control_interface.dart'; import '../wallet_mixin_interfaces/mnemonic_interface.dart'; +import 'external_wallet.dart'; -abstract class CryptonoteWallet extends Wallet +abstract class CryptonoteWallet extends ExternalWallet with MnemonicInterface, CoinControlInterface { CryptonoteWallet(super.currency); } diff --git a/lib/wallets/wallet/intermediate/external_wallet.dart b/lib/wallets/wallet/intermediate/external_wallet.dart new file mode 100644 index 000000000..e5ce6b39b --- /dev/null +++ b/lib/wallets/wallet/intermediate/external_wallet.dart @@ -0,0 +1,12 @@ +import '../../crypto_currency/crypto_currency.dart'; +import '../wallet.dart'; + +// anstract class to be fleshed out for the standardization of wallet implementations +// that rely on bridged code libraries outside, or external native wallet functions +abstract class ExternalWallet extends Wallet { + ExternalWallet(super.currency); + + // wallet opening and initialization separated to prevent db lock collision errors + // must be overridden + Future open(); +} diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index 597853a62..b6b30de34 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -137,11 +137,12 @@ abstract class LibMoneroWallet int currentKnownChainHeight = 0; double highestPercentCached = 0; - void loadWallet({required String path, required String password}); + Future loadWallet({required String path, required String password}); Future getCreatedWallet({ required String path, required String password, + required int wordCount, }); Future getRestoredWallet({ @@ -163,6 +164,13 @@ abstract class LibMoneroWallet bool walletExists(String path); + String getTxKeyFor({required String txid}) { + if (libMoneroWallet == null) { + throw Exception("Cannot get tx key in uninitialized libMoneroWallet"); + } + return libMoneroWallet!.getTxKey(txid); + } + void _setListener() { if (libMoneroWallet != null && libMoneroWallet!.getListeners().isEmpty) { libMoneroWallet?.addListener( @@ -171,13 +179,18 @@ abstract class LibMoneroWallet onNewBlock: onNewBlock, onBalancesChanged: onBalancesChanged, onError: (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w( + "$e\n$s", + error: e, + stackTrace: s, + ); }, ), ); } } + @override Future open() async { bool wasNull = false; @@ -198,7 +211,7 @@ abstract class LibMoneroWallet throw Exception("Password not found $e, $s"); } - loadWallet(path: path, password: password); + await loadWallet(path: path, password: password); _setListener(); @@ -264,6 +277,10 @@ abstract class LibMoneroWallet addressIndex: index, ); + if (address.value.contains("111")) { + throw Exception("111 address found!"); + } + final newReceivingAddress = Address( walletId: walletId, derivationIndex: index, @@ -286,14 +303,24 @@ abstract class LibMoneroWallet if (base == null || (oldInfo != null && oldInfo.name != walletId)) { return null; } - - return CWKeyData( - walletId: walletId, - publicViewKey: base.getPublicViewKey(), - privateViewKey: base.getPrivateViewKey(), - publicSpendKey: base.getPublicSpendKey(), - privateSpendKey: base.getPrivateSpendKey(), - ); + try { + return CWKeyData( + walletId: walletId, + publicViewKey: base.getPublicViewKey(), + privateViewKey: base.getPrivateViewKey(), + publicSpendKey: base.getPublicSpendKey(), + privateSpendKey: base.getPrivateSpendKey(), + ); + } catch (e, s) { + Logging.instance.f("getKeys failed: ", error: e, stackTrace: s); + return CWKeyData( + walletId: walletId, + publicViewKey: "ERROR", + privateViewKey: "ERROR", + publicSpendKey: "ERROR", + privateSpendKey: "ERROR", + ); + } } Future<(String, String)> @@ -307,25 +334,32 @@ abstract class LibMoneroWallet } catch (e, s) { throw Exception("Password not found $e, $s"); } - loadWallet(path: path, password: password); + await loadWallet(path: path, password: password); final wallet = libMoneroWallet!; return (wallet.getAddress().value, wallet.getPrivateViewKey()); } @override - Future init({bool? isRestore}) async { + Future init({bool? isRestore, int? wordCount}) async { final path = await pathForWallet( name: walletId, type: compatType, ); if (!(walletExists(path)) && isRestore != true) { + if (wordCount == null) { + throw Exception("Missing word count for new xmr/wow wallet!"); + } try { final password = generatePassword(); await secureStorageInterface.write( key: lib_monero_compat.libMoneroWalletPasswordKey(walletId), value: password, ); - final wallet = await getCreatedWallet(path: path, password: password); + final wallet = await getCreatedWallet( + path: path, + password: password, + wordCount: wordCount, + ); final height = wallet.getRefreshFromBlockHeight(); @@ -345,7 +379,7 @@ abstract class LibMoneroWallet value: "", ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + Logging.instance.f("", error: e, stackTrace: s); } await updateNode(); } @@ -433,7 +467,8 @@ abstract class LibMoneroWallet isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + Logging.instance.f("", error: e, stackTrace: s); + rethrow; } await updateNode(); _setListener(); @@ -446,10 +481,8 @@ abstract class LibMoneroWallet libMoneroWallet?.startListeners(); libMoneroWallet?.startAutoSaving(); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverFromMnemonic(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Exception rethrown from recoverFromMnemonic(): ", + error: e, stackTrace: s); rethrow; } }); @@ -464,7 +497,8 @@ abstract class LibMoneroWallet Future updateNode() async { final node = getCurrentNode(); - final host = Uri.parse(node.host).host; + final host = + node.host.endsWith(".onion") ? node.host : Uri.parse(node.host).host; ({InternetAddress host, int port})? proxy; if (prefs.useTor) { if (node.clearnetEnabled && !node.torEnabled) { @@ -517,10 +551,8 @@ abstract class LibMoneroWallet _setSyncStatus(lib_monero_compat.ConnectedSyncStatus()); } catch (e, s) { _setSyncStatus(lib_monero_compat.FailedSyncStatus()); - Logging.instance.log( - "Exception caught in $runtimeType.updateNode(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Exception caught in $runtimeType.updateNode(): ", + error: e, stackTrace: s); } return; @@ -534,7 +566,26 @@ abstract class LibMoneroWallet return; } - final transactions = await base.getTxs(refresh: true); + final localTxids = await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .heightGreaterThan(0) + .txidProperty() + .findAll(); + + final allTxids = await base.getAllTxids(refresh: true); + + final txidsToFetch = allTxids.toSet().difference(localTxids.toSet()); + + if (txidsToFetch.isEmpty) { + return; + } + + final transactions = await base.getTxs( + txids: txidsToFetch, + refresh: false, + ); final allOutputs = await base.getOutputs(includeSpent: true, refresh: true); @@ -661,7 +712,7 @@ abstract class LibMoneroWallet fractionDigits: cryptoCurrency.fractionDigits, ); } else { - final transactions = await libMoneroWallet!.getTxs(refresh: true); + final transactions = await libMoneroWallet!.getAllTxs(refresh: true); BigInt transactionBalance = BigInt.zero; for (final tx in transactions) { if (!tx.isSpend) { @@ -683,6 +734,7 @@ abstract class LibMoneroWallet @override Future exit() async { + Logging.instance.i("exit called on $walletId"); libMoneroWallet?.stopAutoSaving(); libMoneroWallet?.stopListeners(); libMoneroWallet?.stopSyncing(); @@ -747,12 +799,21 @@ abstract class LibMoneroWallet void onBalancesChanged({ required BigInt newBalance, required BigInt newUnlockedBalance, - }) { - // do something? + }) async { + try { + await updateBalance(); + await updateTransactions(); + } catch (e, s) { + Logging.instance.w("onBalancesChanged(): ", error: e, stackTrace: s); + } } - void onNewBlock(int nodeHeight) { - // do something? + void onNewBlock(int nodeHeight) async { + try { + await updateTransactions(); + } catch (e, s) { + Logging.instance.w("onNewBlock(): ", error: e, stackTrace: s); + } } final _utxosUpdateLock = Mutex(); @@ -978,8 +1039,8 @@ abstract class LibMoneroWallet .findFirst(); final otherDataMap = { - "keyImage": rawUTXO.keyImage, - "spent": rawUTXO.spent, + UTXOOtherDataKeys.keyImage: rawUTXO.keyImage, + UTXOOtherDataKeys.spent: rawUTXO.spent, }; final utxo = UTXO( @@ -1100,10 +1161,8 @@ abstract class LibMoneroWallet isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( - "Exception in generateNewAddress(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("Exception in generateNewAddress(): ", error: e, stackTrace: s); } } @@ -1113,16 +1172,16 @@ abstract class LibMoneroWallet try { throw Exception(); } catch (_, s) { - Logging.instance.log( - "checkReceivingAddressForTransactions called but reuse address flag set: $s", - level: LogLevel.Error, - ); + Logging.instance.e( + "checkReceivingAddressForTransactions called but reuse address flag set: $s", + error: e, + stackTrace: s); } } try { int highestIndex = -1; - final entries = await libMoneroWallet?.getTxs(refresh: true); + final entries = await libMoneroWallet?.getAllTxs(refresh: true); if (entries != null) { for (final element in entries) { if (!element.isSpend) { @@ -1165,16 +1224,16 @@ abstract class LibMoneroWallet } } } on SocketException catch (se, s) { - Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", - level: LogLevel.Error, - ); + Logging.instance.e( + "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", + error: e, + stackTrace: s); return; } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e( + "Exception rethrown from _checkReceivingAddressForTransactions(): ", + error: e, + stackTrace: s); rethrow; } } @@ -1321,10 +1380,8 @@ abstract class LibMoneroWallet throw ArgumentError("Invalid fee rate argument provided!"); } } catch (e, s) { - Logging.instance.log( - "Exception rethrown from prepare send(): $e\n$s", - level: LogLevel.Info, - ); + Logging.instance.i("Exception rethrown from prepare send(): ", + error: e, stackTrace: s); if (e.toString().contains("Incorrect unlocked balance")) { throw Exception("Insufficient balance!"); @@ -1342,23 +1399,20 @@ abstract class LibMoneroWallet txData.pendingTransaction!, ); - Logging.instance.log( + Logging.instance.d( "transaction ${txData.pendingTransaction!.txid} has been sent", - level: LogLevel.Info, ); return txData.copyWith(txid: txData.pendingTransaction!.txid); } catch (e, s) { - Logging.instance.log( - "${info.name} ${compatType.name.toLowerCase()} confirmSend: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e( + "${info.name} ${compatType.name.toLowerCase()} confirmSend: ", + error: e, + stackTrace: s); rethrow; } } catch (e, s) { - Logging.instance.log( - "Exception rethrown from confirmSend(): $e\n$s", - level: LogLevel.Info, - ); + Logging.instance.e("Exception rethrown from confirmSend(): ", + error: e, stackTrace: s); rethrow; } } @@ -1434,10 +1488,8 @@ abstract class LibMoneroWallet libMoneroWallet?.startListeners(); libMoneroWallet?.startAutoSaving(); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverViewOnly(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Exception rethrown from recoverViewOnly(): ", + error: e, stackTrace: s); rethrow; } }); diff --git a/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart b/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart new file mode 100644 index 000000000..901223469 --- /dev/null +++ b/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart @@ -0,0 +1,438 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:isar/isar.dart'; +import 'package:mutex/mutex.dart'; +import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk; +import 'package:xelis_flutter/src/api/network.dart' as x_network; +import 'package:xelis_flutter/src/api/wallet.dart' as x_wallet; + +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/stack_file_system.dart'; +import '../../crypto_currency/crypto_currency.dart'; +import '../../crypto_currency/intermediate/electrum_currency.dart'; +import '../wallet_mixin_interfaces/mnemonic_interface.dart'; +import 'external_wallet.dart'; + +enum XelisTableSize { + low, + full; + + bool get isLow => this == XelisTableSize.low; + + static XelisTableSize get platformDefault { + if (kIsWeb) { + return XelisTableSize.low; + } + return XelisTableSize.full; + } +} + +class XelisTableState { + final bool isGenerating; + final XelisTableSize currentSize; + final XelisTableSize _desiredSize; + + XelisTableSize get desiredSize { + if (kIsWeb) { + return XelisTableSize.low; + } + return _desiredSize; + } + + const XelisTableState({ + this.isGenerating = false, + this.currentSize = XelisTableSize.low, + XelisTableSize desiredSize = XelisTableSize.full, + }) : _desiredSize = desiredSize; + + XelisTableState copyWith({ + bool? isGenerating, + XelisTableSize? currentSize, + XelisTableSize? desiredSize, + }) { + return XelisTableState( + isGenerating: isGenerating ?? this.isGenerating, + currentSize: currentSize ?? this.currentSize, + desiredSize: kIsWeb ? XelisTableSize.low : (desiredSize ?? _desiredSize), + ); + } + + factory XelisTableState.fromJson(Map json) { + return XelisTableState( + isGenerating: json['isGenerating'] as bool, + currentSize: XelisTableSize.values[json['currentSize'] as int], + desiredSize: XelisTableSize.values[json['desiredSize'] as int], + ); + } + + Map toJson() => { + 'isGenerating': isGenerating, + 'currentSize': currentSize.index, + 'desiredSize': _desiredSize.index, + }; +} + +extension XelisNetworkConversion on CryptoCurrencyNetwork { + x_network.Network get xelisNetwork { + switch (this) { + case CryptoCurrencyNetwork.main: + return x_network.Network.mainnet; + case CryptoCurrencyNetwork.test: + return x_network.Network.testnet; + default: + throw ArgumentError('Unsupported network type for Xelis: $this'); + } + } +} + +extension CryptoCurrencyNetworkConversion on x_network.Network { + CryptoCurrencyNetwork get cryptoCurrencyNetwork { + switch (this) { + case x_network.Network.mainnet: + return CryptoCurrencyNetwork.main; + case x_network.Network.testnet: + return CryptoCurrencyNetwork.test; + default: + throw ArgumentError('Unsupported Xelis network type: $this'); + } + } +} + +sealed class Event { + const Event(); +} + +final class NewTopoheight extends Event { + final int height; + const NewTopoheight(this.height); +} + +final class NewAsset extends Event { + final xelis_sdk.AssetData asset; + const NewAsset(this.asset); +} + +final class NewTransaction extends Event { + final xelis_sdk.TransactionEntry transaction; + const NewTransaction(this.transaction); +} + +final class BalanceChanged extends Event { + final xelis_sdk.BalanceChangedEvent event; + const BalanceChanged(this.event); +} + +final class Rescan extends Event { + final int startTopoheight; + const Rescan(this.startTopoheight); +} + +final class Online extends Event { + const Online(); +} + +final class Offline extends Event { + const Offline(); +} + +final class HistorySynced extends Event { + final int topoheight; + const HistorySynced(this.topoheight); +} + +abstract class LibXelisWallet + extends ExternalWallet + with MnemonicInterface { + LibXelisWallet(super.currency); + + static const String _kHasFullTablesKey = 'xelis_has_full_tables'; + static const String _kGeneratingTablesKey = 'xelis_generating_tables'; + static const String _kWantsFullTablesKey = 'xelis_wants_full_tables'; + static final _tableGenerationMutex = Mutex(); + static Completer? _tableGenerationCompleter; + + x_wallet.XelisWallet? libXelisWallet; + int pruningHeight = 0; + + x_wallet.XelisWallet? get wallet => libXelisWallet; + set wallet(x_wallet.XelisWallet? newWallet) { + if (newWallet == null && libXelisWallet != null) { + throw StateError('Cannot set wallet to null after initialization'); + } + libXelisWallet = newWallet; + } + + void checkInitialized() { + if (libXelisWallet == null) { + throw StateError('libXelisWallet not initialized'); + } + } + + final syncMutex = Mutex(); + Timer? timer; + + StreamSubscription? _eventSubscription; + + Future getPrecomputedTablesPath() async { + if (kIsWeb) { + return ""; + } else { + final appDir = await StackFileSystem.applicationXelisTableDirectory(); + return "${appDir.path}${Platform.pathSeparator}"; + } + } + + Future getTableState() async { + final hasFullTables = + await secureStorageInterface.read(key: _kHasFullTablesKey) == 'true'; + final isGenerating = + await secureStorageInterface.read(key: _kGeneratingTablesKey) == 'true'; + final wantsFull = + await secureStorageInterface.read(key: _kWantsFullTablesKey) != 'false'; + + return XelisTableState( + isGenerating: isGenerating, + currentSize: hasFullTables ? XelisTableSize.full : XelisTableSize.low, + desiredSize: wantsFull ? XelisTableSize.full : XelisTableSize.low, + ); + } + + Future setTableState(XelisTableState state) async { + await secureStorageInterface.write( + key: _kHasFullTablesKey, + value: state.currentSize == XelisTableSize.full ? 'true' : 'false', + ); + await secureStorageInterface.write( + key: _kGeneratingTablesKey, + value: state.isGenerating ? 'true' : 'false', + ); + await secureStorageInterface.write( + key: _kWantsFullTablesKey, + value: state.desiredSize == XelisTableSize.full ? 'true' : 'false', + ); + } + + Stream convertRawEvents() async* { + checkInitialized(); + final rawEventStream = libXelisWallet!.eventsStream(); + + await for (final rawData in rawEventStream) { + final json = jsonDecode(rawData); + try { + final eventType = xelis_sdk.WalletEvent.fromStr( + json['event'] as String, + ); + switch (eventType) { + case xelis_sdk.WalletEvent.newTopoHeight: + yield NewTopoheight(json['data']['topoheight'] as int); + case xelis_sdk.WalletEvent.newAsset: + yield NewAsset( + xelis_sdk.AssetData.fromJson( + json['data'] as Map, + ), + ); + case xelis_sdk.WalletEvent.newTransaction: + yield NewTransaction( + xelis_sdk.TransactionEntry.fromJson( + json['data'] as Map, + ), + ); + case xelis_sdk.WalletEvent.balanceChanged: + yield BalanceChanged( + xelis_sdk.BalanceChangedEvent.fromJson( + json['data'] as Map, + ), + ); + case xelis_sdk.WalletEvent.rescan: + yield Rescan(json['data']['start_topoheight'] as int); + case xelis_sdk.WalletEvent.online: + yield const Online(); + case xelis_sdk.WalletEvent.offline: + yield const Offline(); + case xelis_sdk.WalletEvent.historySynced: + yield HistorySynced(json['data']['topoheight'] as int); + } + } catch (e, s) { + Logging.instance.e( + "Error processing xelis wallet event: $rawData", + error: e, + stackTrace: s, + ); + continue; + } + } + } + + Future handleEvent(Event event) async {} + Future handleNewTopoHeight(int height); + Future handleNewTransaction(xelis_sdk.TransactionEntry tx); + Future handleBalanceChanged(xelis_sdk.BalanceChangedEvent event); + Future handleRescan(int startTopoheight) async {} + Future handleOnline() async {} + Future handleOffline() async {} + Future handleHistorySynced(int topoheight) async {} + Future handleNewAsset(xelis_sdk.AssetData asset) async {} + + @override + Future refresh({int? topoheight}); + + Future connect() async { + final node = getCurrentNode(); + try { + _eventSubscription = convertRawEvents().listen(handleEvent); + + Logging.instance.i("Connecting to node: ${node.host}:${node.port}"); + await libXelisWallet!.onlineMode( + daemonAddress: "${node.host}:${node.port}", + ); + await super.refresh(); + } catch (e, s) { + Logging.instance.e( + "rethrowing error connecting to node: $node", + error: e, + stackTrace: s, + ); + rethrow; + } + } + + List get standardReceivingAddressFilters => [ + FilterCondition.equalTo(property: r"type", value: info.mainAddressType), + const FilterCondition.equalTo( + property: r"subType", + value: AddressSubType.receiving, + ), + ]; + + List get standardChangeAddressFilters => [ + FilterCondition.equalTo(property: r"type", value: info.mainAddressType), + const FilterCondition.equalTo( + property: r"subType", + value: AddressSubType.change, + ), + ]; + + static Future checkWalletExists(String walletId) async { + final xelisDir = await StackFileSystem.applicationXelisDirectory(); + final walletDir = Directory( + "${xelisDir.path}${Platform.pathSeparator}$walletId", + ); + // TODO: should we check for certain files within the dir? + return await walletDir.exists(); + } + + @override + Future open() async { + try { + await connect(); + } catch (e) { + // Logging.instance.log( + // "Failed to start sync: $e", + // level: LogLevel.Error, + // ); + rethrow; + } + unawaited(refresh()); + } + + @override + Future exit() async { + await refreshMutex.protect(() async { + timer?.cancel(); + timer = null; + + await _eventSubscription?.cancel(); + _eventSubscription = null; + + await libXelisWallet?.offlineMode(); + await super.exit(); + }); + } + + void invalidSeedLengthCheck(int length) { + if (!(length == 25)) { + throw Exception("Invalid Xelis mnemonic length found: $length"); + } + } +} + +extension XelisTableManagement on LibXelisWallet { + Future isTableUpgradeAvailable() async { + if (kIsWeb) return false; + + final state = await getTableState(); + return state.currentSize != state.desiredSize; + } + + Future updateTablesToDesiredSize() async { + if (kIsWeb) return; + + await Future.delayed(const Duration(seconds: 1)); + if (LibXelisWallet._tableGenerationCompleter != null) { + try { + await LibXelisWallet._tableGenerationCompleter!.future; + return; + } catch (_) { + // Previous generation failed, we'll try again + } + } + + await LibXelisWallet._tableGenerationMutex.protect(() async { + // Check again after acquiring mutex + if (LibXelisWallet._tableGenerationCompleter != null) { + try { + await LibXelisWallet._tableGenerationCompleter!.future; + return; + } catch (_) { + // Previous generation failed, we'll try again + } + } + + final state = await getTableState(); + if (state.currentSize == state.desiredSize) return; + + LibXelisWallet._tableGenerationCompleter = Completer(); + await setTableState(state.copyWith(isGenerating: true)); + + try { + Logging.instance.i("Xelis: Generating large tables in background"); + + final tablePath = await getPrecomputedTablesPath(); + await x_wallet.updateTables( + precomputedTablesPath: tablePath, + l1Low: state.desiredSize.isLow, + ); + + await setTableState( + XelisTableState( + isGenerating: false, + currentSize: state.desiredSize, + desiredSize: state.desiredSize, + ), + ); + + Logging.instance.i("Xelis: Table upgrade done"); + LibXelisWallet._tableGenerationCompleter!.complete(); + } catch (e) { + // Logging.instance.log( + // "Failed to update tables: $e\n$s", + // level: LogLevel.Error, + // ); + await setTableState(state.copyWith(isGenerating: false)); + + LibXelisWallet._tableGenerationCompleter!.completeError(e); + } finally { + if (!LibXelisWallet._tableGenerationCompleter!.isCompleted) { + LibXelisWallet._tableGenerationCompleter!.completeError( + Exception('Table generation abandoned'), + ); + } + LibXelisWallet._tableGenerationCompleter = null; + } + }); + } +} diff --git a/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart b/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart index d913df779..c11eb2fbc 100644 --- a/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart +++ b/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:isar/isar.dart'; + import '../../../utilities/logger.dart'; import '../../isar/models/wallet_info.dart'; @@ -17,10 +18,7 @@ extension EpiccashWalletInfoExtension on WalletInfo { ), ); } catch (e, s) { - Logging.instance.log( - "ExtraEpiccashWalletInfo.fromMap failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("ExtraEpiccashWalletInfo.fromMap failed: ", error: e, stackTrace: s); return null; } } diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 0beaf2a80..be21c5282 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -47,6 +47,7 @@ import 'impl/stellar_wallet.dart'; import 'impl/sub_wallets/eth_token_wallet.dart'; import 'impl/tezos_wallet.dart'; import 'impl/wownero_wallet.dart'; +import 'impl/xelis_wallet.dart'; import 'intermediate/cryptonote_wallet.dart'; import 'wallet_mixin_interfaces/electrumx_interface.dart'; import 'wallet_mixin_interfaces/lelantus_interface.dart'; @@ -126,7 +127,11 @@ abstract class Wallet { await updateChainHeight(); } catch (e, s) { // do nothing on failure (besides logging) - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w( + "$e\n$s", + error: e, + stackTrace: s, + ); } // return regardless of whether it was updated or not as we want a @@ -168,8 +173,8 @@ abstract class Wallet { value: viewOnlyData!.toJsonEncodedString(), ); } else if (wallet is MnemonicInterface) { - if (wallet is CryptonoteWallet) { - // currently a special case due to the xmr/wow libraries handling their + if (wallet is CryptonoteWallet || wallet is XelisWallet) { // + // currently a special case due to the xmr/wow/xelis libraries handling their // own mnemonic generation on new wallet creation // if its a restore we must set them if (mnemonic != null) { @@ -238,6 +243,13 @@ abstract class Wallet { .walletIdEqualTo(walletId) .findFirst(); + Logging.instance.i( + "Wallet.load loading" + " $walletId " + "${walletInfo?.coin.identifier}" + " ${walletInfo?.name}", + ); + if (walletInfo == null) { throw Exception( "WalletInfo not found for $walletId when trying to call Wallet.load()", @@ -395,6 +407,9 @@ abstract class Wallet { case const (Wownero): return WowneroWallet(net); + case const (Xelis): + return XelisWallet(net); + default: // should never hit in reality throw Exception("Unknown crypto currency: ${walletInfo.coin}"); @@ -415,6 +430,11 @@ abstract class Wallet { } void _periodicPingCheck() async { + if (refreshMutex.isLocked) { + // should be active calls happening so no need to make extra work + return; + } + final bool hasNetwork = await pingCheck(); if (_isConnected != hasNetwork) { @@ -532,7 +552,7 @@ abstract class Wallet { }); } }, - onError: (Object error, StackTrace strace) { + onError: (Object e, StackTrace s) { GlobalEventBus.instance.fire( NodeConnectionStatusChangedEvent( NodeConnectionStatus.disconnected, @@ -547,9 +567,10 @@ abstract class Wallet { cryptoCurrency, ), ); - Logging.instance.log( - "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Error, + Logging.instance.e( + "Caught exception in refreshWalletData()", + error: e, + stackTrace: s, ); }, ); @@ -559,6 +580,13 @@ abstract class Wallet { return future; } + void _fireRefreshPercentChange(double percent) { + if (this is ElectrumXInterface) { + (this as ElectrumXInterface?)?.refreshingPercent = percent; + } + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(percent, walletId)); + } + // Should fire events Future _refresh(Completer completer) async { // Awaiting this lock could be dangerous. @@ -568,22 +596,6 @@ abstract class Wallet { } final start = DateTime.now(); - bool tAlive = true; - final t = Timer.periodic(const Duration(seconds: 1), (timer) async { - if (tAlive) { - final pingSuccess = await pingCheck(); - if (!pingSuccess) { - tAlive = false; - } - } else { - timer.cancel(); - } - }); - - void _checkAlive() { - if (!tAlive) throw Exception("refresh alive ping failure"); - } - final viewOnly = this is ViewOnlyOptionInterface && (this as ViewOnlyOptionInterface).isViewOnly; @@ -600,61 +612,47 @@ abstract class Wallet { ), ); - _checkAlive(); - // add some small buffer before making calls. // this can probably be removed in the future but was added as a // debugging feature await Future.delayed(const Duration(milliseconds: 300)); - _checkAlive(); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. final Set codesToCheck = {}; - _checkAlive(); if (this is PaynymInterface && !viewOnly) { // isSegwit does not matter here at all final myCode = await (this as PaynymInterface).getPaymentCode(isSegwit: false); - _checkAlive(); final nym = await PaynymIsApi().nym(myCode.toString()); - _checkAlive(); if (nym.value != null) { for (final follower in nym.value!.followers) { codesToCheck.add(follower.code); } - _checkAlive(); for (final following in nym.value!.following) { codesToCheck.add(following.code); } } - _checkAlive(); } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); - _checkAlive(); + _fireRefreshPercentChange(0); await updateChainHeight(); - _checkAlive(); if (this is BitcoinFrostWallet) { await (this as BitcoinFrostWallet).lookAhead(); } - _checkAlive(); - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + _fireRefreshPercentChange(0.1); - _checkAlive(); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is MultiAddressInterface) { if (info.otherData[WalletInfoKeys.reuseAddress] != true) { await (this as MultiAddressInterface) .checkReceivingAddressForTransactions(); } - _checkAlive(); } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - _checkAlive(); + _fireRefreshPercentChange(0.2); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is MultiAddressInterface) { @@ -663,67 +661,56 @@ abstract class Wallet { .checkChangeAddressForTransactions(); } } - _checkAlive(); - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + _fireRefreshPercentChange(0.3); if (this is SparkInterface && !viewOnly) { // this should be called before updateTransactions() - await (this as SparkInterface).refreshSparkData(); + await (this as SparkInterface).refreshSparkData((0.3, 0.6)); } - _checkAlive(); - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId)); - _checkAlive(); - final fetchFuture = updateTransactions(); - _checkAlive(); - final utxosRefreshFuture = updateUTXOs(); - // if (currentHeight != storedHeight) { - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId)); - - _checkAlive(); - await utxosRefreshFuture; - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId)); - - _checkAlive(); - await fetchFuture; + if (this is NamecoinWallet) { + await updateUTXOs(); + _fireRefreshPercentChange(0.6); + await (this as NamecoinWallet).checkAutoRegisterNameNewOutputs(); + _fireRefreshPercentChange(0.70); + await updateTransactions(); + } else { + final fetchFuture = updateTransactions(); + _fireRefreshPercentChange(0.6); + final utxosRefreshFuture = updateUTXOs(); + _fireRefreshPercentChange(0.65); + await utxosRefreshFuture; + _fireRefreshPercentChange(0.70); + await fetchFuture; + } // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (!viewOnly && this is PaynymInterface && codesToCheck.isNotEmpty) { - _checkAlive(); await (this as PaynymInterface) .checkForNotificationTransactionsTo(codesToCheck); // check utxos again for notification outputs - _checkAlive(); await updateUTXOs(); } - _checkAlive(); - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); + _fireRefreshPercentChange(0.80); // await getAllTxsToWatch(); - _checkAlive(); // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is LelantusInterface && !viewOnly) { if (info.otherData[WalletInfoKeys.enableLelantusScanning] as bool? ?? false) { await (this as LelantusInterface).refreshLelantusData(); - _checkAlive(); } } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId)); + _fireRefreshPercentChange(0.90); - _checkAlive(); await updateBalance(); - _checkAlive(); - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); - - tAlive = false; // interrupt timer as its not needed anymore + _fireRefreshPercentChange(1.0); completer.complete(); } catch (error, strace) { completer.completeError(error, strace); } finally { - t.cancel(); refreshMutex.release(); if (!completer.isCompleted) { completer.completeError( @@ -732,15 +719,15 @@ abstract class Wallet { ); } - Logging.instance.log( + Logging.instance.i( "Refresh for " - "${info.name}: ${DateTime.now().difference(start)}", - level: LogLevel.Info, + "$walletId::${info.name}: ${DateTime.now().difference(start)}", ); } } Future exit() async { + Logging.instance.i("exit called on $walletId"); _periodicRefreshTimer?.cancel(); _networkAliveTimer?.cancel(); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart index 0fd65ba0d..371236180 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart @@ -20,8 +20,7 @@ mixin BCashInterface required TxData txData, required List utxoSigningData, }) async { - Logging.instance - .log("Starting buildTransaction ----------", level: LogLevel.Info); + Logging.instance.d("Starting buildTransaction ----------"); // TODO: use coinlib @@ -114,10 +113,8 @@ mixin BCashInterface ); } } catch (e, s) { - Logging.instance.log( - "Caught exception while signing transaction: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Caught exception while signing transaction: ", + error: e, stackTrace: s); rethrow; } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart index c22064d03..5a24f32ca 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart @@ -379,7 +379,7 @@ mixin CashFusionInterface return root.derivePath(derivationPath).privateKey.data; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + Logging.instance.f("", error: e, stackTrace: s); throw Exception("Derivation path for pubkey=$pubKey could not be found"); } } @@ -741,9 +741,8 @@ mixin CashFusionInterface if (addr == null) { // A utxo object should always have a non null address. // If non found then just ignore the UTXO (aka don't fuse it) - Logging.instance.log( + Logging.instance.d( "Ignoring utxo=$utxo for address=\"$addressString\" while selecting UTXOs for Fusion", - level: LogLevel.Info, ); continue; } @@ -781,10 +780,7 @@ mixin CashFusionInterface // Also reset the failed count here. _failedFuseCount = 0; } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("", error: e, stackTrace: s); // just continue on attempt failure // Increment the number of failed fusion rounds. @@ -816,10 +812,7 @@ mixin CashFusionInterface } } } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("", error: e, stackTrace: s); // Stop the fusion process and update the UI state. await _mainFusionObject?.stop(); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart index cc49149d9..e393ceb9d 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart @@ -36,12 +36,15 @@ import 'rbf_interface.dart'; import 'view_only_option_interface.dart'; mixin ElectrumXInterface - on Bip39HDWallet implements ViewOnlyOptionInterface { + on Bip39HDWallet + implements ViewOnlyOptionInterface { late ElectrumXClient electrumXClient; late CachedElectrumXClient electrumXCachedClient; int? get maximumFeerate => null; + double? refreshingPercent; + static const _kServerBatchCutoffVersion = [1, 6]; List? _serverVersion; Future get serverCanBatch async { @@ -52,9 +55,10 @@ mixin ElectrumXInterface try { _serverVersion ??= _parseServerVersion( - (await electrumXClient - .getServerFeatures() - .timeout(const Duration(seconds: 2)))["server_version"] as String, + (await electrumXClient.getServerFeatures().timeout( + const Duration(seconds: 2), + ))["server_version"] + as String, ); } catch (_) { // ignore failure as it doesn't matter @@ -72,32 +76,28 @@ mixin ElectrumXInterface } Future> - _helperRecipientsConvert( - List addrs, - List satValues, - ) async { + helperRecipientsConvert(List addrs, List satValues) async { final List<({String address, Amount amount, bool isChange})> results = []; for (int i = 0; i < addrs.length; i++) { - results.add( - ( - address: addrs[i], - amount: Amount( - rawValue: satValues[i], - fractionDigits: cryptoCurrency.fractionDigits, - ), - isChange: (await mainDB.isar.addresses - .where() - .walletIdEqualTo(walletId) - .filter() - .subTypeEqualTo(AddressSubType.change) - .and() - .valueEqualTo(addrs[i]) - .valueProperty() - .findFirst()) != - null + results.add(( + address: addrs[i], + amount: Amount( + rawValue: satValues[i], + fractionDigits: cryptoCurrency.fractionDigits, ), - ); + isChange: + (await mainDB.isar.addresses + .where() + .walletIdEqualTo(walletId) + .filter() + .subTypeEqualTo(AddressSubType.change) + .and() + .valueEqualTo(addrs[i]) + .valueProperty() + .findFirst()) != + null, + )); } return results; @@ -111,8 +111,7 @@ mixin ElectrumXInterface int additionalOutputs = 0, List? utxos, }) async { - Logging.instance - .log("Starting coinSelection ----------", level: LogLevel.Info); + Logging.instance.d("Starting coinSelection ----------"); // TODO: multiple recipients one day assert(txData.recipients!.length == 1); @@ -132,21 +131,24 @@ mixin ElectrumXInterface final canCPFP = this is CpfpInterface && coinControl; - final spendableOutputs = availableOutputs - .where( - (e) => - !e.isBlocked && - (e.used != true) && - (canCPFP || - e.isConfirmed( - currentChainHeight, - cryptoCurrency.minConfirms, - cryptoCurrency.minCoinbaseConfirms, - )), - ) - .toList(); - final spendableSatoshiValue = - spendableOutputs.fold(BigInt.zero, (p, e) => p + BigInt.from(e.value)); + final spendableOutputs = + availableOutputs + .where( + (e) => + !e.isBlocked && + (e.used != true) && + (canCPFP || + e.isConfirmed( + currentChainHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + )), + ) + .toList(); + final spendableSatoshiValue = spendableOutputs.fold( + BigInt.zero, + (p, e) => p + BigInt.from(e.value), + ); if (spendableSatoshiValue < satoshiAmountToSend) { throw Exception("Insufficient balance"); @@ -164,48 +166,41 @@ mixin ElectrumXInterface } else { // sort spendable by age (oldest first) spendableOutputs.sort( - (a, b) => (b.blockTime ?? currentChainHeight) - .compareTo((a.blockTime ?? currentChainHeight)), + (a, b) => (b.blockTime ?? currentChainHeight).compareTo( + (a.blockTime ?? currentChainHeight), + ), ); } - Logging.instance.log( - "spendableOutputs.length: ${spendableOutputs.length}", - level: LogLevel.Info, - ); - Logging.instance.log( - "availableOutputs.length: ${availableOutputs.length}", - level: LogLevel.Info, - ); - Logging.instance - .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info); - Logging.instance.log( - "spendableSatoshiValue: $spendableSatoshiValue", - level: LogLevel.Info, - ); - Logging.instance - .log("satoshiAmountToSend: $satoshiAmountToSend", level: LogLevel.Info); + Logging.instance.d("spendableOutputs.length: ${spendableOutputs.length}"); + Logging.instance.d("availableOutputs.length: ${availableOutputs.length}"); + Logging.instance.d("spendableOutputs: $spendableOutputs"); + Logging.instance.d("spendableSatoshiValue: $spendableSatoshiValue"); + Logging.instance.d("satoshiAmountToSend: $satoshiAmountToSend"); BigInt satoshisBeingUsed = BigInt.zero; int inputsBeingConsumed = 0; final List utxoObjectsToUse = []; if (!coinControl) { - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && - i < spendableOutputs.length; - i++) { + for ( + var i = 0; + satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; + i++ + ) { utxoObjectsToUse.add(spendableOutputs[i]); satoshisBeingUsed += BigInt.from(spendableOutputs[i].value); inputsBeingConsumed += 1; } - for (int i = 0; - i < additionalOutputs && - inputsBeingConsumed < spendableOutputs.length; - i++) { + for ( + int i = 0; + i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; + i++ + ) { utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += - BigInt.from(spendableOutputs[inputsBeingConsumed].value); + satoshisBeingUsed += BigInt.from( + spendableOutputs[inputsBeingConsumed].value, + ); inputsBeingConsumed += 1; } } else { @@ -214,12 +209,9 @@ mixin ElectrumXInterface inputsBeingConsumed = spendableOutputs.length; } - Logging.instance - .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info); - Logging.instance - .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); - Logging.instance - .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); + Logging.instance.d("satoshisBeingUsed: $satoshisBeingUsed"); + Logging.instance.d("inputsBeingConsumed: $inputsBeingConsumed"); + Logging.instance.d('utxoObjectsToUse: $utxoObjectsToUse'); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray final List recipientsArray = [recipientAddress]; @@ -248,18 +240,18 @@ mixin ElectrumXInterface final int vSizeForOneOutput; try { - vSizeForOneOutput = (await buildTransaction( - utxoSigningData: utxoSigningData, - txData: txData.copyWith( - recipients: await _helperRecipientsConvert( - [recipientAddress], - [satoshisBeingUsed - BigInt.one], - ), - ), - )) - .vSize!; - } catch (e) { - Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error); + vSizeForOneOutput = + (await buildTransaction( + utxoSigningData: utxoSigningData, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + [recipientAddress], + [satoshisBeingUsed - BigInt.one], + ), + ), + )).vSize!; + } catch (e, s) { + Logging.instance.e("vSizeForOneOutput: $e", error: e, stackTrace: s); rethrow; } @@ -268,24 +260,24 @@ mixin ElectrumXInterface BigInt maxBI(BigInt a, BigInt b) => a > b ? a : b; try { - vSizeForTwoOutPuts = (await buildTransaction( - utxoSigningData: utxoSigningData, - txData: txData.copyWith( - recipients: await _helperRecipientsConvert( - [recipientAddress, (await getCurrentChangeAddress())!.value], - [ - satoshiAmountToSend, - maxBI( - BigInt.zero, - satoshisBeingUsed - (satoshiAmountToSend + BigInt.one), + vSizeForTwoOutPuts = + (await buildTransaction( + utxoSigningData: utxoSigningData, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + [recipientAddress, (await getCurrentChangeAddress())!.value], + [ + satoshiAmountToSend, + maxBI( + BigInt.zero, + satoshisBeingUsed - (satoshiAmountToSend + BigInt.one), + ), + ], ), - ], - ), - ), - )) - .vSize!; - } catch (e) { - Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error); + ), + )).vSize!; + } catch (e, s) { + Logging.instance.e("vSizeForTwoOutPuts: $e", error: e, stackTrace: s); rethrow; } @@ -294,52 +286,34 @@ mixin ElectrumXInterface satsPerVByte != null ? (satsPerVByte * vSizeForOneOutput) : estimateTxFee( - vSize: vSizeForOneOutput, - feeRatePerKB: selectedTxFeeRate, - ), + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ), ); // Assume 2 outputs, one for recipient and one for change final feeForTwoOutputs = BigInt.from( satsPerVByte != null ? (satsPerVByte * vSizeForTwoOutPuts) : estimateTxFee( - vSize: vSizeForTwoOutPuts, - feeRatePerKB: selectedTxFeeRate, - ), + vSize: vSizeForTwoOutPuts, + feeRatePerKB: selectedTxFeeRate, + ), ); - Logging.instance.log( - "feeForTwoOutputs: $feeForTwoOutputs", - level: LogLevel.Info, - ); - Logging.instance.log( - "feeForOneOutput: $feeForOneOutput", - level: LogLevel.Info, - ); + Logging.instance.d("feeForTwoOutputs: $feeForTwoOutputs"); + Logging.instance.d("feeForOneOutput: $feeForOneOutput"); final difference = satoshisBeingUsed - satoshiAmountToSend; Future singleOutputTxn() async { - Logging.instance.log( - 'Input size: $satoshisBeingUsed', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Fee being paid: $difference sats', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Estimated fee: $feeForOneOutput', - level: LogLevel.Info, - ); + Logging.instance.d('Input size: $satoshisBeingUsed'); + Logging.instance.d('Recipient output size: $satoshiAmountToSend'); + Logging.instance.d('Fee being paid: $difference sats'); + Logging.instance.d('Estimated fee: $feeForOneOutput'); final txnData = await buildTransaction( utxoSigningData: utxoSigningData, txData: txData.copyWith( - recipients: await _helperRecipientsConvert( + recipients: await helperRecipientsConvert( recipientsArray, recipientsAmtArray, ), @@ -356,12 +330,11 @@ mixin ElectrumXInterface // no change output required if (difference == feeForOneOutput) { - Logging.instance.log('1 output in tx', level: LogLevel.Info); + Logging.instance.d('1 output in tx'); return await singleOutputTxn(); } else if (difference < feeForOneOutput) { - Logging.instance.log( + Logging.instance.w( 'Cannot pay tx fee - checking for more outputs and trying again', - level: LogLevel.Warning, ); // try adding more outputs if (spendableOutputs.length > inputsBeingConsumed) { @@ -392,32 +365,17 @@ mixin ElectrumXInterface recipientsArray.add(newChangeAddress); recipientsAmtArray.add(changeOutputSize); - Logging.instance.log('2 outputs in tx', level: LogLevel.Info); - Logging.instance.log( - 'Input size: $satoshisBeingUsed', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Change Output Size: $changeOutputSize', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Difference (fee being paid): $feeBeingPaid sats', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Estimated fee: $feeForTwoOutputs', - level: LogLevel.Info, - ); + Logging.instance.d('2 outputs in tx'); + Logging.instance.d('Input size: $satoshisBeingUsed'); + Logging.instance.d('Recipient output size: $satoshiAmountToSend'); + Logging.instance.d('Change Output Size: $changeOutputSize'); + Logging.instance.d('Difference (fee being paid): $feeBeingPaid sats'); + Logging.instance.d('Estimated fee: $feeForTwoOutputs'); TxData txnData = await buildTransaction( utxoSigningData: utxoSigningData, txData: txData.copyWith( - recipients: await _helperRecipientsConvert( + recipients: await helperRecipientsConvert( recipientsArray, recipientsAmtArray, ), @@ -431,31 +389,22 @@ mixin ElectrumXInterface recipientsAmtArray.removeLast(); recipientsAmtArray.add(changeOutputSize); - Logging.instance.log( - 'Adjusted Input size: $satoshisBeingUsed', - level: LogLevel.Info, - ); - Logging.instance.log( + Logging.instance.d('Adjusted Input size: $satoshisBeingUsed'); + Logging.instance.d( 'Adjusted Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info, ); - Logging.instance.log( + Logging.instance.d( 'Adjusted Change Output Size: $changeOutputSize', - level: LogLevel.Info, ); - Logging.instance.log( + Logging.instance.d( 'Adjusted Difference (fee being paid): $feeBeingPaid sats', - level: LogLevel.Info, - ); - Logging.instance.log( - 'Adjusted Estimated fee: $feeForTwoOutputs', - level: LogLevel.Info, ); + Logging.instance.d('Adjusted Estimated fee: $feeForTwoOutputs'); txnData = await buildTransaction( utxoSigningData: utxoSigningData, txData: txData.copyWith( - recipients: await _helperRecipientsConvert( + recipients: await helperRecipientsConvert( recipientsArray, recipientsAmtArray, ), @@ -473,10 +422,7 @@ mixin ElectrumXInterface } else { // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize // is smaller than or equal to cryptoCurrency.dustLimit. Revert to single output transaction. - Logging.instance.log( - 'Reverting to 1 output in tx', - level: LogLevel.Info, - ); + Logging.instance.d('Reverting to 1 output in tx'); return await singleOutputTxn(); } @@ -495,39 +441,30 @@ mixin ElectrumXInterface required int? satsPerVByte, required int feeRatePerKB, }) async { - Logging.instance - .log("Attempting to send all $cryptoCurrency", level: LogLevel.Info); + Logging.instance.d("Attempting to send all $cryptoCurrency"); if (txData.recipients!.length != 1) { - throw Exception( - "Send all to more than one recipient not yet supported", - ); + throw Exception("Send all to more than one recipient not yet supported"); } - final int vSizeForOneOutput = (await buildTransaction( - utxoSigningData: utxoSigningData, - txData: txData.copyWith( - recipients: await _helperRecipientsConvert( - [recipientAddress], - [satoshisBeingUsed - BigInt.one], - ), - ), - )) - .vSize!; + final int vSizeForOneOutput = + (await buildTransaction( + utxoSigningData: utxoSigningData, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + [recipientAddress], + [satoshisBeingUsed - BigInt.one], + ), + ), + )).vSize!; BigInt feeForOneOutput = BigInt.from( satsPerVByte != null ? (satsPerVByte * vSizeForOneOutput) - : estimateTxFee( - vSize: vSizeForOneOutput, - feeRatePerKB: feeRatePerKB, - ), + : estimateTxFee(vSize: vSizeForOneOutput, feeRatePerKB: feeRatePerKB), ); if (satsPerVByte == null) { - final roughEstimate = roughFeeEstimate( - utxoSigningData.length, - 1, - feeRatePerKB, - ).raw; + final roughEstimate = + roughFeeEstimate(utxoSigningData.length, 1, feeRatePerKB).raw; if (feeForOneOutput < roughEstimate) { feeForOneOutput = roughEstimate; } @@ -543,10 +480,7 @@ mixin ElectrumXInterface final data = await buildTransaction( txData: txData.copyWith( - recipients: await _helperRecipientsConvert( - [recipientAddress], - [amount], - ), + recipients: await helperRecipientsConvert([recipientAddress], [amount]), ), utxoSigningData: utxoSigningData, ); @@ -560,23 +494,19 @@ mixin ElectrumXInterface ); } - Future> fetchBuildTxData( - List utxosToUse, - ) async { + Future> fetchBuildTxData(List utxosToUse) async { // return data final List signingData = []; try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - final derivePathType = - cryptoCurrency.addressType(address: utxosToUse[i].address!); + final derivePathType = cryptoCurrency.addressType( + address: utxosToUse[i].address!, + ); signingData.add( - SigningData( - derivePathType: derivePathType, - utxo: utxosToUse[i], - ), + SigningData(derivePathType: derivePathType, utxo: utxosToUse[i]), ); } @@ -596,9 +526,9 @@ mixin ElectrumXInterface final privateKey = await (this as PaynymInterface) .getPrivateKeyForPaynymReceivingAddress( - paymentCodeString: code!, - index: address.derivationIndex, - ); + paymentCodeString: code!, + index: address.derivationIndex, + ); keys = coinlib.HDPrivateKey.fromKeyAndChainCode( coinlib.ECPrivateKey.fromHex(privateKey.toHex), @@ -626,8 +556,7 @@ mixin ElectrumXInterface return signingData; } catch (e, s) { - Logging.instance - .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); + Logging.instance.e("fetchBuildTxData() threw", error: e, stackTrace: s); rethrow; } } @@ -637,8 +566,7 @@ mixin ElectrumXInterface required TxData txData, required List utxoSigningData, }) async { - Logging.instance - .log("Starting buildTransaction ----------", level: LogLevel.Info); + Logging.instance.d("Starting buildTransaction ----------"); // temp tx data to show in gui while waiting for real data from server final List tempInputs = []; @@ -653,9 +581,10 @@ mixin ElectrumXInterface ); // TODO: [prio=high]: check this opt in rbf - final sequence = this is RbfInterface && (this as RbfInterface).flagOptInRBF - ? 0xffffffff - 10 - : 0xffffffff - 1; + final sequence = + this is RbfInterface && (this as RbfInterface).flagOptInRBF + ? 0xffffffff - 10 + : 0xffffffff - 1; // Add transaction inputs for (var i = 0; i < utxoSigningData.length; i++) { @@ -665,10 +594,7 @@ mixin ElectrumXInterface txid.toUint8ListFromHex.reversed.toList(), ); - final prevOutpoint = coinlib.OutPoint( - hash, - utxoSigningData[i].utxo.vout, - ); + final prevOutpoint = coinlib.OutPoint(hash, utxoSigningData[i].utxo.vout); final prevOutput = coinlib.Output.fromAddress( BigInt.from(utxoSigningData[i].utxo.value), @@ -729,9 +655,10 @@ mixin ElectrumXInterface txid: utxoSigningData[i].utxo.txid, vout: utxoSigningData[i].utxo.vout, ), - addresses: utxoSigningData[i].utxo.address == null - ? [] - : [utxoSigningData[i].utxo.address!], + addresses: + utxoSigningData[i].utxo.address == null + ? [] + : [utxoSigningData[i].utxo.address!], valueStringSats: utxoSigningData[i].utxo.value.toString(), witness: null, innerRedeemScriptAsm: null, @@ -771,10 +698,9 @@ mixin ElectrumXInterface OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "000000", valueStringSats: txData.recipients![i].amount.raw.toString(), - addresses: [ - txData.recipients![i].address.toString(), - ], - walletOwns: (await mainDB.isar.addresses + addresses: [txData.recipients![i].address.toString()], + walletOwns: + (await mainDB.isar.addresses .where() .walletIdEqualTo(walletId) .filter() @@ -790,27 +716,39 @@ mixin ElectrumXInterface // Sign the transaction accordingly for (var i = 0; i < utxoSigningData.length; i++) { final value = BigInt.from(utxoSigningData[i].utxo.value); - coinlib.ECPrivateKey key = utxoSigningData[i].keyPair!.privateKey; + final key = utxoSigningData[i].keyPair!.privateKey; if (clTx.inputs[i] is coinlib.TaprootKeyInput) { final taproot = coinlib.Taproot( internalKey: utxoSigningData[i].keyPair!.publicKey, ); - key = taproot.tweakPrivateKey(key); + clTx = clTx.signTaproot( + inputN: i, + key: taproot.tweakPrivateKey(key), + prevOuts: prevOuts, + ); + } else if (clTx.inputs[i] is coinlib.LegacyWitnessInput) { + clTx = clTx.signLegacyWitness(inputN: i, key: key, value: value); + } else if (clTx.inputs[i] is coinlib.LegacyInput) { + clTx = clTx.signLegacy(inputN: i, key: key); + } else if (clTx.inputs[i] is coinlib.TaprootSingleScriptSigInput) { + clTx = clTx.signTaprootSingleScriptSig( + inputN: i, + key: key, + prevOuts: prevOuts, + ); + } else { + throw Exception( + "Unable to sign input of type ${clTx.inputs[i].runtimeType}", + ); } - - clTx = clTx.sign( - inputN: i, - value: value, - key: key, - prevOuts: prevOuts, - ); } } catch (e, s) { - Logging.instance.log( - "Caught exception while signing transaction: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Caught exception while signing transaction: ", + error: e, + stackTrace: s, ); rethrow; } @@ -856,9 +794,10 @@ mixin ElectrumXInterface await electrumXClient.checkElectrumAdapter(); return await fetchChainHeight(retries: retries); } - Logging.instance.log( + Logging.instance.e( "Exception rethrown in fetchChainHeight\nError: $e\nStack trace: $s", - level: LogLevel.Error, + error: e, + stackTrace: s, ); // completer.completeError(e, s); // return Future.error(e, s); @@ -867,8 +806,9 @@ mixin ElectrumXInterface } Future fetchTxCount({required String addressScriptHash}) async { - final transactions = - await electrumXClient.getHistory(scripthash: addressScriptHash); + final transactions = await electrumXClient.getHistory( + scripthash: addressScriptHash, + ); return transactions.length; } @@ -889,9 +829,10 @@ mixin ElectrumXInterface } return result; } catch (e, s) { - Logging.instance.log( - "Exception rethrown in _getBatchTxCount(address: $addresses: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown in _getBatchTxCount(address: $addresses: ", + error: e, + stackTrace: s, ); rethrow; } @@ -912,30 +853,34 @@ mixin ElectrumXInterface } Future updateElectrumX() async { - final failovers = nodeService - .failoverNodesFor(currency: cryptoCurrency) - .map( - (e) => ElectrumXNode( - address: e.host, - port: e.port, - name: e.name, - id: e.id, - useSSL: e.useSSL, - torEnabled: e.torEnabled, - clearnetEnabled: e.clearnetEnabled, - ), - ) - .toList(); + final failovers = + nodeService + .failoverNodesFor(currency: cryptoCurrency) + .map( + (e) => ElectrumXNode( + address: e.host, + port: e.port, + name: e.name, + id: e.id, + useSSL: e.useSSL, + torEnabled: e.torEnabled, + clearnetEnabled: e.clearnetEnabled, + ), + ) + .toList(); final newNode = await _getCurrentElectrumXNode(); try { await electrumXClient.closeAdapter(); - } catch (e) { + } catch (e, s) { if (e.toString().contains("initialized")) { // Ignore. This should happen every first time the wallet is opened. } else { - Logging.instance - .log("Error closing electrumXClient: $e", level: LogLevel.Error); + Logging.instance.e( + "Error closing electrumXClient", + error: e, + stackTrace: s, + ); } } electrumXClient = ElectrumXClient.from( @@ -961,12 +906,13 @@ mixin ElectrumXInterface int gapCounter = 0; int highestIndexWithHistory = 0; - for (int index = 0; - gapCounter < cryptoCurrency.maxUnusedAddressGap; - index += txCountBatchSize) { - Logging.instance.log( + for ( + int index = 0; + gapCounter < cryptoCurrency.maxUnusedAddressGap; + index += txCountBatchSize + ) { + Logging.instance.d( "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", - level: LogLevel.Info, ); final List txCountCallArgs = []; @@ -1010,9 +956,7 @@ mixin ElectrumXInterface addressArray.add(address); - txCountCallArgs.add( - addressString, - ); + txCountCallArgs.add(addressString); } // get address tx counts @@ -1051,9 +995,8 @@ mixin ElectrumXInterface int index = 0; for (; gapCounter < cryptoCurrency.maxUnusedAddressGap; index++) { - Logging.instance.log( + Logging.instance.d( "index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter", - level: LogLevel.Info, ); final derivePath = cryptoCurrency.constructDerivePath( @@ -1141,8 +1084,9 @@ mixin ElectrumXInterface } for (int i = 0; i < batches.length; i++) { - final response = - await electrumXClient.getBatchHistory(args: batches[i]!); + final response = await electrumXClient.getBatchHistory( + args: batches[i]!, + ); for (int j = 0; j < response.length; j++) { final entry = response[j]; for (int k = 0; k < entry.length; k++) { @@ -1175,19 +1119,16 @@ mixin ElectrumXInterface return allTxHashes; } catch (e, s) { - Logging.instance.log( - "$runtimeType._fetchHistory: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "$runtimeType._fetchHistory: ", + error: e, + stackTrace: s, ); rethrow; } } - /// The optional (nullable) param [checkBlock] is a callback that can be used - /// to check if a utxo should be marked as blocked - Future parseUTXO({ - required Map jsonUTXO, - }) async { + Future parseUTXO({required Map jsonUTXO}) async { final txn = await electrumXCachedClient.getTransaction( txHash: jsonUTXO["tx_hash"] as String, verbose: true, @@ -1209,7 +1150,7 @@ mixin ElectrumXInterface scriptPubKey = output["scriptPubKey"]?["hex"] as String?; utxoOwnerAddress = output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]?["address"] as String?; + output["scriptPubKey"]?["address"] as String?; } } @@ -1228,7 +1169,8 @@ mixin ElectrumXInterface name: checkBlockResult.utxoLabel ?? "", isBlocked: checkBlockResult.blocked, blockedReason: checkBlockResult.blockedReason, - isCoinbase: txn["is_coinbase"] as bool? ?? + isCoinbase: + txn["is_coinbase"] as bool? ?? txn["is-coinbase"] as bool? ?? txn["iscoinbase"] as bool? ?? isCoinbase, @@ -1246,10 +1188,7 @@ mixin ElectrumXInterface @override Future updateChainHeight() async { final height = await fetchChainHeight(); - await info.updateCachedChainHeight( - newHeight: height, - isar: mainDB.isar, - ); + await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar); } @override @@ -1282,27 +1221,31 @@ mixin ElectrumXInterface numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Amount.fromDecimal( - fast, - fractionDigits: info.coin.fractionDigits, - ).raw.toInt(), - medium: Amount.fromDecimal( - medium, - fractionDigits: info.coin.fractionDigits, - ).raw.toInt(), - slow: Amount.fromDecimal( - slow, - fractionDigits: info.coin.fractionDigits, - ).raw.toInt(), + fast: + Amount.fromDecimal( + fast, + fractionDigits: info.coin.fractionDigits, + ).raw.toInt(), + medium: + Amount.fromDecimal( + medium, + fractionDigits: info.coin.fractionDigits, + ).raw.toInt(), + slow: + Amount.fromDecimal( + slow, + fractionDigits: info.coin.fractionDigits, + ).raw.toInt(), ); - Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); + Logging.instance.d("fetched fees: $feeObject"); _cachedFees = feeObject; return _cachedFees!; } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Exception rethrown from _getFees(): $e\nStack trace: $s", - level: LogLevel.Error, + error: e, + stackTrace: s, ); if (_cachedFees == null) { rethrow; @@ -1373,9 +1316,10 @@ mixin ElectrumXInterface try { throw Exception(); } catch (_, s) { - Logging.instance.log( + Logging.instance.e( "checkReceivingAddressForTransactions called but reuse address flag set: $s", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } } @@ -1409,10 +1353,9 @@ mixin ElectrumXInterface } } } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Exception rethrown from _checkReceivingAddressForTransactions" "($cryptoCurrency): $e\n$s", - level: LogLevel.Error, ); rethrow; } @@ -1428,9 +1371,10 @@ mixin ElectrumXInterface try { throw Exception(); } catch (_, s) { - Logging.instance.log( + Logging.instance.e( "checkChangeAddressForTransactions called but reuse address flag set: $s", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } } @@ -1461,10 +1405,9 @@ mixin ElectrumXInterface await checkChangeAddressForTransactions(); } } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Exception rethrown from _checkChangeAddressForTransactions" "($cryptoCurrency): $e\n$s", - level: LogLevel.Error, ); rethrow; } @@ -1501,49 +1444,25 @@ mixin ElectrumXInterface } // receiving addresses - Logging.instance.log( - "checking receiving addresses...", - level: LogLevel.Info, - ); + Logging.instance.i("checking receiving addresses..."); final canBatch = await serverCanBatch; for (final type in cryptoCurrency.supportedDerivationPathTypes) { receiveFutures.add( canBatch - ? checkGapsBatched( - txCountBatchSize, - root, - type, - receiveChain, - ) - : checkGapsLinearly( - root, - type, - receiveChain, - ), + ? checkGapsBatched(txCountBatchSize, root, type, receiveChain) + : checkGapsLinearly(root, type, receiveChain), ); } // change addresses - Logging.instance.log( - "checking change addresses...", - level: LogLevel.Info, - ); + Logging.instance.d("checking change addresses..."); for (final type in cryptoCurrency.supportedDerivationPathTypes) { changeFutures.add( canBatch - ? checkGapsBatched( - txCountBatchSize, - root, - type, - changeChain, - ) - : checkGapsLinearly( - root, - type, - changeChain, - ), + ? checkGapsBatched(txCountBatchSize, root, type, changeChain) + : checkGapsLinearly(root, type, changeChain), ); } @@ -1605,13 +1524,15 @@ mixin ElectrumXInterface final notificationAddress = await (this as PaynymInterface).getMyNotificationAddress(); - await (this as BitcoinWallet) - .updateTransactions(overrideAddresses: [notificationAddress]); + await (this as BitcoinWallet).updateTransactions( + overrideAddresses: [notificationAddress], + ); // get own payment code // isSegwit does not matter here at all - final myCode = - await (this as PaynymInterface).getPaymentCode(isSegwit: false); + final myCode = await (this as PaynymInterface).getPaymentCode( + isSegwit: false, + ); try { final Set codesToCheck = {}; @@ -1632,11 +1553,12 @@ mixin ElectrumXInterface paymentCodeStrings: codesToCheck, ); } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Failed to check ${PaynymIsApi.baseURL} followers/following for history during " "bitcoin wallet ($walletId ${info.name}) " - "_recoverWalletFromBIP32SeedPhrase: $e/n$s", - level: LogLevel.Error, + "_recoverWalletFromBIP32SeedPhrase", + error: e, + stackTrace: s, ); } } @@ -1644,9 +1566,10 @@ mixin ElectrumXInterface unawaited(refresh()); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from electrumx_mixin recover(): $e\n$s", - level: LogLevel.Info, + Logging.instance.e( + "Exception rethrown from electrumx_mixin recover(): ", + error: e, + stackTrace: s, ); rethrow; @@ -1677,8 +1600,9 @@ mixin ElectrumXInterface } for (int i = 0; i < batchArgs.length; i++) { - final response = - await electrumXClient.getBatchUTXOs(args: batchArgs[i]!); + final response = await electrumXClient.getBatchUTXOs( + args: batchArgs[i]!, + ); for (final entry in response) { if (entry.isNotEmpty) { fetchedUtxoList.add(entry); @@ -1702,9 +1626,7 @@ mixin ElectrumXInterface for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - final utxo = await parseUTXO( - jsonUTXO: fetchedUtxoList[i][j], - ); + final utxo = await parseUTXO(jsonUTXO: fetchedUtxoList[i][j]); outputArray.add(utxo); } @@ -1712,9 +1634,10 @@ mixin ElectrumXInterface return await mainDB.updateUTXOs(walletId, outputArray); } catch (e, s) { - Logging.instance.log( - "Output fetch unsuccessful: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Output fetch unsuccessful: ", + error: e, + stackTrace: s, ); return false; } @@ -1723,12 +1646,12 @@ mixin ElectrumXInterface @override Future confirmSend({required TxData txData}) async { try { - Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + Logging.instance.d("confirmSend txData: $txData"); final txHash = await electrumXClient.broadcastTransaction( rawTx: txData.raw!, ); - Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + Logging.instance.d("Sent txHash: $txHash"); txData = txData.copyWith( usedUTXOs: @@ -1743,9 +1666,10 @@ mixin ElectrumXInterface return await updateSentCachedTxData(txData: txData); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from confirmSend(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown from confirmSend(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -1765,7 +1689,8 @@ mixin ElectrumXInterface final bool coinControl = utxos != null; - final isSendAllCoinControlUtxos = coinControl && + final isSendAllCoinControlUtxos = + coinControl && txData.amount!.raw == utxos .map((e) => e.value) @@ -1795,8 +1720,7 @@ mixin ElectrumXInterface isSendAllCoinControlUtxos: isSendAllCoinControlUtxos, ); - Logging.instance - .log("PREPARE SEND RESULT: $result", level: LogLevel.Info); + Logging.instance.d("PREPARE SEND RESULT: $result"); if (result.fee!.raw.toInt() < result.vSize!) { throw Exception( @@ -1835,20 +1759,19 @@ mixin ElectrumXInterface } final result = await coinSelection( - txData: txData.copyWith( - feeRateAmount: rate, - ), + txData: txData.copyWith(feeRateAmount: rate), isSendAll: isSendAll, utxos: utxos?.toList(), coinControl: coinControl, isSendAllCoinControlUtxos: isSendAllCoinControlUtxos, ); - Logging.instance.log("prepare send: $result", level: LogLevel.Info); + Logging.instance.d("prepare send: $result"); if (result.fee!.raw.toInt() < result.vSize!) { throw Exception( - "Error in fee calculation: Transaction fee (${result.fee!.raw.toInt()}) cannot " - "be less than vSize (${result.vSize})"); + "Error in fee calculation: Transaction fee (${result.fee!.raw.toInt()}) cannot " + "be less than vSize (${result.vSize})", + ); } return result; @@ -1856,9 +1779,10 @@ mixin ElectrumXInterface throw ArgumentError("Invalid fee rate argument provided!"); } } catch (e, s) { - Logging.instance.log( - "Exception rethrown from prepareSend(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown from prepareSend(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -1873,31 +1797,34 @@ mixin ElectrumXInterface await super.init(); } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance.log( - "$runtimeType init() did not complete: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "$runtimeType init() did not complete: ", + error: e, + stackTrace: s, ); } } Future _initializeServerVersionAndCheckGenesisHash() async { try { - final features = await electrumXClient - .getServerFeatures() - .timeout(const Duration(seconds: 5)); + final features = await electrumXClient.getServerFeatures().timeout( + const Duration(seconds: 5), + ); - Logging.instance.log("features: $features", level: LogLevel.Info); + Logging.instance.d("features: $features"); - _serverVersion = - _parseServerVersion(features["server_version"] as String); + _serverVersion = _parseServerVersion( + features["server_version"] as String, + ); if (cryptoCurrency.genesisHash != features['genesis_hash']) { throw Exception("Genesis hash does not match!"); } } catch (e, s) { - Logging.instance.log( - "$runtimeType _initializeServerVersionAndCheckGenesisHash() did not complete: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "$runtimeType _initializeServerVersionAndCheckGenesisHash() did not complete: ", + error: e, + stackTrace: s, ); } } @@ -1913,7 +1840,7 @@ mixin ElectrumXInterface /// Certain coins need to check if the utxo should be marked /// as blocked as well as give a reason. Future<({String? blockedReason, bool blocked, String? utxoLabel})> - checkBlockUTXO( + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map jsonTX, @@ -1965,10 +1892,7 @@ mixin ElectrumXInterface } } catch (_) {} - Logging.instance.log( - "${info.name} _parseServerVersion($version) => $result", - level: LogLevel.Info, - ); + Logging.instance.d("${info.name} _parseServerVersion($version) => $result"); return result; } @@ -2021,10 +1945,7 @@ mixin ElectrumXInterface if (root != null) { // receiving addresses - Logging.instance.log( - "checking receiving addresses...", - level: LogLevel.Info, - ); + Logging.instance.i("checking receiving addresses..."); final canBatch = await serverCanBatch; @@ -2040,25 +1961,18 @@ mixin ElectrumXInterface receiveFutures.add( canBatch ? checkGapsBatched( - txCountBatchSize, - root, - type, - receiveChain, - ) - : checkGapsLinearly( - root, - type, - receiveChain, - ), + txCountBatchSize, + root, + type, + receiveChain, + ) + : checkGapsLinearly(root, type, receiveChain), ); } } // change addresses - Logging.instance.log( - "checking change addresses...", - level: LogLevel.Info, - ); + Logging.instance.d("checking change addresses..."); for (final type in cryptoCurrency.supportedDerivationPathTypes) { final path = cryptoCurrency.constructDerivePath( derivePathType: type, @@ -2071,16 +1985,12 @@ mixin ElectrumXInterface changeFutures.add( canBatch ? checkGapsBatched( - txCountBatchSize, - root, - type, - changeChain, - ) - : checkGapsLinearly( - root, - type, - changeChain, - ), + txCountBatchSize, + root, + type, + changeChain, + ) + : checkGapsLinearly(root, type, changeChain), ); } } @@ -2182,9 +2092,10 @@ mixin ElectrumXInterface unawaited(refresh()); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from electrumx_mixin recoverViewOnly(): $e\n$s", - level: LogLevel.Info, + Logging.instance.e( + "Exception rethrown from electrumx_mixin recoverViewOnly(): ", + error: e, + stackTrace: s, ); rethrow; diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart index 3d57f3c36..148bd61a2 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart @@ -82,8 +82,9 @@ mixin LelantusInterface coin.mintIndex, privateKey, ); - } catch (_) { - Logging.instance.log("error bad key", level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e("error bad key"); + Logging.instance.t("error bad key", error: e, stackTrace: s); return lelantus.DartLelantusEntry(1, 0, 0, 0, 0, ''); } }).toList(); @@ -153,9 +154,8 @@ mixin LelantusInterface chaincode: root.chaincode, ); - Logging.instance.log("prepared fee: ${result.fee}", level: LogLevel.Info); - Logging.instance - .log("prepared vSize: ${result.vSize}", level: LogLevel.Info); + Logging.instance.d("prepared fee: ${result.fee}"); + Logging.instance.d("prepared vSize: ${result.vSize}"); // fee should never be less than vSize sanity check if (result.fee!.raw.toInt() < result.vSize!) { @@ -165,9 +165,10 @@ mixin LelantusInterface } return result; } catch (e, s) { - Logging.instance.log( - "Exception rethrown in firo prepareSend(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown in firo prepareSend()", + error: e, + stackTrace: s, ); rethrow; } @@ -231,9 +232,10 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.put(jmint); }); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, + Logging.instance.e( + "", + error: e, + stackTrace: s, ); rethrow; } @@ -284,7 +286,7 @@ mixin LelantusInterface await mainDB.addNewTransactionData(txnsData, walletId); } else { // This is a mint - Logging.instance.log("this is a mint", level: LogLevel.Info); + Logging.instance.t("this is a mint"); final List updatedCoins = []; @@ -309,9 +311,10 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.putAll(updatedCoins); }); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, + Logging.instance.e( + "", + error: e, + stackTrace: s, ); rethrow; } @@ -432,18 +435,20 @@ mixin LelantusInterface txs[address] = txn; } catch (e, s) { - Logging.instance.log( - "Exception caught in getJMintTransactions(): $e\n$s", - level: LogLevel.Info, + Logging.instance.i( + "Exception caught in getJMintTransactions(): ", + error: e, + stackTrace: s, ); rethrow; } } return txs; } catch (e, s) { - Logging.instance.log( - "Exception rethrown in getJMintTransactions(): $e\n$s", - level: LogLevel.Info, + Logging.instance.i( + "Exception rethrown in getJMintTransactions(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -470,9 +475,10 @@ mixin LelantusInterface } return sets; } catch (e, s) { - Logging.instance.log( - "Exception rethrown from refreshAnonymitySets: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown from refreshAnonymitySets: ", + error: e, + stackTrace: s, ); rethrow; } @@ -536,9 +542,11 @@ mixin LelantusInterface final tx = await mainDB.getTransaction(walletId, coin.txid); if (tx == null) { - Logging.instance.log( + Logging.instance.e( + "Transaction with txid=REDACTED not found in local db!", + ); + Logging.instance.d( "Transaction with txid=${coin.txid} not found in local db!", - level: LogLevel.Error, ); } } @@ -555,9 +563,10 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.putAll(updatedCoins); }); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, + Logging.instance.f( + " ", + error: e, + stackTrace: s, ); rethrow; } @@ -638,9 +647,8 @@ mixin LelantusInterface } if (inputTxns.isEmpty) { //some error. - Logging.instance.log( + Logging.instance.f( "cryptic \"//some error\" occurred in staticProcessRestore on lelantus coin: $coin", - level: LogLevel.Error, ); continue; } @@ -698,9 +706,10 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.putAll(result.lelantusCoins); }); } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, + Logging.instance.e( + "", + error: e, + stackTrace: s, ); // don't just rethrow since isar likes to strip stack traces for some reason throw Exception("e=$e & s=$s"); @@ -716,7 +725,7 @@ mixin LelantusInterface final spendTxs = await getJMintTransactions( result.spendTxIds, ); - Logging.instance.log(spendTxs, level: LogLevel.Info); + Logging.instance.d("lelantus spendTxs: $spendTxs"); for (final element in spendTxs.entries) { final address = element.value.address.value ?? @@ -841,7 +850,7 @@ mixin LelantusInterface } for (final mintsElement in txData.mintsMapLelantus!) { - Logging.instance.log("using $mintsElement", level: LogLevel.Info); + Logging.instance.d("using $mintsElement"); final Uint8List mintu8 = Format.stringToUint8List(mintsElement['script'] as String); txb.addOutput(mintu8, mintsElement['value'] as int); @@ -1063,9 +1072,10 @@ mixin LelantusInterface try { anonymitySets = await fetchAnonymitySets(); } catch (e, s) { - Logging.instance.log( - "Firo needs better internet to create mints: $e\n$s", - level: LogLevel.Fatal, + Logging.instance.f( + "Firo needs better internet to create mints: ", + error: e, + stackTrace: s, ); rethrow; } @@ -1087,10 +1097,7 @@ mixin LelantusInterface } if (isUsedMintTag) { - Logging.instance.log( - "Found used index when minting", - level: LogLevel.Warning, - ); + Logging.instance.d("Found used index when minting"); } if (!isUsedMintTag) { @@ -1135,9 +1142,10 @@ mixin LelantusInterface unawaited(refresh()); } catch (e, s) { - Logging.instance.log( - "Exception caught in anonymizeAllLelantus(): $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Exception caught in anonymizeAllLelantus(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -1171,15 +1179,16 @@ mixin LelantusInterface .findFirstSync(); if (txn == null) { - Logging.instance.log( + Logging.instance.e( + "Transaction not found in DB for lelantus coin", + ); + Logging.instance.d( "Transaction not found in DB for lelantus coin: $lelantusCoin", - level: LogLevel.Fatal, ); } else { if (txn.isLelantus != true) { - Logging.instance.log( + Logging.instance.f( "Bad database state found in ${info.name} $walletId for _refreshBalance lelantus", - level: LogLevel.Fatal, ); } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart index e08bc3b45..1d725afb4 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart @@ -345,9 +345,10 @@ mixin NanoInterface on Bip39Wallet { } } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance.log( - "$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "$runtimeType checkSaveInitialReceivingAddress() failed: ", + error: e, + stackTrace: s, ); } } @@ -488,8 +489,11 @@ mixin NanoInterface on Bip39Wallet { txid: decoded["hash"].toString(), ); } catch (e, s) { - Logging.instance - .log("Error sending transaction $e - $s", level: LogLevel.Error); + Logging.instance.e( + "Error sending transaction", + error: e, + stackTrace: s, + ); rethrow; } } @@ -669,9 +673,10 @@ mixin NanoInterface on Bip39Wallet { await info.updateBalance(newBalance: balance, isar: mainDB.isar); } catch (e, s) { - Logging.instance.log( - "Failed to update ${cryptoCurrency.runtimeType} balance: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to update ${cryptoCurrency.runtimeType} balance: ", + error: e, + stackTrace: s, ); } } @@ -705,9 +710,10 @@ mixin NanoInterface on Bip39Wallet { await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar); } catch (e, s) { - Logging.instance.log( - "Failed to update ${cryptoCurrency.runtimeType} chain height: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to update ${cryptoCurrency.runtimeType} chain height: ", + error: e, + stackTrace: s, ); } } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart index 4ca21ef1a..430c46c45 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart @@ -18,8 +18,8 @@ mixin OrdinalsInterface try { return (await _litescribeAPI.getInscriptionsByAddress(address)) .isNotEmpty; - } catch (_) { - Logging.instance.log("Litescribe api failure!", level: LogLevel.Error); + } catch (e, s) { + Logging.instance.e("Litescribe api failure!", error: e, stackTrace: s); return false; } @@ -54,10 +54,8 @@ mixin OrdinalsInterface await mainDB.isar.ordinals.putAll(ords); }); } catch (e, s) { - Logging.instance.log( - "$runtimeType failed refreshInscriptions(): $e\n$s", - level: LogLevel.Warning, - ); + Logging.instance.w("$runtimeType failed refreshInscriptions(): ", + error: e, stackTrace: s); } } // =================== Overrides ============================================= diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart index b20638362..5c0ace879 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart @@ -42,34 +42,28 @@ String _notificationDerivationPath({required bool testnet}) => String _receivingPaynymAddressDerivationPath( int index, { required bool testnet, -}) => - "${_basePaynymDerivePath(testnet: testnet)}/$index/0"; -String _sendPaynymAddressDerivationPath( - int index, { - required bool testnet, -}) => +}) => "${_basePaynymDerivePath(testnet: testnet)}/$index/0"; +String _sendPaynymAddressDerivationPath(int index, {required bool testnet}) => "${_basePaynymDerivePath(testnet: testnet)}/0/$index"; mixin PaynymInterface on Bip39HDWallet, ElectrumXInterface { btc_dart.NetworkType get networkType => btc_dart.NetworkType( - messagePrefix: cryptoCurrency.networkParams.messagePrefix, - bech32: cryptoCurrency.networkParams.bech32Hrp, - bip32: btc_dart.Bip32Type( - public: cryptoCurrency.networkParams.pubHDPrefix, - private: cryptoCurrency.networkParams.privHDPrefix, - ), - pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix, - scriptHash: cryptoCurrency.networkParams.p2shPrefix, - wif: cryptoCurrency.networkParams.wifPrefix, - ); + messagePrefix: cryptoCurrency.networkParams.messagePrefix, + bech32: cryptoCurrency.networkParams.bech32Hrp, + bip32: btc_dart.Bip32Type( + public: cryptoCurrency.networkParams.pubHDPrefix, + private: cryptoCurrency.networkParams.privHDPrefix, + ), + pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix, + scriptHash: cryptoCurrency.networkParams.p2shPrefix, + wif: cryptoCurrency.networkParams.wifPrefix, + ); Future getBip47BaseNode() async { final root = await _getRootNode(); final node = root.derivePath( - _basePaynymDerivePath( - testnet: info.coin.network.isTestNet, - ), + _basePaynymDerivePath(testnet: info.coin.network.isTestNet), ); return node; } @@ -101,25 +95,29 @@ mixin PaynymInterface }) async { final keys = await lookupKey(sender.toString()); - final address = await mainDB - .getAddresses(walletId) - .filter() - .subTypeEqualTo(AddressSubType.paynymReceive) - .and() - .group((q) { - if (isSegwit) { - return q - .typeEqualTo(AddressType.p2sh) - .or() - .typeEqualTo(AddressType.p2wpkh); - } else { - return q.typeEqualTo(AddressType.p2pkh); - } - }) - .and() - .anyOf(keys, (q, String e) => q.otherDataEqualTo(e)) - .sortByDerivationIndexDesc() - .findFirst(); + final address = + await mainDB + .getAddresses(walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymReceive) + .and() + .group((q) { + if (isSegwit) { + return q + .typeEqualTo(AddressType.p2sh) + .or() + .typeEqualTo(AddressType.p2wpkh); + } else { + return q.typeEqualTo(AddressType.p2pkh); + } + }) + .and() + .anyOf( + keys, + (q, String e) => q.otherDataEqualTo(e), + ) + .sortByDerivationIndexDesc() + .findFirst(); if (address == null) { final generatedAddress = await _generatePaynymReceivingAddress( @@ -128,11 +126,12 @@ mixin PaynymInterface generateSegwitAddress: isSegwit, ); - final existing = await mainDB - .getAddresses(walletId) - .filter() - .valueEqualTo(generatedAddress.value) - .findFirst(); + final existing = + await mainDB + .getAddresses(walletId) + .filter() + .valueEqualTo(generatedAddress.value) + .findFirst(); if (existing == null) { // Add that new address @@ -142,10 +141,7 @@ mixin PaynymInterface await mainDB.updateAddress(existing, generatedAddress); } - return currentReceivingPaynymAddress( - isSegwit: isSegwit, - sender: sender, - ); + return currentReceivingPaynymAddress(isSegwit: isSegwit, sender: sender); } else { return address; } @@ -158,9 +154,7 @@ mixin PaynymInterface }) async { final root = await _getRootNode(); final node = root.derivePath( - _basePaynymDerivePath( - testnet: info.coin.network.isTestNet, - ), + _basePaynymDerivePath(testnet: info.coin.network.isTestNet), ); final paymentAddress = PaymentAddress( @@ -170,20 +164,22 @@ mixin PaynymInterface index: 0, ); - final addressString = generateSegwitAddress - ? paymentAddress.getReceiveAddressP2WPKH() - : paymentAddress.getReceiveAddressP2PKH(); + final addressString = + generateSegwitAddress + ? paymentAddress.getReceiveAddressP2WPKH() + : paymentAddress.getReceiveAddressP2PKH(); final address = Address( walletId: walletId, value: addressString, publicKey: [], derivationIndex: index, - derivationPath: DerivationPath() - ..value = _receivingPaynymAddressDerivationPath( - index, - testnet: info.coin.network.isTestNet, - ), + derivationPath: + DerivationPath() + ..value = _receivingPaynymAddressDerivationPath( + index, + testnet: info.coin.network.isTestNet, + ), type: generateSegwitAddress ? AddressType.p2wpkh : AddressType.p2pkh, subType: AddressSubType.paynymReceive, otherData: await storeCode(sender.toString()), @@ -207,20 +203,22 @@ mixin PaynymInterface index: index, ); - final addressString = generateSegwitAddress - ? paymentAddress.getSendAddressP2WPKH() - : paymentAddress.getSendAddressP2PKH(); + final addressString = + generateSegwitAddress + ? paymentAddress.getSendAddressP2WPKH() + : paymentAddress.getSendAddressP2PKH(); final address = Address( walletId: walletId, value: addressString, publicKey: [], derivationIndex: index, - derivationPath: DerivationPath() - ..value = _sendPaynymAddressDerivationPath( - index, - testnet: info.coin.network.isTestNet, - ), + derivationPath: + DerivationPath() + ..value = _sendPaynymAddressDerivationPath( + index, + testnet: info.coin.network.isTestNet, + ), type: AddressType.nonWallet, subType: AddressSubType.paynymSend, otherData: await storeCode(other.toString()), @@ -251,11 +249,12 @@ mixin PaynymInterface generateSegwitAddress: isSegwit, ); - final existing = await mainDB - .getAddresses(walletId) - .filter() - .valueEqualTo(nextAddress.value) - .findFirst(); + final existing = + await mainDB + .getAddresses(walletId) + .filter() + .valueEqualTo(nextAddress.value) + .findFirst(); if (existing == null) { // Add that new address @@ -312,26 +311,18 @@ mixin PaynymInterface Future deriveNotificationBip32Node() async { final root = await _getRootNode(); final node = root - .derivePath( - _basePaynymDerivePath( - testnet: info.coin.network.isTestNet, - ), - ) + .derivePath(_basePaynymDerivePath(testnet: info.coin.network.isTestNet)) .derive(0); return node; } /// fetch or generate this wallet's bip47 payment code - Future getPaymentCode({ - required bool isSegwit, - }) async { + Future getPaymentCode({required bool isSegwit}) async { final node = await _getRootNode(); final paymentCode = PaymentCode.fromBip32Node( node.derivePath( - _basePaynymDerivePath( - testnet: info.coin.network.isTestNet, - ), + _basePaynymDerivePath(testnet: info.coin.network.isTestNet), ), networkType: networkType, shouldSetSegwitBit: isSegwit, @@ -351,8 +342,9 @@ mixin PaynymInterface } Future signStringWithNotificationKey(String data) async { - final bytes = - await signWithNotificationKey(Uint8List.fromList(utf8.encode(data))); + final bytes = await signWithNotificationKey( + Uint8List.fromList(utf8.encode(data)), + ); return Format.uint8listToString(bytes); } @@ -411,15 +403,19 @@ mixin PaynymInterface for (int i = startIndex; i < maxCount; i++) { final keys = await lookupKey(pCode.toString()); - final address = await mainDB - .getAddresses(walletId) - .filter() - .subTypeEqualTo(AddressSubType.paynymSend) - .and() - .anyOf(keys, (q, String e) => q.otherDataEqualTo(e)) - .and() - .derivationIndexEqualTo(i) - .findFirst(); + final address = + await mainDB + .getAddresses(walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymSend) + .and() + .anyOf( + keys, + (q, String e) => q.otherDataEqualTo(e), + ) + .and() + .derivationIndexEqualTo(i) + .findFirst(); if (address != null) { final count = await fetchTxCount( @@ -506,21 +502,26 @@ mixin PaynymInterface int outputsBeingUsed = 0; final List utxoObjectsToUse = []; - for (int i = 0; - satoshisBeingUsed < amountToSend.raw && i < spendableOutputs.length; - i++) { + for ( + int i = 0; + satoshisBeingUsed < amountToSend.raw && i < spendableOutputs.length; + i++ + ) { utxoObjectsToUse.add(spendableOutputs[i]); satoshisBeingUsed += BigInt.from(spendableOutputs[i].value); outputsBeingUsed += 1; } // add additional outputs if required - for (int i = 0; - i < additionalOutputs && outputsBeingUsed < spendableOutputs.length; - i++) { + for ( + int i = 0; + i < additionalOutputs && outputsBeingUsed < spendableOutputs.length; + i++ + ) { utxoObjectsToUse.add(spendableOutputs[outputsBeingUsed]); - satoshisBeingUsed += - BigInt.from(spendableOutputs[outputsBeingUsed].value); + satoshisBeingUsed += BigInt.from( + spendableOutputs[outputsBeingUsed].value, + ); outputsBeingUsed += 1; } @@ -534,8 +535,7 @@ mixin PaynymInterface change: BigInt.zero, // override amount to get around absurd fees error overrideAmountForTesting: satoshisBeingUsed, - )) - .item2, + )).item2, ); final vSizeForWithChange = BigInt.from( @@ -543,8 +543,7 @@ mixin PaynymInterface targetPaymentCodeString: targetPaymentCodeString, utxoSigningData: utxoSigningData, change: satoshisBeingUsed - amountToSend.raw, - )) - .item2, + )).item2, ); // Assume 2 outputs, for recipient and payment code script @@ -836,10 +835,7 @@ mixin PaynymInterface clTx = clTx.addOutput(output); clTx = clTx.addOutput( - coinlib.Output.fromScriptBytes( - BigInt.zero, - opReturnScript, - ), + coinlib.Output.fromScriptBytes(BigInt.zero, opReturnScript), ); // TODO: add possible change output and mark output as dangerous @@ -859,71 +855,97 @@ mixin PaynymInterface clTx = clTx.addOutput(output); } - clTx = clTx.sign( - inputN: 0, - value: BigInt.from(utxo.value), - key: myKeyPair.privateKey, - prevOuts: prevOuts, - ); + if (clTx.inputs[0] is coinlib.TaprootKeyInput) { + final taproot = coinlib.Taproot(internalKey: myKeyPair.publicKey); + + clTx = clTx.signTaproot( + inputN: 0, + key: taproot.tweakPrivateKey(myKeyPair.privateKey), + prevOuts: prevOuts, + ); + } else if (clTx.inputs[0] is coinlib.LegacyWitnessInput) { + clTx = clTx.signLegacyWitness( + inputN: 0, + key: myKeyPair.privateKey, + value: BigInt.from(utxo.value), + ); + } else if (clTx.inputs[0] is coinlib.LegacyInput) { + clTx = clTx.signLegacy(inputN: 0, key: myKeyPair.privateKey); + } else if (clTx.inputs[0] is coinlib.TaprootSingleScriptSigInput) { + clTx = clTx.signTaprootSingleScriptSig( + inputN: 0, + key: myKeyPair.privateKey, + prevOuts: prevOuts, + ); + } else { + throw Exception( + "Unable to sign input of type ${clTx.inputs[0].runtimeType}", + ); + } // sign rest of possible inputs for (int i = 1; i < utxoSigningData.length; i++) { final value = BigInt.from(utxoSigningData[i].utxo.value); - coinlib.ECPrivateKey key = utxoSigningData[i].keyPair!.privateKey; + final key = utxoSigningData[i].keyPair!.privateKey; if (clTx.inputs[i] is coinlib.TaprootKeyInput) { final taproot = coinlib.Taproot( internalKey: utxoSigningData[i].keyPair!.publicKey, ); - key = taproot.tweakPrivateKey(key); + clTx = clTx.signTaproot( + inputN: i, + key: taproot.tweakPrivateKey(key), + prevOuts: prevOuts, + ); + } else if (clTx.inputs[i] is coinlib.LegacyWitnessInput) { + clTx = clTx.signLegacyWitness(inputN: i, key: key, value: value); + } else if (clTx.inputs[i] is coinlib.LegacyInput) { + clTx = clTx.signLegacy(inputN: i, key: key); + } else if (clTx.inputs[i] is coinlib.TaprootSingleScriptSigInput) { + clTx = clTx.signTaprootSingleScriptSig( + inputN: i, + key: key, + prevOuts: prevOuts, + ); + } else { + throw Exception( + "Unable to sign input of type ${clTx.inputs[i].runtimeType}", + ); } - - clTx = clTx.sign( - inputN: i, - value: value, - key: key, - prevOuts: prevOuts, - ); } return Tuple2(clTx.toHex(), clTx.vSize()); } catch (e, s) { - Logging.instance.log( - "_createNotificationTx(): $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("_createNotificationTx(): ", error: e, stackTrace: s); rethrow; } } - Future broadcastNotificationTx({ - required TxData txData, - }) async { + Future broadcastNotificationTx({required TxData txData}) async { try { - Logging.instance - .log("confirmNotificationTx txData: $txData", level: LogLevel.Info); - final txHash = - await electrumXClient.broadcastTransaction(rawTx: txData.raw!); - Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + Logging.instance.d("confirmNotificationTx txData: $txData"); + final txHash = await electrumXClient.broadcastTransaction( + rawTx: txData.raw!, + ); + Logging.instance.d("Sent txHash: $txHash"); try { await updateTransactions(); - } catch (e) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.e( "refresh() failed in confirmNotificationTx (${info.name}::$walletId): $e", - level: LogLevel.Error, + error: e, + stackTrace: s, ); } - return txData.copyWith( - txid: txHash, - txHash: txHash, - ); + return txData.copyWith(txid: txHash, txHash: txHash); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from confirmSend(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown from confirmSend(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -966,12 +988,13 @@ mixin PaynymInterface final myNotificationAddress = await getMyNotificationAddress(); - final txns = await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .subTypeEqualTo(TransactionSubType.bip47Notification) - .findAll(); + final txns = + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .subTypeEqualTo(TransactionSubType.bip47Notification) + .findAll(); for (final tx in txns) { switch (tx.type) { @@ -980,9 +1003,7 @@ mixin PaynymInterface for (final outputAddress in output.addresses) { if (outputAddress == myNotificationAddress.value) { final unBlindedPaymentCode = - await unBlindedPaymentCodeFromTransaction( - transaction: tx, - ); + await unBlindedPaymentCodeFromTransaction(transaction: tx); if (unBlindedPaymentCode != null && paymentCodeString == unBlindedPaymentCode.toString()) { @@ -992,8 +1013,8 @@ mixin PaynymInterface final unBlindedPaymentCodeBad = await unBlindedPaymentCodeFromTransactionBad( - transaction: tx, - ); + transaction: tx, + ); if (unBlindedPaymentCodeBad != null && paymentCodeString == unBlindedPaymentCodeBad.toString()) { @@ -1007,14 +1028,15 @@ mixin PaynymInterface case TransactionType.outgoing: for (final output in tx.outputs) { for (final outputAddress in output.addresses) { - final address = await mainDB.isar.addresses - .where() - .walletIdEqualTo(walletId) - .filter() - .subTypeEqualTo(AddressSubType.paynymNotification) - .and() - .valueEqualTo(outputAddress) - .findFirst(); + final address = + await mainDB.isar.addresses + .where() + .walletIdEqualTo(walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymNotification) + .and() + .valueEqualTo(outputAddress) + .findFirst(); if (address?.otherData != null) { final code = await paymentCodeStringByKey(address!.otherData!); @@ -1047,7 +1069,7 @@ mixin PaynymInterface return (witnessComponents[1] as String).toUint8ListFromHex; } } catch (e, s) { - Logging.instance.log("_pubKeyFromInput: $e\n$s", level: LogLevel.Info); + Logging.instance.e("_pubKeyFromInput()", error: e, stackTrace: s); } } return null; @@ -1057,8 +1079,9 @@ mixin PaynymInterface required TransactionV2 transaction, }) async { try { - final blindedCodeBytes = - Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); + final blindedCodeBytes = Bip47Utils.getBlindedPaymentCodeBytesFrom( + transaction, + ); // transaction does not contain a payment code if (blindedCodeBytes == null) { @@ -1097,9 +1120,15 @@ mixin PaynymInterface return unBlindedPaymentCode; } catch (e, s) { - Logging.instance.log( - "unBlindedPaymentCodeFromTransaction() failed: $e\n$s\nFor tx: $transaction", - level: LogLevel.Warning, + Logging.instance.e( + "unBlindedPaymentCodeFromTransaction()", + error: e, + stackTrace: s, + ); + Logging.instance.d( + "unBlindedPaymentCodeFromTransaction() failed for tx: $transaction", + error: e, + stackTrace: s, ); return null; } @@ -1109,8 +1138,9 @@ mixin PaynymInterface required TransactionV2 transaction, }) async { try { - final blindedCodeBytes = - Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); + final blindedCodeBytes = Bip47Utils.getBlindedPaymentCodeBytesFrom( + transaction, + ); // transaction does not contain a payment code if (blindedCodeBytes == null) { @@ -1148,23 +1178,30 @@ mixin PaynymInterface ); return unBlindedPaymentCode; - } catch (e) { - Logging.instance.log( + } catch (e, s) { + Logging.instance.e( + "unBlindedPaymentCodeFromTransactionBad()n", + error: e, + stackTrace: s, + ); + Logging.instance.d( "unBlindedPaymentCodeFromTransactionBad() failed: $e\nFor tx: $transaction", - level: LogLevel.Warning, + error: e, + stackTrace: s, ); return null; } } Future> - getAllPaymentCodesFromNotificationTransactions() async { - final txns = await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .subTypeEqualTo(TransactionSubType.bip47Notification) - .findAll(); + getAllPaymentCodesFromNotificationTransactions() async { + final txns = + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .subTypeEqualTo(TransactionSubType.bip47Notification) + .findAll(); final List codes = []; @@ -1172,20 +1209,23 @@ mixin PaynymInterface // tx is sent so we can check the address's otherData for the code String if (tx.type == TransactionType.outgoing) { for (final output in tx.outputs) { - for (final outputAddress - in output.addresses.where((e) => e.isNotEmpty)) { - final address = await mainDB.isar.addresses - .where() - .walletIdEqualTo(walletId) - .filter() - .subTypeEqualTo(AddressSubType.paynymNotification) - .and() - .valueEqualTo(outputAddress) - .findFirst(); + for (final outputAddress in output.addresses.where( + (e) => e.isNotEmpty, + )) { + final address = + await mainDB.isar.addresses + .where() + .walletIdEqualTo(walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymNotification) + .and() + .valueEqualTo(outputAddress) + .findFirst(); if (address?.otherData != null) { - final codeString = - await paymentCodeStringByKey(address!.otherData!); + final codeString = await paymentCodeStringByKey( + address!.otherData!, + ); if (codeString != null && codes.where((e) => e.toString() == codeString).isEmpty) { codes.add( @@ -1226,14 +1266,15 @@ mixin PaynymInterface Future checkForNotificationTransactionsTo( Set otherCodeStrings, ) async { - final sentNotificationTransactions = await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .subTypeEqualTo(TransactionSubType.bip47Notification) - .and() - .typeEqualTo(TransactionType.outgoing) - .findAll(); + final sentNotificationTransactions = + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .subTypeEqualTo(TransactionSubType.bip47Notification) + .and() + .typeEqualTo(TransactionType.outgoing) + .findAll(); final List codes = []; for (final codeString in otherCodeStrings) { @@ -1250,8 +1291,10 @@ mixin PaynymInterface final notificationAddress = code.notificationAddressP2PKH(); if (outputAddress == notificationAddress) { - Address? storedAddress = - await mainDB.getAddress(walletId, outputAddress); + Address? storedAddress = await mainDB.getAddress( + walletId, + outputAddress, + ); if (storedAddress == null) { // most likely not mine storedAddress = Address( @@ -1333,10 +1376,12 @@ mixin PaynymInterface int outgoingGapCounter = 0; // non segwit receiving - for (int i = 0; - i < maxNumberOfIndexesToCheck && - receivingGapCounter < maxUnusedAddressGap; - i++) { + for ( + int i = 0; + i < maxNumberOfIndexesToCheck && + receivingGapCounter < maxUnusedAddressGap; + i++ + ) { if (receivingGapCounter < maxUnusedAddressGap) { final address = await _generatePaynymReceivingAddress( sender: other, @@ -1361,10 +1406,11 @@ mixin PaynymInterface } // non segwit sends - for (int i = 0; - i < maxNumberOfIndexesToCheck && - outgoingGapCounter < maxUnusedAddressGap; - i++) { + for ( + int i = 0; + i < maxNumberOfIndexesToCheck && outgoingGapCounter < maxUnusedAddressGap; + i++ + ) { if (outgoingGapCounter < maxUnusedAddressGap) { final address = await _generatePaynymSendAddress( other: other, @@ -1393,10 +1439,12 @@ mixin PaynymInterface int receivingGapCounterSegwit = 0; int outgoingGapCounterSegwit = 0; // segwit receiving - for (int i = 0; - i < maxNumberOfIndexesToCheck && - receivingGapCounterSegwit < maxUnusedAddressGap; - i++) { + for ( + int i = 0; + i < maxNumberOfIndexesToCheck && + receivingGapCounterSegwit < maxUnusedAddressGap; + i++ + ) { if (receivingGapCounterSegwit < maxUnusedAddressGap) { final address = await _generatePaynymReceivingAddress( sender: other, @@ -1421,10 +1469,12 @@ mixin PaynymInterface } // segwit sends - for (int i = 0; - i < maxNumberOfIndexesToCheck && - outgoingGapCounterSegwit < maxUnusedAddressGap; - i++) { + for ( + int i = 0; + i < maxNumberOfIndexesToCheck && + outgoingGapCounterSegwit < maxUnusedAddressGap; + i++ + ) { if (outgoingGapCounterSegwit < maxUnusedAddressGap) { final address = await _generatePaynymSendAddress( other: other, @@ -1453,25 +1503,24 @@ mixin PaynymInterface } Future
getMyNotificationAddress() async { - final storedAddress = await mainDB - .getAddresses(walletId) - .filter() - .subTypeEqualTo(AddressSubType.paynymNotification) - .and() - .typeEqualTo(AddressType.p2pkh) - .and() - .not() - .typeEqualTo(AddressType.nonWallet) - .findFirst(); + final storedAddress = + await mainDB + .getAddresses(walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymNotification) + .and() + .typeEqualTo(AddressType.p2pkh) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .findFirst(); if (storedAddress != null) { return storedAddress; } else { final root = await _getRootNode(); final node = root.derivePath( - _basePaynymDerivePath( - testnet: info.coin.network.isTestNet, - ), + _basePaynymDerivePath(testnet: info.coin.network.isTestNet), ); final paymentCode = PaymentCode.fromBip32Node( node, @@ -1483,23 +1532,19 @@ mixin PaynymInterface pubkey: paymentCode.notificationPublicKey(), ); - final addressString = btc_dart - .P2PKH( - data: data, - network: networkType, - ) - .data - .address!; + final addressString = + btc_dart.P2PKH(data: data, network: networkType).data.address!; Address address = Address( walletId: walletId, value: addressString, publicKey: paymentCode.getPubKey(), derivationIndex: 0, - derivationPath: DerivationPath() - ..value = _notificationDerivationPath( - testnet: info.coin.network.isTestNet, - ), + derivationPath: + DerivationPath() + ..value = _notificationDerivationPath( + testnet: info.coin.network.isTestNet, + ), type: AddressType.p2pkh, subType: AddressSubType.paynymNotification, otherData: await storeCode(paymentCode.toString()), @@ -1510,16 +1555,17 @@ mixin PaynymInterface // beginning to see if there already was notification address. This would // lead to a Unique Index violation error await mainDB.isar.writeTxn(() async { - final storedAddress = await mainDB - .getAddresses(walletId) - .filter() - .subTypeEqualTo(AddressSubType.paynymNotification) - .and() - .typeEqualTo(AddressType.p2pkh) - .and() - .not() - .typeEqualTo(AddressType.nonWallet) - .findFirst(); + final storedAddress = + await mainDB + .getAddresses(walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymNotification) + .and() + .typeEqualTo(AddressType.p2pkh) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .findFirst(); if (storedAddress == null) { await mainDB.isar.addresses.put(address); @@ -1597,45 +1643,43 @@ mixin PaynymInterface overrideAddresses ?? await fetchAddressesForElectrumXScan(); // Separate receiving and change addresses. - final Set receivingAddresses = allAddressesOld - .where( - (e) => - e.subType == AddressSubType.receiving || - e.subType == AddressSubType.paynymNotification || - e.subType == AddressSubType.paynymReceive, - ) - .map((e) => e.value) - .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => e.value) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where( + (e) => + e.subType == AddressSubType.receiving || + e.subType == AddressSubType.paynymNotification || + e.subType == AddressSubType.paynymReceive, + ) + .map((e) => e.value) + .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => e.value) + .toSet(); // Remove duplicates. final allAddressesSet = {...receivingAddresses, ...changeAddresses}; // Fetch history from ElectrumX. - final List> allTxHashes = - await fetchHistory(allAddressesSet); - - final unconfirmedTxs = await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .heightIsNull() - .or() - .heightEqualTo(0) - .txidProperty() - .findAll(); - - allTxHashes.addAll( - unconfirmedTxs.map( - (e) => { - "tx_hash": e, - }, - ), + final List> allTxHashes = await fetchHistory( + allAddressesSet, ); + final unconfirmedTxs = + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .heightIsNull() + .or() + .heightEqualTo(0) + .txidProperty() + .findAll(); + + allTxHashes.addAll(unconfirmedTxs.map((e) => {"tx_hash": e})); + // Only parse new txs (not in db yet). final List> allTransactions = []; for (final txHash in allTxHashes) { @@ -1660,16 +1704,17 @@ mixin PaynymInterface } catch (e) { // tx no longer exists then delete from local db if (e.toString().contains( - "JSON-RPC error 2: daemon error: DaemonError({'code': -5, " - "'message': 'No such mempool or blockchain transaction", - )) { + "JSON-RPC error 2: daemon error: DaemonError({'code': -5, " + "'message': 'No such mempool or blockchain transaction", + )) { await mainDB.isar.writeTxn( - () async => await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .txidEqualTo(txid) - .deleteFirst(), + () async => + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .txidEqualTo(txid) + .deleteFirst(), ); continue; } else { @@ -1792,8 +1837,9 @@ mixin PaynymInterface TransactionSubType subType = TransactionSubType.none; if (outputs.length > 1 && inputs.isNotEmpty) { for (int i = 0; i < outputs.length; i++) { - final List? scriptChunks = - outputs[i].scriptPubKeyAsm?.split(" "); + final List? scriptChunks = outputs[i].scriptPubKeyAsm?.split( + " ", + ); if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") { final blindedPaymentCode = scriptChunks![1]; final bytes = blindedPaymentCode.toUint8ListFromHex; @@ -1826,10 +1872,8 @@ mixin PaynymInterface // TODO: [prio=none] Check for special Bitcoin outputs like ordinals. } else { - Logging.instance.log( - "Unexpected tx found (ignoring it): $txData", - level: LogLevel.Error, - ); + Logging.instance.w("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } @@ -1848,7 +1892,8 @@ mixin PaynymInterface txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -1864,12 +1909,8 @@ mixin PaynymInterface } @override - Future< - ({ - String? blockedReason, - bool blocked, - String? utxoLabel, - })> checkBlockUTXO( + Future<({String? blockedReason, bool blocked, String? utxoLabel})> + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map? jsonTX, @@ -1897,7 +1938,8 @@ mixin PaynymInterface blocked = true; blockedReason = "Incoming paynym notification transaction."; } else { - blockedReason = "Paynym notification change output. Incautious " + blockedReason = + "Paynym notification change output. Incautious " "handling of change outputs from notification transactions " "may cause unintended loss of privacy."; utxoLabel = blockedReason; @@ -1912,23 +1954,21 @@ mixin PaynymInterface return ( blockedReason: blockedReason, blocked: blocked, - utxoLabel: utxoLabel + utxoLabel: utxoLabel, ); } @override FilterOperation? get transactionFilterOperation => FilterGroup.not( - const FilterGroup.and( - [ - FilterCondition.equalTo( - property: r"subType", - value: TransactionSubType.bip47Notification, - ), - FilterCondition.equalTo( - property: r"type", - value: TransactionType.incoming, - ), - ], - ), - ); + const FilterGroup.and([ + FilterCondition.equalTo( + property: r"subType", + value: TransactionSubType.bip47Notification, + ), + FilterCondition.equalTo( + property: r"type", + value: TransactionType.incoming, + ), + ]), + ); } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart index 80bff1a7a..2e609aeb0 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart @@ -148,10 +148,7 @@ mixin RbfInterface ), ], ); - Logging.instance.log( - "RBF on assumed send all", - level: LogLevel.Debug, - ); + Logging.instance.d("RBF on assumed send all"); return await prepareSend(txData: txData); } else if (txData.recipients!.where((e) => e.isChange).length == 1) { final newFee = BigInt.from(oldTransaction.vSize! * newRate); @@ -184,9 +181,8 @@ mixin RbfInterface isChange: removed.isChange, ), ); - Logging.instance.log( + Logging.instance.d( "RBF with same utxo set with increased fee and reduced change", - level: LogLevel.Debug, ); } else { // new change amount is less than dust limit. @@ -198,9 +194,8 @@ mixin RbfInterface // oh well... // do nothing here as we already removed the change output above - Logging.instance.log( + Logging.instance.d( "RBF with same utxo set with increased fee and no change", - level: LogLevel.Debug, ); } return await buildTransaction( @@ -255,10 +250,9 @@ mixin RbfInterface // TODO: remove assert assert(newUtxoSet.length == txData.utxos!.length + extraUtxos.length); - Logging.instance.log( + Logging.instance.d( "RBF with ${extraUtxos.length} extra utxo(s)" " added to pay for the new fee", - level: LogLevel.Debug, ); return await buildTransaction( diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 977a95124..347bf9f2c 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -1,11 +1,15 @@ import 'dart:convert'; +import 'dart:isolate'; import 'dart:math'; import 'package:bitcoindart/bitcoindart.dart' as btc; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart' as spark + show Log; import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart'; import 'package:isar/isar.dart'; +import 'package:logger/logger.dart'; import '../../../db/sqlite/firo_cache.dart'; import '../../../models/balance.dart'; @@ -14,10 +18,13 @@ import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../models/isar/models/isar_models.dart'; import '../../../models/signing_data.dart'; +import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; +import '../../../services/event_bus/global_event_bus.dart'; import '../../../utilities/amount/amount.dart'; import '../../../utilities/enums/derive_path_type_enum.dart'; import '../../../utilities/extensions/extensions.dart'; import '../../../utilities/logger.dart'; +import '../../../utilities/prefs.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; import '../../isar/models/spark_coin.dart'; import '../../isar/models/wallet_info.dart'; @@ -48,6 +55,75 @@ String _hashTag(String tag) { return hash; } +void initSparkLogging(Level level) { + final levels = Level.values.where((e) => e >= level).map((e) => e.name); + spark.Log.levels + .addAll(LoggingLevel.values.where((e) => levels.contains(e.name))); + spark.Log.onLog = ( + level, + value, { + error, + stackTrace, + required time, + }) { + Logging.instance.log( + level.getLoggerLevel(), + value, + error: error, + stackTrace: stackTrace, + time: time, + ); + }; +} + +abstract class _SparkIsolate { + static Isolate? _isolate; + static SendPort? _sendPort; + static final ReceivePort _receivePort = ReceivePort(); + + static Future initialize() async { + final level = Prefs.instance.logLevel; + + _isolate = await Isolate.spawn( + (SendPort sendPort) { + initSparkLogging(level); // ensure logging is set up in isolate + + final receivePort = ReceivePort(); + + sendPort.send(receivePort.sendPort); + + receivePort.listen((message) async { + if (message is List && message.length == 3) { + final function = message[0] as Function; + final argument = message[1]; + final replyPort = message[2] as SendPort; + + final result = await function(argument); + replyPort.send(result); + } + }); + }, + _receivePort.sendPort, + ); + _sendPort = await _receivePort.first as SendPort; + } + + static Future run(ComputeCallback task, M argument) async { + if (_isolate == null || _sendPort == null) await initialize(); + + final ReceivePort responsePort = ReceivePort(); + _sendPort?.send([task, argument, responsePort.sendPort]); + return await responsePort.first as R; + } +} + +Future computeWithLibSparkLogging( + ComputeCallback callback, + M message, +) async { + return _SparkIsolate.run(callback, message); +} + mixin SparkInterface on Bip39HDWallet, ElectrumXInterface { String? _sparkChangeAddressCached; @@ -68,7 +144,7 @@ mixin SparkInterface Future hashTag(String tag) async { try { - return await compute(_hashTag, tag); + return await computeWithLibSparkLogging(_hashTag, tag); } catch (_) { throw ArgumentError("Invalid tag string format", "tag"); } @@ -103,10 +179,7 @@ mixin SparkInterface } } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance.log( - "$runtimeType init() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType init() failed", error: e, stackTrace: s); } // await info.updateReceivingAddress( @@ -551,7 +624,7 @@ mixin SparkInterface ); extractedTx.setPayload(Uint8List(0)); - final spend = await compute( + final spend = await computeWithLibSparkLogging( _createSparkSend, ( privateKeyHex: privateKey.toHex, @@ -683,12 +756,12 @@ mixin SparkInterface required TxData txData, }) async { try { - Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + Logging.instance.d("confirmSend txData: $txData"); final txHash = await electrumXClient.broadcastTransaction( rawTx: txData.raw!, ); - Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + Logging.instance.d("Sent txHash: $txHash"); txData = txData.copyWith( // TODO revisit setting these both @@ -707,9 +780,10 @@ mixin SparkInterface return await updateSentCachedTxData(txData: txData); } catch (e, s) { - Logging.instance.log( - "Exception rethrown from confirmSend(): $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Exception rethrown from confirmSend(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -762,7 +836,7 @@ mixin SparkInterface // if there is new data we try and identify the coins if (rawCoins.isNotEmpty) { // run identify off main isolate - final myCoins = await compute( + final myCoins = await computeWithLibSparkLogging( _identifyCoins, ( anonymitySetCoins: rawCoins, @@ -780,22 +854,40 @@ mixin SparkInterface } return result; - } catch (e) { - Logging.instance.log( - "_refreshSparkCoinsMempoolCheck() failed: $e", - level: LogLevel.Error, + } catch (e, s) { + Logging.instance.e( + "_refreshSparkCoinsMempoolCheck() failed", + error: e, + stackTrace: s, ); return []; } finally { - Logging.instance.log( + Logging.instance.d( "$walletId ${info.name} _refreshSparkCoinsMempoolCheck() run " "duration: ${DateTime.now().difference(start)}", - level: LogLevel.Debug, ); } } - Future refreshSparkData() async { + // returns next percent + double _triggerEventHelper(double current, double increment) { + refreshingPercent = current; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + current, + walletId, + ), + ); + return current + increment; + } + + // Linearly make calls so there is less chance of timing out or otherwise breaking + Future refreshSparkData( + ( + double startingPercent, + double endingPercent, + )? refreshProgressRange, + ) async { final start = DateTime.now(); try { // start by checking if any previous sets are missing from db and add the @@ -816,30 +908,61 @@ mixin SparkInterface } groupIds.add(latestGroupId); - // start fetch and update process for each set groupId as required - final possibleFutures = groupIds.map( - (e) => - FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId( - e, + final steps = groupIds.length + + 1 // get used tags step + + + 1 // check updated cache step + + + 1 // identify coins step + + + 1 // cross ref coins and txns + + + 1; // update balance + + final percentIncrement = refreshProgressRange == null + ? null + : (refreshProgressRange.$2 - refreshProgressRange.$1) / steps; + double currentPercent = refreshProgressRange?.$1 ?? 0; + + // fetch and update process for each set groupId as required + for (final gId in groupIds) { + await FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId( + gId, electrumXClient, cryptoCurrency.network, - ), + // null, + (a, b) { + if (percentIncrement != null) { + _triggerEventHelper( + currentPercent + (percentIncrement * (a / b)), + 0, + ); + } + }, + ); + if (percentIncrement != null) { + currentPercent += percentIncrement; + } + } + + if (percentIncrement != null) { + currentPercent = _triggerEventHelper(currentPercent, percentIncrement); + } + + await FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags( + electrumXClient, + cryptoCurrency.network, ); - // wait for each fetch and update to complete - await Future.wait([ - ...possibleFutures, - FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags( - electrumXClient, - cryptoCurrency.network, - ), - ]); + if (percentIncrement != null) { + currentPercent = _triggerEventHelper(currentPercent, percentIncrement); + } - // Get cached timestamps per groupId. These timestamps are used to check + // Get cached block hashes per groupId. These hashes are used to check // and try to id coins that were added to the spark anon set cache - // after that timestamp. - final groupIdTimestampUTCMap = - info.otherData[WalletInfoKeys.firoSparkCacheSetTimestampCache] + // after that block. + final groupIdBlockHashMap = + info.otherData[WalletInfoKeys.firoSparkCacheSetBlockHashCache] as Map? ?? {}; @@ -847,8 +970,7 @@ mixin SparkInterface // processed by this wallet yet final Map>> rawCoinsBySetId = {}; for (int i = 1; i <= latestGroupId; i++) { - final lastCheckedTimeStampUTC = - groupIdTimestampUTCMap[i.toString()] as int? ?? 0; + final lastCheckedHash = groupIdBlockHashMap[i.toString()] as String?; final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId( i, cryptoCurrency.network, @@ -856,7 +978,7 @@ mixin SparkInterface final anonymitySetResult = await FiroCacheCoordinator.getSetCoinsForGroupId( i, - newerThanTimeStamp: lastCheckedTimeStampUTC, + afterBlockHash: lastCheckedHash, network: cryptoCurrency.network, ); final coinsRaw = anonymitySetResult @@ -873,11 +995,12 @@ mixin SparkInterface rawCoinsBySetId[i] = coinsRaw; } - // update last checked timestamp data - groupIdTimestampUTCMap[i.toString()] = max( - lastCheckedTimeStampUTC, - info?.timestampUTC ?? lastCheckedTimeStampUTC, - ); + // update last checked + groupIdBlockHashMap[i.toString()] = info?.blockHash; + } + + if (percentIncrement != null) { + currentPercent = _triggerEventHelper(currentPercent, percentIncrement); } // get address(es) to get the private key hex strings required for @@ -899,7 +1022,7 @@ mixin SparkInterface // try to identify any coins in the unchecked set data final List newlyIdCoins = []; for (final groupId in rawCoinsBySetId.keys) { - final myCoins = await compute( + final myCoins = await computeWithLibSparkLogging( _identifyCoins, ( anonymitySetCoins: rawCoinsBySetId[groupId]!, @@ -918,15 +1041,18 @@ mixin SparkInterface }); } - // finally update the cached timestamps in the database + // finally update the cached block hashes in the database await info.updateOtherData( newEntries: { - WalletInfoKeys.firoSparkCacheSetTimestampCache: - groupIdTimestampUTCMap, + WalletInfoKeys.firoSparkCacheSetBlockHashCache: groupIdBlockHashMap, }, isar: mainDB.isar, ); + if (percentIncrement != null) { + currentPercent = _triggerEventHelper(currentPercent, percentIncrement); + } + // check for spark coins in mempool final mempoolMyCoins = await _refreshSparkCoinsMempoolCheck( privateKeyHexSet: privateKeyHexSet, @@ -960,34 +1086,42 @@ mixin SparkInterface } // check and update coins if required - final List updatedCoins = []; + final List checkedCoins = []; for (final coin in coinsToCheck) { - SparkCoin updated = coin; + final SparkCoin checked; - if (updated.height == null) { + if (coin.height == null) { final tx = await electrumXCachedClient.getTransaction( - txHash: updated.txHash, + txHash: coin.txHash, cryptoCurrency: info.coin, ); if (tx["height"] is int) { - updated = updated.copyWith(height: tx["height"] as int); + checked = coin.copyWith( + height: tx["height"] as int, + isUsed: spentCoinTags!.contains(coin.lTagHash), + ); + } else { + checked = coin; } + } else { + checked = spentCoinTags!.contains(coin.lTagHash) + ? coin.copyWith(isUsed: true) + : coin; } - if (updated.height != null && - spentCoinTags!.contains(updated.lTagHash)) { - updated = coin.copyWith(isUsed: true); - } - - updatedCoins.add(updated); + checkedCoins.add(checked); } - // update in db if any have changed - if (updatedCoins.isNotEmpty) { + // add/update in db + if (checkedCoins.isNotEmpty) { await mainDB.isar.writeTxn(() async { - await mainDB.isar.sparkCoins.putAll(updatedCoins); + await mainDB.isar.sparkCoins.putAll(checkedCoins); }); } + if (percentIncrement != null) { + currentPercent = _triggerEventHelper(currentPercent, percentIncrement); + } + // used to check if balance is spendable or total final currentHeight = await chainHeight; @@ -1008,9 +1142,7 @@ mixin SparkInterface final spendable = Amount( rawValue: unusedCoins .where( - (e) => - e.height != null && - e.height! + cryptoCurrency.minConfirms <= currentHeight, + (e) => e.isConfirmed(currentHeight, cryptoCurrency.minConfirms), ) .map((e) => e.value) .fold(BigInt.zero, (prev, e) => prev + e), @@ -1033,21 +1165,18 @@ mixin SparkInterface isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.log( - "$runtimeType $walletId ${info.name}: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("$runtimeType $walletId ${info.name}: ", error: e, stackTrace: s); rethrow; } finally { - Logging.instance.log( + Logging.instance.d( "${info.name} refreshSparkData() duration:" " ${DateTime.now().difference(start)}", - level: LogLevel.Debug, ); } } - Future> getMissingSparkSpendTransactionIds() async { + Future> getSparkSpendTransactionIds() async { final tags = await mainDB.isar.sparkCoins .where() .walletIdEqualToAnyLTagHash(walletId) @@ -1056,21 +1185,11 @@ mixin SparkInterface .lTagHashProperty() .findAll(); - final usedCoinTxidsFoundLocally = await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .subTypeEqualTo(TransactionSubType.sparkSpend) - .txidProperty() - .findAll(); - final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor( tags: tags, network: cryptoCurrency.network, ); - pairs.removeWhere((e) => usedCoinTxidsFoundLocally.contains(e.txid)); - return pairs.toSet(); } @@ -1086,12 +1205,10 @@ mixin SparkInterface } try { - await refreshSparkData(); + await refreshSparkData(null); } catch (e, s) { - Logging.instance.log( - "$runtimeType $walletId ${info.name}: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance + .e("$runtimeType $walletId ${info.name}: ", error: e, stackTrace: s); rethrow; } } @@ -1622,9 +1739,10 @@ mixin SparkInterface ); } } catch (e, s) { - Logging.instance.log( - "Caught exception while signing spark mint transaction: $e\n$s", - level: LogLevel.Error, + Logging.instance.e( + "Caught exception while signing spark mint transaction: ", + error: e, + stackTrace: s, ); rethrow; } @@ -1775,9 +1893,10 @@ mixin SparkInterface await confirmSparkMintTransactions(txData: TxData(sparkMints: mints)); } catch (e, s) { - Logging.instance.log( - "Exception caught in anonymizeAllSpark(): $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Exception caught in anonymizeAllSpark(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -1882,9 +2001,10 @@ mixin SparkInterface return txData.copyWith(sparkMints: mints); } catch (e, s) { - Logging.instance.log( - "Exception caught in prepareSparkMintTransaction(): $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Exception caught in prepareSparkMintTransaction(): ", + error: e, + stackTrace: s, ); rethrow; } @@ -2095,7 +2215,7 @@ Future _asyncSparkFeesWrapper({ required List serializedCoins, required int privateRecipientsCount, }) async { - return await compute( + return await computeWithLibSparkLogging( _estSparkFeeComputeFunc, ( privateKeyHex: privateKeyHex, diff --git a/lib/widgets/custom_buttons/paynym_follow_toggle_button.dart b/lib/widgets/custom_buttons/paynym_follow_toggle_button.dart index daa87415c..a2c4db100 100644 --- a/lib/widgets/custom_buttons/paynym_follow_toggle_button.dart +++ b/lib/widgets/custom_buttons/paynym_follow_toggle_button.dart @@ -22,6 +22,7 @@ import '../../providers/global/wallets_provider.dart'; import '../../providers/wallet/my_paynym_account_state_provider.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/assets.dart'; +import '../../utilities/logger.dart'; import '../../utilities/util.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import '../desktop/primary_button.dart'; @@ -109,23 +110,25 @@ class _PaynymFollowToggleButtonState ); await Future.delayed(const Duration(milliseconds: 200)); - print("RRR result: $result"); + Logging.instance.d("PayNym follow result: $result"); } - print("Follow result: $result on try $i"); + Logging.instance.d("Follow result: $result on try $i"); if (result.value!.following == followedAccount.value!.nymID) { if (!loadingPopped && mounted) { Navigator.of(context, rootNavigator: isDesktop).pop(); } - unawaited( - showFloatingFlushBar( - type: FlushBarType.success, - message: "You are following ${followedAccount.value!.nymName}", - context: context, - ), - ); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "You are following ${followedAccount.value!.nymName}", + context: context, + ), + ); + } final myAccount = ref.read(myPaynymAccountStateProvider.state).state!; @@ -150,13 +153,15 @@ class _PaynymFollowToggleButtonState Navigator.of(context, rootNavigator: isDesktop).pop(); } - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Failed to follow ${followedAccount.value!.nymName}", - context: context, - ), - ); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to follow ${followedAccount.value!.nymName}", + context: context, + ), + ); + } return false; } @@ -212,23 +217,25 @@ class _PaynymFollowToggleButtonState followedAccount.value!.nonSegwitPaymentCode.code, ); await Future.delayed(const Duration(milliseconds: 200)); - print("unfollow RRR result: $result"); + Logging.instance.d("PayNym unfollow result: $result"); } - print("Unfollow result: $result on try $i"); + Logging.instance.d("Unfollow result: $result on try $i"); if (result.value!.unfollowing == followedAccount.value!.nymID) { if (!loadingPopped && mounted) { Navigator.of(context, rootNavigator: isDesktop).pop(); } - unawaited( - showFloatingFlushBar( - type: FlushBarType.success, - message: "You have unfollowed ${followedAccount.value!.nymName}", - context: context, - ), - ); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "You have unfollowed ${followedAccount.value!.nymName}", + context: context, + ), + ); + } final myAccount = ref.read(myPaynymAccountStateProvider.state).state!; @@ -247,13 +254,15 @@ class _PaynymFollowToggleButtonState Navigator.of(context, rootNavigator: isDesktop).pop(); } - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Failed to unfollow ${followedAccount.value!.nymName}", - context: context, - ), - ); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to unfollow ${followedAccount.value!.nymName}", + context: context, + ), + ); + } return false; } diff --git a/lib/widgets/date_picker/date_picker.dart b/lib/widgets/date_picker/date_picker.dart index cd843e542..328e2c096 100644 --- a/lib/widgets/date_picker/date_picker.dart +++ b/lib/widgets/date_picker/date_picker.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:calendar_date_picker2/calendar_date_picker2.dart'; import 'package:flutter/material.dart'; + import '../../themes/stack_colors.dart'; import '../../utilities/constants.dart'; import '../../utilities/util.dart'; @@ -22,8 +23,6 @@ Future showSWDatePicker(BuildContext context) async { _size.height >= 550 ? 450 : _size.height - 32, ); } - print("====================================="); - print(size); final now = DateTime.now(); diff --git a/lib/widgets/desktop/desktop_fee_dialog.dart b/lib/widgets/desktop/desktop_fee_dialog.dart index 322fbb876..713aaa109 100644 --- a/lib/widgets/desktop/desktop_fee_dialog.dart +++ b/lib/widgets/desktop/desktop_fee_dialog.dart @@ -1,5 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:cs_monero/cs_monero.dart' as lib_monero; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/models.dart'; diff --git a/lib/widgets/desktop/desktop_scaffold.dart b/lib/widgets/desktop/desktop_scaffold.dart index 25b18963b..920c66306 100644 --- a/lib/widgets/desktop/desktop_scaffold.dart +++ b/lib/widgets/desktop/desktop_scaffold.dart @@ -9,6 +9,7 @@ */ import 'package:flutter/material.dart'; + import '../../themes/stack_colors.dart'; import '../background.dart'; @@ -70,8 +71,7 @@ class MasterScaffold extends StatelessWidget { } else { return Background( child: Scaffold( - backgroundColor: background ?? - Theme.of(context).extension()!.background, + backgroundColor: background ?? Colors.transparent, appBar: appBar as PreferredSizeWidget?, body: body, ), diff --git a/lib/widgets/desktop/qr_code_scanner_dialog.dart b/lib/widgets/desktop/qr_code_scanner_dialog.dart index edb6a074a..53b21bb9c 100644 --- a/lib/widgets/desktop/qr_code_scanner_dialog.dart +++ b/lib/widgets/desktop/qr_code_scanner_dialog.dart @@ -23,14 +23,10 @@ import 'primary_button.dart'; import 'secondary_button.dart'; class QrCodeScannerDialog extends StatefulWidget { - final Function(String) onQrCodeDetected; - - QrCodeScannerDialog({ - required this.onQrCodeDetected, - }); + const QrCodeScannerDialog({super.key}); @override - _QrCodeScannerDialogState createState() => _QrCodeScannerDialogState(); + State createState() => _QrCodeScannerDialogState(); } class _QrCodeScannerDialogState extends State { @@ -43,39 +39,37 @@ class _QrCodeScannerDialogState extends State { bool _isScanning = false; int _cameraId = -1; String? _macOSDeviceId; - final int _imageDelayInMs = 250; + final int _imageDelayInMs = Platform.isLinux ? 500 : 250; @override void initState() { super.initState(); - _isCameraOpen = false; - _isScanning = false; - _initializeCamera(); - } - @override - void dispose() { - _stopCamera(); - super.dispose(); + _initializeCamera().then((camOpen) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && camOpen) { + setState(() { + _isCameraOpen = true; + }); + unawaited(_captureAndScanImage()); + } + }); + }); } - Future _initializeCamera() async { + Future _initializeCamera() async { try { - setState(() { - _isScanning = true; // Show the progress indicator - }); - if (Platform.isLinux && _cameraLinuxPlugin != null) { - await _cameraLinuxPlugin!.initializeCamera(); - Logging.instance.log("Linux Camera initialized", level: LogLevel.Info); + await _cameraLinuxPlugin.initializeCamera(); + Logging.instance.d("Linux Camera initialized"); } else if (Platform.isWindows && _cameraWindowsPlugin != null) { final List cameras = - await _cameraWindowsPlugin!.availableCameras(); + await _cameraWindowsPlugin.availableCameras(); if (cameras.isEmpty) { throw CameraException('No cameras available', 'No cameras found.'); } final CameraDescription camera = cameras[0]; // Could be user-selected. - _cameraId = await _cameraWindowsPlugin!.createCameraWithSettings( + _cameraId = await _cameraWindowsPlugin.createCameraWithSettings( camera, const MediaSettings( resolutionPreset: ResolutionPreset.low, @@ -84,11 +78,12 @@ class _QrCodeScannerDialogState extends State { enableAudio: false, ), ); - await _cameraWindowsPlugin!.initializeCamera(_cameraId); + await _cameraWindowsPlugin.initializeCamera(_cameraId); // await _cameraWindowsPlugin!.onCameraInitialized(_cameraId).first; // TODO [prio=low]: Make this work. ^^^ - Logging.instance.log("Windows Camera initialized with ID: $_cameraId", - level: LogLevel.Info); + Logging.instance.d( + "Windows Camera initialized with ID: $_cameraId", + ); } else if (Platform.isMacOS) { final List videoDevices = await CameraMacOS.instance .listDevices(deviceType: CameraMacOSDeviceType.video); @@ -99,43 +94,33 @@ class _QrCodeScannerDialogState extends State { await CameraMacOS.instance .initialize(cameraMacOSMode: CameraMacOSMode.photo); - setState(() { - _isCameraOpen = true; - }); - - Logging.instance.log( - "macOS Camera initialized with ID: $_macOSDeviceId", - level: LogLevel.Info); - } - if (mounted) { - setState(() { - _isCameraOpen = true; - _isScanning = true; - }); + Logging.instance.d( + "macOS Camera initialized with ID: $_macOSDeviceId", + ); } - unawaited(_captureAndScanImage()); // Could be awaited. + + return true; } catch (e, s) { - Logging.instance - .log("Failed to initialize camera: $e\n$s", level: LogLevel.Error); - if (mounted) { - // widget.onSnackbar("Failed to initialize camera. Please try again."); - setState(() { - _isScanning = false; - }); - } + Logging.instance.e( + "Failed to initialize camera", + error: e, + stackTrace: s, + ); + return false; } } Future _stopCamera() async { + _isScanning = false; + try { if (Platform.isLinux && _cameraLinuxPlugin != null) { - _cameraLinuxPlugin!.stopCamera(); - Logging.instance.log("Linux Camera stopped", level: LogLevel.Info); + _cameraLinuxPlugin.stopCamera(); + Logging.instance.d("Linux Camera stopped"); } else if (Platform.isWindows && _cameraWindowsPlugin != null) { // if (_cameraId >= 0) { - await _cameraWindowsPlugin!.dispose(_cameraId); - Logging.instance.log("Windows Camera stopped with ID: $_cameraId", - level: LogLevel.Info); + await _cameraWindowsPlugin.dispose(_cameraId); + Logging.instance.d("Windows Camera stopped with ID: $_cameraId"); // } else { // Logging.instance.log("Windows Camera ID is null. Cannot dispose.", // level: LogLevel.Error); @@ -143,32 +128,28 @@ class _QrCodeScannerDialogState extends State { } else if (Platform.isMacOS) { // if (_macOSDeviceId != null) { await CameraMacOS.instance.stopImageStream(); - Logging.instance.log("macOS Camera stopped with ID: $_macOSDeviceId", - level: LogLevel.Info); + Logging.instance.d("macOS Camera stopped with ID: $_macOSDeviceId"); // } else { // Logging.instance.log("macOS Camera ID is null. Cannot stop.", // level: LogLevel.Error); // } } } catch (e, s) { - Logging.instance - .log("Failed to stop camera: $e\n$s", level: LogLevel.Error); - } finally { - if (mounted) { - setState(() { - _isScanning = false; - _isCameraOpen = false; - }); - } + Logging.instance.e( + "Failed to stop camera", + error: e, + stackTrace: s, + ); } } Future _captureAndScanImage() async { - while (_isCameraOpen && _isScanning) { + _isScanning = true; + while (_isScanning) { try { String? base64Image; if (Platform.isLinux && _cameraLinuxPlugin != null) { - base64Image = await _cameraLinuxPlugin!.captureImage(); + base64Image = await _cameraLinuxPlugin.captureImage(); } else if (Platform.isWindows) { final XFile xfile = await _cameraWindowsPlugin!.takePicture(_cameraId); @@ -178,16 +159,14 @@ class _QrCodeScannerDialogState extends State { } else if (Platform.isMacOS) { final macOSimg = await CameraMacOS.instance.takePicture(); if (macOSimg == null) { - Logging.instance - .log("Failed to capture image", level: LogLevel.Error); - await Future.delayed(Duration(milliseconds: _imageDelayInMs)); + Logging.instance.w("Failed to capture image"); + await Future.delayed(Duration(milliseconds: _imageDelayInMs)); continue; } final img.Image? image = img.decodeImage(macOSimg.bytes!); if (image == null) { - Logging.instance - .log("Failed to capture image", level: LogLevel.Error); - await Future.delayed(Duration(milliseconds: _imageDelayInMs)); + Logging.instance.w("Failed to capture image"); + await Future.delayed(Duration(milliseconds: _imageDelayInMs)); continue; } base64Image = base64Encode(img.encodePng(image)); @@ -196,7 +175,7 @@ class _QrCodeScannerDialogState extends State { // Logging.instance // .log("Failed to capture image", level: LogLevel.Error); // Spammy. - await Future.delayed(Duration(milliseconds: _imageDelayInMs)); + await Future.delayed(Duration(milliseconds: _imageDelayInMs)); continue; } final img.Image? image = img.decodeImage(base64Decode(base64Image)); @@ -204,8 +183,8 @@ class _QrCodeScannerDialogState extends State { // > WARNING Since this will check the image data against all known // > decoders, it is much slower than using an explicit decoder if (image == null) { - Logging.instance.log("Failed to decode image", level: LogLevel.Error); - await Future.delayed(Duration(milliseconds: _imageDelayInMs)); + Logging.instance.w("Failed to decode image"); + await Future.delayed(Duration(milliseconds: _imageDelayInMs)); continue; } @@ -220,22 +199,23 @@ class _QrCodeScannerDialogState extends State { final String? scanResult = await _scanImage(image); if (scanResult != null && scanResult.isNotEmpty) { - widget.onQrCodeDetected(scanResult); + await _stopCamera(); + if (mounted) { - Navigator.of(context).pop(); + Navigator.of(context).pop(scanResult); } break; } else { - // Logging.instance.log("No QR code found in the image", level: LogLevel.Info); + // Logging.instance.log("No QR code found in the image"); // if (mounted) { // widget.onSnackbar("No QR code found in the image."); // } // Spammy. } - await Future.delayed(Duration(milliseconds: _imageDelayInMs)); - } catch (e, s) { - // Logging.instance.log("Failed to capture and scan image: $e\n$s", level: LogLevel.Error); + await Future.delayed(Duration(milliseconds: _imageDelayInMs)); + } catch (e) { + // Logging.instance.log("Failed to capture and scan image", error: e, stackTrace: s,); // Spammy. // if (mounted) { @@ -266,8 +246,8 @@ class _QrCodeScannerDialogState extends State { return null; } return qrDecode.text; - } catch (e, s) { - // Logging.instance.log("Failed to decode QR code: $e\n$s", level: LogLevel.Error); + } catch (e) { + // Logging.instance.log("Failed to decode QR code", error: e, stackTrace: s,); // Spammy. return null; } @@ -275,126 +255,140 @@ class _QrCodeScannerDialogState extends State { @override Widget build(BuildContext context) { - return DesktopDialog( - maxWidth: 696, - maxHeight: 600, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "Scan QR code", - style: STextStyles.desktopH3(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Expanded( - child: _isCameraOpen - ? _image != null - ? _image! - : const Center( - child: CircularProgressIndicator(), - ) - : const Center( - child: - CircularProgressIndicator(), // Show progress indicator immediately - ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: Row( + return PopScope( + onPopInvokedWithResult: (_, __) { + _stopCamera(); + }, + child: DesktopDialog( + maxWidth: 696, + maxHeight: 600, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: Container()), - // "Select file" button. - SecondaryButton( - buttonHeight: ButtonHeight.l, - label: "Select file", - width: 200, - onPressed: () async { - final result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ["png", "jpg", "jpeg"], - ); - - if (result == null || result.files.single.path == null) { - await showFloatingFlushBar( - type: FlushBarType.info, - message: "No file selected", - iconAsset: Assets.svg.file, - context: context, + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Scan QR code", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: _isCameraOpen + ? _image != null + ? _image! + : const Center( + child: CircularProgressIndicator(), + ) + : const Center( + child: + CircularProgressIndicator(), // Show progress indicator immediately + ), + ), + Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded(child: Container()), + // "Select file" button. + SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Select file", + width: 200, + onPressed: () async { + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ["png", "jpg", "jpeg"], ); - return; - } - final filePath = result?.files.single.path!; - if (filePath == null) { - await showFloatingFlushBar( - type: FlushBarType.info, - message: "Error selecting file.", - iconAsset: Assets.svg.file, - context: context, - ); - return; - } - try { - final img.Image? image = - img.decodeImage(File(filePath!).readAsBytesSync()); - if (image == null) { - await showFloatingFlushBar( - type: FlushBarType.info, - message: "Failed to decode image.", - iconAsset: Assets.svg.file, - context: context, - ); - return; - } + if (context.mounted) { + if (result == null || + result.files.single.path == null) { + await showFloatingFlushBar( + type: FlushBarType.info, + message: "No file selected", + iconAsset: Assets.svg.file, + context: context, + ); + return; + } + + final filePath = result.files.single.path; + if (filePath == null) { + await showFloatingFlushBar( + type: FlushBarType.info, + message: "Error selecting file.", + iconAsset: Assets.svg.file, + context: context, + ); + return; + } + + try { + final img.Image? image = + img.decodeImage(File(filePath).readAsBytesSync()); + if (image == null) { + await showFloatingFlushBar( + type: FlushBarType.info, + message: "Failed to decode image.", + iconAsset: Assets.svg.file, + context: context, + ); + return; + } - final String? scanResult = await _scanImage(image); - if (scanResult != null && scanResult.isNotEmpty) { - widget.onQrCodeDetected(scanResult); - Navigator.of(context).pop(); - } else { - await showFloatingFlushBar( - type: FlushBarType.info, - message: "No QR code found in the image.", - iconAsset: Assets.svg.file, - context: context, - ); + final String? scanResult = await _scanImage(image); + if (context.mounted) { + if (scanResult != null && scanResult.isNotEmpty) { + Navigator.of(context).pop(scanResult); + } else { + await showFloatingFlushBar( + type: FlushBarType.info, + message: "No QR code found in the image.", + iconAsset: Assets.svg.file, + context: context, + ); + } + } + } catch (e, s) { + Logging.instance.e( + "Failed to decode image: ", + error: e, + stackTrace: s, + ); + if (context.mounted) { + await showFloatingFlushBar( + type: FlushBarType.info, + message: + "Error processing the image. Please try again.", + iconAsset: Assets.svg.file, + context: context, + ); + } + } } - } catch (e, s) { - Logging.instance.log("Failed to decode image: $e\n$s", - level: LogLevel.Error); - await showFloatingFlushBar( - type: FlushBarType.info, - message: - "Error processing the image. Please try again.", - iconAsset: Assets.svg.file, - context: context, - ); - } - }, - ), - const SizedBox(width: 16), - // Close button. - PrimaryButton( - buttonHeight: ButtonHeight.l, - label: "Close", - width: 272.5, - onPressed: () { - _stopCamera(); - Navigator.of(context).pop(); - }, - ), - ], + }, + ), + const SizedBox(width: 16), + // Close button. + PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Close", + width: 272.5, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/widgets/dialogs/s_dialog.dart b/lib/widgets/dialogs/s_dialog.dart new file mode 100644 index 000000000..a6b32148c --- /dev/null +++ b/lib/widgets/dialogs/s_dialog.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; + +import '../../themes/stack_colors.dart'; +import '../../utilities/util.dart'; +import '../conditional_parent.dart'; + +class SDialog extends StatelessWidget { + const SDialog({ + super.key, + required this.child, + this.padding = EdgeInsets.zero, + this.contentCanScroll = true, + this.margin, + this.background, + this.mainAxisAlignment, + this.crossAxisAlignment, + }); + + final Widget child; + final bool contentCanScroll; + final Color? background; + final EdgeInsets? margin; + final EdgeInsets padding; + final MainAxisAlignment? mainAxisAlignment; + final CrossAxisAlignment? crossAxisAlignment; + + @override + Widget build(BuildContext context) { + return Padding( + padding: margin ?? EdgeInsets.all(Util.isDesktop ? 32 : 16), + child: Column( + mainAxisAlignment: mainAxisAlignment ?? + (Util.isDesktop ? MainAxisAlignment.center : MainAxisAlignment.end), + crossAxisAlignment: crossAxisAlignment ?? CrossAxisAlignment.center, + children: [ + Flexible( + child: Material( + borderRadius: BorderRadius.circular(20), + child: Container( + decoration: BoxDecoration( + color: background ?? + Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular( + 20, + ), + ), + child: ConditionalParent( + condition: contentCanScroll, + builder: (child) => SingleChildScrollView( + child: child, + ), + child: Padding( + padding: padding, + child: child, + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/isar_collection_watcher_list.dart b/lib/widgets/isar_collection_watcher_list.dart new file mode 100644 index 000000000..bd4c2596e --- /dev/null +++ b/lib/widgets/isar_collection_watcher_list.dart @@ -0,0 +1,182 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:isar/isar.dart'; + +import '../../themes/stack_colors.dart'; +import '../../utilities/text_styles.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../utilities/util.dart'; +import 'detail_item.dart'; + +class IsarCollectionWatcherList extends StatefulWidget { + const IsarCollectionWatcherList({ + super.key, + required this.queryBuilder, + required this.itemBuilder, + required this.itemName, + }); + + final String itemName; + final QueryBuilder Function() queryBuilder; + final List<(String title, String value, int flex)> Function(T?) itemBuilder; + + @override + State> createState() => + _IsarCollectionWatcherListState(); +} + +class _IsarCollectionWatcherListState + extends State> { + List _items = []; + + Stream>? sparkCoinsCollectionWatcher; + + late final StreamSubscription> _streamSubscription; + + void _onSparkCoinsCollectionWatcherEvent(List items) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _items = items; + }); + } + }); + } + + @override + void initState() { + super.initState(); + + sparkCoinsCollectionWatcher = + widget.queryBuilder().watch(fireImmediately: true); + _streamSubscription = sparkCoinsCollectionWatcher! + .listen((data) => _onSparkCoinsCollectionWatcherEvent(data)); + } + + @override + void dispose() { + _streamSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + children: [ + Text( + "Total ${widget.itemName}: ${_items.length}", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + children: [ + ...widget.itemBuilder(null).map( + (e) => Expanded( + flex: e.$3, + child: Text( + e.$1, + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), + ), + ), + ], + ), + ), + ), + Expanded( + child: ListView.separated( + shrinkWrap: true, + itemCount: _items.length, + separatorBuilder: (_, __) => Container( + height: 1, + color: Theme.of(context) + .extension()! + .backgroundAppBar, + ), + itemBuilder: (_, index) => Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ...widget.itemBuilder(_items[index]).map( + (e) => Expanded( + flex: e.$3, + child: SelectableText( + e.$2, + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.left, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ], + ); + } else { + return ListView.builder( + itemCount: _items.length + 1, + itemBuilder: (ctx, index) { + return Padding( + padding: const EdgeInsets.only( + bottom: 16, + left: 16, + right: 16, + ), + child: RoundedWhiteContainer( + child: index == 0 + ? Row( + children: [ + Text( + "Total ${widget.itemName}: ${_items.length}", + style: STextStyles.itemSubtitle(context), + ), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...widget.itemBuilder(_items[index - 1]).map( + (e) => DetailItem( + title: e.$1, + detail: e.$2, + ), + ), + ], + ), + ), + ); + }, + ); + } + } +} diff --git a/lib/widgets/log_level_preference_widget.dart b/lib/widgets/log_level_preference_widget.dart new file mode 100644 index 000000000..0e8b713c3 --- /dev/null +++ b/lib/widgets/log_level_preference_widget.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logger/logger.dart'; + +import '../providers/global/prefs_provider.dart'; +import '../themes/stack_colors.dart'; +import '../utilities/extensions/extensions.dart'; +import '../utilities/text_styles.dart'; +import '../utilities/util.dart'; +import 'rounded_container.dart'; + +class LogLevelPreferenceWidget extends ConsumerStatefulWidget { + const LogLevelPreferenceWidget({super.key}); + + @override + ConsumerState createState() => + _LogLevelPreferenceWidgetState(); +} + +class _LogLevelPreferenceWidgetState + extends ConsumerState { + double _sliderValue = 0; + static const List _levels = [ + Level.off, + Level.fatal, + Level.error, + Level.warning, + Level.info, + Level.debug, + Level.trace, + ]; + + @override + void initState() { + super.initState(); + _sliderValue = _levels + .indexOf(ref.read(prefsChangeNotifierProvider).logLevel) + .toDouble(); + } + + @override + Widget build(BuildContext context) { + final current = + ref.watch(prefsChangeNotifierProvider.select((s) => s.logLevel)); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Current level: ${current.name.capitalize()}", + style: Util.isDesktop + ? STextStyles.desktopTextFieldLabel(context) + : STextStyles.fieldLabel(context), + textAlign: TextAlign.left, + ), + Slider( + min: 0, + max: _levels.length - 1, + divisions: _levels.length - 1, + value: _sliderValue, + onChanged: (value) { + // setState(() { + _sliderValue = value; + // }); + ref.read(prefsChangeNotifierProvider).logLevel = + _levels[_sliderValue.toInt()]; + }, + ), + if (current == Level.debug || + current == Level.trace || + current == Level.info) + Padding( + padding: const EdgeInsets.only(top: 10), + child: RoundedContainer( + color: + Theme.of(context).extension()!.warningBackground, + child: SelectableText.rich( + TextSpan( + text: "Privacy Warning: ", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + children: [ + TextSpan( + text: "Selecting ", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + ), + TextSpan( + text: "Trace", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + ), + TextSpan( + text: " or ", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + ), + TextSpan( + text: "Debug", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + ), + TextSpan( + text: " may log sensitive metadata, such as transaction" + " details, amounts, addresses, or network activity. While ", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + ), + TextSpan( + text: "Info", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + ), + TextSpan( + text: " logs are less likely to contain sensitive data, " + "they may still include some. No private keys, " + "mnemonics, or credentials will ever be logged, but " + "enabling these levels could expose information that " + "might compromise privacy if accessed by unauthorized parties.", + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: Util.isDesktop ? 14 : 12, + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/textfields/frost_step_field.dart b/lib/widgets/textfields/frost_step_field.dart index d6e6b0cb8..3c86f1fed 100644 --- a/lib/widgets/textfields/frost_step_field.dart +++ b/lib/widgets/textfields/frost_step_field.dart @@ -91,29 +91,25 @@ class _FrostStepFieldState extends State { _changed(widget.controller.text); } else { // Platform.isLinux, Platform.isWindows, or Platform.isMacOS. - await showDialog( + final qrResult = await showDialog( context: context, - builder: (context) { - return QrCodeScannerDialog( - onQrCodeDetected: (qrCodeData) { - try { - // TODO [prio=low]: Validate QR code data. - widget.controller.text = qrCodeData; - - _changed(widget.controller.text); - } catch (e, s) { - Logging.instance.log("Error processing QR code data: $e\n$s", - level: LogLevel.Error); - } - }, - ); - }, + builder: (context) => const QrCodeScannerDialog(), ); + + if (qrResult == null) { + Logging.instance.d("Qr scanning cancelled"); + } else { + // TODO [prio=low]: Validate QR code data. + widget.controller.text = qrResult; + + _changed(widget.controller.text); + } } } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, + Logging.instance.w( + "Failed to get camera permissions while trying to scan qr code: ", + error: e, + stackTrace: s, ); } } diff --git a/lib/widgets/tx_key_widget.dart b/lib/widgets/tx_key_widget.dart new file mode 100644 index 000000000..ba8e9b33c --- /dev/null +++ b/lib/widgets/tx_key_widget.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../pages/pinpad_views/pinpad_dialog.dart'; +import '../pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; +import '../pages_desktop_specific/password/request_desktop_auth_dialog.dart'; +import '../providers/global/wallets_provider.dart'; +import '../utilities/text_styles.dart'; +import '../utilities/util.dart'; +import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import 'custom_buttons/blue_text_button.dart'; +import 'custom_buttons/simple_copy_button.dart'; +import 'detail_item.dart'; + +class TxKeyWidget extends ConsumerStatefulWidget { + /// The [walletId] MUST be the id of a [LibMoneroWallet]! + const TxKeyWidget({ + super.key, + required this.walletId, + required this.txid, + }); + + final String walletId; + final String txid; + + @override + ConsumerState createState() => _TxKeyWidgetState(); +} + +class _TxKeyWidgetState extends ConsumerState { + String? _private; + + bool _lock = false; + + Future _loadTxKey() async { + if (_lock) { + return; + } + _lock = true; + + try { + final verified = await showDialog( + context: context, + builder: (context) => Util.isDesktop + ? const RequestDesktopAuthDialog(title: "Show private view key") + : const PinpadDialog( + biometricsAuthenticationTitle: "Show private view key", + biometricsLocalizedReason: + "Authenticate to show private view key", + biometricsCancelButtonString: "CANCEL", + ), + barrierDismissible: !Util.isDesktop, + ); + + if (verified == "verified success" && mounted) { + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet; + + _private = wallet.getTxKeyFor(txid: widget.txid); + if (_private!.isEmpty) { + _private = "Unavailable"; + } + + if (context.mounted) { + setState(() {}); + } else { + _private == null; + } + } + } finally { + _lock = false; + } + } + + @override + Widget build(BuildContext context) { + return DetailItemBase( + button: _private == null + ? CustomTextButton( + text: "Show", + onTap: _loadTxKey, + enabled: _private == null, + ) + : Util.isDesktop + ? IconCopyButton( + data: _private!, + ) + : SimpleCopyButton( + data: _private!, + ), + title: Text( + "Private view key", + style: STextStyles.itemSubtitle(context), + ), + detail: SelectableText( + // TODO + _private ?? "*" * 52, // 52 is approx length + style: STextStyles.w500_14(context), + ), + ); + } +} diff --git a/lib/widgets/wallet_card.dart b/lib/widgets/wallet_card.dart index 814d4b321..ed49e5ebb 100644 --- a/lib/widgets/wallet_card.dart +++ b/lib/widgets/wallet_card.dart @@ -28,7 +28,7 @@ import '../utilities/util.dart'; import '../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../wallets/wallet/impl/ethereum_wallet.dart'; import '../wallets/wallet/impl/sub_wallets/eth_token_wallet.dart'; -import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../wallets/wallet/intermediate/external_wallet.dart'; import '../wallets/wallet/wallet.dart'; import 'conditional_parent.dart'; import 'desktop/primary_button.dart'; @@ -111,7 +111,7 @@ class SimpleWalletCard extends ConsumerWidget { if (context.mounted) { final Future loadFuture; - if (wallet is LibMoneroWallet) { + if (wallet is ExternalWallet) { loadFuture = wallet.init().then((value) async => await (wallet).open()); } else { loadFuture = wallet.init(); @@ -159,9 +159,8 @@ class SimpleWalletCard extends ConsumerWidget { if (!success!) { // TODO: show error dialog here? - Logging.instance.log( + Logging.instance.e( "Failed to load token wallet for $contract", - level: LogLevel.Error, ); return; } diff --git a/lib/widgets/xelis_table_progress.dart b/lib/widgets/xelis_table_progress.dart new file mode 100644 index 000000000..ffecba861 --- /dev/null +++ b/lib/widgets/xelis_table_progress.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/progress_bar.dart'; + +import '../providers/providers.dart'; + +class XelisTableProgress extends ConsumerWidget { + const XelisTableProgress({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final progressAsyncValue = ref.watch(xelisTableProgressProvider); + + return DefaultTextStyle( + style: TextStyle( + color: Theme.of(context).textTheme.bodyLarge?.color ?? Colors.black, + fontSize: 14, + ), + child: Center( + child: progressAsyncValue.when( + data: (progress) => Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular(12), + ), + constraints: const BoxConstraints(maxWidth: 450), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Generating Precomputed Tables...", + style: STextStyles.desktopH3(context).copyWith( + fontSize: 24, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + "These tables are required for the fast decryption of private transactions. This is a one-time process upon the creation of your first Xelis wallet in Stack Wallet.", + style: STextStyles.subtitle600(context).copyWith( + fontSize: 14, + color: Theme.of(context).extension()!.textSubtitle1, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + Text( + progress.currentStep.displayName, + style: STextStyles.titleBold12(context), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + ProgressBar( + width: 200, + height: 8, + fillColor: const Color.fromARGB(255,2,255,207), + backgroundColor: Theme.of(context).extension()!.textFieldDefaultBG, + percent: progress.tableProgress ?? 0.0, + ), + const SizedBox(height: 4), + Text( + "${((progress.tableProgress ?? 0.0) * 100).toStringAsFixed(1)}%", + style: STextStyles.label(context), + ), + ], + ), + ), + loading: () => const SizedBox.shrink(), + error: (_, __) => const SizedBox.shrink(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 72a81025c..aa67f97ac 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -21,6 +21,7 @@ list(APPEND FLUTTER_FFI_PLUGIN_LIST flutter_libsparkmobile frostdart tor_ffi_plugin + xelis_flutter ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 66db749d9..245400e1a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,6 +11,7 @@ import cs_monero_flutter_libs_macos import desktop_drop import device_info_plus import devicelocale +import file_picker import flutter_libepiccash import flutter_local_notifications import flutter_secure_storage_macos @@ -33,6 +34,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FlutterLibepiccashPlugin.register(with: registry.registrar(forPlugin: "FlutterLibepiccashPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index b9159cd55..a51d63f71 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -7,7 +7,7 @@ PODS: - connectivity_plus (0.0.1): - FlutterMacOS - ReachabilitySwift - - cs_monero_flutter_libs (0.0.1): + - cs_monero_flutter_libs_macos (0.0.1): - FlutterMacOS - desktop_drop (0.0.1): - FlutterMacOS @@ -15,6 +15,8 @@ PODS: - FlutterMacOS - devicelocale (0.0.1): - FlutterMacOS + - file_picker (0.0.1): + - FlutterMacOS - flutter_libepiccash (0.0.1): - FlutterMacOS - flutter_libsparkmobile (0.0.1): @@ -38,14 +40,12 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - ReachabilitySwift (5.0.0) + - ReachabilitySwift (5.2.3) - share_plus (0.0.1): - FlutterMacOS - "sqlite3 (3.46.0+1)": - "sqlite3/common (= 3.46.0+1)" - "sqlite3/common (3.46.0+1)" - - "sqlite3/dbstatvtab (3.46.0+1)": - - sqlite3/common - "sqlite3/fts5 (3.46.0+1)": - sqlite3/common - "sqlite3/perf-threadsafe (3.46.0+1)": @@ -54,8 +54,7 @@ PODS: - sqlite3/common - sqlite3_flutter_libs (0.0.1): - FlutterMacOS - - "sqlite3 (~> 3.46.0+1)" - - sqlite3/dbstatvtab + - sqlite3 (~> 3.46.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree @@ -68,15 +67,18 @@ PODS: - FlutterMacOS - window_size (0.0.2): - FlutterMacOS + - xelis_flutter (0.0.1): + - FlutterMacOS DEPENDENCIES: - camera_macos (from `Flutter/ephemeral/.symlinks/plugins/camera_macos/macos`) - coinlib_flutter (from `Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin`) - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - - cs_monero_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs/macos`) + - cs_monero_flutter_libs_macos (from `Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs_macos/macos`) - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`) + - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - flutter_libepiccash (from `Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos`) - flutter_libsparkmobile (from `Flutter/ephemeral/.symlinks/plugins/flutter_libsparkmobile/macos`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) @@ -95,6 +97,7 @@ DEPENDENCIES: - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) + - xelis_flutter (from `Flutter/ephemeral/.symlinks/plugins/xelis_flutter/macos`) SPEC REPOS: trunk: @@ -108,14 +111,16 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin connectivity_plus: :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos - cs_monero_flutter_libs: - :path: Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs/macos + cs_monero_flutter_libs_macos: + :path: Flutter/ephemeral/.symlinks/plugins/cs_monero_flutter_libs_macos/macos desktop_drop: :path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos devicelocale: :path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos + file_picker: + :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos flutter_libepiccash: :path: Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos flutter_libsparkmobile: @@ -152,36 +157,40 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos window_size: :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos + xelis_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/xelis_flutter/macos SPEC CHECKSUMS: camera_macos: c2603f5eed16f05076cf17e12030d2ce55a77839 coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5 connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - cs_monero_flutter_libs: e91a436103857259f5855cad4971301a5a29b38d + cs_monero_flutter_libs_macos: b901f94d39d1338f706312b026aba928d23582d4 desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 + file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af flutter_libepiccash: be1560a04150c5cc85bcf08d236ec2b3d1f5d8da flutter_libsparkmobile: df2d36af1691379c81249e7be7b68be3c81d388b - flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 + flutter_local_notifications: 4b427ffabf278fc6ea9484c97505e231166927a5 flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 frostdart: e6bf3119527ccfbcec1b8767da6ede5bb4c4f716 isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a lelantus: 308e42c5a648598936a07a234471dd8cf8e687a0 local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 - package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630 - sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b + sqlite3_flutter_libs: 1be4459672f8168ded2d8667599b8e3ca5e72b83 stack_wallet_backup: 6ebc60b1bdcf11cf1f1cbad9aa78332e1e15778c tor_ffi_plugin: 2566c1ed174688cca560fa0c64b7a799c66f07cb - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 + xelis_flutter: 34e05f3621e46381fb1b10d7c11f63764d3f7a80 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index 3d2512f1e..4dad60b7a 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -6,13 +6,15 @@ com.apple.security.cs.allow-jit - com.apple.security.network.server + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.files.user-selected.read-write com.apple.security.network.client - - com.apple.security.device.audio-input - com.apple.security.device.camera + com.apple.security.network.server diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 4094a6367..7c2b75281 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,13 +4,15 @@ com.apple.security.app-sandbox - com.apple.security.network.client + com.apple.security.device.audio-input - com.apple.security.network.server + com.apple.security.device.camera - com.apple.security.device.audio-input + com.apple.security.files.user-selected.read-write - com.apple.security.device.camera + com.apple.security.network.client + + com.apple.security.network.server diff --git a/pubspec.lock b/pubspec.lock index a973ef92f..ffcc7e9a8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: "direct dev" description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" another_flushbar: dependency: "direct main" description: @@ -50,26 +50,26 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: "direct main" description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" barcode_scan2: dependency: "direct main" description: name: barcode_scan2 - sha256: a2ab566027cd57b2795ea42aa26835dbaa8fe70bcc1aff54942a14d3705dff97 + sha256: efbe38629e6df2200e4d60ebe252e8e041cd5ae7b50f194a20f01779ade9d1c3 url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.5.0" basic_utils: dependency: "direct main" description: @@ -159,10 +159,10 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" borsh_annotation: dependency: transitive description: @@ -183,50 +183,58 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172 + url: "https://pub.dev" + source: hosted + version: "2.1.0" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "8.0.0" built_collection: dependency: transitive description: @@ -239,10 +247,10 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.5" calendar_date_picker2: dependency: "direct main" description: @@ -254,10 +262,11 @@ packages: camera_linux: dependency: "direct main" description: - name: camera_linux - sha256: "6ea08c23f643364e650e8fad73653747c049cbd00803a7c317132379ee3653ac" - url: "https://pub.dev" - source: hosted + path: "." + ref: ecb412474c5d240347b04ac1eb9f019802ff7034 + resolved-ref: ecb412474c5d240347b04ac1eb9f019802ff7034 + url: "https://github.com/cypherstack/camera-linux" + source: git version: "0.0.8" camera_macos: dependency: "direct main" @@ -296,10 +305,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" charcode: dependency: transitive description: @@ -328,10 +337,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -344,28 +353,28 @@ packages: dependency: "direct overridden" description: path: coinlib - ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739" - resolved-ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739" - url: "https://github.com/julian-CStack/coinlib.git" + ref: fd5f658320f00a2e281ccaee97c2d2a77b4aa966 + resolved-ref: fd5f658320f00a2e281ccaee97c2d2a77b4aa966 + url: "https://www.github.com/julian-CStack/coinlib" source: git - version: "2.2.0" + version: "3.1.0" coinlib_flutter: dependency: "direct main" description: path: coinlib_flutter - ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739" - resolved-ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739" - url: "https://github.com/julian-CStack/coinlib.git" + ref: fd5f658320f00a2e281ccaee97c2d2a77b4aa966 + resolved-ref: fd5f658320f00a2e281ccaee97c2d2a77b4aa966 + url: "https://www.github.com/julian-CStack/coinlib" source: git - version: "2.2.0" + version: "3.0.0" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" compat: dependency: "direct main" description: @@ -403,10 +412,10 @@ packages: dependency: transitive description: name: coverage - sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.1" cross_file: dependency: transitive description: @@ -435,10 +444,10 @@ packages: dependency: "direct main" description: name: cs_monero - sha256: b9b9db6602361587b1ce512002f174fd833818ff2a63787c3058e0532fc0d9d8 + sha256: ed81d9e74ea71a8b8b0bfed07a284e14b6e5f4d0dbde774735f9f0a9ab60b7fb url: "https://pub.dev" source: hosted - version: "1.0.0-pre" + version: "1.0.0-pre.2" cs_monero_flutter_libs: dependency: "direct main" description: @@ -563,10 +572,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.8" dartx: dependency: transitive description: @@ -579,10 +588,10 @@ packages: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" decimal: dependency: "direct main" description: @@ -619,34 +628,43 @@ packages: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" devicelocale: dependency: "direct main" description: - name: devicelocale - sha256: "0812b66f9eac57bc55c6ed4c178e0779440aa4e4e7c7e32fe1db02a758501d0e" + path: "." + ref: ba7d7d87a3772e972adb1358a5ec9a111b514fce + resolved-ref: ba7d7d87a3772e972adb1358a5ec9a111b514fce + url: "https://github.com/cypherstack/flutter-devicelocale" + source: git + version: "0.8.1" + digest_auth: + dependency: "direct main" + description: + name: digest_auth + sha256: c8f4a8d65300bd58c4a2ca84ea6bd63cb584e8021e5689c600ee7efae34d73ea url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "1.0.1" dio: dependency: transitive description: name: dio - sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0+1" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.1" dropdown_button2: dependency: "direct main" description: @@ -683,8 +701,8 @@ packages: dependency: "direct main" description: path: "." - ref: "6bf385b2e1e18c8aa23783cb8afeabace299cf68" - resolved-ref: "6bf385b2e1e18c8aa23783cb8afeabace299cf68" + ref: f0b1300140d45c13e7722f8f8d20308efeba8449 + resolved-ref: f0b1300140d45c13e7722f8f8d20308efeba8449 url: "https://github.com/cypherstack/electrum_adapter.git" source: git version: "3.0.0" @@ -700,10 +718,10 @@ packages: dependency: "direct main" description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" ethereum_addresses: dependency: "direct main" description: @@ -724,35 +742,35 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: "direct main" description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_picker: dependency: "direct main" description: path: "." - ref: "9abc0930081c9859884e073bd25ab88b2114d9e7" - resolved-ref: "9abc0930081c9859884e073bd25ab88b2114d9e7" + ref: b2849e63e1d418ad8d943c886cd3f4ed20d0ff23 + resolved-ref: b2849e63e1d418ad8d943c886cd3f4ed20d0ff23 url: "https://github.com/cypherstack/flutter_file_picker.git" source: git - version: "8.0.3" + version: "8.3.1" fixnum: dependency: transitive description: @@ -814,11 +832,11 @@ packages: dependency: "direct main" description: path: "." - ref: "9318bdd8e76e4dc8a49e3d64e8851c85e017eff3" - resolved-ref: "9318bdd8e76e4dc8a49e3d64e8851c85e017eff3" + ref: e8c502652da7836cd1a22893339838553675b464 + resolved-ref: e8c502652da7836cd1a22893339838553675b464 url: "https://github.com/cypherstack/flutter_libsparkmobile.git" source: git - version: "0.0.1" + version: "0.0.2" flutter_lints: dependency: "direct dev" description: @@ -863,10 +881,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.24" flutter_riverpod: dependency: "direct main" description: @@ -875,6 +893,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + flutter_rust_bridge: + dependency: transitive + description: + name: flutter_rust_bridge + sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611" + url: "https://pub.dev" + source: hosted + version: "2.9.0" flutter_secure_storage: dependency: "direct main" description: @@ -927,10 +953,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "1b7723a814d84fb65869ea7115cdb3ee7c3be5a27a755c1ec60e049f6b9fcbb2" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -982,10 +1008,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" google_fonts: dependency: "direct main" description: @@ -1062,18 +1088,18 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" ieee754: dependency: transitive description: @@ -1115,10 +1141,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" isar: dependency: "direct main" description: @@ -1171,10 +1197,18 @@ packages: dependency: transitive description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.0" + jsontool: + dependency: transitive + description: + name: jsontool + sha256: e49bf419e82d90f009426cd7fdec8d54ba8382975b3454ed16a3af3ee1d1b697 + url: "https://pub.dev" + source: hosted + version: "2.1.0" keyboard_dismisser: dependency: "direct main" description: @@ -1187,18 +1221,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -1213,7 +1247,7 @@ packages: path: "crypto_plugins/flutter_liblelantus" relative: true source: path - version: "0.0.2" + version: "0.0.3" lints: dependency: transitive description: @@ -1234,10 +1268,10 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "6763aaf8965f21822624cb2fd3c03d2a8b3791037b5efb0fe4b13e110f5afc92" + sha256: "0abe4e72f55c785b28900de52a2522c86baba0988838b5dc22241b072ecccd74" url: "https://pub.dev" source: hosted - version: "1.0.46" + version: "1.0.48" local_auth_darwin: dependency: transitive description: @@ -1263,13 +1297,14 @@ packages: source: hosted version: "1.0.11" logger: - dependency: transitive + dependency: "direct main" description: - name: logger - sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" - url: "https://pub.dev" - source: hosted - version: "2.4.0" + path: "." + ref: "3c0cba27868ebb5c7d65ebc30a8e6e5342186692" + resolved-ref: "3c0cba27868ebb5c7d65ebc30a8e6e5342186692" + url: "https://github.com/cypherstack/logger" + source: git + version: "2.5.0" logging: dependency: transitive description: @@ -1290,18 +1325,18 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -1322,10 +1357,10 @@ packages: dependency: "direct main" description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1358,6 +1393,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + monero_rpc: + dependency: "direct main" + description: + name: monero_rpc + sha256: "6052b6812e3e831015d776645d0d880fce5b9632d9df2cacae54b5e10ffe2db5" + url: "https://pub.dev" + source: hosted + version: "2.0.0" mutex: dependency: "direct main" description: @@ -1366,6 +1409,15 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + namecoin: + dependency: "direct main" + description: + path: "." + ref: "819b21164ef93cc0889049d4a8a1be2d0cc36a1b" + resolved-ref: "819b21164ef93cc0889049d4a8a1be2d0cc36a1b" + url: "https://github.com/Cyrix126/namecoin_dart" + source: git + version: "2.0.0" nanodart: dependency: "direct main" description: @@ -1402,42 +1454,42 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998 + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.2.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: name: path_parsing - sha256: caa17e8f0b386eb190dd5b6a3b71211c76375aa8b6ffb4465b5863d019bdb334 + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.1.0" path_provider: dependency: "direct main" description: @@ -1450,18 +1502,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.16" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1490,42 +1542,42 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" url: "https://pub.dev" source: hosted - version: "11.3.1" + version: "11.4.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc url: "https://pub.dev" source: hosted - version: "12.0.13" + version: "12.1.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 url: "https://pub.dev" source: hosted - version: "9.4.5" + version: "9.4.7" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.3+5" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "4.3.0" permission_handler_windows: dependency: transitive description: @@ -1554,10 +1606,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1594,10 +1646,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" protobuf: dependency: transitive description: @@ -1610,18 +1662,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" qr: dependency: transitive description: @@ -1706,10 +1758,10 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_packages_handler: dependency: transitive description: @@ -1730,15 +1782,15 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" socks5_proxy: dependency: "direct main" description: @@ -1752,10 +1804,10 @@ packages: description: path: "." ref: master - resolved-ref: b1fa8ca505e7e488edb4c2859f0218d48b15dead + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 url: "https://github.com/cypherstack/socks_socket.git" source: git - version: "1.0.0" + version: "1.1.1" solana: dependency: "direct main" description: @@ -1777,10 +1829,10 @@ packages: dependency: transitive description: name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.4" + version: "1.3.5" source_map_stack_trace: dependency: transitive description: @@ -1793,18 +1845,18 @@ packages: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sqlite3: dependency: "direct main" description: @@ -1825,10 +1877,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stack_wallet_backup: dependency: "direct main" description: @@ -1858,26 +1910,26 @@ packages: dependency: "direct main" description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" string_validator: dependency: "direct main" description: @@ -1898,34 +1950,34 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test: dependency: transitive description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.8" tezart: dependency: "direct main" description: @@ -1955,10 +2007,10 @@ packages: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" tint: dependency: transitive description: @@ -1979,8 +2031,8 @@ packages: dependency: "direct main" description: path: "." - ref: "534ec251b339199446b723c01a25d324ae7bb974" - resolved-ref: "534ec251b339199446b723c01a25d324ae7bb974" + ref: "752f054b65c500adb9cad578bf183a978e012502" + resolved-ref: "752f054b65c500adb9cad578bf183a978e012502" url: "https://github.com/cypherstack/tor.git" source: git version: "0.0.1" @@ -2036,26 +2088,26 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.3" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -2068,18 +2120,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" uuid: dependency: "direct main" description: @@ -2092,26 +2144,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "0b9149c6ddb013818075b072b9ddc1b89a5122fff1275d4648d297086b46c4f0" + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.18" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: f3b9b6e4591c11394d4be4806c63e72d3a41778547b2c1e2a8a04fadcfd7d173 + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.16" vector_math: dependency: transitive description: @@ -2120,14 +2172,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + very_good_analysis: + dependency: transitive + description: + name: very_good_analysis + sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63" + url: "https://pub.dev" + source: hosted + version: "7.0.0" vm_service: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.1" wakelock_platform_interface: dependency: transitive description: @@ -2140,18 +2200,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484 + sha256: b90fbcc8d7bdf3b883ea9706d9d76b9978cb1dfa4351fcc8014d6ec31a493354 url: "https://pub.dev" source: hosted - version: "1.2.8" + version: "1.2.11" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" wakelock_windows: dependency: "direct overridden" description: @@ -2181,12 +2241,12 @@ packages: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: - dependency: transitive + dependency: "direct overridden" description: name: web sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" @@ -2209,14 +2269,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.5" + web_socket_client: + dependency: transitive + description: + name: web_socket_client + sha256: "0ec5230852349191188c013112e4d2be03e3fc83dbe80139ead9bf3a136e53b5" + url: "https://pub.dev" + source: hosted + version: "0.1.5" webdriver: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" webkit_inspection_protocol: dependency: transitive description: @@ -2229,10 +2297,10 @@ packages: dependency: "direct overridden" description: name: win32 - sha256: "10169d3934549017f0ae278ccb07f828f9d6ea21573bab0fb77b0e1ef0fce454" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.7.2" + version: "5.10.1" win32_registry: dependency: transitive description: @@ -2258,6 +2326,23 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xelis_dart_sdk: + dependency: transitive + description: + name: xelis_dart_sdk + sha256: "2a7f8ab4c30fad2fd824ba6ea4e83ac20c726b47c7aa4f1e713ef3971a3ec1f7" + url: "https://pub.dev" + source: hosted + version: "0.24.0" + xelis_flutter: + dependency: "direct main" + description: + path: "." + ref: "v0.1.1" + resolved-ref: ee7a5756f5f403e6a371990c2817f38eb21a97b2 + url: "https://github.com/xelis-project/xelis-flutter-ffi.git" + source: git + version: "0.1.1" xml: dependency: transitive description: @@ -2278,10 +2363,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" zxcvbn: dependency: "direct main" description: @@ -2299,5 +2384,5 @@ packages: source: hosted version: "0.2.3" sdks: - dart: ">=3.5.3 <4.0.0" - flutter: ">=3.24.3" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/scripts/android/build_all.sh b/scripts/android/build_all.sh index 60aa13c6a..5438234ea 100755 --- a/scripts/android/build_all.sh +++ b/scripts/android/build_all.sh @@ -2,19 +2,20 @@ set -x -e -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - mkdir -p build . ./config.sh -./install_ndk.sh PLUGINS_DIR=../../crypto_plugins (cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh ) wait diff --git a/scripts/android/build_all_campfire.sh b/scripts/android/build_all_campfire.sh index 60aa13c6a..5438234ea 100755 --- a/scripts/android/build_all_campfire.sh +++ b/scripts/android/build_all_campfire.sh @@ -2,19 +2,20 @@ set -x -e -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - mkdir -p build . ./config.sh -./install_ndk.sh PLUGINS_DIR=../../crypto_plugins (cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh ) wait diff --git a/scripts/android/build_all_duo.sh b/scripts/android/build_all_duo.sh index d67e700a8..40be4bee4 100755 --- a/scripts/android/build_all_duo.sh +++ b/scripts/android/build_all_duo.sh @@ -4,19 +4,20 @@ set -x -e # todo: revisit following at some point -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - mkdir -p build . ./config.sh -./install_ndk.sh PLUGINS_DIR=../../crypto_plugins (cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh ) wait diff --git a/scripts/android/config.sh b/scripts/android/config.sh index 864c78c18..8d560ec36 100644 --- a/scripts/android/config.sh +++ b/scripts/android/config.sh @@ -1,7 +1,5 @@ #!/bin/sh export WORKDIR="$(pwd)/"build -export ANDROID_NDK_ZIP=${WORKDIR}/android-ndk-r20b.zip -export TOOLCHAIN_DIR="${WORKDIR}/toolchain" # Change this Value to a lower number if you run out of memory while compiling export OVERRIDE_THREADS="$(nproc)" \ No newline at end of file diff --git a/scripts/android/install_ndk.sh b/scripts/android/install_ndk.sh deleted file mode 100755 index 0864a2bde..000000000 --- a/scripts/android/install_ndk.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -mkdir -p build -. ./config.sh -ANDROID_NDK_SHA256="8381c440fe61fcbb01e209211ac01b519cd6adf51ab1c2281d5daad6ca4c8c8c" - -if [ ! -e "$ANDROID_NDK_ZIP" ]; then - curl https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip -o "${ANDROID_NDK_ZIP}" -fi -echo "${ANDROID_NDK_SHA256}" "${ANDROID_NDK_ZIP}" | sha256sum -c || exit 1 - - -PLUGINS_DIR=../../crypto_plugins - -mkdir -p "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/build -mkdir -p "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android/build -mkdir -p "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android/build - -cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/build/ -cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android/build/ -cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android/build/ diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 0fd8e5e8a..e46420fa0 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -73,6 +73,7 @@ final List _supportedCoins = List.unmodifiable([ Stellar(CryptoCurrencyNetwork.main), Tezos(CryptoCurrencyNetwork.main), Wownero(CryptoCurrencyNetwork.main), + Xelis(CryptoCurrencyNetwork.main), Bitcoin(CryptoCurrencyNetwork.test), Bitcoin(CryptoCurrencyNetwork.test4), Bitcoincash(CryptoCurrencyNetwork.test), @@ -83,6 +84,7 @@ final List _supportedCoins = List.unmodifiable([ Litecoin(CryptoCurrencyNetwork.test), Peercoin(CryptoCurrencyNetwork.test), Stellar(CryptoCurrencyNetwork.test), + Xelis(CryptoCurrencyNetwork.test), ]); final ({String from, String to}) _swapDefaults = (from: "BTC", to: "XMR"); diff --git a/scripts/app_config/templates/android/app/build.gradle b/scripts/app_config/templates/android/app/build.gradle index b9cebc7b4..dc1cb233a 100644 --- a/scripts/app_config/templates/android/app/build.gradle +++ b/scripts/app_config/templates/android/app/build.gradle @@ -15,7 +15,7 @@ android { namespace "com.place.holder" compileSdk flutter.compileSdkVersion // ndkVersion flutter.ndkVersion - ndkVersion = "26.1.10909125" + ndkVersion = "28.0.13004108" packagingOptions { pickFirst 'lib/x86/libc++_shared.so' @@ -90,6 +90,7 @@ android { task.doFirst { println "The compileSdkVersion is $flutter.compileSdkVersion" println "The targetSdkVersion is $flutter.targetSdkVersion" + println "The ndkVersion is $ndkVersion" } } } diff --git a/scripts/app_config/templates/android/app/src/debug/AndroidManifest.xml b/scripts/app_config/templates/android/app/src/debug/AndroidManifest.xml index 9aa98e2e8..399f6981d 100644 --- a/scripts/app_config/templates/android/app/src/debug/AndroidManifest.xml +++ b/scripts/app_config/templates/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml b/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml index 78a92aa5c..07f7a3ef0 100644 --- a/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml +++ b/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,4 @@ - - + + android:fullBackupContent="false"> - diff --git a/scripts/app_config/templates/macos/Runner.xcodeproj/project.pbxproj b/scripts/app_config/templates/macos/Runner.xcodeproj/project.pbxproj index b0e43be79..6db80d9b0 100644 --- a/scripts/app_config/templates/macos/Runner.xcodeproj/project.pbxproj +++ b/scripts/app_config/templates/macos/Runner.xcodeproj/project.pbxproj @@ -97,9 +97,7 @@ B98151802A674022009D013C /* mobileliblelantus.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = mobileliblelantus.framework; path = ../crypto_plugins/flutter_liblelantus/scripts/macos/mobileliblelantus/mobileliblelantus.framework; sourceTree = ""; }; B98151832A674143009D013C /* libsqlite3.0.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; }; BF5E76865ACB46314AC27D8F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - CEA2021C2BDD4D7100FE1D27 /* wownero_libwallet2_api_c.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = wownero_libwallet2_api_c.dylib; sourceTree = ""; }; - CEA2021F2BDD4F0B00FE1D27 /* monero_libwallet2_api_c.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = monero_libwallet2_api_c.dylib; sourceTree = ""; }; - E6036BF01BF05EA773C76D22 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E6036BF01BF05EA773C76D22 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F1FA2C4D2BA4B49F00BDA1BB /* frostdart.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = frostdart.dylib; path = ../crypto_plugins/frostdart/macos/frostdart.dylib; sourceTree = ""; }; F1FA2C4F2BA4B4CA00BDA1BB /* frostdart.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = frostdart.dylib; path = ../crypto_plugins/frostdart/macos/frostdart.dylib; sourceTree = ""; }; /* End PBXFileReference section */ @@ -149,9 +147,7 @@ 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( - CEA2021F2BDD4F0B00FE1D27 /* monero_libwallet2_api_c.dylib */, - CEA2021C2BDD4D7100FE1D27 /* wownero_libwallet2_api_c.dylib */, - F1FA2C4F2BA4B4CA00BDA1BB /* frostdart.dylib */, + F1FA2C4F2BA4B4CA00BDA1BB /* frostdart.dylib */, 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 331C80D6294CF71000263BE5 /* RunnerTests */, @@ -281,7 +277,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { @@ -599,10 +595,7 @@ "$(inherited)", "\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_monero\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_shared_external\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_wownero\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/desktop_drop\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/desktop_drop\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/devicelocale\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_libepiccash\"", @@ -627,10 +620,7 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_monero/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_shared_external/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_wownero/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos/libs\"", + "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos/libs\"", "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos\"", /usr/lib/swift, "$(PATH)/crypto_plugins/frostdart/macos\n", @@ -763,9 +753,6 @@ "$(inherited)", "\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_monero\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_shared_external\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_wownero\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/desktop_drop\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/devicelocale\"", @@ -791,9 +778,6 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_monero/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_shared_external/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_wownero/macos/External/macos/lib\"", "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos/libs\"", "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos\"", /usr/lib/swift, @@ -818,9 +802,6 @@ "$(inherited)", "\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_monero\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_shared_external\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cw_wownero\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/desktop_drop\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/devicelocale\"", @@ -846,10 +827,7 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_monero/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_shared_external/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/cw_wownero/macos/External/macos/lib\"", - "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos/libs\"", + "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos/libs\"", "\"${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos\"", /usr/lib/swift, "$(PATH)/crypto_plugins/frostdart/macos\n", diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index 862731c6e..9c6c48cb4 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -14,8 +14,8 @@ description: PLACEHOLDER version: PLACEHOLDER_V+PLACEHOLDER_B environment: - sdk: ">=3.5.3 <4.0.0" - flutter: ^3.24.3 + sdk: ">=3.7.0 <4.0.0" + flutter: ^3.29.0 dependencies: flutter: @@ -30,10 +30,15 @@ dependencies: frostdart: path: ./crypto_plugins/frostdart + xelis_flutter: + git: + url: https://github.com/xelis-project/xelis-flutter-ffi.git + ref: v0.1.1 + flutter_libsparkmobile: git: url: https://github.com/cypherstack/flutter_libsparkmobile.git - ref: 9318bdd8e76e4dc8a49e3d64e8851c85e017eff3 + ref: e8c502652da7836cd1a22893339838553675b464 # cs_monero compat (unpublished) compat: @@ -63,7 +68,7 @@ dependencies: tor_ffi_plugin: git: url: https://github.com/cypherstack/tor.git - ref: 647cadc3c82c276dc07915b02d24538fd610f220 + ref: 752f054b65c500adb9cad578bf183a978e012502 fusiondart: git: @@ -120,10 +125,13 @@ dependencies: event_bus: ^2.0.0 uuid: ^3.0.5 crypto: ^3.0.2 - barcode_scan2: ^4.3.3 + barcode_scan2: ^4.5.0 wakelock_plus: ^1.2.8 intl: ^0.17.0 - devicelocale: ^0.7.1 + devicelocale: + git: + url: https://github.com/cypherstack/flutter-devicelocale + ref: ba7d7d87a3772e972adb1358a5ec9a111b514fce device_info_plus: ^10.1.2 keyboard_dismisser: ^3.0.0 another_flushbar: ^1.10.28 @@ -135,7 +143,10 @@ dependencies: pointycastle: ^3.6.0 package_info_plus: ^8.0.2 lottie: ^2.3.2 - file_picker: ^8.0.3 + file_picker: + git: + url: https://github.com/cypherstack/flutter_file_picker.git + ref: b2849e63e1d418ad8d943c886cd3f4ed20d0ff23 connectivity_plus: ^4.0.1 isar: version: 3.1.8 @@ -165,16 +176,11 @@ dependencies: convert: ^3.1.1 flutter_hooks: ^0.20.3 meta: ^1.9.1 -# coinlib_flutter: ^2.1.0-rc.1 - coinlib_flutter: - git: - url: https://github.com/julian-CStack/coinlib.git - ref: 0acacfd17eacf72135c693a7b862bd9b7cc56739 - path: coinlib_flutter + coinlib_flutter: ^3.0.0 electrum_adapter: git: url: https://github.com/cypherstack/electrum_adapter.git - ref: 6bf385b2e1e18c8aa23783cb8afeabace299cf68 + ref: f0b1300140d45c13e7722f8f8d20308efeba8449 stream_channel: ^2.1.0 solana: git: # TODO [prio=low]: Revert to official package once Tor support is merged upstream. @@ -184,7 +190,11 @@ dependencies: calendar_date_picker2: ^1.0.2 sqlite3: 2.4.3 sqlite3_flutter_libs: 0.5.22 - camera_linux: ^0.0.8 +# camera_linux: ^0.0.8 + camera_linux: + git: + url: https://github.com/cypherstack/camera-linux + ref: ecb412474c5d240347b04ac1eb9f019802ff7034 zxing2: ^0.2.3 camera_windows: git: # TODO [prio=low]: Revert to official after https://github.com/flutter/packages/pull/7067. @@ -195,8 +205,19 @@ dependencies: blockchain_utils: ^3.3.0 on_chain: ^4.0.1 cbor: ^6.3.3 - cs_monero: 1.0.0-pre + cs_monero: 1.0.0-pre.2 cs_monero_flutter_libs: 1.0.0-pre.0 + monero_rpc: ^2.0.0 + digest_auth: ^1.0.1 +# logger: ^2.5.0 + logger: + git: + url: https://github.com/cypherstack/logger + ref: 3c0cba27868ebb5c7d65ebc30a8e6e5342186692 + namecoin: + git: + url: https://github.com/Cyrix126/namecoin_dart + ref: 819b21164ef93cc0889049d4a8a1be2d0cc36a1b dev_dependencies: flutter_test: @@ -224,28 +245,31 @@ flutter_native_splash: android_disable_fullscreen: true dependency_overrides: + logger: + git: + url: https://github.com/cypherstack/logger + ref: 3c0cba27868ebb5c7d65ebc30a8e6e5342186692 + + # required to make devicelocale work + web: ^0.5.0 # needed for dart 3.5+ (at least for now) win32: ^5.5.4 - # coin lib git for testing while waiting for publishing + # namecoin names lib needs to be updated + #coinlib: ^3.0.0 + #coinlib_flutter: ^3.0.0 coinlib: git: - url: https://github.com/julian-CStack/coinlib.git - ref: 0acacfd17eacf72135c693a7b862bd9b7cc56739 + url: https://www.github.com/julian-CStack/coinlib path: coinlib + ref: fd5f658320f00a2e281ccaee97c2d2a77b4aa966 coinlib_flutter: git: - url: https://github.com/julian-CStack/coinlib.git - ref: 0acacfd17eacf72135c693a7b862bd9b7cc56739 + url: https://www.github.com/julian-CStack/coinlib path: coinlib_flutter - - # adding here due to pure laziness - tor_ffi_plugin: - git: - url: https://github.com/cypherstack/tor.git - ref: 534ec251b339199446b723c01a25d324ae7bb974 + ref: fd5f658320f00a2e281ccaee97c2d2a77b4aa966 bip47: git: @@ -265,12 +289,6 @@ dependency_overrides: url: https://github.com/cypherstack/stack-bip39.git ref: 0cd6d54e2860bea68fc50c801cb9db2a760192fb - - file_picker: - git: - url: https://github.com/cypherstack/flutter_file_picker.git - ref: 9abc0930081c9859884e073bd25ab88b2114d9e7 - crypto: 3.0.2 analyzer: ^6.7.0 pinenacl: ^0.6.0 diff --git a/scripts/ios/build_all.sh b/scripts/ios/build_all.sh index bcb03e991..c47228cdc 100755 --- a/scripts/ios/build_all.sh +++ b/scripts/ios/build_all.sh @@ -2,10 +2,6 @@ set -x -e -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - # ensure ios rust triples are there rustup target add aarch64-apple-ios rustup target add x86_64-apple-ios @@ -15,8 +11,14 @@ rustup target add aarch64-apple-ios rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) wait diff --git a/scripts/ios/build_all_campfire.sh b/scripts/ios/build_all_campfire.sh index bcb03e991..c47228cdc 100755 --- a/scripts/ios/build_all_campfire.sh +++ b/scripts/ios/build_all_campfire.sh @@ -2,10 +2,6 @@ set -x -e -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - # ensure ios rust triples are there rustup target add aarch64-apple-ios rustup target add x86_64-apple-ios @@ -15,8 +11,14 @@ rustup target add aarch64-apple-ios rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) wait diff --git a/scripts/ios/build_all_duo.sh b/scripts/ios/build_all_duo.sh index 89e6f4641..3e3a73119 100755 --- a/scripts/ios/build_all_duo.sh +++ b/scripts/ios/build_all_duo.sh @@ -4,10 +4,6 @@ set -x -e # todo: revisit following at some point -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - # ensure ios rust triples are there rustup target add aarch64-apple-ios rustup target add x86_64-apple-ios @@ -17,8 +13,14 @@ rustup target add aarch64-apple-ios rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) wait diff --git a/scripts/linux/build_all.sh b/scripts/linux/build_all.sh index 423646185..011d972b3 100755 --- a/scripts/linux/build_all.sh +++ b/scripts/linux/build_all.sh @@ -2,10 +2,6 @@ set -x -e -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - # for arm # flutter-elinux clean # flutter-elinux pub get @@ -13,8 +9,14 @@ set_rust_to_1671 mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) ./build_secp256k1.sh diff --git a/scripts/linux/build_all_campfire.sh b/scripts/linux/build_all_campfire.sh index 423646185..011d972b3 100755 --- a/scripts/linux/build_all_campfire.sh +++ b/scripts/linux/build_all_campfire.sh @@ -2,10 +2,6 @@ set -x -e -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 - # for arm # flutter-elinux clean # flutter-elinux pub get @@ -13,8 +9,14 @@ set_rust_to_1671 mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) ./build_secp256k1.sh diff --git a/scripts/linux/build_all_duo.sh b/scripts/linux/build_all_duo.sh index 78067b478..aa804e73e 100755 --- a/scripts/linux/build_all_duo.sh +++ b/scripts/linux/build_all_duo.sh @@ -4,9 +4,6 @@ set -x -e # todo: revisit following at some point -# libepiccash requires old rust -source ../rust_version.sh -set_rust_to_1671 # for arm # flutter-elinux clean @@ -15,8 +12,14 @@ set_rust_to_1671 mkdir -p build ./build_secure_storage_deps.sh & (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) + +# libepiccash requires old rust +source ../rust_version.sh +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) -set_rust_to_1720 +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) ./build_secp256k1.sh diff --git a/scripts/linux/build_secp256k1.sh b/scripts/linux/build_secp256k1.sh index f00b6b82b..17bcd32c9 100755 --- a/scripts/linux/build_secp256k1.sh +++ b/scripts/linux/build_secp256k1.sh @@ -11,4 +11,4 @@ cmake .. cmake --build . mkdir -p ../../../../../build cp lib/libsecp256k1.so.2.2.2 "../../../../../build/libsecp256k1.so" -cd ../../../ +cd ../../../ \ No newline at end of file diff --git a/scripts/linux/build_secure_storage_deps.sh b/scripts/linux/build_secure_storage_deps.sh index 9956d286c..737508ab0 100755 --- a/scripts/linux/build_secure_storage_deps.sh +++ b/scripts/linux/build_secure_storage_deps.sh @@ -25,7 +25,7 @@ cd "$LINUX_DIRECTORY" || exit 1 #pip3 install --user meson markdown tomli --upgrade # pip3 install --user gi-docgen cd build || exit 1 -git -C libsecret pull origin $LIBSECRET_TAG || git clone https://gitlab.gnome.org/GNOME/libsecret.git libsecret +git -C libsecret pull origin $LIBSECRET_TAG || git clone https://git.cypherstack.com/Cypher_Stack/libsecret.git libsecret cd libsecret || exit 1 git checkout $LIBSECRET_TAG if ! [ -x "$(command -v meson)" ]; then diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh index af608846f..37dda0e64 100755 --- a/scripts/macos/build_all.sh +++ b/scripts/macos/build_all.sh @@ -2,17 +2,17 @@ set -x -e + # libepiccash requires old rust source ../rust_version.sh -set_rust_to_1671 +set_rust_version_for_libepiccash +(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) -(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) -set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) wait echo "Done building" -# set rust (back) to a more recent stable release to allow stack wallet to build tor -set_rust_to_1720 diff --git a/scripts/macos/build_all_campfire.sh b/scripts/macos/build_all_campfire.sh index af608846f..59a93ef26 100755 --- a/scripts/macos/build_all_campfire.sh +++ b/scripts/macos/build_all_campfire.sh @@ -2,17 +2,16 @@ set -x -e + # libepiccash requires old rust source ../rust_version.sh -set_rust_to_1671 +set_rust_version_for_libepiccash +(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) -(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) -set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) wait echo "Done building" - -# set rust (back) to a more recent stable release to allow stack wallet to build tor -set_rust_to_1720 diff --git a/scripts/macos/build_all_duo.sh b/scripts/macos/build_all_duo.sh index 8a53e5801..960f5a44a 100755 --- a/scripts/macos/build_all_duo.sh +++ b/scripts/macos/build_all_duo.sh @@ -4,17 +4,17 @@ set -x -e # todo: revisit following at some point + # libepiccash requires old rust source ../rust_version.sh -set_rust_to_1671 +set_rust_version_for_libepiccash +(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) -(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) -set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) wait echo "Done building" -# set rust (back) to a more recent stable release to allow stack wallet to build tor -set_rust_to_1720 diff --git a/scripts/rust_version.sh b/scripts/rust_version.sh index 8cda1229b..b894b7a5d 100755 --- a/scripts/rust_version.sh +++ b/scripts/rust_version.sh @@ -1,19 +1,21 @@ #!/bin/sh -set_rust_to_1671() { - if rustup toolchain list | grep -q "1.67.1"; then - rustup default 1.67.1 + +set_rust_to_everything_else() { + if rustup toolchain list | grep -q "1.85.1"; then + rustup default 1.85.1 else - echo "Rust version 1.67.1 is not installed. Please install it using 'rustup install 1.67.1'." >&2 + echo "Rust version 1.85.1 is not installed. Please install it using 'rustup install 1.85.1'." >&2 exit 1 fi } -set_rust_to_1720() { - if rustup toolchain list | grep -q "1.72.0"; then - rustup default 1.72.0 +set_rust_version_for_libepiccash() { + if rustup toolchain list | grep -q "1.81.0"; then + rustup default 1.81 else - echo "Rust version 1.72.0 is not installed. Please install it using 'rustup install 1.72.0'." >&2 + echo "Rust version 1.81.0 is not installed. Please install it using 'rustup install 1.81.0'." >&2 exit 1 fi -} \ No newline at end of file +} + diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh index 191a46cc0..cf3ad76d9 100755 --- a/scripts/windows/build_all.sh +++ b/scripts/windows/build_all.sh @@ -2,14 +2,16 @@ set -x -e +mkdir -p build + # libepiccash requires old rust source ../rust_version.sh -set_rust_to_1671 - -mkdir -p build +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) -set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) ./build_secp256k1_wsl.sh diff --git a/scripts/windows/build_all_campfire.sh b/scripts/windows/build_all_campfire.sh index 191a46cc0..cf3ad76d9 100755 --- a/scripts/windows/build_all_campfire.sh +++ b/scripts/windows/build_all_campfire.sh @@ -2,14 +2,16 @@ set -x -e +mkdir -p build + # libepiccash requires old rust source ../rust_version.sh -set_rust_to_1671 - -mkdir -p build +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) -set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) ./build_secp256k1_wsl.sh diff --git a/scripts/windows/build_all_duo.sh b/scripts/windows/build_all_duo.sh index 3e27eff02..e54986e7d 100755 --- a/scripts/windows/build_all_duo.sh +++ b/scripts/windows/build_all_duo.sh @@ -4,14 +4,16 @@ set -x -e # todo: revisit following at some point +mkdir -p build + # libepiccash requires old rust source ../rust_version.sh -set_rust_to_1671 - -mkdir -p build +set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) +# set rust (back) to a more recent stable release after building epiccash +set_rust_to_everything_else + (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) -set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) ./build_secp256k1_wsl.sh diff --git a/scripts/windows/build_secp256k1.bat b/scripts/windows/build_secp256k1.bat index f5777b974..bae7c9788 100644 --- a/scripts/windows/build_secp256k1.bat +++ b/scripts/windows/build_secp256k1.bat @@ -7,6 +7,6 @@ git reset --hard cmake -G "Visual Studio 17 2022" -A x64 -S . -B build cd build cmake --build . -if not exist "..\..\..\..\build\" mkdir "..\..\..\..\build\" -xcopy bin\Debug\libsecp256k1-2.dll "..\..\..\..\build\secp256k1.dll" /Y +if not exist "..\..\..\..\..\build\" mkdir "..\..\..\..\..\build\" +xcopy bin\Debug\libsecp256k1-2.dll "..\..\..\..\..\build\secp256k1.dll" /Y cd ..\..\..\ diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 993f73f69..4ac0b9849 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -3,21 +3,24 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i12; +import 'dart:async' as _i9; +import 'dart:ui' as _i15; -import 'package:decimal/decimal.dart' as _i3; +import 'package:decimal/decimal.dart' as _i4; +import 'package:logger/logger.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i6; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i5; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i11; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i10; -import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i9; -import 'package:stackwallet/utilities/prefs.dart' as _i8; +import 'package:mockito/src/dummies.dart' as _i8; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i6; +import 'package:stackwallet/models/electrumx_response/spark_models.dart' as _i3; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i14; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i12; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i11; +import 'package:stackwallet/utilities/prefs.dart' as _i10; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart' as _i7; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' as _i2; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart' - as _i4; + as _i5; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -53,8 +56,9 @@ class _FakeDuration_1 extends _i1.SmartFake implements Duration { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { - _FakeDecimal_2( +class _FakeSparkAnonymitySetMeta_2 extends _i1.SmartFake + implements _i3.SparkAnonymitySetMeta { + _FakeSparkAnonymitySetMeta_2( Object parent, Invocation parentInvocation, ) : super( @@ -63,8 +67,18 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { ); } -class _FakeFusionInfo_3 extends _i1.SmartFake implements _i4.FusionInfo { - _FakeFusionInfo_3( +class _FakeDecimal_3 extends _i1.SmartFake implements _i4.Decimal { + _FakeDecimal_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFusionInfo_4 extends _i1.SmartFake implements _i5.FusionInfo { + _FakeFusionInfo_4( Object parent, Invocation parentInvocation, ) : super( @@ -76,7 +90,7 @@ class _FakeFusionInfo_3 extends _i1.SmartFake implements _i4.FusionInfo { /// A class which mocks [ElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { +class MockElectrumXClient extends _i1.Mock implements _i6.ElectrumXClient { MockElectrumXClient() { _i1.throwOnMissingStub(this); } @@ -91,13 +105,10 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { ) as _i2.CryptoCurrency); @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( - Invocation.setter( - #failovers, - _failovers, - ), - returnValueForMissingStub: null, - ); + _i7.TorPlainNetworkOption get netType => (super.noSuchMethod( + Invocation.getter(#netType), + returnValue: _i7.TorPlainNetworkOption.tor, + ) as _i7.TorPlainNetworkOption); @override int get currentFailoverIndex => (super.noSuchMethod( @@ -127,7 +138,7 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { @override String get host => (super.noSuchMethod( Invocation.getter(#host), - returnValue: _i6.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#host), ), @@ -146,27 +157,27 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { ) as bool); @override - _i7.Future closeAdapter() => (super.noSuchMethod( + _i9.Future closeAdapter() => (super.noSuchMethod( Invocation.method( #closeAdapter, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i7.Future checkElectrumAdapter() => (super.noSuchMethod( + _i9.Future checkElectrumAdapter() => (super.noSuchMethod( Invocation.method( #checkElectrumAdapter, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i7.Future request({ + _i9.Future request({ required String? command, List? args = const [], String? requestID, @@ -185,11 +196,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #requestTimeout: requestTimeout, }, ), - returnValue: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + ) as _i9.Future); @override - _i7.Future> batchRequest({ + _i9.Future> batchRequest({ required String? command, required List? args, Duration? requestTimeout = const Duration(seconds: 60), @@ -206,11 +217,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #retries: retries, }, ), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); @override - _i7.Future ping({ + _i9.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -223,11 +234,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #retryCount: retryCount, }, ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i7.Future> getBlockHeadTip({String? requestID}) => + _i9.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -235,11 +246,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future> getServerFeatures({String? requestID}) => + _i9.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -247,11 +258,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future broadcastTransaction({ + _i9.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -264,7 +275,7 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i7.Future.value(_i6.dummyValue( + returnValue: _i9.Future.value(_i8.dummyValue( this, Invocation.method( #broadcastTransaction, @@ -275,10 +286,10 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { }, ), )), - ) as _i7.Future); + ) as _i9.Future); @override - _i7.Future> getBalance({ + _i9.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -292,11 +303,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future>> getHistory({ + _i9.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -309,12 +320,12 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i7.Future>>.value( + returnValue: _i9.Future>>.value( >[]), - ) as _i7.Future>>); + ) as _i9.Future>>); @override - _i7.Future>>> getBatchHistory( + _i9.Future>>> getBatchHistory( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -322,12 +333,12 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { [], {#args: args}, ), - returnValue: _i7.Future>>>.value( + returnValue: _i9.Future>>>.value( >>[]), - ) as _i7.Future>>>); + ) as _i9.Future>>>); @override - _i7.Future>> getUTXOs({ + _i9.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -340,12 +351,12 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i7.Future>>.value( + returnValue: _i9.Future>>.value( >[]), - ) as _i7.Future>>); + ) as _i9.Future>>); @override - _i7.Future>>> getBatchUTXOs( + _i9.Future>>> getBatchUTXOs( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -353,12 +364,12 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { [], {#args: args}, ), - returnValue: _i7.Future>>>.value( + returnValue: _i9.Future>>>.value( >>[]), - ) as _i7.Future>>>); + ) as _i9.Future>>>); @override - _i7.Future> getTransaction({ + _i9.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -374,11 +385,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future> getLelantusAnonymitySet({ + _i9.Future> getLelantusAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -394,11 +405,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future getLelantusMintData({ + _i9.Future getLelantusMintData({ dynamic mints, String? requestID, }) => @@ -411,11 +422,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + ) as _i9.Future); @override - _i7.Future> getLelantusUsedCoinSerials({ + _i9.Future> getLelantusUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -429,22 +440,22 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future getLelantusLatestCoinId({String? requestID}) => + _i9.Future getLelantusLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLelantusLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i7.Future> getSparkAnonymitySet({ + _i9.Future> getSparkAnonymitySet({ String? coinGroupId = r'1', String? startBlockHash = r'', String? requestID, @@ -460,58 +471,33 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future>> getSparkMintMetaData({ - String? requestID, - required List? sparkCoinHashes, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkMintMetaData, - [], - { - #requestID: requestID, - #sparkCoinHashes: sparkCoinHashes, - }, - ), - returnValue: _i7.Future>>.value( - >[]), - ) as _i7.Future>>); - - @override - _i7.Future getSparkLatestCoinId({String? requestID}) => + _i9.Future getSparkLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getSparkLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i7.Future> getMempoolTxids({String? requestID}) => + _i9.Future> getMempoolTxids({String? requestID}) => (super.noSuchMethod( Invocation.method( #getMempoolTxids, [], {#requestID: requestID}, ), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>> getMempoolSparkData({ + _i9.Future> getMempoolSparkData({ String? requestID, required List? txids, }) => @@ -524,30 +510,12 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #txids: txids, }, ), - returnValue: _i7.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>.value(<({ - List coins, - List lTags, - List serialContext, - String txid - })>[]), - ) as _i7.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>); - - @override - _i7.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ + returnValue: _i9.Future>.value( + <_i3.SparkMempoolData>[]), + ) as _i9.Future>); + + @override + _i9.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ String? requestID, required int? startNumber, }) => @@ -560,11 +528,62 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #startNumber: startNumber, }, ), - returnValue: _i7.Future>>.value(>[]), - ) as _i7.Future>>); + returnValue: _i9.Future>>.value(>[]), + ) as _i9.Future>>); + + @override + _i9.Future<_i3.SparkAnonymitySetMeta> getSparkAnonymitySetMeta({ + String? requestID, + required int? coinGroupId, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + returnValue: _i9.Future<_i3.SparkAnonymitySetMeta>.value( + _FakeSparkAnonymitySetMeta_2( + this, + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + )), + ) as _i9.Future<_i3.SparkAnonymitySetMeta>); + + @override + _i9.Future> getSparkAnonymitySetBySector({ + String? requestID, + required int? coinGroupId, + required String? latestBlock, + required int? startIndex, + required int? endIndex, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetBySector, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + #latestBlock: latestBlock, + #startIndex: startIndex, + #endIndex: endIndex, + }, + ), + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); @override - _i7.Future isMasterNodeCollateral({ + _i9.Future isMasterNodeCollateral({ String? requestID, required String? txid, required int? index, @@ -579,11 +598,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #index: index, }, ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i7.Future> getFeeRate({String? requestID}) => + _i9.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -591,11 +610,11 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i7.Future<_i3.Decimal> estimateFee({ + _i9.Future<_i4.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -608,7 +627,7 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { #blocks: blocks, }, ), - returnValue: _i7.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i9.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #estimateFee, @@ -619,16 +638,16 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { }, ), )), - ) as _i7.Future<_i3.Decimal>); + ) as _i9.Future<_i4.Decimal>); @override - _i7.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i9.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i7.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i9.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #relayFee, @@ -636,13 +655,13 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { {#requestID: requestID}, ), )), - ) as _i7.Future<_i3.Decimal>); + ) as _i9.Future<_i4.Decimal>); } /// A class which mocks [Prefs]. /// /// See the documentation for Mockito's code generation for more information. -class MockPrefs extends _i1.Mock implements _i8.Prefs { +class MockPrefs extends _i1.Mock implements _i10.Prefs { MockPrefs() { _i1.throwOnMissingStub(this); } @@ -706,13 +725,13 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override - _i9.SyncingType get syncType => (super.noSuchMethod( + _i11.SyncingType get syncType => (super.noSuchMethod( Invocation.getter(#syncType), - returnValue: _i9.SyncingType.currentWalletOnly, - ) as _i9.SyncingType); + returnValue: _i11.SyncingType.currentWalletOnly, + ) as _i11.SyncingType); @override - set syncType(_i9.SyncingType? syncType) => super.noSuchMethod( + set syncType(_i11.SyncingType? syncType) => super.noSuchMethod( Invocation.setter( #syncType, syncType, @@ -753,7 +772,7 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { @override String get language => (super.noSuchMethod( Invocation.getter(#language), - returnValue: _i6.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#language), ), @@ -771,7 +790,7 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { @override String get currency => (super.noSuchMethod( Invocation.getter(#currency), - returnValue: _i6.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#currency), ), @@ -901,13 +920,13 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override - _i10.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( + _i12.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i10.BackupFrequencyType.everyTenMinutes, - ) as _i10.BackupFrequencyType); + returnValue: _i12.BackupFrequencyType.everyTenMinutes, + ) as _i12.BackupFrequencyType); @override - set backupFrequencyType(_i10.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i12.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter( #backupFrequencyType, @@ -1014,7 +1033,7 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { @override String get themeId => (super.noSuchMethod( Invocation.getter(#themeId), - returnValue: _i6.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#themeId), ), @@ -1032,7 +1051,7 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { @override String get systemBrightnessLightThemeId => (super.noSuchMethod( Invocation.getter(#systemBrightnessLightThemeId), - returnValue: _i6.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#systemBrightnessLightThemeId), ), @@ -1051,7 +1070,7 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { @override String get systemBrightnessDarkThemeId => (super.noSuchMethod( Invocation.getter(#systemBrightnessDarkThemeId), - returnValue: _i6.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#systemBrightnessDarkThemeId), ), @@ -1112,6 +1131,45 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { returnValueForMissingStub: null, ); + @override + bool get advancedFiroFeatures => (super.noSuchMethod( + Invocation.getter(#advancedFiroFeatures), + returnValue: false, + ) as bool); + + @override + set advancedFiroFeatures(bool? advancedFiroFeatures) => super.noSuchMethod( + Invocation.setter( + #advancedFiroFeatures, + advancedFiroFeatures, + ), + returnValueForMissingStub: null, + ); + + @override + set logsPath(String? logsPath) => super.noSuchMethod( + Invocation.setter( + #logsPath, + logsPath, + ), + returnValueForMissingStub: null, + ); + + @override + _i13.Level get logLevel => (super.noSuchMethod( + Invocation.getter(#logLevel), + returnValue: _i13.Level.all, + ) as _i13.Level); + + @override + set logLevel(_i13.Level? logLevel) => super.noSuchMethod( + Invocation.setter( + #logLevel, + logLevel, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -1119,67 +1177,67 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ) as bool); @override - _i7.Future init() => (super.noSuchMethod( + _i9.Future init() => (super.noSuchMethod( Invocation.method( #init, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i7.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i9.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method( #incrementCurrentNotificationIndex, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i7.Future isExternalCallsSet() => (super.noSuchMethod( + _i9.Future isExternalCallsSet() => (super.noSuchMethod( Invocation.method( #isExternalCallsSet, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i7.Future saveUserID(String? userId) => (super.noSuchMethod( + _i9.Future saveUserID(String? userId) => (super.noSuchMethod( Invocation.method( #saveUserID, [userId], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i7.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + _i9.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( Invocation.method( #saveSignupEpoch, [signupEpoch], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i11.AmountUnit amountUnit(_i2.CryptoCurrency? coin) => (super.noSuchMethod( + _i14.AmountUnit amountUnit(_i2.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #amountUnit, [coin], ), - returnValue: _i11.AmountUnit.normal, - ) as _i11.AmountUnit); + returnValue: _i14.AmountUnit.normal, + ) as _i14.AmountUnit); @override void updateAmountUnit({ required _i2.CryptoCurrency? coin, - required _i11.AmountUnit? amountUnit, + required _i14.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method( @@ -1220,25 +1278,25 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override - _i4.FusionInfo getFusionServerInfo(_i2.CryptoCurrency? coin) => + _i5.FusionInfo getFusionServerInfo(_i2.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #getFusionServerInfo, [coin], ), - returnValue: _FakeFusionInfo_3( + returnValue: _FakeFusionInfo_4( this, Invocation.method( #getFusionServerInfo, [coin], ), ), - ) as _i4.FusionInfo); + ) as _i5.FusionInfo); @override void setFusionServerInfo( _i2.CryptoCurrency? coin, - _i4.FusionInfo? fusionServerInfo, + _i5.FusionInfo? fusionServerInfo, ) => super.noSuchMethod( Invocation.method( @@ -1252,7 +1310,7 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1261,7 +1319,7 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index fee6a0d7f..476ab3883 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -7,6 +7,7 @@ import 'dart:async' as _i10; import 'dart:typed_data' as _i19; import 'dart:ui' as _i14; +import 'package:logger/logger.dart' as _i22; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i16; import 'package:stackwallet/db/isar/main_db.dart' as _i3; @@ -17,7 +18,7 @@ import 'package:stackwallet/services/locale_service.dart' as _i15; import 'package:stackwallet/services/node_service.dart' as _i2; import 'package:stackwallet/services/wallets.dart' as _i9; import 'package:stackwallet/themes/theme_service.dart' as _i17; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i22; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i23; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i21; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i20; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -1128,6 +1129,45 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { returnValueForMissingStub: null, ); + @override + bool get advancedFiroFeatures => (super.noSuchMethod( + Invocation.getter(#advancedFiroFeatures), + returnValue: false, + ) as bool); + + @override + set advancedFiroFeatures(bool? advancedFiroFeatures) => super.noSuchMethod( + Invocation.setter( + #advancedFiroFeatures, + advancedFiroFeatures, + ), + returnValueForMissingStub: null, + ); + + @override + set logsPath(String? logsPath) => super.noSuchMethod( + Invocation.setter( + #logsPath, + logsPath, + ), + returnValueForMissingStub: null, + ); + + @override + _i22.Level get logLevel => (super.noSuchMethod( + Invocation.getter(#logLevel), + returnValue: _i22.Level.all, + ) as _i22.Level); + + @override + set logLevel(_i22.Level? logLevel) => super.noSuchMethod( + Invocation.setter( + #logLevel, + logLevel, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -1184,18 +1224,18 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ) as _i10.Future); @override - _i22.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( + _i23.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #amountUnit, [coin], ), - returnValue: _i22.AmountUnit.normal, - ) as _i22.AmountUnit); + returnValue: _i23.AmountUnit.normal, + ) as _i23.AmountUnit); @override void updateAmountUnit({ required _i4.CryptoCurrency? coin, - required _i22.AmountUnit? amountUnit, + required _i23.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method( diff --git a/test/price_test.dart b/test/price_test.dart index dc7aaeb9e..2a5b9c038 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -30,7 +30,7 @@ void main() { url: Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash" - ",namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos" + ",namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos,xelis" "&order=market_cap_desc&per_page=50" "&page=1&sparkline=false"), headers: { @@ -93,7 +93,10 @@ void main() { 'max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864' ',"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_chang' 'e_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi' - '":null,"last_updated":"2022-08-22T16:38:32.826Z"}]'), + '":null,"last_updated":"2022-08-22T16:38:32.826Z"},{"id":"xelis","sy' + 'mbol":"xel","name":"Xelis","image":"https://assets.coingecko.com/co' + 'ins/images/37615/large/green_background_black_logo.png","current_pr' + 'ice":0.00001234,"price_change_percentage_24h":5.67}]'), 200)); final priceAPI = PriceAPI(client); @@ -125,7 +128,8 @@ void main() { 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0]' + 'Coin.stellarTestnet: [0, 0.0], ' + 'Coin.xelis: [0.00001234, 5.67]' '}', ); verify(client.get( @@ -134,7 +138,7 @@ void main() { "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos" + ",tezos,xelis" "&order=market_cap_desc&per_page=50&page=1&sparkline=false", ), headers: {'Content-Type': 'application/json'})).called(1); @@ -151,7 +155,7 @@ void main() { "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&" "ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos" + ",tezos,xelis" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -213,7 +217,10 @@ void main() { '21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentag' 'e":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-0' '7,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01' - '.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]'), + '.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"},{"id":' + '"xelis","symbol":"xel","name":"Xelis","image":"https://assets.coing' + 'ecko.com/coins/images/37615/large/green_background_black_logo.png",' + '"current_price":0.00001234,"price_change_percentage_24h":5.67}]'), 200)); final priceAPI = PriceAPI(client); @@ -247,7 +254,8 @@ void main() { 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0]' + 'Coin.stellarTestnet: [0, 0.0], ' + 'Coin.xelis: [0.00001234, 5.67]' '}', ); @@ -258,7 +266,7 @@ void main() { "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos" + ",tezos,xelis" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: {'Content-Type': 'application/json'})).called(1); @@ -274,7 +282,7 @@ void main() { "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos" + ",tezos,xelis" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -337,7 +345,9 @@ void main() { 'y":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_perce' 'ntage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74' '028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T' - '16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]'), + '16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"' + '},{"id":"xelis","symbol":xel,"name":com/coins/images/37615/large/g' + 'reen_background_black_logo.png,"image":"https://assets.coingecko'), 200)); final priceAPI = PriceAPI(client); @@ -368,7 +378,8 @@ void main() { 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0]' + 'Coin.stellarTestnet: [0, 0.0], ' + 'Coin.xelis: [0, 0.0]' '}', ); }); @@ -382,7 +393,7 @@ void main() { "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos" + ",tezos,xelis" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -418,7 +429,8 @@ void main() { 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0]' + 'Coin.stellarTestnet: [0, 0.0], ' + 'Coin.xelis: [0, 0.0]' '}', ); }); diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index b7a8d49d5..565f05f82 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -3,40 +3,41 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i9; -import 'dart:ui' as _i12; +import 'dart:async' as _i10; +import 'dart:ui' as _i13; -import 'package:decimal/decimal.dart' as _i18; +import 'package:decimal/decimal.dart' as _i19; +import 'package:logger/logger.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i7; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' - as _i21; + as _i22; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' - as _i23; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' as _i24; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' + as _i25; import 'package:stackwallet/models/exchange/response_objects/estimate.dart' - as _i20; + as _i21; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart' - as _i22; + as _i23; import 'package:stackwallet/models/exchange/response_objects/range.dart' - as _i19; + as _i20; import 'package:stackwallet/models/exchange/response_objects/trade.dart' - as _i14; -import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i17; -import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i25; + as _i15; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i18; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i26; import 'package:stackwallet/networking/http.dart' as _i3; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' - as _i16; + as _i17; import 'package:stackwallet/services/exchange/exchange_response.dart' as _i4; -import 'package:stackwallet/services/trade_notes_service.dart' as _i15; -import 'package:stackwallet/services/trade_service.dart' as _i13; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i10; +import 'package:stackwallet/services/trade_notes_service.dart' as _i16; +import 'package:stackwallet/services/trade_service.dart' as _i14; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i11; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6; import 'package:stackwallet/utilities/prefs.dart' as _i5; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' - as _i11; + as _i12; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart' as _i2; @@ -557,6 +558,45 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValueForMissingStub: null, ); + @override + bool get advancedFiroFeatures => (super.noSuchMethod( + Invocation.getter(#advancedFiroFeatures), + returnValue: false, + ) as bool); + + @override + set advancedFiroFeatures(bool? advancedFiroFeatures) => super.noSuchMethod( + Invocation.setter( + #advancedFiroFeatures, + advancedFiroFeatures, + ), + returnValueForMissingStub: null, + ); + + @override + set logsPath(String? logsPath) => super.noSuchMethod( + Invocation.setter( + #logsPath, + logsPath, + ), + returnValueForMissingStub: null, + ); + + @override + _i9.Level get logLevel => (super.noSuchMethod( + Invocation.getter(#logLevel), + returnValue: _i9.Level.all, + ) as _i9.Level); + + @override + set logLevel(_i9.Level? logLevel) => super.noSuchMethod( + Invocation.setter( + #logLevel, + logLevel, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -564,67 +604,67 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ) as bool); @override - _i9.Future init() => (super.noSuchMethod( + _i10.Future init() => (super.noSuchMethod( Invocation.method( #init, [], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i9.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i10.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method( #incrementCurrentNotificationIndex, [], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i9.Future isExternalCallsSet() => (super.noSuchMethod( + _i10.Future isExternalCallsSet() => (super.noSuchMethod( Invocation.method( #isExternalCallsSet, [], ), - returnValue: _i9.Future.value(false), - ) as _i9.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i9.Future saveUserID(String? userId) => (super.noSuchMethod( + _i10.Future saveUserID(String? userId) => (super.noSuchMethod( Invocation.method( #saveUserID, [userId], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i9.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + _i10.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( Invocation.method( #saveSignupEpoch, [signupEpoch], ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i10.AmountUnit amountUnit(_i11.CryptoCurrency? coin) => (super.noSuchMethod( + _i11.AmountUnit amountUnit(_i12.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #amountUnit, [coin], ), - returnValue: _i10.AmountUnit.normal, - ) as _i10.AmountUnit); + returnValue: _i11.AmountUnit.normal, + ) as _i11.AmountUnit); @override void updateAmountUnit({ - required _i11.CryptoCurrency? coin, - required _i10.AmountUnit? amountUnit, + required _i12.CryptoCurrency? coin, + required _i11.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method( @@ -639,7 +679,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ); @override - int maxDecimals(_i11.CryptoCurrency? coin) => (super.noSuchMethod( + int maxDecimals(_i12.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #maxDecimals, [coin], @@ -649,7 +689,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { @override void updateMaxDecimals({ - required _i11.CryptoCurrency? coin, + required _i12.CryptoCurrency? coin, required int? maxDecimals, }) => super.noSuchMethod( @@ -665,7 +705,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ); @override - _i2.FusionInfo getFusionServerInfo(_i11.CryptoCurrency? coin) => + _i2.FusionInfo getFusionServerInfo(_i12.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #getFusionServerInfo, @@ -682,7 +722,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { @override void setFusionServerInfo( - _i11.CryptoCurrency? coin, + _i12.CryptoCurrency? coin, _i2.FusionInfo? fusionServerInfo, ) => super.noSuchMethod( @@ -697,7 +737,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -706,7 +746,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -736,16 +776,16 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { /// A class which mocks [TradesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTradesService extends _i1.Mock implements _i13.TradesService { +class MockTradesService extends _i1.Mock implements _i14.TradesService { MockTradesService() { _i1.throwOnMissingStub(this); } @override - List<_i14.Trade> get trades => (super.noSuchMethod( + List<_i15.Trade> get trades => (super.noSuchMethod( Invocation.getter(#trades), - returnValue: <_i14.Trade>[], - ) as List<_i14.Trade>); + returnValue: <_i15.Trade>[], + ) as List<_i15.Trade>); @override bool get hasListeners => (super.noSuchMethod( @@ -754,14 +794,14 @@ class MockTradesService extends _i1.Mock implements _i13.TradesService { ) as bool); @override - _i14.Trade? get(String? tradeId) => (super.noSuchMethod(Invocation.method( + _i15.Trade? get(String? tradeId) => (super.noSuchMethod(Invocation.method( #get, [tradeId], - )) as _i14.Trade?); + )) as _i15.Trade?); @override - _i9.Future add({ - required _i14.Trade? trade, + _i10.Future add({ + required _i15.Trade? trade, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -773,13 +813,13 @@ class MockTradesService extends _i1.Mock implements _i13.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i9.Future edit({ - required _i14.Trade? trade, + _i10.Future edit({ + required _i15.Trade? trade, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -791,13 +831,13 @@ class MockTradesService extends _i1.Mock implements _i13.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i9.Future delete({ - required _i14.Trade? trade, + _i10.Future delete({ + required _i15.Trade? trade, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -809,12 +849,12 @@ class MockTradesService extends _i1.Mock implements _i13.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i9.Future deleteByUuid({ + _i10.Future deleteByUuid({ required String? uuid, required bool? shouldNotifyListeners, }) => @@ -827,12 +867,12 @@ class MockTradesService extends _i1.Mock implements _i13.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -841,7 +881,7 @@ class MockTradesService extends _i1.Mock implements _i13.TradesService { ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -871,7 +911,7 @@ class MockTradesService extends _i1.Mock implements _i13.TradesService { /// A class which mocks [TradeNotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTradeNotesService extends _i1.Mock implements _i15.TradeNotesService { +class MockTradeNotesService extends _i1.Mock implements _i16.TradeNotesService { MockTradeNotesService() { _i1.throwOnMissingStub(this); } @@ -906,7 +946,7 @@ class MockTradeNotesService extends _i1.Mock implements _i15.TradeNotesService { ) as String); @override - _i9.Future set({ + _i10.Future set({ required String? tradeId, required String? note, }) => @@ -919,23 +959,23 @@ class MockTradeNotesService extends _i1.Mock implements _i15.TradeNotesService { #note: note, }, ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i9.Future delete({required String? tradeId}) => (super.noSuchMethod( + _i10.Future delete({required String? tradeId}) => (super.noSuchMethod( Invocation.method( #delete, [], {#tradeId: tradeId}, ), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value(), - ) as _i9.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -944,7 +984,7 @@ class MockTradeNotesService extends _i1.Mock implements _i15.TradeNotesService { ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -974,7 +1014,7 @@ class MockTradeNotesService extends _i1.Mock implements _i15.TradeNotesService { /// A class which mocks [ChangeNowAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { +class MockChangeNowAPI extends _i1.Mock implements _i17.ChangeNowAPI { MockChangeNowAPI() { _i1.throwOnMissingStub(this); } @@ -989,54 +1029,55 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { ) as _i3.HTTP); @override - _i9.Future<_i4.ExchangeResponse>> getAvailableCurrencies({ + _i10.Future<_i4.ExchangeResponse>> + getAvailableCurrencies({ bool? fixedRate, bool? active, }) => - (super.noSuchMethod( - Invocation.method( - #getAvailableCurrencies, - [], - { - #fixedRate: fixedRate, - #active: active, - }, - ), - returnValue: - _i9.Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( - this, - Invocation.method( - #getAvailableCurrencies, - [], - { - #fixedRate: fixedRate, - #active: active, - }, - ), - )), - ) as _i9.Future<_i4.ExchangeResponse>>); + (super.noSuchMethod( + Invocation.method( + #getAvailableCurrencies, + [], + { + #fixedRate: fixedRate, + #active: active, + }, + ), + returnValue: + _i10.Future<_i4.ExchangeResponse>>.value( + _FakeExchangeResponse_2>( + this, + Invocation.method( + #getAvailableCurrencies, + [], + { + #fixedRate: fixedRate, + #active: active, + }, + ), + )), + ) as _i10.Future<_i4.ExchangeResponse>>); @override - _i9.Future<_i4.ExchangeResponse>> getCurrenciesV2() => + _i10.Future<_i4.ExchangeResponse>> getCurrenciesV2() => (super.noSuchMethod( Invocation.method( #getCurrenciesV2, [], ), returnValue: - _i9.Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( + _i10.Future<_i4.ExchangeResponse>>.value( + _FakeExchangeResponse_2>( this, Invocation.method( #getCurrenciesV2, [], ), )), - ) as _i9.Future<_i4.ExchangeResponse>>); + ) as _i10.Future<_i4.ExchangeResponse>>); @override - _i9.Future<_i4.ExchangeResponse>> getPairedCurrencies({ + _i10.Future<_i4.ExchangeResponse>> getPairedCurrencies({ required String? ticker, bool? fixedRate, }) => @@ -1050,8 +1091,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), returnValue: - _i9.Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( + _i10.Future<_i4.ExchangeResponse>>.value( + _FakeExchangeResponse_2>( this, Invocation.method( #getPairedCurrencies, @@ -1062,10 +1103,10 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse>>); + ) as _i10.Future<_i4.ExchangeResponse>>); @override - _i9.Future<_i4.ExchangeResponse<_i18.Decimal>> getMinimalExchangeAmount({ + _i10.Future<_i4.ExchangeResponse<_i19.Decimal>> getMinimalExchangeAmount({ required String? fromTicker, required String? toTicker, String? apiKey, @@ -1080,8 +1121,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i9.Future<_i4.ExchangeResponse<_i18.Decimal>>.value( - _FakeExchangeResponse_2<_i18.Decimal>( + returnValue: _i10.Future<_i4.ExchangeResponse<_i19.Decimal>>.value( + _FakeExchangeResponse_2<_i19.Decimal>( this, Invocation.method( #getMinimalExchangeAmount, @@ -1093,10 +1134,10 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i18.Decimal>>); + ) as _i10.Future<_i4.ExchangeResponse<_i19.Decimal>>); @override - _i9.Future<_i4.ExchangeResponse<_i19.Range>> getRange({ + _i10.Future<_i4.ExchangeResponse<_i20.Range>> getRange({ required String? fromTicker, required String? toTicker, required bool? isFixedRate, @@ -1113,8 +1154,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i9.Future<_i4.ExchangeResponse<_i19.Range>>.value( - _FakeExchangeResponse_2<_i19.Range>( + returnValue: _i10.Future<_i4.ExchangeResponse<_i20.Range>>.value( + _FakeExchangeResponse_2<_i20.Range>( this, Invocation.method( #getRange, @@ -1127,13 +1168,13 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i19.Range>>); + ) as _i10.Future<_i4.ExchangeResponse<_i20.Range>>); @override - _i9.Future<_i4.ExchangeResponse<_i20.Estimate>> getEstimatedExchangeAmount({ + _i10.Future<_i4.ExchangeResponse<_i21.Estimate>> getEstimatedExchangeAmount({ required String? fromTicker, required String? toTicker, - required _i18.Decimal? fromAmount, + required _i19.Decimal? fromAmount, String? apiKey, }) => (super.noSuchMethod( @@ -1147,8 +1188,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i9.Future<_i4.ExchangeResponse<_i20.Estimate>>.value( - _FakeExchangeResponse_2<_i20.Estimate>( + returnValue: _i10.Future<_i4.ExchangeResponse<_i21.Estimate>>.value( + _FakeExchangeResponse_2<_i21.Estimate>( this, Invocation.method( #getEstimatedExchangeAmount, @@ -1161,14 +1202,14 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i20.Estimate>>); + ) as _i10.Future<_i4.ExchangeResponse<_i21.Estimate>>); @override - _i9.Future<_i4.ExchangeResponse<_i20.Estimate>> + _i10.Future<_i4.ExchangeResponse<_i21.Estimate>> getEstimatedExchangeAmountFixedRate({ required String? fromTicker, required String? toTicker, - required _i18.Decimal? fromAmount, + required _i19.Decimal? fromAmount, required bool? reversed, bool? useRateId = true, String? apiKey, @@ -1186,8 +1227,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i9.Future<_i4.ExchangeResponse<_i20.Estimate>>.value( - _FakeExchangeResponse_2<_i20.Estimate>( + returnValue: _i10.Future<_i4.ExchangeResponse<_i21.Estimate>>.value( + _FakeExchangeResponse_2<_i21.Estimate>( this, Invocation.method( #getEstimatedExchangeAmountFixedRate, @@ -1202,18 +1243,18 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i20.Estimate>>); + ) as _i10.Future<_i4.ExchangeResponse<_i21.Estimate>>); @override - _i9.Future<_i4.ExchangeResponse<_i21.CNExchangeEstimate>> + _i10.Future<_i4.ExchangeResponse<_i22.CNExchangeEstimate>> getEstimatedExchangeAmountV2({ required String? fromTicker, required String? toTicker, - required _i21.CNEstimateType? fromOrTo, - required _i18.Decimal? amount, + required _i22.CNEstimateType? fromOrTo, + required _i19.Decimal? amount, String? fromNetwork, String? toNetwork, - _i21.CNFlowType? flow = _i21.CNFlowType.standard, + _i22.CNFlowType? flow = _i22.CNFlowType.standard, String? apiKey, }) => (super.noSuchMethod( @@ -1231,9 +1272,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: - _i9.Future<_i4.ExchangeResponse<_i21.CNExchangeEstimate>>.value( - _FakeExchangeResponse_2<_i21.CNExchangeEstimate>( + returnValue: _i10 + .Future<_i4.ExchangeResponse<_i22.CNExchangeEstimate>>.value( + _FakeExchangeResponse_2<_i22.CNExchangeEstimate>( this, Invocation.method( #getEstimatedExchangeAmountV2, @@ -1250,19 +1291,19 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i21.CNExchangeEstimate>>); + ) as _i10.Future<_i4.ExchangeResponse<_i22.CNExchangeEstimate>>); @override - _i9.Future<_i4.ExchangeResponse>> + _i10.Future<_i4.ExchangeResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( Invocation.method( #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}, ), - returnValue: _i9 - .Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( + returnValue: _i10 + .Future<_i4.ExchangeResponse>>.value( + _FakeExchangeResponse_2>( this, Invocation.method( #getAvailableFixedRateMarkets, @@ -1270,15 +1311,15 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { {#apiKey: apiKey}, ), )), - ) as _i9.Future<_i4.ExchangeResponse>>); + ) as _i10.Future<_i4.ExchangeResponse>>); @override - _i9.Future<_i4.ExchangeResponse<_i23.ExchangeTransaction>> + _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>> createStandardExchangeTransaction({ required String? fromTicker, required String? toTicker, required String? receivingAddress, - required _i18.Decimal? amount, + required _i19.Decimal? amount, String? extraId = r'', String? userId = r'', String? contactEmail = r'', @@ -1303,9 +1344,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i9 - .Future<_i4.ExchangeResponse<_i23.ExchangeTransaction>>.value( - _FakeExchangeResponse_2<_i23.ExchangeTransaction>( + returnValue: _i10 + .Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>.value( + _FakeExchangeResponse_2<_i24.ExchangeTransaction>( this, Invocation.method( #createStandardExchangeTransaction, @@ -1324,15 +1365,15 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i23.ExchangeTransaction>>); + ) as _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>); @override - _i9.Future<_i4.ExchangeResponse<_i23.ExchangeTransaction>> + _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>> createFixedRateExchangeTransaction({ required String? fromTicker, required String? toTicker, required String? receivingAddress, - required _i18.Decimal? amount, + required _i19.Decimal? amount, required String? rateId, required bool? reversed, String? extraId = r'', @@ -1361,9 +1402,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i9 - .Future<_i4.ExchangeResponse<_i23.ExchangeTransaction>>.value( - _FakeExchangeResponse_2<_i23.ExchangeTransaction>( + returnValue: _i10 + .Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>.value( + _FakeExchangeResponse_2<_i24.ExchangeTransaction>( this, Invocation.method( #createFixedRateExchangeTransaction, @@ -1384,12 +1425,12 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i23.ExchangeTransaction>>); + ) as _i10.Future<_i4.ExchangeResponse<_i24.ExchangeTransaction>>); @override - _i9.Future< + _i10.Future< _i4 - .ExchangeResponse<_i24.ExchangeTransactionStatus>> getTransactionStatus({ + .ExchangeResponse<_i25.ExchangeTransactionStatus>> getTransactionStatus({ required String? id, String? apiKey, }) => @@ -1402,9 +1443,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i9 - .Future<_i4.ExchangeResponse<_i24.ExchangeTransactionStatus>>.value( - _FakeExchangeResponse_2<_i24.ExchangeTransactionStatus>( + returnValue: _i10 + .Future<_i4.ExchangeResponse<_i25.ExchangeTransactionStatus>>.value( + _FakeExchangeResponse_2<_i25.ExchangeTransactionStatus>( this, Invocation.method( #getTransactionStatus, @@ -1415,10 +1456,10 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { }, ), )), - ) as _i9.Future<_i4.ExchangeResponse<_i24.ExchangeTransactionStatus>>); + ) as _i10.Future<_i4.ExchangeResponse<_i25.ExchangeTransactionStatus>>); @override - _i9.Future<_i4.ExchangeResponse>> + _i10.Future<_i4.ExchangeResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super.noSuchMethod( Invocation.method( @@ -1427,8 +1468,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { {#includePartners: includePartners}, ), returnValue: - _i9.Future<_i4.ExchangeResponse>>.value( - _FakeExchangeResponse_2>( + _i10.Future<_i4.ExchangeResponse>>.value( + _FakeExchangeResponse_2>( this, Invocation.method( #getAvailableFloatingRatePairs, @@ -1436,5 +1477,5 @@ class MockChangeNowAPI extends _i1.Mock implements _i16.ChangeNowAPI { {#includePartners: includePartners}, ), )), - ) as _i9.Future<_i4.ExchangeResponse>>); + ) as _i10.Future<_i4.ExchangeResponse>>); } diff --git a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart index e38af7ce9..a36aefef0 100644 --- a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart +++ b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i8; -import 'package:decimal/decimal.dart' as _i3; +import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i5; -import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i4; +import 'package:mockito/src/dummies.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i5; +import 'package:stackwallet/models/electrumx_response/spark_models.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i8; + as _i10; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart' as _i6; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' as _i2; @@ -49,8 +51,9 @@ class _FakeDuration_1 extends _i1.SmartFake implements Duration { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { - _FakeDecimal_2( +class _FakeSparkAnonymitySetMeta_2 extends _i1.SmartFake + implements _i3.SparkAnonymitySetMeta { + _FakeSparkAnonymitySetMeta_2( Object parent, Invocation parentInvocation, ) : super( @@ -59,9 +62,19 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { ); } -class _FakeElectrumXClient_3 extends _i1.SmartFake - implements _i4.ElectrumXClient { - _FakeElectrumXClient_3( +class _FakeDecimal_3 extends _i1.SmartFake implements _i4.Decimal { + _FakeDecimal_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeElectrumXClient_4 extends _i1.SmartFake + implements _i5.ElectrumXClient { + _FakeElectrumXClient_4( Object parent, Invocation parentInvocation, ) : super( @@ -73,7 +86,7 @@ class _FakeElectrumXClient_3 extends _i1.SmartFake /// A class which mocks [ElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { +class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { MockElectrumXClient() { _i1.throwOnMissingStub(this); } @@ -88,13 +101,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as _i2.CryptoCurrency); @override - set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( - Invocation.setter( - #failovers, - _failovers, - ), - returnValueForMissingStub: null, - ); + _i6.TorPlainNetworkOption get netType => (super.noSuchMethod( + Invocation.getter(#netType), + returnValue: _i6.TorPlainNetworkOption.tor, + ) as _i6.TorPlainNetworkOption); @override int get currentFailoverIndex => (super.noSuchMethod( @@ -124,7 +134,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { @override String get host => (super.noSuchMethod( Invocation.getter(#host), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#host), ), @@ -143,27 +153,27 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as bool); @override - _i6.Future closeAdapter() => (super.noSuchMethod( + _i8.Future closeAdapter() => (super.noSuchMethod( Invocation.method( #closeAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future checkElectrumAdapter() => (super.noSuchMethod( + _i8.Future checkElectrumAdapter() => (super.noSuchMethod( Invocation.method( #checkElectrumAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future request({ + _i8.Future request({ required String? command, List? args = const [], String? requestID, @@ -182,11 +192,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestTimeout: requestTimeout, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> batchRequest({ + _i8.Future> batchRequest({ required String? command, required List? args, Duration? requestTimeout = const Duration(seconds: 60), @@ -203,11 +213,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retries: retries, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future ping({ + _i8.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -220,11 +230,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i8.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -232,11 +242,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i8.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -244,11 +254,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future broadcastTransaction({ + _i8.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -261,7 +271,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(_i5.dummyValue( + returnValue: _i8.Future.value(_i7.dummyValue( this, Invocation.method( #broadcastTransaction, @@ -272,10 +282,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future); + ) as _i8.Future); @override - _i6.Future> getBalance({ + _i8.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -289,11 +299,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getHistory({ + _i8.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -306,12 +316,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchHistory( + _i8.Future>>> getBatchHistory( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -319,12 +329,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future>> getUTXOs({ + _i8.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -337,12 +347,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i8.Future>>> getBatchUTXOs( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -350,12 +360,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -371,11 +381,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getLelantusAnonymitySet({ + _i8.Future> getLelantusAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -391,11 +401,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusMintData({ + _i8.Future getLelantusMintData({ dynamic mints, String? requestID, }) => @@ -408,11 +418,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> getLelantusUsedCoinSerials({ + _i8.Future> getLelantusUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -426,22 +436,22 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusLatestCoinId({String? requestID}) => + _i8.Future getLelantusLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLelantusLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getSparkAnonymitySet({ + _i8.Future> getSparkAnonymitySet({ String? coinGroupId = r'1', String? startBlockHash = r'', String? requestID, @@ -457,58 +467,33 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getSparkMintMetaData({ - String? requestID, - required List? sparkCoinHashes, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkMintMetaData, - [], - { - #requestID: requestID, - #sparkCoinHashes: sparkCoinHashes, - }, - ), - returnValue: _i6.Future>>.value( - >[]), - ) as _i6.Future>>); - - @override - _i6.Future getSparkLatestCoinId({String? requestID}) => + _i8.Future getSparkLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getSparkLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getMempoolTxids({String? requestID}) => + _i8.Future> getMempoolTxids({String? requestID}) => (super.noSuchMethod( Invocation.method( #getMempoolTxids, [], {#requestID: requestID}, ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>> getMempoolSparkData({ + _i8.Future> getMempoolSparkData({ String? requestID, required List? txids, }) => @@ -521,30 +506,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #txids: txids, }, ), - returnValue: _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>.value(<({ - List coins, - List lTags, - List serialContext, - String txid - })>[]), - ) as _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>); - - @override - _i6.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ + returnValue: _i8.Future>.value( + <_i3.SparkMempoolData>[]), + ) as _i8.Future>); + + @override + _i8.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ String? requestID, required int? startNumber, }) => @@ -557,11 +524,62 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #startNumber: startNumber, }, ), - returnValue: _i6.Future>>.value(>[]), - ) as _i6.Future>>); + returnValue: _i8.Future>>.value(>[]), + ) as _i8.Future>>); + + @override + _i8.Future<_i3.SparkAnonymitySetMeta> getSparkAnonymitySetMeta({ + String? requestID, + required int? coinGroupId, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + returnValue: _i8.Future<_i3.SparkAnonymitySetMeta>.value( + _FakeSparkAnonymitySetMeta_2( + this, + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + )), + ) as _i8.Future<_i3.SparkAnonymitySetMeta>); + + @override + _i8.Future> getSparkAnonymitySetBySector({ + String? requestID, + required int? coinGroupId, + required String? latestBlock, + required int? startIndex, + required int? endIndex, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetBySector, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + #latestBlock: latestBlock, + #startIndex: startIndex, + #endIndex: endIndex, + }, + ), + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future isMasterNodeCollateral({ + _i8.Future isMasterNodeCollateral({ String? requestID, required String? txid, required int? index, @@ -576,11 +594,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #index: index, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i8.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -588,11 +606,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future<_i3.Decimal> estimateFee({ + _i8.Future<_i4.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -605,7 +623,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #blocks: blocks, }, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #estimateFee, @@ -616,16 +634,16 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); @override - _i6.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i8.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #relayFee, @@ -633,29 +651,29 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); } /// A class which mocks [CachedElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. class MockCachedElectrumXClient extends _i1.Mock - implements _i7.CachedElectrumXClient { + implements _i9.CachedElectrumXClient { MockCachedElectrumXClient() { _i1.throwOnMissingStub(this); } @override - _i4.ElectrumXClient get electrumXClient => (super.noSuchMethod( + _i5.ElectrumXClient get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumXClient_3( + returnValue: _FakeElectrumXClient_4( this, Invocation.getter(#electrumXClient), ), - ) as _i4.ElectrumXClient); + ) as _i5.ElectrumXClient); @override - _i6.Future> getAnonymitySet({ + _i8.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', required _i2.CryptoCurrency? cryptoCurrency, @@ -671,8 +689,8 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( @@ -680,7 +698,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToHex, @@ -695,7 +713,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToReverseHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToReverseHex, @@ -705,7 +723,7 @@ class MockCachedElectrumXClient extends _i1.Mock ) as String); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, required _i2.CryptoCurrency? cryptoCurrency, bool? verbose = true, @@ -721,11 +739,11 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getUsedCoinSerials({ + _i8.Future> getUsedCoinSerials({ required _i2.CryptoCurrency? cryptoCurrency, int? startNumber = 0, }) => @@ -738,11 +756,11 @@ class MockCachedElectrumXClient extends _i1.Mock #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future clearSharedTransactionCache( + _i8.Future clearSharedTransactionCache( {required _i2.CryptoCurrency? cryptoCurrency}) => (super.noSuchMethod( Invocation.method( @@ -750,16 +768,16 @@ class MockCachedElectrumXClient extends _i1.Mock [], {#cryptoCurrency: cryptoCurrency}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i8.TransactionNotificationTracker { + implements _i10.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -767,7 +785,7 @@ class MockTransactionNotificationTracker extends _i1.Mock @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#walletId), ), @@ -795,14 +813,14 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( @@ -814,22 +832,22 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future deleteTransaction(String? txid) => (super.noSuchMethod( + _i8.Future deleteTransaction(String? txid) => (super.noSuchMethod( Invocation.method( #deleteTransaction, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index edbb3c632..c853a3f37 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i8; -import 'package:decimal/decimal.dart' as _i3; +import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i5; -import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i4; +import 'package:mockito/src/dummies.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i5; +import 'package:stackwallet/models/electrumx_response/spark_models.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i8; + as _i10; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart' as _i6; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' as _i2; @@ -49,8 +51,9 @@ class _FakeDuration_1 extends _i1.SmartFake implements Duration { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { - _FakeDecimal_2( +class _FakeSparkAnonymitySetMeta_2 extends _i1.SmartFake + implements _i3.SparkAnonymitySetMeta { + _FakeSparkAnonymitySetMeta_2( Object parent, Invocation parentInvocation, ) : super( @@ -59,9 +62,19 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { ); } -class _FakeElectrumXClient_3 extends _i1.SmartFake - implements _i4.ElectrumXClient { - _FakeElectrumXClient_3( +class _FakeDecimal_3 extends _i1.SmartFake implements _i4.Decimal { + _FakeDecimal_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeElectrumXClient_4 extends _i1.SmartFake + implements _i5.ElectrumXClient { + _FakeElectrumXClient_4( Object parent, Invocation parentInvocation, ) : super( @@ -73,7 +86,7 @@ class _FakeElectrumXClient_3 extends _i1.SmartFake /// A class which mocks [ElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { +class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { MockElectrumXClient() { _i1.throwOnMissingStub(this); } @@ -88,13 +101,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as _i2.CryptoCurrency); @override - set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( - Invocation.setter( - #failovers, - _failovers, - ), - returnValueForMissingStub: null, - ); + _i6.TorPlainNetworkOption get netType => (super.noSuchMethod( + Invocation.getter(#netType), + returnValue: _i6.TorPlainNetworkOption.tor, + ) as _i6.TorPlainNetworkOption); @override int get currentFailoverIndex => (super.noSuchMethod( @@ -124,7 +134,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { @override String get host => (super.noSuchMethod( Invocation.getter(#host), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#host), ), @@ -143,27 +153,27 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as bool); @override - _i6.Future closeAdapter() => (super.noSuchMethod( + _i8.Future closeAdapter() => (super.noSuchMethod( Invocation.method( #closeAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future checkElectrumAdapter() => (super.noSuchMethod( + _i8.Future checkElectrumAdapter() => (super.noSuchMethod( Invocation.method( #checkElectrumAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future request({ + _i8.Future request({ required String? command, List? args = const [], String? requestID, @@ -182,11 +192,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestTimeout: requestTimeout, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> batchRequest({ + _i8.Future> batchRequest({ required String? command, required List? args, Duration? requestTimeout = const Duration(seconds: 60), @@ -203,11 +213,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retries: retries, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future ping({ + _i8.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -220,11 +230,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i8.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -232,11 +242,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i8.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -244,11 +254,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future broadcastTransaction({ + _i8.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -261,7 +271,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(_i5.dummyValue( + returnValue: _i8.Future.value(_i7.dummyValue( this, Invocation.method( #broadcastTransaction, @@ -272,10 +282,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future); + ) as _i8.Future); @override - _i6.Future> getBalance({ + _i8.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -289,11 +299,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getHistory({ + _i8.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -306,12 +316,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchHistory( + _i8.Future>>> getBatchHistory( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -319,12 +329,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future>> getUTXOs({ + _i8.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -337,12 +347,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i8.Future>>> getBatchUTXOs( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -350,12 +360,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -371,11 +381,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getLelantusAnonymitySet({ + _i8.Future> getLelantusAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -391,11 +401,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusMintData({ + _i8.Future getLelantusMintData({ dynamic mints, String? requestID, }) => @@ -408,11 +418,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> getLelantusUsedCoinSerials({ + _i8.Future> getLelantusUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -426,22 +436,22 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusLatestCoinId({String? requestID}) => + _i8.Future getLelantusLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLelantusLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getSparkAnonymitySet({ + _i8.Future> getSparkAnonymitySet({ String? coinGroupId = r'1', String? startBlockHash = r'', String? requestID, @@ -457,58 +467,33 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getSparkMintMetaData({ - String? requestID, - required List? sparkCoinHashes, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkMintMetaData, - [], - { - #requestID: requestID, - #sparkCoinHashes: sparkCoinHashes, - }, - ), - returnValue: _i6.Future>>.value( - >[]), - ) as _i6.Future>>); - - @override - _i6.Future getSparkLatestCoinId({String? requestID}) => + _i8.Future getSparkLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getSparkLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getMempoolTxids({String? requestID}) => + _i8.Future> getMempoolTxids({String? requestID}) => (super.noSuchMethod( Invocation.method( #getMempoolTxids, [], {#requestID: requestID}, ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>> getMempoolSparkData({ + _i8.Future> getMempoolSparkData({ String? requestID, required List? txids, }) => @@ -521,30 +506,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #txids: txids, }, ), - returnValue: _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>.value(<({ - List coins, - List lTags, - List serialContext, - String txid - })>[]), - ) as _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>); - - @override - _i6.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ + returnValue: _i8.Future>.value( + <_i3.SparkMempoolData>[]), + ) as _i8.Future>); + + @override + _i8.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ String? requestID, required int? startNumber, }) => @@ -557,11 +524,62 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #startNumber: startNumber, }, ), - returnValue: _i6.Future>>.value(>[]), - ) as _i6.Future>>); + returnValue: _i8.Future>>.value(>[]), + ) as _i8.Future>>); + + @override + _i8.Future<_i3.SparkAnonymitySetMeta> getSparkAnonymitySetMeta({ + String? requestID, + required int? coinGroupId, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + returnValue: _i8.Future<_i3.SparkAnonymitySetMeta>.value( + _FakeSparkAnonymitySetMeta_2( + this, + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + )), + ) as _i8.Future<_i3.SparkAnonymitySetMeta>); + + @override + _i8.Future> getSparkAnonymitySetBySector({ + String? requestID, + required int? coinGroupId, + required String? latestBlock, + required int? startIndex, + required int? endIndex, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetBySector, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + #latestBlock: latestBlock, + #startIndex: startIndex, + #endIndex: endIndex, + }, + ), + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future isMasterNodeCollateral({ + _i8.Future isMasterNodeCollateral({ String? requestID, required String? txid, required int? index, @@ -576,11 +594,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #index: index, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i8.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -588,11 +606,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future<_i3.Decimal> estimateFee({ + _i8.Future<_i4.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -605,7 +623,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #blocks: blocks, }, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #estimateFee, @@ -616,16 +634,16 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); @override - _i6.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i8.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #relayFee, @@ -633,29 +651,29 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); } /// A class which mocks [CachedElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. class MockCachedElectrumXClient extends _i1.Mock - implements _i7.CachedElectrumXClient { + implements _i9.CachedElectrumXClient { MockCachedElectrumXClient() { _i1.throwOnMissingStub(this); } @override - _i4.ElectrumXClient get electrumXClient => (super.noSuchMethod( + _i5.ElectrumXClient get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumXClient_3( + returnValue: _FakeElectrumXClient_4( this, Invocation.getter(#electrumXClient), ), - ) as _i4.ElectrumXClient); + ) as _i5.ElectrumXClient); @override - _i6.Future> getAnonymitySet({ + _i8.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', required _i2.CryptoCurrency? cryptoCurrency, @@ -671,8 +689,8 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( @@ -680,7 +698,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToHex, @@ -695,7 +713,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToReverseHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToReverseHex, @@ -705,7 +723,7 @@ class MockCachedElectrumXClient extends _i1.Mock ) as String); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, required _i2.CryptoCurrency? cryptoCurrency, bool? verbose = true, @@ -721,11 +739,11 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getUsedCoinSerials({ + _i8.Future> getUsedCoinSerials({ required _i2.CryptoCurrency? cryptoCurrency, int? startNumber = 0, }) => @@ -738,11 +756,11 @@ class MockCachedElectrumXClient extends _i1.Mock #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future clearSharedTransactionCache( + _i8.Future clearSharedTransactionCache( {required _i2.CryptoCurrency? cryptoCurrency}) => (super.noSuchMethod( Invocation.method( @@ -750,16 +768,16 @@ class MockCachedElectrumXClient extends _i1.Mock [], {#cryptoCurrency: cryptoCurrency}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i8.TransactionNotificationTracker { + implements _i10.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -767,7 +785,7 @@ class MockTransactionNotificationTracker extends _i1.Mock @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#walletId), ), @@ -795,14 +813,14 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( @@ -814,22 +832,22 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future deleteTransaction(String? txid) => (super.noSuchMethod( + _i8.Future deleteTransaction(String? txid) => (super.noSuchMethod( Invocation.method( #deleteTransaction, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } diff --git a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart index c5c167366..08f884b57 100644 --- a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart +++ b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i8; -import 'package:decimal/decimal.dart' as _i3; +import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i5; -import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i4; +import 'package:mockito/src/dummies.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i5; +import 'package:stackwallet/models/electrumx_response/spark_models.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i8; + as _i10; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart' as _i6; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' as _i2; @@ -49,8 +51,9 @@ class _FakeDuration_1 extends _i1.SmartFake implements Duration { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { - _FakeDecimal_2( +class _FakeSparkAnonymitySetMeta_2 extends _i1.SmartFake + implements _i3.SparkAnonymitySetMeta { + _FakeSparkAnonymitySetMeta_2( Object parent, Invocation parentInvocation, ) : super( @@ -59,9 +62,19 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { ); } -class _FakeElectrumXClient_3 extends _i1.SmartFake - implements _i4.ElectrumXClient { - _FakeElectrumXClient_3( +class _FakeDecimal_3 extends _i1.SmartFake implements _i4.Decimal { + _FakeDecimal_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeElectrumXClient_4 extends _i1.SmartFake + implements _i5.ElectrumXClient { + _FakeElectrumXClient_4( Object parent, Invocation parentInvocation, ) : super( @@ -73,7 +86,7 @@ class _FakeElectrumXClient_3 extends _i1.SmartFake /// A class which mocks [ElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { +class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { MockElectrumXClient() { _i1.throwOnMissingStub(this); } @@ -88,13 +101,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as _i2.CryptoCurrency); @override - set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( - Invocation.setter( - #failovers, - _failovers, - ), - returnValueForMissingStub: null, - ); + _i6.TorPlainNetworkOption get netType => (super.noSuchMethod( + Invocation.getter(#netType), + returnValue: _i6.TorPlainNetworkOption.tor, + ) as _i6.TorPlainNetworkOption); @override int get currentFailoverIndex => (super.noSuchMethod( @@ -124,7 +134,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { @override String get host => (super.noSuchMethod( Invocation.getter(#host), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#host), ), @@ -143,27 +153,27 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as bool); @override - _i6.Future closeAdapter() => (super.noSuchMethod( + _i8.Future closeAdapter() => (super.noSuchMethod( Invocation.method( #closeAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future checkElectrumAdapter() => (super.noSuchMethod( + _i8.Future checkElectrumAdapter() => (super.noSuchMethod( Invocation.method( #checkElectrumAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future request({ + _i8.Future request({ required String? command, List? args = const [], String? requestID, @@ -182,11 +192,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestTimeout: requestTimeout, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> batchRequest({ + _i8.Future> batchRequest({ required String? command, required List? args, Duration? requestTimeout = const Duration(seconds: 60), @@ -203,11 +213,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retries: retries, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future ping({ + _i8.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -220,11 +230,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i8.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -232,11 +242,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i8.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -244,11 +254,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future broadcastTransaction({ + _i8.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -261,7 +271,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(_i5.dummyValue( + returnValue: _i8.Future.value(_i7.dummyValue( this, Invocation.method( #broadcastTransaction, @@ -272,10 +282,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future); + ) as _i8.Future); @override - _i6.Future> getBalance({ + _i8.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -289,11 +299,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getHistory({ + _i8.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -306,12 +316,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchHistory( + _i8.Future>>> getBatchHistory( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -319,12 +329,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future>> getUTXOs({ + _i8.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -337,12 +347,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i8.Future>>> getBatchUTXOs( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -350,12 +360,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -371,11 +381,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getLelantusAnonymitySet({ + _i8.Future> getLelantusAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -391,11 +401,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusMintData({ + _i8.Future getLelantusMintData({ dynamic mints, String? requestID, }) => @@ -408,11 +418,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> getLelantusUsedCoinSerials({ + _i8.Future> getLelantusUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -426,22 +436,22 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusLatestCoinId({String? requestID}) => + _i8.Future getLelantusLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLelantusLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getSparkAnonymitySet({ + _i8.Future> getSparkAnonymitySet({ String? coinGroupId = r'1', String? startBlockHash = r'', String? requestID, @@ -457,58 +467,33 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getSparkMintMetaData({ - String? requestID, - required List? sparkCoinHashes, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkMintMetaData, - [], - { - #requestID: requestID, - #sparkCoinHashes: sparkCoinHashes, - }, - ), - returnValue: _i6.Future>>.value( - >[]), - ) as _i6.Future>>); - - @override - _i6.Future getSparkLatestCoinId({String? requestID}) => + _i8.Future getSparkLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getSparkLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getMempoolTxids({String? requestID}) => + _i8.Future> getMempoolTxids({String? requestID}) => (super.noSuchMethod( Invocation.method( #getMempoolTxids, [], {#requestID: requestID}, ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>> getMempoolSparkData({ + _i8.Future> getMempoolSparkData({ String? requestID, required List? txids, }) => @@ -521,30 +506,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #txids: txids, }, ), - returnValue: _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>.value(<({ - List coins, - List lTags, - List serialContext, - String txid - })>[]), - ) as _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>); - - @override - _i6.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ + returnValue: _i8.Future>.value( + <_i3.SparkMempoolData>[]), + ) as _i8.Future>); + + @override + _i8.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ String? requestID, required int? startNumber, }) => @@ -557,11 +524,62 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #startNumber: startNumber, }, ), - returnValue: _i6.Future>>.value(>[]), - ) as _i6.Future>>); + returnValue: _i8.Future>>.value(>[]), + ) as _i8.Future>>); + + @override + _i8.Future<_i3.SparkAnonymitySetMeta> getSparkAnonymitySetMeta({ + String? requestID, + required int? coinGroupId, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + returnValue: _i8.Future<_i3.SparkAnonymitySetMeta>.value( + _FakeSparkAnonymitySetMeta_2( + this, + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + )), + ) as _i8.Future<_i3.SparkAnonymitySetMeta>); + + @override + _i8.Future> getSparkAnonymitySetBySector({ + String? requestID, + required int? coinGroupId, + required String? latestBlock, + required int? startIndex, + required int? endIndex, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetBySector, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + #latestBlock: latestBlock, + #startIndex: startIndex, + #endIndex: endIndex, + }, + ), + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future isMasterNodeCollateral({ + _i8.Future isMasterNodeCollateral({ String? requestID, required String? txid, required int? index, @@ -576,11 +594,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #index: index, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i8.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -588,11 +606,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future<_i3.Decimal> estimateFee({ + _i8.Future<_i4.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -605,7 +623,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #blocks: blocks, }, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #estimateFee, @@ -616,16 +634,16 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); @override - _i6.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i8.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #relayFee, @@ -633,29 +651,29 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); } /// A class which mocks [CachedElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. class MockCachedElectrumXClient extends _i1.Mock - implements _i7.CachedElectrumXClient { + implements _i9.CachedElectrumXClient { MockCachedElectrumXClient() { _i1.throwOnMissingStub(this); } @override - _i4.ElectrumXClient get electrumXClient => (super.noSuchMethod( + _i5.ElectrumXClient get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumXClient_3( + returnValue: _FakeElectrumXClient_4( this, Invocation.getter(#electrumXClient), ), - ) as _i4.ElectrumXClient); + ) as _i5.ElectrumXClient); @override - _i6.Future> getAnonymitySet({ + _i8.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', required _i2.CryptoCurrency? cryptoCurrency, @@ -671,8 +689,8 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( @@ -680,7 +698,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToHex, @@ -695,7 +713,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToReverseHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToReverseHex, @@ -705,7 +723,7 @@ class MockCachedElectrumXClient extends _i1.Mock ) as String); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, required _i2.CryptoCurrency? cryptoCurrency, bool? verbose = true, @@ -721,11 +739,11 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getUsedCoinSerials({ + _i8.Future> getUsedCoinSerials({ required _i2.CryptoCurrency? cryptoCurrency, int? startNumber = 0, }) => @@ -738,11 +756,11 @@ class MockCachedElectrumXClient extends _i1.Mock #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future clearSharedTransactionCache( + _i8.Future clearSharedTransactionCache( {required _i2.CryptoCurrency? cryptoCurrency}) => (super.noSuchMethod( Invocation.method( @@ -750,16 +768,16 @@ class MockCachedElectrumXClient extends _i1.Mock [], {#cryptoCurrency: cryptoCurrency}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i8.TransactionNotificationTracker { + implements _i10.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -767,7 +785,7 @@ class MockTransactionNotificationTracker extends _i1.Mock @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#walletId), ), @@ -795,14 +813,14 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( @@ -814,22 +832,22 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future deleteTransaction(String? txid) => (super.noSuchMethod( + _i8.Future deleteTransaction(String? txid) => (super.noSuchMethod( Invocation.method( #deleteTransaction, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } diff --git a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart index 80e8c8922..cd27b6654 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i8; -import 'package:decimal/decimal.dart' as _i3; +import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i5; -import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i4; +import 'package:mockito/src/dummies.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i5; +import 'package:stackwallet/models/electrumx_response/spark_models.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i8; + as _i10; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart' as _i6; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' as _i2; @@ -49,8 +51,9 @@ class _FakeDuration_1 extends _i1.SmartFake implements Duration { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { - _FakeDecimal_2( +class _FakeSparkAnonymitySetMeta_2 extends _i1.SmartFake + implements _i3.SparkAnonymitySetMeta { + _FakeSparkAnonymitySetMeta_2( Object parent, Invocation parentInvocation, ) : super( @@ -59,9 +62,19 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { ); } -class _FakeElectrumXClient_3 extends _i1.SmartFake - implements _i4.ElectrumXClient { - _FakeElectrumXClient_3( +class _FakeDecimal_3 extends _i1.SmartFake implements _i4.Decimal { + _FakeDecimal_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeElectrumXClient_4 extends _i1.SmartFake + implements _i5.ElectrumXClient { + _FakeElectrumXClient_4( Object parent, Invocation parentInvocation, ) : super( @@ -73,7 +86,7 @@ class _FakeElectrumXClient_3 extends _i1.SmartFake /// A class which mocks [ElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { +class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { MockElectrumXClient() { _i1.throwOnMissingStub(this); } @@ -88,13 +101,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as _i2.CryptoCurrency); @override - set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( - Invocation.setter( - #failovers, - _failovers, - ), - returnValueForMissingStub: null, - ); + _i6.TorPlainNetworkOption get netType => (super.noSuchMethod( + Invocation.getter(#netType), + returnValue: _i6.TorPlainNetworkOption.tor, + ) as _i6.TorPlainNetworkOption); @override int get currentFailoverIndex => (super.noSuchMethod( @@ -124,7 +134,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { @override String get host => (super.noSuchMethod( Invocation.getter(#host), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#host), ), @@ -143,27 +153,27 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as bool); @override - _i6.Future closeAdapter() => (super.noSuchMethod( + _i8.Future closeAdapter() => (super.noSuchMethod( Invocation.method( #closeAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future checkElectrumAdapter() => (super.noSuchMethod( + _i8.Future checkElectrumAdapter() => (super.noSuchMethod( Invocation.method( #checkElectrumAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future request({ + _i8.Future request({ required String? command, List? args = const [], String? requestID, @@ -182,11 +192,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestTimeout: requestTimeout, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> batchRequest({ + _i8.Future> batchRequest({ required String? command, required List? args, Duration? requestTimeout = const Duration(seconds: 60), @@ -203,11 +213,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retries: retries, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future ping({ + _i8.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -220,11 +230,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i8.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -232,11 +242,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i8.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -244,11 +254,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future broadcastTransaction({ + _i8.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -261,7 +271,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(_i5.dummyValue( + returnValue: _i8.Future.value(_i7.dummyValue( this, Invocation.method( #broadcastTransaction, @@ -272,10 +282,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future); + ) as _i8.Future); @override - _i6.Future> getBalance({ + _i8.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -289,11 +299,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getHistory({ + _i8.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -306,12 +316,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchHistory( + _i8.Future>>> getBatchHistory( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -319,12 +329,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future>> getUTXOs({ + _i8.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -337,12 +347,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i8.Future>>> getBatchUTXOs( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -350,12 +360,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -371,11 +381,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getLelantusAnonymitySet({ + _i8.Future> getLelantusAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -391,11 +401,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusMintData({ + _i8.Future getLelantusMintData({ dynamic mints, String? requestID, }) => @@ -408,11 +418,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> getLelantusUsedCoinSerials({ + _i8.Future> getLelantusUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -426,22 +436,22 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusLatestCoinId({String? requestID}) => + _i8.Future getLelantusLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLelantusLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getSparkAnonymitySet({ + _i8.Future> getSparkAnonymitySet({ String? coinGroupId = r'1', String? startBlockHash = r'', String? requestID, @@ -457,58 +467,33 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getSparkMintMetaData({ - String? requestID, - required List? sparkCoinHashes, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkMintMetaData, - [], - { - #requestID: requestID, - #sparkCoinHashes: sparkCoinHashes, - }, - ), - returnValue: _i6.Future>>.value( - >[]), - ) as _i6.Future>>); - - @override - _i6.Future getSparkLatestCoinId({String? requestID}) => + _i8.Future getSparkLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getSparkLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getMempoolTxids({String? requestID}) => + _i8.Future> getMempoolTxids({String? requestID}) => (super.noSuchMethod( Invocation.method( #getMempoolTxids, [], {#requestID: requestID}, ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>> getMempoolSparkData({ + _i8.Future> getMempoolSparkData({ String? requestID, required List? txids, }) => @@ -521,30 +506,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #txids: txids, }, ), - returnValue: _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>.value(<({ - List coins, - List lTags, - List serialContext, - String txid - })>[]), - ) as _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>); - - @override - _i6.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ + returnValue: _i8.Future>.value( + <_i3.SparkMempoolData>[]), + ) as _i8.Future>); + + @override + _i8.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ String? requestID, required int? startNumber, }) => @@ -557,11 +524,62 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #startNumber: startNumber, }, ), - returnValue: _i6.Future>>.value(>[]), - ) as _i6.Future>>); + returnValue: _i8.Future>>.value(>[]), + ) as _i8.Future>>); + + @override + _i8.Future<_i3.SparkAnonymitySetMeta> getSparkAnonymitySetMeta({ + String? requestID, + required int? coinGroupId, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + returnValue: _i8.Future<_i3.SparkAnonymitySetMeta>.value( + _FakeSparkAnonymitySetMeta_2( + this, + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + )), + ) as _i8.Future<_i3.SparkAnonymitySetMeta>); + + @override + _i8.Future> getSparkAnonymitySetBySector({ + String? requestID, + required int? coinGroupId, + required String? latestBlock, + required int? startIndex, + required int? endIndex, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetBySector, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + #latestBlock: latestBlock, + #startIndex: startIndex, + #endIndex: endIndex, + }, + ), + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future isMasterNodeCollateral({ + _i8.Future isMasterNodeCollateral({ String? requestID, required String? txid, required int? index, @@ -576,11 +594,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #index: index, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i8.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -588,11 +606,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future<_i3.Decimal> estimateFee({ + _i8.Future<_i4.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -605,7 +623,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #blocks: blocks, }, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #estimateFee, @@ -616,16 +634,16 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); @override - _i6.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i8.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #relayFee, @@ -633,29 +651,29 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); } /// A class which mocks [CachedElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. class MockCachedElectrumXClient extends _i1.Mock - implements _i7.CachedElectrumXClient { + implements _i9.CachedElectrumXClient { MockCachedElectrumXClient() { _i1.throwOnMissingStub(this); } @override - _i4.ElectrumXClient get electrumXClient => (super.noSuchMethod( + _i5.ElectrumXClient get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumXClient_3( + returnValue: _FakeElectrumXClient_4( this, Invocation.getter(#electrumXClient), ), - ) as _i4.ElectrumXClient); + ) as _i5.ElectrumXClient); @override - _i6.Future> getAnonymitySet({ + _i8.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', required _i2.CryptoCurrency? cryptoCurrency, @@ -671,8 +689,8 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( @@ -680,7 +698,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToHex, @@ -695,7 +713,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToReverseHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToReverseHex, @@ -705,7 +723,7 @@ class MockCachedElectrumXClient extends _i1.Mock ) as String); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, required _i2.CryptoCurrency? cryptoCurrency, bool? verbose = true, @@ -721,11 +739,11 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getUsedCoinSerials({ + _i8.Future> getUsedCoinSerials({ required _i2.CryptoCurrency? cryptoCurrency, int? startNumber = 0, }) => @@ -738,11 +756,11 @@ class MockCachedElectrumXClient extends _i1.Mock #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future clearSharedTransactionCache( + _i8.Future clearSharedTransactionCache( {required _i2.CryptoCurrency? cryptoCurrency}) => (super.noSuchMethod( Invocation.method( @@ -750,16 +768,16 @@ class MockCachedElectrumXClient extends _i1.Mock [], {#cryptoCurrency: cryptoCurrency}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i8.TransactionNotificationTracker { + implements _i10.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -767,7 +785,7 @@ class MockTransactionNotificationTracker extends _i1.Mock @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#walletId), ), @@ -795,14 +813,14 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( @@ -814,22 +832,22 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future deleteTransaction(String? txid) => (super.noSuchMethod( + _i8.Future deleteTransaction(String? txid) => (super.noSuchMethod( Invocation.method( #deleteTransaction, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } diff --git a/test/services/coins/particl/particl_wallet_test.mocks.dart b/test/services/coins/particl/particl_wallet_test.mocks.dart index 0fde0b645..1360bd6db 100644 --- a/test/services/coins/particl/particl_wallet_test.mocks.dart +++ b/test/services/coins/particl/particl_wallet_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i8; -import 'package:decimal/decimal.dart' as _i3; +import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i5; -import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i4; +import 'package:mockito/src/dummies.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i5; +import 'package:stackwallet/models/electrumx_response/spark_models.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i8; + as _i10; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart' as _i6; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' as _i2; @@ -49,8 +51,9 @@ class _FakeDuration_1 extends _i1.SmartFake implements Duration { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { - _FakeDecimal_2( +class _FakeSparkAnonymitySetMeta_2 extends _i1.SmartFake + implements _i3.SparkAnonymitySetMeta { + _FakeSparkAnonymitySetMeta_2( Object parent, Invocation parentInvocation, ) : super( @@ -59,9 +62,19 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal { ); } -class _FakeElectrumXClient_3 extends _i1.SmartFake - implements _i4.ElectrumXClient { - _FakeElectrumXClient_3( +class _FakeDecimal_3 extends _i1.SmartFake implements _i4.Decimal { + _FakeDecimal_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeElectrumXClient_4 extends _i1.SmartFake + implements _i5.ElectrumXClient { + _FakeElectrumXClient_4( Object parent, Invocation parentInvocation, ) : super( @@ -73,7 +86,7 @@ class _FakeElectrumXClient_3 extends _i1.SmartFake /// A class which mocks [ElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { +class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { MockElectrumXClient() { _i1.throwOnMissingStub(this); } @@ -88,13 +101,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as _i2.CryptoCurrency); @override - set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( - Invocation.setter( - #failovers, - _failovers, - ), - returnValueForMissingStub: null, - ); + _i6.TorPlainNetworkOption get netType => (super.noSuchMethod( + Invocation.getter(#netType), + returnValue: _i6.TorPlainNetworkOption.tor, + ) as _i6.TorPlainNetworkOption); @override int get currentFailoverIndex => (super.noSuchMethod( @@ -124,7 +134,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { @override String get host => (super.noSuchMethod( Invocation.getter(#host), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#host), ), @@ -143,27 +153,27 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { ) as bool); @override - _i6.Future closeAdapter() => (super.noSuchMethod( + _i8.Future closeAdapter() => (super.noSuchMethod( Invocation.method( #closeAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future checkElectrumAdapter() => (super.noSuchMethod( + _i8.Future checkElectrumAdapter() => (super.noSuchMethod( Invocation.method( #checkElectrumAdapter, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future request({ + _i8.Future request({ required String? command, List? args = const [], String? requestID, @@ -182,11 +192,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestTimeout: requestTimeout, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> batchRequest({ + _i8.Future> batchRequest({ required String? command, required List? args, Duration? requestTimeout = const Duration(seconds: 60), @@ -203,11 +213,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retries: retries, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future ping({ + _i8.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -220,11 +230,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i8.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -232,11 +242,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i8.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -244,11 +254,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future broadcastTransaction({ + _i8.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -261,7 +271,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(_i5.dummyValue( + returnValue: _i8.Future.value(_i7.dummyValue( this, Invocation.method( #broadcastTransaction, @@ -272,10 +282,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future); + ) as _i8.Future); @override - _i6.Future> getBalance({ + _i8.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -289,11 +299,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getHistory({ + _i8.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -306,12 +316,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchHistory( + _i8.Future>>> getBatchHistory( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -319,12 +329,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future>> getUTXOs({ + _i8.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -337,12 +347,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i8.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i8.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i8.Future>>> getBatchUTXOs( {required List? args}) => (super.noSuchMethod( Invocation.method( @@ -350,12 +360,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i8.Future>>>.value( >>[]), - ) as _i6.Future>>>); + ) as _i8.Future>>>); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -371,11 +381,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getLelantusAnonymitySet({ + _i8.Future> getLelantusAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -391,11 +401,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusMintData({ + _i8.Future getLelantusMintData({ dynamic mints, String? requestID, }) => @@ -408,11 +418,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future> getLelantusUsedCoinSerials({ + _i8.Future> getLelantusUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -426,22 +436,22 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future getLelantusLatestCoinId({String? requestID}) => + _i8.Future getLelantusLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLelantusLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getSparkAnonymitySet({ + _i8.Future> getSparkAnonymitySet({ String? coinGroupId = r'1', String? startBlockHash = r'', String? requestID, @@ -457,58 +467,33 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future>> getSparkMintMetaData({ - String? requestID, - required List? sparkCoinHashes, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkMintMetaData, - [], - { - #requestID: requestID, - #sparkCoinHashes: sparkCoinHashes, - }, - ), - returnValue: _i6.Future>>.value( - >[]), - ) as _i6.Future>>); - - @override - _i6.Future getSparkLatestCoinId({String? requestID}) => + _i8.Future getSparkLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getSparkLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i6.Future> getMempoolTxids({String? requestID}) => + _i8.Future> getMempoolTxids({String? requestID}) => (super.noSuchMethod( Invocation.method( #getMempoolTxids, [], {#requestID: requestID}, ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>> getMempoolSparkData({ + _i8.Future> getMempoolSparkData({ String? requestID, required List? txids, }) => @@ -521,30 +506,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #txids: txids, }, ), - returnValue: _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>.value(<({ - List coins, - List lTags, - List serialContext, - String txid - })>[]), - ) as _i6.Future< - List< - ({ - List coins, - List lTags, - List serialContext, - String txid - })>>); - - @override - _i6.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ + returnValue: _i8.Future>.value( + <_i3.SparkMempoolData>[]), + ) as _i8.Future>); + + @override + _i8.Future>> getSparkUnhashedUsedCoinsTagsWithTxHashes({ String? requestID, required int? startNumber, }) => @@ -557,11 +524,62 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #startNumber: startNumber, }, ), - returnValue: _i6.Future>>.value(>[]), - ) as _i6.Future>>); + returnValue: _i8.Future>>.value(>[]), + ) as _i8.Future>>); + + @override + _i8.Future<_i3.SparkAnonymitySetMeta> getSparkAnonymitySetMeta({ + String? requestID, + required int? coinGroupId, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + returnValue: _i8.Future<_i3.SparkAnonymitySetMeta>.value( + _FakeSparkAnonymitySetMeta_2( + this, + Invocation.method( + #getSparkAnonymitySetMeta, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + }, + ), + )), + ) as _i8.Future<_i3.SparkAnonymitySetMeta>); + + @override + _i8.Future> getSparkAnonymitySetBySector({ + String? requestID, + required int? coinGroupId, + required String? latestBlock, + required int? startIndex, + required int? endIndex, + }) => + (super.noSuchMethod( + Invocation.method( + #getSparkAnonymitySetBySector, + [], + { + #requestID: requestID, + #coinGroupId: coinGroupId, + #latestBlock: latestBlock, + #startIndex: startIndex, + #endIndex: endIndex, + }, + ), + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future isMasterNodeCollateral({ + _i8.Future isMasterNodeCollateral({ String? requestID, required String? txid, required int? index, @@ -576,11 +594,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #index: index, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i8.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -588,11 +606,11 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future<_i3.Decimal> estimateFee({ + _i8.Future<_i4.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -605,7 +623,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { #blocks: blocks, }, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #estimateFee, @@ -616,16 +634,16 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { }, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); @override - _i6.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i8.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2( + returnValue: _i8.Future<_i4.Decimal>.value(_FakeDecimal_3( this, Invocation.method( #relayFee, @@ -633,29 +651,29 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient { {#requestID: requestID}, ), )), - ) as _i6.Future<_i3.Decimal>); + ) as _i8.Future<_i4.Decimal>); } /// A class which mocks [CachedElectrumXClient]. /// /// See the documentation for Mockito's code generation for more information. class MockCachedElectrumXClient extends _i1.Mock - implements _i7.CachedElectrumXClient { + implements _i9.CachedElectrumXClient { MockCachedElectrumXClient() { _i1.throwOnMissingStub(this); } @override - _i4.ElectrumXClient get electrumXClient => (super.noSuchMethod( + _i5.ElectrumXClient get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumXClient_3( + returnValue: _FakeElectrumXClient_4( this, Invocation.getter(#electrumXClient), ), - ) as _i4.ElectrumXClient); + ) as _i5.ElectrumXClient); @override - _i6.Future> getAnonymitySet({ + _i8.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', required _i2.CryptoCurrency? cryptoCurrency, @@ -671,8 +689,8 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( @@ -680,7 +698,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToHex, @@ -695,7 +713,7 @@ class MockCachedElectrumXClient extends _i1.Mock #base64ToReverseHex, [source], ), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.method( #base64ToReverseHex, @@ -705,7 +723,7 @@ class MockCachedElectrumXClient extends _i1.Mock ) as String); @override - _i6.Future> getTransaction({ + _i8.Future> getTransaction({ required String? txHash, required _i2.CryptoCurrency? cryptoCurrency, bool? verbose = true, @@ -721,11 +739,11 @@ class MockCachedElectrumXClient extends _i1.Mock }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i6.Future> getUsedCoinSerials({ + _i8.Future> getUsedCoinSerials({ required _i2.CryptoCurrency? cryptoCurrency, int? startNumber = 0, }) => @@ -738,11 +756,11 @@ class MockCachedElectrumXClient extends _i1.Mock #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i6.Future clearSharedTransactionCache( + _i8.Future clearSharedTransactionCache( {required _i2.CryptoCurrency? cryptoCurrency}) => (super.noSuchMethod( Invocation.method( @@ -750,16 +768,16 @@ class MockCachedElectrumXClient extends _i1.Mock [], {#cryptoCurrency: cryptoCurrency}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i8.TransactionNotificationTracker { + implements _i10.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -767,7 +785,7 @@ class MockTransactionNotificationTracker extends _i1.Mock @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), - returnValue: _i5.dummyValue( + returnValue: _i7.dummyValue( this, Invocation.getter(#walletId), ), @@ -795,14 +813,14 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( @@ -814,22 +832,22 @@ class MockTransactionNotificationTracker extends _i1.Mock ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i8.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i6.Future deleteTransaction(String? txid) => (super.noSuchMethod( + _i8.Future deleteTransaction(String? txid) => (super.noSuchMethod( Invocation.method( #deleteTransaction, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } diff --git a/test/services/node_service_test.dart b/test/services/node_service_test.dart index cb402deed..2fac42b96 100644 --- a/test/services/node_service_test.dart +++ b/test/services/node_service_test.dart @@ -270,7 +270,11 @@ void main() { final service = NodeService(secureStorageInterface: fakeStore); final currentLength = service.nodes.length; - final editedNode = nodeA.copyWith(name: "Some new kind of name"); + final editedNode = nodeA.copyWith( + name: "Some new kind of name", + loginName: null, + trusted: null, + ); await service.edit(editedNode, "123456", true); diff --git a/test/utilities/node_uri_util_test.dart b/test/utilities/node_uri_util_test.dart new file mode 100644 index 000000000..42d8474a0 --- /dev/null +++ b/test/utilities/node_uri_util_test.dart @@ -0,0 +1,104 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/utilities/node_uri_util.dart'; + +void main() { + test("Valid xmrrpc scheme node uri", () { + expect( + NodeQrUtil.decodeUri( + "xmrrpc://nodo:password@bob.onion:18083?label=Nodo Tor Node", + ), + isA(), + ); + }); + + test("Valid wowrpc scheme node uri", () { + expect( + NodeQrUtil.decodeUri( + "wowrpc://nodo:password@10.0.0.10:18083", + ), + isA(), + ); + }); + + test("Invalid authority node uri", () { + String? message; + try { + NodeQrUtil.decodeUri( + "nodo:password@bob.onion:18083?label=Nodo Tor Node", + ); + } catch (e) { + message = e.toString(); + } + expect(message, "Exception: Uri has no authority."); + }); + + test("Empty uri string", () { + String? message; + try { + NodeQrUtil.decodeUri(""); + } catch (e) { + message = e.toString(); + } + expect(message, "Exception: Uri has no authority."); + }); + + test("Invalid uri string", () { + String? message; + try { + NodeQrUtil.decodeUri("::invalid@@@.ok"); + } catch (e) { + message = e.toString(); + } + expect(message, "Exception: Invalid uri string."); + }); + + test("Unknown uri string", () { + String? message; + try { + NodeQrUtil.decodeUri("http://u:p@host.com:80/lol?hmm=42"); + } catch (e) { + message = e.toString(); + } + expect(message, "Exception: Unknown node uri scheme \"http\" found."); + }); + + test("decoding to model", () { + final data = NodeQrUtil.decodeUri( + "xmrrpc://nodo:password@bob.onion:18083?label=Nodo+Tor+Node", + ); + expect(data.scheme, "xmrrpc"); + expect(data.host, "bob.onion"); + expect(data.port, 18083); + expect(data.label, "Nodo Tor Node"); + expect((data as MoneroNodeQrData?)?.user, "nodo"); + expect((data as MoneroNodeQrData?)?.password, "password"); + }); + + test("encoding to string", () { + const validString = + "xmrrpc://nodo:password@bob.onion:18083?label=Nodo+Tor+Node"; + final data = NodeQrUtil.decodeUri( + validString, + ); + expect(data.encode(), validString); + }); + + test("normal to string", () { + const validString = + "xmrrpc://nodo:password@bob.onion:18083?label=Nodo+Tor+Node"; + final data = NodeQrUtil.decodeUri( + validString, + ); + expect( + data.toString(), + "MoneroNodeQrData {" + "scheme: xmrrpc, " + "host: bob.onion, " + "port: 18083, " + "user: nodo, " + "password: password, " + "label: Nodo Tor Node" + "}", + ); + }); +} diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart index c2bc00d2c..218b9c0c5 100644 --- a/test/widget_tests/managed_favorite_test.mocks.dart +++ b/test/widget_tests/managed_favorite_test.mocks.dart @@ -5,19 +5,20 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i10; import 'dart:typed_data' as _i15; -import 'dart:ui' as _i20; +import 'dart:ui' as _i21; +import 'package:logger/logger.dart' as _i19; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i17; import 'package:stackwallet/db/isar/main_db.dart' as _i3; import 'package:stackwallet/models/isar/stack_theme.dart' as _i14; -import 'package:stackwallet/models/node_model.dart' as _i22; +import 'package:stackwallet/models/node_model.dart' as _i23; import 'package:stackwallet/networking/http.dart' as _i6; -import 'package:stackwallet/services/locale_service.dart' as _i21; +import 'package:stackwallet/services/locale_service.dart' as _i22; import 'package:stackwallet/services/node_service.dart' as _i2; import 'package:stackwallet/services/wallets.dart' as _i9; import 'package:stackwallet/themes/theme_service.dart' as _i13; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i19; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i20; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i18; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i16; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -833,6 +834,45 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { returnValueForMissingStub: null, ); + @override + bool get advancedFiroFeatures => (super.noSuchMethod( + Invocation.getter(#advancedFiroFeatures), + returnValue: false, + ) as bool); + + @override + set advancedFiroFeatures(bool? advancedFiroFeatures) => super.noSuchMethod( + Invocation.setter( + #advancedFiroFeatures, + advancedFiroFeatures, + ), + returnValueForMissingStub: null, + ); + + @override + set logsPath(String? logsPath) => super.noSuchMethod( + Invocation.setter( + #logsPath, + logsPath, + ), + returnValueForMissingStub: null, + ); + + @override + _i19.Level get logLevel => (super.noSuchMethod( + Invocation.getter(#logLevel), + returnValue: _i19.Level.all, + ) as _i19.Level); + + @override + set logLevel(_i19.Level? logLevel) => super.noSuchMethod( + Invocation.setter( + #logLevel, + logLevel, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -889,18 +929,18 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ) as _i10.Future); @override - _i19.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( + _i20.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #amountUnit, [coin], ), - returnValue: _i19.AmountUnit.normal, - ) as _i19.AmountUnit); + returnValue: _i20.AmountUnit.normal, + ) as _i20.AmountUnit); @override void updateAmountUnit({ required _i4.CryptoCurrency? coin, - required _i19.AmountUnit? amountUnit, + required _i20.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method( @@ -973,7 +1013,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i21.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -982,7 +1022,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i21.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1012,7 +1052,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i21.LocaleService { +class MockLocaleService extends _i1.Mock implements _i22.LocaleService { MockLocaleService() { _i1.throwOnMissingStub(this); } @@ -1044,7 +1084,7 @@ class MockLocaleService extends _i1.Mock implements _i21.LocaleService { ) as _i10.Future); @override - void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i21.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1053,7 +1093,7 @@ class MockLocaleService extends _i1.Mock implements _i21.LocaleService { ); @override - void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i21.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1098,16 +1138,16 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i8.SecureStorageInterface); @override - List<_i22.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i23.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i22.NodeModel>[], - ) as List<_i22.NodeModel>); + returnValue: <_i23.NodeModel>[], + ) as List<_i23.NodeModel>); @override - List<_i22.NodeModel> get nodes => (super.noSuchMethod( + List<_i23.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i22.NodeModel>[], - ) as List<_i22.NodeModel>); + returnValue: <_i23.NodeModel>[], + ) as List<_i23.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( @@ -1128,7 +1168,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { @override _i10.Future setPrimaryNodeFor({ required _i4.CryptoCurrency? coin, - required _i22.NodeModel? node, + required _i23.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -1146,33 +1186,33 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i10.Future); @override - _i22.NodeModel? getPrimaryNodeFor({required _i4.CryptoCurrency? currency}) => + _i23.NodeModel? getPrimaryNodeFor({required _i4.CryptoCurrency? currency}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#currency: currency}, - )) as _i22.NodeModel?); + )) as _i23.NodeModel?); @override - List<_i22.NodeModel> getNodesFor(_i4.CryptoCurrency? coin) => + List<_i23.NodeModel> getNodesFor(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i22.NodeModel>[], - ) as List<_i22.NodeModel>); + returnValue: <_i23.NodeModel>[], + ) as List<_i23.NodeModel>); @override - _i22.NodeModel? getNodeById({required String? id}) => + _i23.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i22.NodeModel?); + )) as _i23.NodeModel?); @override - List<_i22.NodeModel> failoverNodesFor( + List<_i23.NodeModel> failoverNodesFor( {required _i4.CryptoCurrency? currency}) => (super.noSuchMethod( Invocation.method( @@ -1180,12 +1220,12 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { [], {#currency: currency}, ), - returnValue: <_i22.NodeModel>[], - ) as List<_i22.NodeModel>); + returnValue: <_i23.NodeModel>[], + ) as List<_i23.NodeModel>); @override _i10.Future add( - _i22.NodeModel? node, + _i23.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -1240,7 +1280,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { @override _i10.Future edit( - _i22.NodeModel? editedNode, + _i23.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -1268,7 +1308,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i10.Future); @override - void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i21.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1277,7 +1317,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ); @override - void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i21.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index 5e878015f..cf7098de6 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -5,18 +5,19 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i10; import 'dart:io' as _i8; -import 'dart:ui' as _i17; +import 'dart:ui' as _i18; +import 'package:logger/logger.dart' as _i16; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i14; import 'package:stackwallet/db/isar/main_db.dart' as _i3; -import 'package:stackwallet/models/node_model.dart' as _i18; +import 'package:stackwallet/models/node_model.dart' as _i19; import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart' - as _i20; + as _i21; import 'package:stackwallet/services/node_service.dart' as _i2; -import 'package:stackwallet/services/tor_service.dart' as _i19; +import 'package:stackwallet/services/tor_service.dart' as _i20; import 'package:stackwallet/services/wallets.dart' as _i9; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i16; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i17; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i15; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i13; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -28,7 +29,7 @@ import 'package:stackwallet/wallets/isar/models/wallet_info.dart' as _i11; import 'package:stackwallet/wallets/wallet/wallet.dart' as _i5; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart' as _i6; -import 'package:tor_ffi_plugin/tor_ffi_plugin.dart' as _i21; +import 'package:tor_ffi_plugin/tor_ffi_plugin.dart' as _i22; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -708,6 +709,45 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { returnValueForMissingStub: null, ); + @override + bool get advancedFiroFeatures => (super.noSuchMethod( + Invocation.getter(#advancedFiroFeatures), + returnValue: false, + ) as bool); + + @override + set advancedFiroFeatures(bool? advancedFiroFeatures) => super.noSuchMethod( + Invocation.setter( + #advancedFiroFeatures, + advancedFiroFeatures, + ), + returnValueForMissingStub: null, + ); + + @override + set logsPath(String? logsPath) => super.noSuchMethod( + Invocation.setter( + #logsPath, + logsPath, + ), + returnValueForMissingStub: null, + ); + + @override + _i16.Level get logLevel => (super.noSuchMethod( + Invocation.getter(#logLevel), + returnValue: _i16.Level.all, + ) as _i16.Level); + + @override + set logLevel(_i16.Level? logLevel) => super.noSuchMethod( + Invocation.setter( + #logLevel, + logLevel, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -764,18 +804,18 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ) as _i10.Future); @override - _i16.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( + _i17.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #amountUnit, [coin], ), - returnValue: _i16.AmountUnit.normal, - ) as _i16.AmountUnit); + returnValue: _i17.AmountUnit.normal, + ) as _i17.AmountUnit); @override void updateAmountUnit({ required _i4.CryptoCurrency? coin, - required _i16.AmountUnit? amountUnit, + required _i17.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method( @@ -848,7 +888,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -857,7 +897,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -902,16 +942,16 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i7.SecureStorageInterface); @override - List<_i18.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i19.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i18.NodeModel>[], - ) as List<_i18.NodeModel>); + returnValue: <_i19.NodeModel>[], + ) as List<_i19.NodeModel>); @override - List<_i18.NodeModel> get nodes => (super.noSuchMethod( + List<_i19.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i18.NodeModel>[], - ) as List<_i18.NodeModel>); + returnValue: <_i19.NodeModel>[], + ) as List<_i19.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( @@ -932,7 +972,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { @override _i10.Future setPrimaryNodeFor({ required _i4.CryptoCurrency? coin, - required _i18.NodeModel? node, + required _i19.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -950,33 +990,33 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i10.Future); @override - _i18.NodeModel? getPrimaryNodeFor({required _i4.CryptoCurrency? currency}) => + _i19.NodeModel? getPrimaryNodeFor({required _i4.CryptoCurrency? currency}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#currency: currency}, - )) as _i18.NodeModel?); + )) as _i19.NodeModel?); @override - List<_i18.NodeModel> getNodesFor(_i4.CryptoCurrency? coin) => + List<_i19.NodeModel> getNodesFor(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i18.NodeModel>[], - ) as List<_i18.NodeModel>); + returnValue: <_i19.NodeModel>[], + ) as List<_i19.NodeModel>); @override - _i18.NodeModel? getNodeById({required String? id}) => + _i19.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i18.NodeModel?); + )) as _i19.NodeModel?); @override - List<_i18.NodeModel> failoverNodesFor( + List<_i19.NodeModel> failoverNodesFor( {required _i4.CryptoCurrency? currency}) => (super.noSuchMethod( Invocation.method( @@ -984,12 +1024,12 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { [], {#currency: currency}, ), - returnValue: <_i18.NodeModel>[], - ) as List<_i18.NodeModel>); + returnValue: <_i19.NodeModel>[], + ) as List<_i19.NodeModel>); @override _i10.Future add( - _i18.NodeModel? node, + _i19.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -1044,7 +1084,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { @override _i10.Future edit( - _i18.NodeModel? editedNode, + _i19.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -1072,7 +1112,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i10.Future); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1081,7 +1121,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1111,16 +1151,16 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { /// A class which mocks [TorService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTorService extends _i1.Mock implements _i19.TorService { +class MockTorService extends _i1.Mock implements _i20.TorService { MockTorService() { _i1.throwOnMissingStub(this); } @override - _i20.TorConnectionStatus get status => (super.noSuchMethod( + _i21.TorConnectionStatus get status => (super.noSuchMethod( Invocation.getter(#status), - returnValue: _i20.TorConnectionStatus.disconnected, - ) as _i20.TorConnectionStatus); + returnValue: _i21.TorConnectionStatus.disconnected, + ) as _i21.TorConnectionStatus); @override ({_i8.InternetAddress host, int port}) getProxyInfo() => (super.noSuchMethod( @@ -1143,7 +1183,7 @@ class MockTorService extends _i1.Mock implements _i19.TorService { @override void init({ required String? torDataDirPath, - _i21.Tor? mockableOverride, + _i22.Tor? mockableOverride, }) => super.noSuchMethod( Invocation.method( diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index a5816bdcb..a146a9abf 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -4,27 +4,28 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i11; -import 'dart:typed_data' as _i25; +import 'dart:typed_data' as _i26; import 'dart:ui' as _i17; -import 'package:decimal/decimal.dart' as _i22; +import 'package:decimal/decimal.dart' as _i23; import 'package:isar/isar.dart' as _i9; +import 'package:logger/logger.dart' as _i20; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i16; import 'package:stackwallet/db/isar/main_db.dart' as _i3; -import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i27; +import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i28; import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart' - as _i29; -import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i26; -import 'package:stackwallet/models/isar/models/isar_models.dart' as _i28; -import 'package:stackwallet/models/isar/stack_theme.dart' as _i24; + as _i30; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i27; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i29; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i25; import 'package:stackwallet/networking/http.dart' as _i8; import 'package:stackwallet/services/locale_service.dart' as _i15; import 'package:stackwallet/services/node_service.dart' as _i2; -import 'package:stackwallet/services/price_service.dart' as _i21; +import 'package:stackwallet/services/price_service.dart' as _i22; import 'package:stackwallet/services/wallets.dart' as _i10; -import 'package:stackwallet/themes/theme_service.dart' as _i23; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i20; +import 'package:stackwallet/themes/theme_service.dart' as _i24; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i21; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i19; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i18; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -817,6 +818,45 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { returnValueForMissingStub: null, ); + @override + bool get advancedFiroFeatures => (super.noSuchMethod( + Invocation.getter(#advancedFiroFeatures), + returnValue: false, + ) as bool); + + @override + set advancedFiroFeatures(bool? advancedFiroFeatures) => super.noSuchMethod( + Invocation.setter( + #advancedFiroFeatures, + advancedFiroFeatures, + ), + returnValueForMissingStub: null, + ); + + @override + set logsPath(String? logsPath) => super.noSuchMethod( + Invocation.setter( + #logsPath, + logsPath, + ), + returnValueForMissingStub: null, + ); + + @override + _i20.Level get logLevel => (super.noSuchMethod( + Invocation.getter(#logLevel), + returnValue: _i20.Level.all, + ) as _i20.Level); + + @override + set logLevel(_i20.Level? logLevel) => super.noSuchMethod( + Invocation.setter( + #logLevel, + logLevel, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -873,18 +913,18 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ) as _i11.Future); @override - _i20.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( + _i21.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #amountUnit, [coin], ), - returnValue: _i20.AmountUnit.normal, - ) as _i20.AmountUnit); + returnValue: _i21.AmountUnit.normal, + ) as _i21.AmountUnit); @override void updateAmountUnit({ required _i4.CryptoCurrency? coin, - required _i20.AmountUnit? amountUnit, + required _i21.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method( @@ -996,7 +1036,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { /// A class which mocks [PriceService]. /// /// See the documentation for Mockito's code generation for more information. -class MockPriceService extends _i1.Mock implements _i21.PriceService { +class MockPriceService extends _i1.Mock implements _i22.PriceService { MockPriceService() { _i1.throwOnMissingStub(this); } @@ -1042,36 +1082,36 @@ class MockPriceService extends _i1.Mock implements _i21.PriceService { ) as bool); @override - _i7.Tuple2<_i22.Decimal, double> getPrice(_i4.CryptoCurrency? coin) => + _i7.Tuple2<_i23.Decimal, double> getPrice(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #getPrice, [coin], ), - returnValue: _FakeTuple2_5<_i22.Decimal, double>( + returnValue: _FakeTuple2_5<_i23.Decimal, double>( this, Invocation.method( #getPrice, [coin], ), ), - ) as _i7.Tuple2<_i22.Decimal, double>); + ) as _i7.Tuple2<_i23.Decimal, double>); @override - _i7.Tuple2<_i22.Decimal, double> getTokenPrice(String? contractAddress) => + _i7.Tuple2<_i23.Decimal, double> getTokenPrice(String? contractAddress) => (super.noSuchMethod( Invocation.method( #getTokenPrice, [contractAddress], ), - returnValue: _FakeTuple2_5<_i22.Decimal, double>( + returnValue: _FakeTuple2_5<_i23.Decimal, double>( this, Invocation.method( #getTokenPrice, [contractAddress], ), ), - ) as _i7.Tuple2<_i22.Decimal, double>); + ) as _i7.Tuple2<_i23.Decimal, double>); @override _i11.Future updatePrice() => (super.noSuchMethod( @@ -1141,7 +1181,7 @@ class MockPriceService extends _i1.Mock implements _i21.PriceService { /// A class which mocks [ThemeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockThemeService extends _i1.Mock implements _i23.ThemeService { +class MockThemeService extends _i1.Mock implements _i24.ThemeService { MockThemeService() { _i1.throwOnMissingStub(this); } @@ -1174,10 +1214,10 @@ class MockThemeService extends _i1.Mock implements _i23.ThemeService { ) as _i3.MainDB); @override - List<_i24.StackTheme> get installedThemes => (super.noSuchMethod( + List<_i25.StackTheme> get installedThemes => (super.noSuchMethod( Invocation.getter(#installedThemes), - returnValue: <_i24.StackTheme>[], - ) as List<_i24.StackTheme>); + returnValue: <_i25.StackTheme>[], + ) as List<_i25.StackTheme>); @override void init(_i3.MainDB? db) => super.noSuchMethod( @@ -1189,7 +1229,7 @@ class MockThemeService extends _i1.Mock implements _i23.ThemeService { ); @override - _i11.Future install({required _i25.Uint8List? themeArchiveData}) => + _i11.Future install({required _i26.Uint8List? themeArchiveData}) => (super.noSuchMethod( Invocation.method( #install, @@ -1233,35 +1273,35 @@ class MockThemeService extends _i1.Mock implements _i23.ThemeService { ) as _i11.Future); @override - _i11.Future> fetchThemes() => + _i11.Future> fetchThemes() => (super.noSuchMethod( Invocation.method( #fetchThemes, [], ), - returnValue: _i11.Future>.value( - <_i23.StackThemeMetaData>[]), - ) as _i11.Future>); + returnValue: _i11.Future>.value( + <_i24.StackThemeMetaData>[]), + ) as _i11.Future>); @override - _i11.Future<_i25.Uint8List> fetchTheme( - {required _i23.StackThemeMetaData? themeMetaData}) => + _i11.Future<_i26.Uint8List> fetchTheme( + {required _i24.StackThemeMetaData? themeMetaData}) => (super.noSuchMethod( Invocation.method( #fetchTheme, [], {#themeMetaData: themeMetaData}, ), - returnValue: _i11.Future<_i25.Uint8List>.value(_i25.Uint8List(0)), - ) as _i11.Future<_i25.Uint8List>); + returnValue: _i11.Future<_i26.Uint8List>.value(_i26.Uint8List(0)), + ) as _i11.Future<_i26.Uint8List>); @override - _i24.StackTheme? getTheme({required String? themeId}) => + _i25.StackTheme? getTheme({required String? themeId}) => (super.noSuchMethod(Invocation.method( #getTheme, [], {#themeId: themeId}, - )) as _i24.StackTheme?); + )) as _i25.StackTheme?); } /// A class which mocks [MainDB]. @@ -1314,13 +1354,13 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - List<_i26.ContactEntry> getContactEntries() => (super.noSuchMethod( + List<_i27.ContactEntry> getContactEntries() => (super.noSuchMethod( Invocation.method( #getContactEntries, [], ), - returnValue: <_i26.ContactEntry>[], - ) as List<_i26.ContactEntry>); + returnValue: <_i27.ContactEntry>[], + ) as List<_i27.ContactEntry>); @override _i11.Future deleteContactEntry({required String? id}) => @@ -1345,16 +1385,16 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i26.ContactEntry? getContactEntry({required String? id}) => + _i27.ContactEntry? getContactEntry({required String? id}) => (super.noSuchMethod(Invocation.method( #getContactEntry, [], {#id: id}, - )) as _i26.ContactEntry?); + )) as _i27.ContactEntry?); @override _i11.Future putContactEntry( - {required _i26.ContactEntry? contactEntry}) => + {required _i27.ContactEntry? contactEntry}) => (super.noSuchMethod( Invocation.method( #putContactEntry, @@ -1365,17 +1405,17 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i27.TransactionBlockExplorer? getTransactionBlockExplorer( + _i28.TransactionBlockExplorer? getTransactionBlockExplorer( {required _i4.CryptoCurrency? cryptoCurrency}) => (super.noSuchMethod(Invocation.method( #getTransactionBlockExplorer, [], {#cryptoCurrency: cryptoCurrency}, - )) as _i27.TransactionBlockExplorer?); + )) as _i28.TransactionBlockExplorer?); @override _i11.Future putTransactionBlockExplorer( - _i27.TransactionBlockExplorer? explorer) => + _i28.TransactionBlockExplorer? explorer) => (super.noSuchMethod( Invocation.method( #putTransactionBlockExplorer, @@ -1385,13 +1425,13 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i9.QueryBuilder<_i28.Address, _i28.Address, _i9.QAfterWhereClause> + _i9.QueryBuilder<_i29.Address, _i29.Address, _i9.QAfterWhereClause> getAddresses(String? walletId) => (super.noSuchMethod( Invocation.method( #getAddresses, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i28.Address, _i28.Address, + returnValue: _FakeQueryBuilder_8<_i29.Address, _i29.Address, _i9.QAfterWhereClause>( this, Invocation.method( @@ -1400,10 +1440,10 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ), ), ) as _i9 - .QueryBuilder<_i28.Address, _i28.Address, _i9.QAfterWhereClause>); + .QueryBuilder<_i29.Address, _i29.Address, _i9.QAfterWhereClause>); @override - _i11.Future putAddress(_i28.Address? address) => (super.noSuchMethod( + _i11.Future putAddress(_i29.Address? address) => (super.noSuchMethod( Invocation.method( #putAddress, [address], @@ -1412,7 +1452,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i11.Future> putAddresses(List<_i28.Address>? addresses) => + _i11.Future> putAddresses(List<_i29.Address>? addresses) => (super.noSuchMethod( Invocation.method( #putAddresses, @@ -1422,7 +1462,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future>); @override - _i11.Future> updateOrPutAddresses(List<_i28.Address>? addresses) => + _i11.Future> updateOrPutAddresses(List<_i29.Address>? addresses) => (super.noSuchMethod( Invocation.method( #updateOrPutAddresses, @@ -1432,7 +1472,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future>); @override - _i11.Future<_i28.Address?> getAddress( + _i11.Future<_i29.Address?> getAddress( String? walletId, String? address, ) => @@ -1444,13 +1484,13 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { address, ], ), - returnValue: _i11.Future<_i28.Address?>.value(), - ) as _i11.Future<_i28.Address?>); + returnValue: _i11.Future<_i29.Address?>.value(), + ) as _i11.Future<_i29.Address?>); @override _i11.Future updateAddress( - _i28.Address? oldAddress, - _i28.Address? newAddress, + _i29.Address? oldAddress, + _i29.Address? newAddress, ) => (super.noSuchMethod( Invocation.method( @@ -1464,13 +1504,13 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i9.QueryBuilder<_i28.Transaction, _i28.Transaction, _i9.QAfterWhereClause> + _i9.QueryBuilder<_i29.Transaction, _i29.Transaction, _i9.QAfterWhereClause> getTransactions(String? walletId) => (super.noSuchMethod( Invocation.method( #getTransactions, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i28.Transaction, _i28.Transaction, + returnValue: _FakeQueryBuilder_8<_i29.Transaction, _i29.Transaction, _i9.QAfterWhereClause>( this, Invocation.method( @@ -1478,11 +1518,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { [walletId], ), ), - ) as _i9.QueryBuilder<_i28.Transaction, _i28.Transaction, + ) as _i9.QueryBuilder<_i29.Transaction, _i29.Transaction, _i9.QAfterWhereClause>); @override - _i11.Future putTransaction(_i28.Transaction? transaction) => + _i11.Future putTransaction(_i29.Transaction? transaction) => (super.noSuchMethod( Invocation.method( #putTransaction, @@ -1493,7 +1533,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { @override _i11.Future> putTransactions( - List<_i28.Transaction>? transactions) => + List<_i29.Transaction>? transactions) => (super.noSuchMethod( Invocation.method( #putTransactions, @@ -1503,7 +1543,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future>); @override - _i11.Future<_i28.Transaction?> getTransaction( + _i11.Future<_i29.Transaction?> getTransaction( String? walletId, String? txid, ) => @@ -1515,11 +1555,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { txid, ], ), - returnValue: _i11.Future<_i28.Transaction?>.value(), - ) as _i11.Future<_i28.Transaction?>); + returnValue: _i11.Future<_i29.Transaction?>.value(), + ) as _i11.Future<_i29.Transaction?>); @override - _i11.Stream<_i28.Transaction?> watchTransaction({ + _i11.Stream<_i29.Transaction?> watchTransaction({ required int? id, bool? fireImmediately = false, }) => @@ -1532,11 +1572,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i28.Transaction?>.empty(), - ) as _i11.Stream<_i28.Transaction?>); + returnValue: _i11.Stream<_i29.Transaction?>.empty(), + ) as _i11.Stream<_i29.Transaction?>); @override - _i9.QueryBuilder<_i28.UTXO, _i28.UTXO, _i9.QAfterWhereClause> getUTXOs( + _i9.QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterWhereClause> getUTXOs( String? walletId) => (super.noSuchMethod( Invocation.method( @@ -1544,17 +1584,17 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { [walletId], ), returnValue: - _FakeQueryBuilder_8<_i28.UTXO, _i28.UTXO, _i9.QAfterWhereClause>( + _FakeQueryBuilder_8<_i29.UTXO, _i29.UTXO, _i9.QAfterWhereClause>( this, Invocation.method( #getUTXOs, [walletId], ), ), - ) as _i9.QueryBuilder<_i28.UTXO, _i28.UTXO, _i9.QAfterWhereClause>); + ) as _i9.QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterWhereClause>); @override - _i9.QueryBuilder<_i28.UTXO, _i28.UTXO, _i9.QAfterFilterCondition> + _i9.QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterFilterCondition> getUTXOsByAddress( String? walletId, String? address, @@ -1567,7 +1607,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { address, ], ), - returnValue: _FakeQueryBuilder_8<_i28.UTXO, _i28.UTXO, + returnValue: _FakeQueryBuilder_8<_i29.UTXO, _i29.UTXO, _i9.QAfterFilterCondition>( this, Invocation.method( @@ -1579,10 +1619,10 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ), ), ) as _i9 - .QueryBuilder<_i28.UTXO, _i28.UTXO, _i9.QAfterFilterCondition>); + .QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterFilterCondition>); @override - _i11.Future putUTXO(_i28.UTXO? utxo) => (super.noSuchMethod( + _i11.Future putUTXO(_i29.UTXO? utxo) => (super.noSuchMethod( Invocation.method( #putUTXO, [utxo], @@ -1592,7 +1632,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i11.Future putUTXOs(List<_i28.UTXO>? utxos) => (super.noSuchMethod( + _i11.Future putUTXOs(List<_i29.UTXO>? utxos) => (super.noSuchMethod( Invocation.method( #putUTXOs, [utxos], @@ -1604,7 +1644,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { @override _i11.Future updateUTXOs( String? walletId, - List<_i28.UTXO>? utxos, + List<_i29.UTXO>? utxos, ) => (super.noSuchMethod( Invocation.method( @@ -1618,7 +1658,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i11.Stream<_i28.UTXO?> watchUTXO({ + _i11.Stream<_i29.UTXO?> watchUTXO({ required int? id, bool? fireImmediately = false, }) => @@ -1631,11 +1671,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i28.UTXO?>.empty(), - ) as _i11.Stream<_i28.UTXO?>); + returnValue: _i11.Stream<_i29.UTXO?>.empty(), + ) as _i11.Stream<_i29.UTXO?>); @override - _i9.QueryBuilder<_i28.TransactionNote, _i28.TransactionNote, + _i9.QueryBuilder<_i29.TransactionNote, _i29.TransactionNote, _i9.QAfterWhereClause> getTransactionNotes( String? walletId) => (super.noSuchMethod( @@ -1643,19 +1683,19 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #getTransactionNotes, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i28.TransactionNote, - _i28.TransactionNote, _i9.QAfterWhereClause>( + returnValue: _FakeQueryBuilder_8<_i29.TransactionNote, + _i29.TransactionNote, _i9.QAfterWhereClause>( this, Invocation.method( #getTransactionNotes, [walletId], ), ), - ) as _i9.QueryBuilder<_i28.TransactionNote, _i28.TransactionNote, + ) as _i9.QueryBuilder<_i29.TransactionNote, _i29.TransactionNote, _i9.QAfterWhereClause>); @override - _i11.Future putTransactionNote(_i28.TransactionNote? transactionNote) => + _i11.Future putTransactionNote(_i29.TransactionNote? transactionNote) => (super.noSuchMethod( Invocation.method( #putTransactionNote, @@ -1667,7 +1707,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { @override _i11.Future putTransactionNotes( - List<_i28.TransactionNote>? transactionNotes) => + List<_i29.TransactionNote>? transactionNotes) => (super.noSuchMethod( Invocation.method( #putTransactionNotes, @@ -1678,7 +1718,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i11.Future<_i28.TransactionNote?> getTransactionNote( + _i11.Future<_i29.TransactionNote?> getTransactionNote( String? walletId, String? txid, ) => @@ -1690,11 +1730,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { txid, ], ), - returnValue: _i11.Future<_i28.TransactionNote?>.value(), - ) as _i11.Future<_i28.TransactionNote?>); + returnValue: _i11.Future<_i29.TransactionNote?>.value(), + ) as _i11.Future<_i29.TransactionNote?>); @override - _i11.Stream<_i28.TransactionNote?> watchTransactionNote({ + _i11.Stream<_i29.TransactionNote?> watchTransactionNote({ required int? id, bool? fireImmediately = false, }) => @@ -1707,29 +1747,29 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i28.TransactionNote?>.empty(), - ) as _i11.Stream<_i28.TransactionNote?>); + returnValue: _i11.Stream<_i29.TransactionNote?>.empty(), + ) as _i11.Stream<_i29.TransactionNote?>); @override - _i9.QueryBuilder<_i28.AddressLabel, _i28.AddressLabel, _i9.QAfterWhereClause> + _i9.QueryBuilder<_i29.AddressLabel, _i29.AddressLabel, _i9.QAfterWhereClause> getAddressLabels(String? walletId) => (super.noSuchMethod( Invocation.method( #getAddressLabels, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i28.AddressLabel, - _i28.AddressLabel, _i9.QAfterWhereClause>( + returnValue: _FakeQueryBuilder_8<_i29.AddressLabel, + _i29.AddressLabel, _i9.QAfterWhereClause>( this, Invocation.method( #getAddressLabels, [walletId], ), ), - ) as _i9.QueryBuilder<_i28.AddressLabel, _i28.AddressLabel, + ) as _i9.QueryBuilder<_i29.AddressLabel, _i29.AddressLabel, _i9.QAfterWhereClause>); @override - _i11.Future putAddressLabel(_i28.AddressLabel? addressLabel) => + _i11.Future putAddressLabel(_i29.AddressLabel? addressLabel) => (super.noSuchMethod( Invocation.method( #putAddressLabel, @@ -1739,7 +1779,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - int putAddressLabelSync(_i28.AddressLabel? addressLabel) => + int putAddressLabelSync(_i29.AddressLabel? addressLabel) => (super.noSuchMethod( Invocation.method( #putAddressLabelSync, @@ -1749,7 +1789,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as int); @override - _i11.Future putAddressLabels(List<_i28.AddressLabel>? addressLabels) => + _i11.Future putAddressLabels(List<_i29.AddressLabel>? addressLabels) => (super.noSuchMethod( Invocation.method( #putAddressLabels, @@ -1760,7 +1800,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i11.Future<_i28.AddressLabel?> getAddressLabel( + _i11.Future<_i29.AddressLabel?> getAddressLabel( String? walletId, String? addressString, ) => @@ -1772,11 +1812,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { addressString, ], ), - returnValue: _i11.Future<_i28.AddressLabel?>.value(), - ) as _i11.Future<_i28.AddressLabel?>); + returnValue: _i11.Future<_i29.AddressLabel?>.value(), + ) as _i11.Future<_i29.AddressLabel?>); @override - _i28.AddressLabel? getAddressLabelSync( + _i29.AddressLabel? getAddressLabelSync( String? walletId, String? addressString, ) => @@ -1786,10 +1826,10 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { walletId, addressString, ], - )) as _i28.AddressLabel?); + )) as _i29.AddressLabel?); @override - _i11.Stream<_i28.AddressLabel?> watchAddressLabel({ + _i11.Stream<_i29.AddressLabel?> watchAddressLabel({ required int? id, bool? fireImmediately = false, }) => @@ -1802,11 +1842,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i28.AddressLabel?>.empty(), - ) as _i11.Stream<_i28.AddressLabel?>); + returnValue: _i11.Stream<_i29.AddressLabel?>.empty(), + ) as _i11.Stream<_i29.AddressLabel?>); @override - _i11.Future updateAddressLabel(_i28.AddressLabel? addressLabel) => + _i11.Future updateAddressLabel(_i29.AddressLabel? addressLabel) => (super.noSuchMethod( Invocation.method( #updateAddressLabel, @@ -1850,7 +1890,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { @override _i11.Future addNewTransactionData( - List<_i7.Tuple2<_i28.Transaction, _i28.Address?>>? transactionsData, + List<_i7.Tuple2<_i29.Transaction, _i29.Address?>>? transactionsData, String? walletId, ) => (super.noSuchMethod( @@ -1867,7 +1907,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { @override _i11.Future> updateOrPutTransactionV2s( - List<_i29.TransactionV2>? transactions) => + List<_i30.TransactionV2>? transactions) => (super.noSuchMethod( Invocation.method( #updateOrPutTransactionV2s, @@ -1877,13 +1917,13 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future>); @override - _i9.QueryBuilder<_i28.EthContract, _i28.EthContract, _i9.QWhere> + _i9.QueryBuilder<_i29.EthContract, _i29.EthContract, _i9.QWhere> getEthContracts() => (super.noSuchMethod( Invocation.method( #getEthContracts, [], ), - returnValue: _FakeQueryBuilder_8<_i28.EthContract, _i28.EthContract, + returnValue: _FakeQueryBuilder_8<_i29.EthContract, _i29.EthContract, _i9.QWhere>( this, Invocation.method( @@ -1892,27 +1932,27 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ), ), ) as _i9 - .QueryBuilder<_i28.EthContract, _i28.EthContract, _i9.QWhere>); + .QueryBuilder<_i29.EthContract, _i29.EthContract, _i9.QWhere>); @override - _i11.Future<_i28.EthContract?> getEthContract(String? contractAddress) => + _i11.Future<_i29.EthContract?> getEthContract(String? contractAddress) => (super.noSuchMethod( Invocation.method( #getEthContract, [contractAddress], ), - returnValue: _i11.Future<_i28.EthContract?>.value(), - ) as _i11.Future<_i28.EthContract?>); + returnValue: _i11.Future<_i29.EthContract?>.value(), + ) as _i11.Future<_i29.EthContract?>); @override - _i28.EthContract? getEthContractSync(String? contractAddress) => + _i29.EthContract? getEthContractSync(String? contractAddress) => (super.noSuchMethod(Invocation.method( #getEthContractSync, [contractAddress], - )) as _i28.EthContract?); + )) as _i29.EthContract?); @override - _i11.Future putEthContract(_i28.EthContract? contract) => + _i11.Future putEthContract(_i29.EthContract? contract) => (super.noSuchMethod( Invocation.method( #putEthContract, @@ -1922,7 +1962,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as _i11.Future); @override - _i11.Future putEthContracts(List<_i28.EthContract>? contracts) => + _i11.Future putEthContracts(List<_i29.EthContract>? contracts) => (super.noSuchMethod( Invocation.method( #putEthContracts, @@ -1947,7 +1987,7 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { /// A class which mocks [IThemeAssets]. /// /// See the documentation for Mockito's code generation for more information. -class MockIThemeAssets extends _i1.Mock implements _i24.IThemeAssets { +class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { MockIThemeAssets() { _i1.throwOnMissingStub(this); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 2f370e685..55c2cc622 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -24,6 +24,7 @@ list(APPEND FLUTTER_FFI_PLUGIN_LIST flutter_libsparkmobile frostdart tor_ffi_plugin + xelis_flutter ) set(PLUGIN_BUNDLED_LIBRARIES)