From 253e0b0cf615a096c46f00b0f8dea392621666ae Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:57:01 +0600 Subject: [PATCH] feat(gui, tauri): Save settings in Tauri storage (#102) - Implemented dual persistence strategy: - **User Settings**: Persisted across app restarts using `tauri-plugin-store`. - **Transient State**: Persisted across page reloads using `sessionStorage`. - Added `settingsSlice` reducer for managing persistent user settings. - Updated Redux store configuration to handle multiple persistence layers. - Added a new Settings page in the GUI where users can specify custom Electrum RPC URLs for Bitcoin and Monero node URLs. - Users can input their preferred Electrum server (`ssl://host:port`) and Monero daemon (`http://host:port`). - Input fields include validation to ensure correct URL formats. - Settings persist across application restarts using Tauri's storage plugin. - A reset option is available to revert to default settings. - Improved the Daemon Controller in the Help page: - Renamed `RpcControlBox` to `DaemonControlBox` for clarity. - Users can now start the daemon manually if it isn't running or has failed. - Added a "Restart GUI" button to apply new settings immediately. - Displayed the daemon's status within the controller. - Upgraded Tauri and related plugins to stable version `2.0.0`: - Updated `tauri`, `tauri-build`, and `tauri-utils` to `2.0.0`. - Ensured compatibility with the latest stable release. - Updated Tauri plugins to version `2.0.0`: - `tauri-plugin-clipboard-manager` - `tauri-plugin-shell` - Added new plugins: - `tauri-plugin-store` for settings persistence. - `tauri-plugin-process` to enable application relaunch. - Deferred Context initialization until explicitly triggered from the frontend. - Moved Context setup from the `setup` function to a new `initialize_context` Tauri command. - Allows the application to start without immediately initializing the backend context. - Context initialization now considers user-provided settings for Electrum and Monero nodes. - Introduced a `ValidatedTextField` component for form inputs with validation logic. - Provides immediate feedback on input validity. - Used in the Settings page for Electrum and Monero node URLs. - If the user provides an override Monero remote daemon, we check if it reachable and on the correct network before starting the `monero-wallet-rpc` - Changed `bitcoin_confirmation_target` type from `usize` to `u16`. --- .github/ISSUE_TEMPLATE/bug_report.md | 7 +- .github/workflows/build-release-binaries.yml | 2 +- Cargo.lock | 575 +++++++++++++----- docs/cli/README.md | 2 +- node_modules/.yarn-integrity | 10 - src-gui/README.md | 2 +- src-gui/index.html | 7 +- src-gui/package.json | 10 +- .../components/alert/DaemonStatusAlert.tsx | 49 +- .../alert/FundsLeftInWalletAlert.tsx | 2 +- .../components/alert/UnfinishedSwapsAlert.tsx | 2 +- .../modal/listSellers/ListSellersDialog.tsx | 4 +- .../components/modal/swap/SwapDialog.tsx | 2 +- .../modal/swap/SwapStateStepper.tsx | 24 +- .../modal/swap/pages/SwapStatePage.tsx | 6 +- .../components/other/ValidatedTextField.tsx | 55 ++ ...RpcControlBox.tsx => DaemonControlBox.tsx} | 44 +- .../components/pages/help/HelpPage.tsx | 9 +- .../components/pages/help/SettingsBox.tsx | 153 +++++ .../history/table/HistoryRowExpanded.tsx | 22 +- .../components/pages/swap/SwapWidget.tsx | 8 +- .../pages/wallet/WithdrawWidget.tsx | 4 +- src-gui/src/renderer/rpc.ts | 18 +- src-gui/src/renderer/store/storeRenderer.ts | 56 +- src-gui/src/store/combinedReducer.ts | 2 + src-gui/src/store/config.ts | 10 +- src-gui/src/store/features/settingsSlice.ts | 43 ++ src-gui/src/store/hooks.ts | 7 +- src-gui/yarn.lock | 160 ++--- src-tauri/Cargo.toml | 8 +- src-tauri/capabilities/default.json | 4 +- src-tauri/src/lib.rs | 94 +-- swap/sqlx-data.json | 35 +- swap/src/asb/config.rs | 4 +- swap/src/bitcoin/wallet.rs | 15 +- swap/src/cli/api.rs | 8 +- swap/src/cli/api/tauri_bindings.rs | 19 +- swap/src/cli/command.rs | 8 +- swap/src/common/tracing_util.rs | 10 +- swap/src/monero/wallet_rpc.rs | 75 ++- 40 files changed, 1124 insertions(+), 451 deletions(-) delete mode 100644 node_modules/.yarn-integrity create mode 100644 src-gui/src/renderer/components/other/ValidatedTextField.tsx rename src-gui/src/renderer/components/pages/help/{RpcControlBox.tsx => DaemonControlBox.tsx} (54%) create mode 100644 src-gui/src/renderer/components/pages/help/SettingsBox.tsx create mode 100644 src-gui/src/store/features/settingsSlice.ts diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a69c2914..f8e0a26f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Describe the bug** diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 36166cc0..44c1d908 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -176,4 +176,4 @@ jobs: file: ./Dockerfile push: true tags: ${{ env.DOCKER_IMAGE_NAME }}:${{ github.event.release.tag_name }} - if: steps.docker_tags.outputs.preview == 'true' \ No newline at end of file + if: steps.docker_tags.outputs.preview == 'true' diff --git a/Cargo.lock b/Cargo.lock index 99136123..b3223d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,28 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite 0.2.13", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.46", +] + [[package]] name = "async-trait" version = "0.1.81" @@ -279,7 +301,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.18", + "hermit-abi", "libc", "winapi", ] @@ -290,6 +312,51 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", + "itoa 1.0.11", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite 0.2.13", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.11", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backoff" version = "0.4.0" @@ -753,9 +820,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" dependencies = [ "serde", ] @@ -1009,11 +1076,10 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys 0.48.0", ] @@ -1493,6 +1559,48 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "devtools-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78cdd51f6f62ad4eb9b6581d7e238e1779db3144ddbd711388f552e6ed3194b" +dependencies = [ + "async-stream", + "bytes", + "devtools-wire-format", + "futures", + "http 0.2.11", + "hyper 0.14.28", + "log", + "prost-types 0.12.6", + "ringbuf", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tonic-health", + "tonic-web", + "tower", + "tower-http 0.4.4", + "tower-layer", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "devtools-wire-format" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1c0de542960449c9566001c1879d10ede95f3f2e0013fdae0cc3b153bfabb0d" +dependencies = [ + "bitflags 2.6.0", + "prost 0.12.6", + "prost-types 0.12.6", + "tonic", + "tracing-core", +] + [[package]] name = "dialoguer" version = "0.11.0" @@ -2178,19 +2286,6 @@ dependencies = [ "x11", ] -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.48.0", -] - [[package]] name = "generic-array" version = "0.14.4" @@ -2571,12 +2666,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" - [[package]] name = "hex" version = "0.4.3" @@ -2653,7 +2742,7 @@ checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa 1.0.11", ] [[package]] @@ -2664,7 +2753,7 @@ checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa 1.0.11", ] [[package]] @@ -2734,8 +2823,9 @@ dependencies = [ "http-body 0.4.6", "httparse", "httpdate", - "itoa 1.0.1", + "itoa 1.0.11", "pin-project-lite 0.2.13", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -2756,7 +2846,7 @@ dependencies = [ "http-body 1.0.0", "httparse", "httpdate", - "itoa 1.0.1", + "itoa 1.0.11", "pin-project-lite 0.2.13", "smallvec", "tokio", @@ -2781,6 +2871,18 @@ dependencies = [ "webpki-roots 0.26.1", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.28", + "pin-project-lite 0.2.13", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-util" version = "0.1.3" @@ -2978,17 +3080,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi 0.3.8", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is-wsl" version = "0.4.0" @@ -3025,9 +3116,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "javascriptcore-rs" @@ -3433,7 +3524,7 @@ dependencies = [ "multistream-select", "parking_lot 0.11.2", "pin-project 1.1.5", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.3", "ring 0.16.20", @@ -3480,7 +3571,7 @@ dependencies = [ "log", "open-metrics-client", "pin-project 1.1.5", - "prost", + "prost 0.9.0", "prost-build", "rand 0.7.3", "regex", @@ -3502,7 +3593,7 @@ dependencies = [ "libp2p-swarm", "log", "lru", - "prost", + "prost 0.9.0", "prost-build", "smallvec", ] @@ -3524,7 +3615,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.7.3", "serde", @@ -3579,7 +3670,7 @@ dependencies = [ "lazy_static", "libp2p-core", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.3", "sha2 0.10.8", @@ -3619,7 +3710,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.3", "sha2 0.10.8", @@ -3756,6 +3847,18 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +[[package]] +name = "local-ip-address" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612ed4ea9ce5acfb5d26339302528a5e1e59dfed95e9e11af3c083236ff1d15d" +dependencies = [ + "libc", + "neli", + "thiserror", + "windows-sys 0.48.0", +] + [[package]] name = "lock_api" version = "0.4.6" @@ -3771,21 +3874,6 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - [[package]] name = "lru" version = "0.7.5" @@ -3854,6 +3942,12 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.4" @@ -4032,16 +4126,17 @@ dependencies = [ [[package]] name = "muda" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8ac4080fb1e097c2c22acae467e46e4da72d941f02e82b67a87a2a89fa38b1" +checksum = "b8123dfd4996055ac9b15a60ad263b44b01e539007523ad7a4a533a3d93b0591" dependencies = [ - "cocoa", "crossbeam-channel", "dpi", "gtk", "keyboard-types", - "objc", + "objc2", + "objc2-app-kit", + "objc2-foundation", "once_cell", "png", "serde", @@ -4154,6 +4249,31 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "neli" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43" +dependencies = [ + "byteorder", + "libc", + "log", + "neli-proc-macros", +] + +[[package]] +name = "neli-proc-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" +dependencies = [ + "either", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -4215,7 +4335,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi 0.1.18", + "hermit-abi", "libc", ] @@ -4234,7 +4354,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", "syn 2.0.46", @@ -4415,7 +4535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f85842b073145726190373213c63f852020fb884c841a3a1f390637267a2fb8c" dependencies = [ "dtoa", - "itoa 1.0.1", + "itoa 1.0.11", "open-metrics-client-derive-text-encode", "owning_ref", ] @@ -4876,6 +4996,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2110609fb863cdb367d4e69d6c43c81ba6a8c7d18e80082fe9f3ef16b23afeed" +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -4999,7 +5125,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", ] [[package]] @@ -5015,8 +5151,8 @@ dependencies = [ "log", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "tempfile", "which", @@ -5035,6 +5171,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.46", +] + [[package]] name = "prost-types" version = "0.9.0" @@ -5042,7 +5191,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes", - "prost", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", ] [[package]] @@ -5413,7 +5571,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tokio-rustls 0.26.0", "tokio-socks", @@ -5467,6 +5625,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ringbuf" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726bb493fe9cac765e8f96a144c3a8396bdf766dedad22e504b70b908dcbceb4" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + [[package]] name = "rkyv" version = "0.7.39" @@ -5743,6 +5911,7 @@ dependencies = [ "serde", "serde_json", "url", + "uuid", ] [[package]] @@ -5757,12 +5926,6 @@ dependencies = [ "syn 2.0.46", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -6039,7 +6202,7 @@ version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ - "itoa 1.0.1", + "itoa 1.0.11", "memchr", "ryu", "serde", @@ -6072,7 +6235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa 1.0.11", "ryu", "serde", ] @@ -6516,7 +6679,7 @@ dependencies = [ "hashlink", "hex", "indexmap 1.9.3", - "itoa 1.0.1", + "itoa 1.0.11", "libc", "libsqlite3-sys", "log", @@ -6577,15 +6740,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "state" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" -dependencies = [ - "loom", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -6778,7 +6932,7 @@ dependencies = [ "toml 0.8.2", "torut", "tower", - "tower-http", + "tower-http 0.3.5", "tracing", "tracing-appender", "tracing-futures", @@ -6824,6 +6978,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -6860,9 +7020,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.30.1" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e7ede56f9ef03a0bb384c7b2bed4f3985ee7f3f79ec887c50d8466eec21096" +checksum = "06e48d7c56b3f7425d061886e8ce3b6acfab1993682ed70bef50fd133d721ee6" dependencies = [ "bitflags 2.6.0", "cocoa", @@ -6891,7 +7051,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.58.0", + "windows", "windows-core 0.58.0", "windows-version", "x11-dl", @@ -6916,9 +7076,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.0.0-rc.15" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3c3b1c7ac5b72d59da307b84af900a0098c74c9d7369f65018cd8ec0eb50fb" +checksum = "3c9c08beea86d5095b6f5fb1c788fe8759b23c3f71927c66a69e725a91d089cd" dependencies = [ "anyhow", "bytes", @@ -6947,7 +7107,6 @@ dependencies = [ "serde_json", "serde_repr", "serialize-to-javascript", - "state", "swift-rs", "tauri-build", "tauri-macros", @@ -6956,20 +7115,21 @@ dependencies = [ "tauri-utils", "thiserror", "tokio", + "tracing", "tray-icon", "url", "urlpattern", "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.58.0", + "windows", ] [[package]] name = "tauri-build" -version = "2.0.0-rc.12" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5713e81e02e0b99f5219b275abbd7d2c0cc0f30180e25b1b650e08feeac63" +checksum = "93bb649a284aec2ab43e8df6831b8c8060d231ec8ddf05bf021d58cb67570e1f" dependencies = [ "anyhow", "cargo_toml", @@ -6989,9 +7149,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.0-rc.12" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5370f2591dcc93d4ff08d9dd168f5097f79b34e859883586a409c627544190e3" +checksum = "a4511912612ba0da11aeb300e18e18b2c7067fd14aa886eac46bdcc43b4fa3ee" dependencies = [ "base64 0.22.1", "brotli 6.0.0", @@ -7016,9 +7176,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.0-rc.11" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19442dc8ee002ab1926586f6aecb90114f3a1226766008b0c9ac2d9fec9eeb7e" +checksum = "62ee976578a14b779996d7b6879d7e625c8ce674bc87e223953664f37def2eef" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -7030,9 +7190,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.0.0-rc.12" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3368e91a98aa55ea4e3e8ccff516bc1ed2f85872c335ec35e9b345469032e0" +checksum = "774d084450b7ec8e445ad119079307f935b7bf3d736da139a8664eb1d4909aa5" dependencies = [ "anyhow", "glob", @@ -7062,10 +7222,47 @@ dependencies = [ ] [[package]] -name = "tauri-plugin-shell" -version = "2.0.0-rc.3" +name = "tauri-plugin-devtools" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83800ddf78b820172efb5ed7310344e8e4f97fd30cd8237a3f20c12a79eb136" +checksum = "8e5cd17faa36a826e5686bd0fda5bc3f4c903682263f00cd50f2f778fc4bb866" +dependencies = [ + "async-stream", + "bytes", + "cocoa", + "colored", + "devtools-core", + "futures", + "local-ip-address", + "log", + "objc", + "serde", + "serde_json", + "swift-rs", + "tauri", + "tauri-plugin", + "tokio", + "tonic", + "tonic-health", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tauri-plugin-process" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73a682de610de60cfeea5212cbbaca9a6c25bd48854067f2aee3c27ee87ae65c" +dependencies = [ + "tauri", + "tauri-plugin", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2929bb35edb7255949e0cbcb2285ff6b02371bf826ad03471077b6b3bf4e6d60" dependencies = [ "encoding_rs", "log", @@ -7083,10 +7280,26 @@ dependencies = [ ] [[package]] -name = "tauri-runtime" -version = "2.0.0-rc.12" +name = "tauri-plugin-store" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f38d8aaa1e81d20e8e208e3e317f81b59fb75c530fbae8a90e72d02001d687" +checksum = "c824c56d35d3aeb97eda0f827c9c419d8fd153a2958f3f40e8e9f34ecc564e6d" +dependencies = [ + "dunce", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2570e1f33f332a2d2d9967ebb3903bc4e1f92b9c47e4d1b302c10ea4153fcdbb" dependencies = [ "dpi", "gtk", @@ -7098,14 +7311,14 @@ dependencies = [ "tauri-utils", "thiserror", "url", - "windows 0.58.0", + "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.0.0-rc.13" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1ef5171e14c8fe3b5a63e75004c20d057747bc3e7fdc5f8ded625f0b29f5c7" +checksum = "8147d8f9ed418d83a90af3d64fbdca5e0e924ae28e5351da88f9568169db8665" dependencies = [ "gtk", "http 1.1.0", @@ -7120,18 +7333,19 @@ dependencies = [ "tao", "tauri-runtime", "tauri-utils", + "tracing", "url", "webkit2gtk", "webview2-com", - "windows 0.58.0", + "windows", "wry", ] [[package]] name = "tauri-utils" -version = "2.0.0-rc.12" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31fe4c9148e1b35225e1c00753f24b517ce00041d02eb4b4d6fd10613a47736c" +checksum = "f87856e9d7fa91fd710362f3c73fccbf6bfd036934908791e65bd803d54dc8a8" dependencies = [ "brotli 6.0.0", "cargo_metadata", @@ -7160,6 +7374,7 @@ dependencies = [ "toml 0.8.2", "url", "urlpattern", + "uuid", "walkdir", ] @@ -7296,7 +7511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", - "itoa 1.0.1", + "itoa 1.0.11", "libc", "num-conv", "num_threads", @@ -7365,6 +7580,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite 0.2.13", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.3.0" @@ -7546,6 +7771,66 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2 0.3.18", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-timeout", + "percent-encoding", + "pin-project 1.1.5", + "prost 0.12.6", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-health" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f80db390246dfb46553481f6024f0082ba00178ea495dbb99e70ba9a4fafb5e1" +dependencies = [ + "async-stream", + "prost 0.12.6", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "tonic-web" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddb2a37b247e6adcb9f239f4e5cefdcc5ed526141a416b943929f13aea2cce" +dependencies = [ + "base64 0.21.7", + "bytes", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", + "pin-project 1.1.5", + "tokio-stream", + "tonic", + "tower-http 0.4.4", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "torut" version = "0.2.1" @@ -7615,6 +7900,24 @@ dependencies = [ "uuid", ] +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-util", + "http 0.2.11", + "http-body 0.4.6", + "http-range-header", + "pin-project-lite 0.2.13", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -7730,9 +8033,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044d7738b3d50f288ddef035b793228740ad4d927f5466b0af55dc15e7e03cfe" +checksum = "533fc2d4105e0e3d96ce1c71f2d308c9fbbe2ef9c587cab63dd627ab5bde218f" dependencies = [ "core-graphics 0.24.0", "crossbeam-channel", @@ -8009,7 +8312,11 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-clipboard-manager", + "tauri-plugin-devtools", + "tauri-plugin-process", "tauri-plugin-shell", + "tauri-plugin-store", + "tracing", ] [[package]] @@ -8368,7 +8675,7 @@ checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.58.0", + "windows", "windows-core 0.58.0", "windows-implement", "windows-interface", @@ -8392,7 +8699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" dependencies = [ "thiserror", - "windows 0.58.0", + "windows", "windows-core 0.58.0", ] @@ -8463,15 +8770,6 @@ dependencies = [ "windows-version", ] -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.0", -] - [[package]] name = "windows" version = "0.58.0" @@ -8852,9 +9150,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.43.1" +version = "0.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4d715cf5fe88e9647f3d17b207b6d060d4a88e7171d4ccb2d2c657dd1d44728" +checksum = "440600584cfbd8b0d28eace95c1f2c253db05dae43780b79380aa1e868f04c73" dependencies = [ "base64 0.22.1", "block", @@ -8881,10 +9179,11 @@ dependencies = [ "soup3", "tao-macros", "thiserror", + "tracing", "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.58.0", + "windows", "windows-core 0.58.0", "windows-version", "x11-dl", diff --git a/docs/cli/README.md b/docs/cli/README.md index eff9d072..25c2bad7 100644 --- a/docs/cli/README.md +++ b/docs/cli/README.md @@ -66,7 +66,7 @@ OPTIONS: --change-address The bitcoin address where any form of change or excess funds should be sent to --receive-address The monero address where you would like to receive monero --seller The seller's address. Must include a peer ID part, i.e. `/p2p/` - + --electrum-rpc Provide the Bitcoin Electrum RPC URL --bitcoin-target-block Estimate Bitcoin fees such that transactions are confirmed within the specified number of blocks --monero-daemon-address Specify to connect to a monero daemon of your choice: : diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity deleted file mode 100644 index 9d4d6a80..00000000 --- a/node_modules/.yarn-integrity +++ /dev/null @@ -1,10 +0,0 @@ -{ - "systemParams": "win32-x64-127", - "modulesFolders": [], - "flags": [], - "linkedModules": [], - "topLevelPatterns": [], - "lockfileEntries": {}, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/src-gui/README.md b/src-gui/README.md index e34f93a6..b10efc7c 100644 --- a/src-gui/README.md +++ b/src-gui/README.md @@ -21,7 +21,7 @@ yarn install && yarn run dev ```bash cd src-tauri -cargo tauri dev +cargo tauri dev # let this run as well ``` diff --git a/src-gui/index.html b/src-gui/index.html index 524e9b81..1e8a4c9b 100644 --- a/src-gui/index.html +++ b/src-gui/index.html @@ -5,9 +5,9 @@ + rel="stylesheet" + href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" + /> @@ -31,6 +31,7 @@ margin: 0; overflow: auto; overscroll-behavior: none; /* Prevents the bounce effect */ + overscroll-behavior-y: contain; /* Prevents the bounce effect on the y-axis */ } diff --git a/src-gui/package.json b/src-gui/package.json index 32d0a1a3..89cffe8f 100644 --- a/src-gui/package.json +++ b/src-gui/package.json @@ -19,9 +19,11 @@ "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.61", "@reduxjs/toolkit": "^2.2.6", - "@tauri-apps/api": "2.0.0-rc.1", - "@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.0", - "@tauri-apps/plugin-shell": "^2.0.0-rc.0", + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-clipboard-manager": "^2.0.0", + "@tauri-apps/plugin-process": "^2.0.0", + "@tauri-apps/plugin-shell": "^2.0.0", + "@tauri-apps/plugin-store": "^2.0.0", "humanize-duration": "^3.32.1", "lodash": "^4.17.21", "multiaddr": "^10.0.1", @@ -39,7 +41,7 @@ }, "devDependencies": { "@eslint/js": "^9.9.0", - "@tauri-apps/cli": ">=2.0.0-beta.0", + "@tauri-apps/cli": "^2.0.0", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", "@types/humanize-duration": "^3.27.4", diff --git a/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx b/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx index f5bd23d2..f9bf2b48 100644 --- a/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/DaemonStatusAlert.tsx @@ -1,44 +1,20 @@ -import { CircularProgress } from "@material-ui/core"; +import { Button, CircularProgress } from "@material-ui/core"; import { Alert, AlertProps } from "@material-ui/lab"; import { TauriContextInitializationProgress } from "models/tauriModel"; -import { useState } from "react"; +import { useNavigate } from "react-router-dom"; import { useAppSelector } from "store/hooks"; import { exhaustiveGuard } from "utils/typescriptUtils"; -const FUNNY_INIT_MESSAGES = [ - "Initializing quantum entanglement...", - "Generating one-time pads from cosmic background radiation...", - "Negotiating key exchange with aliens...", - "Optimizing elliptic curves for maximum sneakiness...", - "Transforming plaintext into ciphertext via arcane XOR rituals...", - "Salting your hash with exotic mathematical seasonings...", - "Performing advanced modular arithmetic gymnastics...", - "Consulting the Oracle of Randomness...", - "Executing top-secret permutation protocols...", - "Summoning prime factors from the mathematical aether...", - "Deploying steganographic squirrels to hide your nuts of data...", - "Initializing the quantum superposition of your keys...", - "Applying post-quantum cryptographic voodoo...", - "Encrypting your data with the tears of frustrated regulators...", -]; - function LoadingSpinnerAlert({ ...rest }: AlertProps) { return } {...rest} />; } export default function DaemonStatusAlert() { const contextStatus = useAppSelector((s) => s.rpc.status); + const navigate = useNavigate(); - const [initMessage] = useState( - FUNNY_INIT_MESSAGES[Math.floor(Math.random() * FUNNY_INIT_MESSAGES.length)], - ); - - if (contextStatus == null) { - return ( - - {initMessage} - - ); + if (contextStatus === null) { + return The daemon is not running; } switch (contextStatus.type) { @@ -68,7 +44,20 @@ export default function DaemonStatusAlert() { return The daemon is running; case "Failed": return ( - The daemon has stopped unexpectedly + navigate("/help")} + > + View Logs + + } + > + The daemon has stopped unexpectedly + ); default: return exhaustiveGuard(contextStatus); diff --git a/src-gui/src/renderer/components/alert/FundsLeftInWalletAlert.tsx b/src-gui/src/renderer/components/alert/FundsLeftInWalletAlert.tsx index ce683875..a7d52199 100644 --- a/src-gui/src/renderer/components/alert/FundsLeftInWalletAlert.tsx +++ b/src-gui/src/renderer/components/alert/FundsLeftInWalletAlert.tsx @@ -14,7 +14,7 @@ export default function FundsLeftInWalletAlert() { severity="info" action={ diff --git a/src-gui/src/renderer/rpc.ts b/src-gui/src/renderer/rpc.ts index 76ee9796..ad484fc6 100644 --- a/src-gui/src/renderer/rpc.ts +++ b/src-gui/src/renderer/rpc.ts @@ -31,6 +31,7 @@ import { Provider } from "models/apiModel"; import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils"; import { MoneroRecoveryResponse } from "models/rpcModel"; import { ListSellersResponse } from "../models/tauriModel"; +import logger from "utils/logger"; export async function initEventListeners() { // This operation is in-expensive @@ -38,6 +39,12 @@ export async function initEventListeners() { // TOOD: Replace this with a more reliable mechanism (such as an event replay mechanism) if (await checkContextAvailability()) { store.dispatch(contextStatusEventReceived({ type: "Available" })); + } else { + // Warning: If we reload the page while the Context is being initialized, this function will throw an error + initializeContext().catch((e) => { + logger.error(e, "Failed to initialize context on page load. This might be because we reloaded the page while the context was being initialized"); + }); + initializeContext(); } listen("swap-progress-update", (event) => { @@ -52,8 +59,8 @@ export async function initEventListeners() { listen("cli-log-emitted", (event) => { console.log("Received cli log event", event.payload); - store.dispatch(receivedCliLog(event.payload)) - }) + store.dispatch(receivedCliLog(event.payload)); + }); } async function invoke( @@ -161,3 +168,10 @@ export async function listSellersAtRendezvousPoint( rendezvous_point: rendezvousPointAddress, }); } + +export async function initializeContext() { + const settings = store.getState().settings; + await invokeUnsafe("initialize_context", { + settings, + }); +} diff --git a/src-gui/src/renderer/store/storeRenderer.ts b/src-gui/src/renderer/store/storeRenderer.ts index 5d52841b..64fb6a49 100644 --- a/src-gui/src/renderer/store/storeRenderer.ts +++ b/src-gui/src/renderer/store/storeRenderer.ts @@ -3,29 +3,63 @@ import { persistReducer, persistStore } from "redux-persist"; import sessionStorage from "redux-persist/lib/storage/session"; import { reducers } from "store/combinedReducer"; import { createMainListeners } from "store/middleware/storeListener"; +import { createStore } from "@tauri-apps/plugin-store"; +import { getNetworkName } from "store/config"; -// We persist the redux store in sessionStorage -// The point of this is to preserve the store across reloads while not persisting it across GUI restarts -// -// If the user reloads the page, while a swap is running we want to -// continue displaying the correct state of the swap -const persistConfig = { +// Goal: Maintain application state across page reloads while allowing a clean slate on application restart +// Settings are persisted across application restarts, while the rest of the state is cleared + +// Persist user settings across application restarts +// We use Tauri's storage for settings to ensure they're retained even when the application is closed +const rootPersistConfig = { key: "gui-global-state-store", storage: sessionStorage, + blacklist: ["settings"], }; -const persistedReducer = persistReducer( - persistConfig, - combineReducers(reducers), +// Use Tauri's store plugin for persistent settings +const tauriStore = await createStore(`${getNetworkName()}_settings.bin`, { + autoSave: 1000 as unknown as boolean, +}); + +// Configure how settings are stored and retrieved using Tauri's storage +const settingsPersistConfig = { + key: "settings", + storage: { + getItem: (key: string) => tauriStore.get(key), + setItem: (key: string, value: unknown) => tauriStore.set(key, value), + removeItem: (key: string) => tauriStore.delete(key), + }, +}; + +// Create a persisted version of the settings reducer +const persistedSettingsReducer = persistReducer( + settingsPersistConfig, + reducers.settings, ); +// Combine all reducers, using the persisted settings reducer +const rootReducer = combineReducers({ + ...reducers, + settings: persistedSettingsReducer, +}); + +// Enable persistence for the entire application state +const persistedReducer = persistReducer(rootPersistConfig, rootReducer); + +// Set up the Redux store with persistence and custom middleware export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => - getDefaultMiddleware().prepend(createMainListeners().middleware), + getDefaultMiddleware({ + // Disable serializable to silence warnings about non-serializable actions + serializableCheck: false, + }).prepend(createMainListeners().middleware), }); +// Create a persistor to manage the persisted store export const persistor = persistStore(store); +// TypeScript type definitions for easier use of the store in the application export type AppDispatch = typeof store.dispatch; -export type RootState = ReturnType; +export type RootState = ReturnType; \ No newline at end of file diff --git a/src-gui/src/store/combinedReducer.ts b/src-gui/src/store/combinedReducer.ts index 5acd0839..e0490cf7 100644 --- a/src-gui/src/store/combinedReducer.ts +++ b/src-gui/src/store/combinedReducer.ts @@ -4,6 +4,7 @@ import ratesSlice from "./features/ratesSlice"; import rpcSlice from "./features/rpcSlice"; import swapReducer from "./features/swapSlice"; import torSlice from "./features/torSlice"; +import settingsSlice from "./features/settingsSlice"; export const reducers = { swap: swapReducer, @@ -12,4 +13,5 @@ export const reducers = { rpc: rpcSlice, alerts: alertsSlice, rates: ratesSlice, + settings: settingsSlice, }; diff --git a/src-gui/src/store/config.ts b/src-gui/src/store/config.ts index 2640180b..c9f60034 100644 --- a/src-gui/src/store/config.ts +++ b/src-gui/src/store/config.ts @@ -9,8 +9,6 @@ export function getStubTestnetProvider(): ExtendedProviderStatus | null { const stubProviderAddress = import.meta.env .VITE_TESTNET_STUB_PROVIDER_ADDRESS; - console.log(import.meta.env); - if (stubProviderAddress != null) { try { const [multiAddr, peerId] = @@ -31,3 +29,11 @@ export function getStubTestnetProvider(): ExtendedProviderStatus | null { return null; } + +export function getNetworkName(): string { + if (isTestnet()) { + return "Testnet"; + }else { + return "Mainnet"; + } +} \ No newline at end of file diff --git a/src-gui/src/store/features/settingsSlice.ts b/src-gui/src/store/features/settingsSlice.ts new file mode 100644 index 00000000..5485941e --- /dev/null +++ b/src-gui/src/store/features/settingsSlice.ts @@ -0,0 +1,43 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { TauriSettings } from "models/tauriModel"; + +const initialState: TauriSettings = { + bitcoin_confirmation_target: 1, + electrum_rpc_url: null, + monero_node_url: null, +}; + +const alertsSlice = createSlice({ + name: "settings", + initialState, + reducers: { + setBitcoinConfirmationTarget(slice, action: PayloadAction) { + slice.bitcoin_confirmation_target = action.payload; + }, + setElectrumRpcUrl(slice, action: PayloadAction) { + if (action.payload === null || action.payload === "") { + slice.electrum_rpc_url = null; + } else { + slice.electrum_rpc_url = action.payload; + } + }, + setMoneroNodeUrl(slice, action: PayloadAction) { + if (action.payload === null || action.payload === "") { + slice.monero_node_url = null; + } else { + slice.monero_node_url = action.payload; + } + }, + resetSettings(slice) { + return initialState; + } + }, +}); + +export const { + setBitcoinConfirmationTarget, + setElectrumRpcUrl, + setMoneroNodeUrl, + resetSettings +} = alertsSlice.actions; +export default alertsSlice.reducer; diff --git a/src-gui/src/store/hooks.ts b/src-gui/src/store/hooks.ts index 47d03a20..cedd3e39 100644 --- a/src-gui/src/store/hooks.ts +++ b/src-gui/src/store/hooks.ts @@ -3,7 +3,6 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; import type { AppDispatch, RootState } from "renderer/store/storeRenderer"; import { parseDateString } from "utils/parseUtils"; -// Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; @@ -29,7 +28,7 @@ export function useIsContextAvailable() { export function useSwapInfo(swapId: string | null) { return useAppSelector((state) => - swapId ? (state.rpc.state.swapInfos[swapId] ?? null) : null, + swapId ? state.rpc.state.swapInfos[swapId] ?? null : null, ); } @@ -58,3 +57,7 @@ export function useSwapInfosSortedByDate() { (swap) => -parseDateString(swap.start_date), ); } + +export function useSettings() { + return useAppSelector((state) => state.settings); +} diff --git a/src-gui/yarn.lock b/src-gui/yarn.lock index 43825c5c..03386ef7 100644 --- a/src-gui/yarn.lock +++ b/src-gui/yarn.lock @@ -717,100 +717,104 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.2.tgz#c770006ccc780b2de7b2151fc7f37b49121a21c1" integrity sha512-Yy8So+SoRz8I3NS4Bjh91BICPOSVgdompTIPYTByUqU66AXSIOgmW3Lv1ke3NORPqxdF+RdrZET+8vYai6f4aA== -"@tauri-apps/api@2.0.0-rc.1": - version "2.0.0-rc.1" - resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-rc.1.tgz#ec858f239e34792625e311f687fcaca0581e0904" - integrity sha512-qubAWjM9sqofUh7fe+7UAbBY3wlkfCyxm+PNRYpq9mnNng7lvSQq3sYsFUEB12AYvgGARZSb54VMVUvRuVLi7w== +"@tauri-apps/api@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.1.tgz#dc49d899fb873b96ee1d46a171384625ba5ad404" + integrity sha512-eoQWT+Tq1qSwQpHV+nw1eNYe5B/nm1PoRjQCRiEOS12I1b+X4PUcREfXVX8dPcBT6GrzWGDtaecY0+1p0Rfqlw== -"@tauri-apps/api@^2.0.0-rc.0": - version "2.0.0-rc.3" - resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-rc.3.tgz#1dd17530de9cafd854f77d3feeca1732a985a81e" - integrity sha512-k1erUfnoOFJwL5VNFZz0BQZ2agNstG7CNOjwpdWMl1vOaVuSn4DhJtXB0Deh9lZaaDlfrykKOyZs9c3XXpMi5Q== +"@tauri-apps/cli-darwin-arm64@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.1.tgz#5816c0099977f705d1a7249822fa51f5d3c3750a" + integrity sha512-oWjCZoFbm57V0eLEkIbc6aUmB4iW65QF7J8JVh5sNzH4xHGP9rzlQarbkg7LOn89z7mFSZpaLJAWlaaZwoV2Ug== -"@tauri-apps/api@^2.0.0-rc.4": - version "2.0.0-rc.4" - resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-rc.4.tgz#2b4c3493d86382981787c52006c6c9e5bf16bc08" - integrity sha512-UNiIhhKG08j4ooss2oEEVexffmWkgkYlC2M3GcX3VPtNsqFgVNL8Mcw/4Y7rO9M9S+ffAMnLOF5ypzyuyb8tyg== +"@tauri-apps/cli-darwin-x64@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.1.tgz#84b5b5af0d00ed30ffce0c25b64e7ef1cc6a935f" + integrity sha512-bARd5yAnDGpG/FPhSh87+tzQ6D0TPyP2mZ5bg6cioeoXDmry68nT/FBzp87ySR1/KHvuhEQYWM/4RPrDjvI1Yg== -"@tauri-apps/cli-darwin-arm64@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-beta.21.tgz#9dc6f306b14d58b0b4fbf218ffbb31831e28cf4d" - integrity sha512-okI7PRSC6RO4JfrOTqu4oWf0IfBPbkGHisyDOTay6K5uhz4zzry5fFJVa8S/DTrKtdjau4vcik/EDCxiGRun9Q== +"@tauri-apps/cli-linux-arm-gnueabihf@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.1.tgz#0fc6ea259b3c53320b45d9dad000e7cd350acd94" + integrity sha512-OK3/RpxujoZAUbV7GHe4IPAUsIO6IuWEHT++jHXP+YW5Y7QezGGjQRc43IlWaQYej/yE8wfcrwrbqisc5wtiCw== -"@tauri-apps/cli-darwin-x64@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-beta.21.tgz#77a0bdd820301f120acbb93c57b6c8acb9ae4f82" - integrity sha512-mXoJDXB6CBoqUnFb4TCsSVC6FJRZsN1DHRZAyn6iNLIhOrObcM4L2xz8rzt3WirANwJ/ayrNv95fEt8Fq1jmgA== +"@tauri-apps/cli-linux-arm64-gnu@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.1.tgz#3ffbfdc3896c08e4196f9723c79b7e3a67bc552d" + integrity sha512-MGSQJduiMEApspMK97mFt4kr6ig0OtxO5SUFpPDfYPw/XmY9utaRa9CEG6LcH8e0GN9xxYMhCv+FeU48spYPhA== -"@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-beta.21.tgz#bc9214feff536d917d55bddeadb724555f9ac698" - integrity sha512-LYPOx3LE2eZ0g8Zh/HYaNg6B1pZzH4BPMcma7wGZ0XPu+4fKLLGgav13xP2lknLnxiRP9jJCaTIBKXgcQEtLyg== +"@tauri-apps/cli-linux-arm64-musl@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.1.tgz#95806815aa78db218ba995f45a691a7d3493497e" + integrity sha512-R6+vgxaPpxgGi4suMkQgGuhjMbZzMJfVyWfv2DOE/xxOzSK1BAOc54/HOjfOLxlnkA6uD6V69MwCwXgxW00A2g== -"@tauri-apps/cli-linux-arm64-gnu@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-beta.21.tgz#69167099a4756944eb5d3d15905cbf4d903307ad" - integrity sha512-VP2L729tgY889OZj5U436EntjwkI8MyVB+GrvBv8k2mj1nWB651KiVIpcUmsUgjXZ2r01bifN9J0l+3EFEXUAQ== +"@tauri-apps/cli-linux-x64-gnu@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.1.tgz#9521005df14bc4d7c6805bb4ed788a34cdfeb3e4" + integrity sha512-xrasYQnUZVhKJhBxHAeu4KxZbofaQlsG9KfZ9p1Bx+hmjs5BuujzwMnXsVD2a4l6GPW6gwblf2a6d600rySmWQ== -"@tauri-apps/cli-linux-arm64-musl@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-beta.21.tgz#d66796e672c2606d2e08a232def55919a5fa9542" - integrity sha512-s1rV01RIdowlPHfw7hTBnCEm2C3mZbynF+xpyRSv9vSczu4dpfwILMRwxB4nzMzdJ7RPHsf/R+5Ww86e8QM4Gw== +"@tauri-apps/cli-linux-x64-musl@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.1.tgz#9ebeacc7b5a3d379bbcc32e0c3a0612b125929cf" + integrity sha512-SPk+EzRTlbvk46p5aURc7O4GihzxbqG80m74vstm0rolnmQ0FX3qqIh3as3cQpDiZWLod4j6EEmX0mTU3QpvXA== -"@tauri-apps/cli-linux-x64-gnu@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-beta.21.tgz#ed02923c94b71f2377ef5c4cc72bf1de12487296" - integrity sha512-yGh7ktUycHT3mAnKxC7cx/vjcbjJzoxQCxnjWpmIayVwq+iXLD1mK7nRXRdJpL/rnBFTqqD29CKuypCEFiq3/A== +"@tauri-apps/cli-win32-arm64-msvc@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.1.tgz#6d394c1c8c5fc164e1b4ed5852596d9bd4c4fd4b" + integrity sha512-LAELK01eOMyEt+JZLmx4EUOdRuPYr1a+mHjlxAxCnCaS3dpeg/c5/NMZfbRAJbAH4id+STRHIfPXTdCT2zUNAw== -"@tauri-apps/cli-linux-x64-musl@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-beta.21.tgz#511293e6508a5d41e758d6f0bf98e834b22c63cb" - integrity sha512-+79b8O3tsjbGR47pJtcSKGmtqj4rsSxB5AfMb4UCkmoNkbaOzB0YS/ZieUGAb+SHXZ/MMs7mcl96N9SqYOL7hw== +"@tauri-apps/cli-win32-ia32-msvc@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.1.tgz#d789acf15bc5bc5a84045c91c78849edee8142f9" + integrity sha512-eMUgOS4mAusk5njU2TBxBjCUO1P4cV4uzY5CHihysoXSL2TVQdWrXT42VGeoahJh+yeQWkYFka2s4Bu0iWDMXg== -"@tauri-apps/cli-win32-arm64-msvc@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-beta.21.tgz#736c5dba48385bfebf030f4ad641592f0db14258" - integrity sha512-rKlpcjx6t1ECZciMmHT5xkXKjC+O+TVxRKmA21tEq/Ezt7XdnufGko1hduwQmVJWkHxKg6ab7uf98ImMpDC5UA== +"@tauri-apps/cli-win32-x64-msvc@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.1.tgz#9f97f166cc0d1ffd3558d41c504f2aaf0cebbf46" + integrity sha512-U9esAOcFIv80/slzlpwjkG31Wx1OqbfDgC5KjGT1Dd9iUOSuJZCwbiY7m3rYG2I6RWLfd9zhNu86CVohsKjBfA== -"@tauri-apps/cli-win32-ia32-msvc@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-beta.21.tgz#bf0a8dbfc1d5b724fd9f1ed2db14817821bd9b43" - integrity sha512-ExdhvRfgAoZi4/7re6OkmfqsHvTJQgWouTNphHWRilUEqBM7TEQV1UxYtwWfgyOKelyx4cxUYDFAJxootTb2Nw== - -"@tauri-apps/cli-win32-x64-msvc@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-beta.21.tgz#56842ab8088a794276cbf74bf0edcda6e96ee8ee" - integrity sha512-JtNTwNXIOfE04Cs3ieTvkdcMyJM9Sujw5MM9zNmusJKE03s/OLqbNK/2ISlcb/puwYGGPhhyYtL5hCmYXIrHHQ== - -"@tauri-apps/cli@>=2.0.0-beta.0": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.0.0-beta.21.tgz#aef1b9f5d80da38265820ff3ab8558724e3309eb" - integrity sha512-lqV4pD0iTs8ASd19slH0eRoVAjbxtD0cCsZFVD7kG4sYkeZ0IkvtxbvnHAOUbALfvnHZr1dVXFDVxQUqJK2OXw== +"@tauri-apps/cli@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.0.1.tgz#04bfb48507fc7c6edff4b1083fd7f5ca7578eb42" + integrity sha512-fCheW0iWYWUtFV3ui3HlMhk3ZJpAQ5KJr7B7UmfhDzBSy1h5JBdrCtvDwy+3AcPN+Fg5Ey3JciF8zEP8eBx+vQ== optionalDependencies: - "@tauri-apps/cli-darwin-arm64" "2.0.0-beta.21" - "@tauri-apps/cli-darwin-x64" "2.0.0-beta.21" - "@tauri-apps/cli-linux-arm-gnueabihf" "2.0.0-beta.21" - "@tauri-apps/cli-linux-arm64-gnu" "2.0.0-beta.21" - "@tauri-apps/cli-linux-arm64-musl" "2.0.0-beta.21" - "@tauri-apps/cli-linux-x64-gnu" "2.0.0-beta.21" - "@tauri-apps/cli-linux-x64-musl" "2.0.0-beta.21" - "@tauri-apps/cli-win32-arm64-msvc" "2.0.0-beta.21" - "@tauri-apps/cli-win32-ia32-msvc" "2.0.0-beta.21" - "@tauri-apps/cli-win32-x64-msvc" "2.0.0-beta.21" + "@tauri-apps/cli-darwin-arm64" "2.0.1" + "@tauri-apps/cli-darwin-x64" "2.0.1" + "@tauri-apps/cli-linux-arm-gnueabihf" "2.0.1" + "@tauri-apps/cli-linux-arm64-gnu" "2.0.1" + "@tauri-apps/cli-linux-arm64-musl" "2.0.1" + "@tauri-apps/cli-linux-x64-gnu" "2.0.1" + "@tauri-apps/cli-linux-x64-musl" "2.0.1" + "@tauri-apps/cli-win32-arm64-msvc" "2.0.1" + "@tauri-apps/cli-win32-ia32-msvc" "2.0.1" + "@tauri-apps/cli-win32-x64-msvc" "2.0.1" -"@tauri-apps/plugin-clipboard-manager@^2.0.0-rc.0": - version "2.0.0-rc.0" - resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-rc.0.tgz#8371fa2a2092c67d0cfd9322698c14115735459e" - integrity sha512-2fS3wbRQEtorkk3Np2msJUeKCXRqLQ9sSo2FzlFdUPYNzThsu43uWCF55McGLAfltNOvXQIcQLUBf05jbBL/5w== +"@tauri-apps/plugin-clipboard-manager@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0.tgz#cf08df2338c055d15a60cbb61140766859e06a77" + integrity sha512-V1sXmbjnwfXt/r48RJMwfUmDMSaP/8/YbH4CLNxt+/sf1eHlIP8PRFdFDQwLN0cNQKu2rqQVbG/Wc/Ps6cDUhw== dependencies: - "@tauri-apps/api" "^2.0.0-rc.0" + "@tauri-apps/api" "^2.0.0" -"@tauri-apps/plugin-shell@^2.0.0-rc.0": - version "2.0.0-rc.1" - resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-rc.1.tgz#9facf3bbcedfa2de676cb4cfc703687377aa12a3" - integrity sha512-JtNROc0rqEwN/g93ig5pK4cl1vUo2yn+osCpY9de64cy/d9hRzof7AuYOgvt/Xcd5VPQmlgo2AGvUh5sQRSR1A== +"@tauri-apps/plugin-process@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-process/-/plugin-process-2.0.0.tgz#002fd73f0d7b1ae2a5aacf442aa657e83dc2960b" + integrity sha512-OYzi0GnkrF4NAnsHZU7U3tjSoP0PbeAlO7T1Z+vJoBUH9sFQ1NSLqWYWQyf8hcb3gVWe7P1JggjiskO+LST1ug== dependencies: - "@tauri-apps/api" "^2.0.0-rc.4" + "@tauri-apps/api" "^2.0.0" + +"@tauri-apps/plugin-shell@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0.tgz#b6fc88ab070fd5f620e46405715779aa44eb8428" + integrity sha512-OpW2+ycgJLrEoZityWeWYk+6ZWP9VyiAfbO+N/O8VfLkqyOym8kXh7odKDfINx9RAotkSGBtQM4abyKfJDkcUg== + dependencies: + "@tauri-apps/api" "^2.0.0" + +"@tauri-apps/plugin-store@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-store/-/plugin-store-2.0.0.tgz#7563bff85795bc501ac606dab0c329760ef28134" + integrity sha512-l4xsbxAXrKGdBdYNNswrLfcRv3v1kOatdycOcVPYW+jKwkznCr1HEOrPXkPhXsZLSLyYmNXpgfOmdSZNmcykDg== + dependencies: + "@tauri-apps/api" "^2.0.0" "@testing-library/react@^16.0.1": version "16.0.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 18426da6..edeb1cbb 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,6 +20,10 @@ once_cell = "1" serde = { version = "1", features = [ "derive" ] } serde_json = "1" swap = { path = "../swap", features = [ "tauri" ] } -tauri = { version = "2.0.0-rc.1", features = [ "config-json5" ] } +tauri = { version = "2.0.0", features = [ "config-json5" ] } tauri-plugin-clipboard-manager = "2.1.0-beta.7" -tauri-plugin-shell = "2.0.0-rc.2" +tauri-plugin-devtools = "2.0.0" +tauri-plugin-process = "2.0.0" +tauri-plugin-shell = "2.0.0" +tauri-plugin-store = "2.0.0" +tracing = "0.1.40" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 773af9f0..613d0614 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -7,6 +7,8 @@ "core:event:allow-emit", "core:event:default", "clipboard-manager:allow-write-text", - "shell:allow-open" + "shell:allow-open", + "store:default", + "process:default" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e1f0bb27..0aa45343 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,3 +1,4 @@ +use anyhow::Context as AnyhowContext; use std::result::Result; use std::sync::Arc; use swap::cli::{ @@ -7,7 +8,7 @@ use swap::cli::{ GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs, }, - tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle}, + tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle, TauriSettings}, Context, ContextBuilder, }, command::{Bitcoin, Monero}, @@ -104,13 +105,12 @@ impl State { fn try_get_context(&self) -> Result, String> { self.context .clone() - .ok_or("Context not available") - .to_string_result() + .ok_or("Context not available".to_string()) } } /// Sets up the Tauri application -/// Initializes the Tauri state and spawns an async task to set up the Context +/// Initializes the Tauri state fn setup(app: &mut tauri::App) -> Result<(), Box> { let app_handle = app.app_handle().to_owned(); @@ -118,44 +118,14 @@ fn setup(app: &mut tauri::App) -> Result<(), Box> { // If we don't do this, Tauri commands will panic at runtime if no value is present app_handle.manage::>(RwLock::new(State::new())); - tauri::async_runtime::spawn(async move { - let tauri_handle = TauriHandle::new(app_handle.clone()); - - let context = ContextBuilder::new(true) - .with_bitcoin(Bitcoin { - bitcoin_electrum_rpc_url: None, - bitcoin_target_block: None, - }) - .with_monero(Monero { - monero_daemon_address: None, - }) - .with_json(false) - .with_debug(true) - .with_tauri(tauri_handle.clone()) - .build() - .await; - - match context { - Ok(context) => { - let state = app_handle.state::>(); - state.write().await.set_context(Arc::new(context)); - // To display to the user that the setup is done, we emit an event to the Tauri frontend - tauri_handle.emit_context_init_progress_event(TauriContextStatusEvent::Available); - } - Err(e) => { - println!("Error while initializing context: {:?}", e); - // To display to the user that the setup failed, we emit an event to the Tauri frontend - tauri_handle.emit_context_init_progress_event(TauriContextStatusEvent::Failed); - } - } - }); - Ok(()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_shell::init()) .invoke_handler(tauri::generate_handler![ @@ -171,6 +141,7 @@ pub fn run() { suspend_current_swap, cancel_and_refund, is_context_available, + initialize_context, ]) .setup(setup) .build(tauri::generate_context!()) @@ -220,5 +191,56 @@ tauri_command!(get_history, GetHistoryArgs, no_args); /// Here we define Tauri commands whose implementation is not delegated to the Request trait #[tauri::command] async fn is_context_available(context: tauri::State<'_, RwLock>) -> Result { + // TODO: Here we should return more information about status of the context (e.g. initializing, failed) Ok(context.read().await.try_get_context().is_ok()) } + +/// Tauri command to initialize the Context +#[tauri::command] +async fn initialize_context( + settings: TauriSettings, + app_handle: tauri::AppHandle, + state: tauri::State<'_, RwLock>, +) -> Result<(), String> { + // Acquire a write lock on the state + let mut state_write_lock = state + .try_write() + .context("Context is already being initialized") + .to_string_result()?; + + // Get app handle and create a Tauri handle + let tauri_handle = TauriHandle::new(app_handle.clone()); + + let context_result = ContextBuilder::new(true) + .with_bitcoin(Bitcoin { + bitcoin_electrum_rpc_url: settings.electrum_rpc_url, + bitcoin_target_block: settings.bitcoin_confirmation_target.into(), + }) + .with_monero(Monero { + monero_daemon_address: settings.monero_node_url, + }) + .with_json(false) + .with_debug(true) + .with_tauri(tauri_handle.clone()) + .build() + .await; + + match context_result { + Ok(context_instance) => { + state_write_lock.set_context(Arc::new(context_instance)); + + tracing::info!("Context initialized"); + + // Emit event to frontend + tauri_handle.emit_context_init_progress_event(TauriContextStatusEvent::Available); + Ok(()) + } + Err(e) => { + tracing::error!(error = ?e, "Failed to initialize context"); + + // Emit event to frontend + tauri_handle.emit_context_init_progress_event(TauriContextStatusEvent::Failed); + Err(e.to_string()) + } + } +} diff --git a/swap/sqlx-data.json b/swap/sqlx-data.json index 04630918..0b84475b 100644 --- a/swap/sqlx-data.json +++ b/swap/sqlx-data.json @@ -9,9 +9,7 @@ "type_info": "Text" } ], - "nullable": [ - false - ], + "nullable": [false], "parameters": { "Right": 1 } @@ -37,9 +35,7 @@ "type_info": "Text" } ], - "nullable": [ - true - ], + "nullable": [true], "parameters": { "Right": 1 } @@ -60,10 +56,7 @@ "type_info": "Text" } ], - "nullable": [ - false, - false - ], + "nullable": [false, false], "parameters": { "Right": 0 } @@ -99,9 +92,7 @@ "type_info": "Text" } ], - "nullable": [ - false - ], + "nullable": [false], "parameters": { "Right": 1 } @@ -127,9 +118,7 @@ "type_info": "Text" } ], - "nullable": [ - false - ], + "nullable": [false], "parameters": { "Right": 1 } @@ -145,9 +134,7 @@ "type_info": "Text" } ], - "nullable": [ - false - ], + "nullable": [false], "parameters": { "Right": 1 } @@ -163,9 +150,7 @@ "type_info": "Text" } ], - "nullable": [ - false - ], + "nullable": [false], "parameters": { "Right": 1 } @@ -191,13 +176,11 @@ "type_info": "Text" } ], - "nullable": [ - false - ], + "nullable": [false], "parameters": { "Right": 1 } }, "query": "\n SELECT proof\n FROM buffered_transfer_proofs\n WHERE swap_id = ?\n " } -} \ No newline at end of file +} diff --git a/swap/src/asb/config.rs b/swap/src/asb/config.rs index ac651c71..555eee71 100644 --- a/swap/src/asb/config.rs +++ b/swap/src/asb/config.rs @@ -27,7 +27,7 @@ pub struct Defaults { electrum_rpc_url: Url, monero_wallet_rpc_url: Url, price_ticker_ws_url: Url, - bitcoin_confirmation_target: usize, + bitcoin_confirmation_target: u16, } impl GetDefaults for Testnet { @@ -185,7 +185,7 @@ mod addr_list { #[serde(deny_unknown_fields)] pub struct Bitcoin { pub electrum_rpc_url: Url, - pub target_block: usize, + pub target_block: u16, pub finality_confirmations: Option, #[serde(with = "crate::bitcoin::network")] pub network: bitcoin::Network, diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 9748d740..cb4663b0 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -42,7 +42,7 @@ pub struct Wallet { wallet: Arc>>, finality_confirmations: u32, network: Network, - target_block: usize, + target_block: u16, } impl Wallet { @@ -51,7 +51,7 @@ impl Wallet { data_dir: impl AsRef, xprivkey: ExtendedPrivKey, env_config: env::Config, - target_block: usize, + target_block: u16, ) -> Result { let data_dir = data_dir.as_ref(); let wallet_dir = data_dir.join(WALLET); @@ -577,7 +577,7 @@ impl Wallet { } pub trait EstimateFeeRate { - fn estimate_feerate(&self, target_block: usize) -> Result; + fn estimate_feerate(&self, target_block: u16) -> Result; fn min_relay_fee(&self) -> Result; } @@ -589,7 +589,7 @@ pub struct StaticFeeRate { #[cfg(test)] impl EstimateFeeRate for StaticFeeRate { - fn estimate_feerate(&self, _target_block: usize) -> Result { + fn estimate_feerate(&self, _target_block: u16) -> Result { Ok(self.fee_rate) } @@ -726,8 +726,10 @@ impl Client { let config = bdk::electrum_client::ConfigBuilder::default() .retry(5) .build(); + let electrum = bdk::electrum_client::Client::from_config(electrum_rpc_url.as_str(), config) .context("Failed to initialize Electrum RPC client")?; + // Initially fetch the latest block for storing the height. // We do not act on this subscription after this call. let latest_block = electrum @@ -736,6 +738,7 @@ impl Client { let client = bdk::electrum_client::Client::new(electrum_rpc_url.as_str()) .context("Failed to initialize Electrum RPC client")?; + let blockchain = ElectrumBlockchain::from(client); let last_sync = Instant::now() .checked_sub(interval) @@ -867,10 +870,10 @@ impl Client { } impl EstimateFeeRate for Client { - fn estimate_feerate(&self, target_block: usize) -> Result { + fn estimate_feerate(&self, target_block: u16) -> Result { // https://github.com/romanz/electrs/blob/f9cf5386d1b5de6769ee271df5eef324aa9491bc/src/rpc.rs#L213 // Returned estimated fees are per BTC/kb. - let fee_per_byte = self.electrum.estimate_fee(target_block)?; + let fee_per_byte = self.electrum.estimate_fee(target_block.into())?; // we do not expect fees being that high. #[allow(clippy::cast_possible_truncation)] Ok(FeeRate::from_btc_per_kvb(fee_per_byte as f32)) diff --git a/swap/src/cli/api.rs b/swap/src/cli/api.rs index 7c3e3d21..7c70d24d 100644 --- a/swap/src/cli/api.rs +++ b/swap/src/cli/api.rs @@ -435,7 +435,7 @@ async fn init_bitcoin_wallet( seed: &Seed, data_dir: PathBuf, env_config: EnvConfig, - bitcoin_target_block: usize, + bitcoin_target_block: u16, ) -> Result { let wallet_dir = data_dir.join("wallet"); @@ -467,14 +467,16 @@ async fn init_monero_wallet( let monero_wallet_rpc_process = monero_wallet_rpc .run(network, Some(monero_daemon_address)) - .await?; + .await + .context("Failed to start monero-wallet-rpc process")?; let monero_wallet = monero::Wallet::open_or_create( monero_wallet_rpc_process.endpoint(), MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME.to_string(), env_config, ) - .await?; + .await + .context("Failed to open or create Monero wallet")?; Ok((monero_wallet, monero_wallet_rpc_process)) } diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index d10ee328..79111c83 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -1,9 +1,10 @@ use crate::{monero, network::quote::BidQuote}; use anyhow::Result; use bitcoin::Txid; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use strum::Display; use typeshare::typeshare; +use url::Url; use uuid::Uuid; const SWAP_PROGRESS_EVENT_NAME: &str = "swap-progress-update"; @@ -83,6 +84,7 @@ pub enum TauriContextInitializationProgress { #[derive(Display, Clone, Serialize)] #[serde(tag = "type", content = "content")] pub enum TauriContextStatusEvent { + NotInitialized, Initializing(TauriContextInitializationProgress), Available, Failed, @@ -174,5 +176,18 @@ pub enum TauriSwapProgressEvent { #[typeshare] pub struct CliLogEmittedEvent { /// The serialized object containing the log message and metadata. - pub buffer: String + pub buffer: String, +} + +/// This struct contains the settings for the Context +#[typeshare] +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TauriSettings { + /// This is used for estimating the target block for Bitcoin (fee) + pub bitcoin_confirmation_target: u16, + /// The URL of the Monero node e.g `http://xmr.node:18081` + pub monero_node_url: Option, + /// The URL of the Electrum RPC server e.g `ssl://bitcoin.com:50001` + #[typeshare(serialized_as = "string")] + pub electrum_rpc_url: Option, } diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 190ec5ba..32cabc66 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -30,8 +30,8 @@ const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700"; // See: https://1209k.com/bitcoin-eye/ele.php?chain=tbtc pub const DEFAULT_ELECTRUM_RPC_URL_TESTNET: &str = "ssl://testnet.foundation.xyz:50002"; -const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 1; -pub const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: usize = 1; +const DEFAULT_BITCOIN_CONFIRMATION_TARGET: u16 = 1; +pub const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: u16 = 1; const DEFAULT_TOR_SOCKS5_PORT: &str = "9050"; @@ -534,11 +534,11 @@ pub struct Bitcoin { long = "bitcoin-target-block", help = "Estimate Bitcoin fees such that transactions are confirmed within the specified number of blocks" )] - pub bitcoin_target_block: Option, + pub bitcoin_target_block: Option, } impl Bitcoin { - pub fn apply_defaults(self, testnet: bool) -> Result<(Url, usize)> { + pub fn apply_defaults(self, testnet: bool) -> Result<(Url, u16)> { let bitcoin_electrum_rpc_url = if let Some(url) = self.bitcoin_electrum_rpc_url { url } else if testnet { diff --git a/swap/src/common/tracing_util.rs b/swap/src/common/tracing_util.rs index 8be333ab..b8b505e4 100644 --- a/swap/src/common/tracing_util.rs +++ b/swap/src/common/tracing_util.rs @@ -64,13 +64,13 @@ pub fn init( .with(file_layer) .with(tauri_layer) .with(terminal_layer.json().with_filter(level_filter)) - .init(); + .try_init()?; } else { tracing_subscriber::registry() .with(file_layer) .with(tauri_layer) .with(terminal_layer.with_filter(level_filter)) - .init(); + .try_init()?; } // Now we can use the tracing macros to log messages @@ -83,7 +83,11 @@ pub fn init( fn env_filter(level_filter: LevelFilter) -> Result { Ok(EnvFilter::from_default_env() .add_directive(Directive::from_str(&format!("asb={}", &level_filter))?) - .add_directive(Directive::from_str(&format!("swap={}", &level_filter))?)) + .add_directive(Directive::from_str(&format!("swap={}", &level_filter))?) + .add_directive(Directive::from_str(&format!( + "unstoppableswap-gui-rs={}", + &level_filter + ))?)) } /// A writer that forwards tracing log messages to the tauri guest. diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs index 4745237d..561530b1 100644 --- a/swap/src/monero/wallet_rpc.rs +++ b/swap/src/monero/wallet_rpc.rs @@ -87,6 +87,7 @@ pub struct WalletRpcProcess { port: u16, } +#[derive(Debug, Copy, Clone)] struct MoneroDaemon { address: &'static str, port: u16, @@ -102,6 +103,16 @@ impl MoneroDaemon { } } + pub fn from_str(address: String, network: Network) -> Result { + let (address, port) = extract_host_and_port(address)?; + + Ok(Self { + address, + port, + network, + }) + } + /// Checks if the Monero daemon is available by sending a request to its `get_info` endpoint. async fn is_available(&self, client: &reqwest::Client) -> Result { let url = format!("http://{}:{}/get_info", self.address, self.port); @@ -144,7 +155,7 @@ struct MoneroDaemonGetInfoResponse { } /// Chooses an available Monero daemon based on the specified network. -async fn choose_monero_daemon(network: Network) -> Result<&'static MoneroDaemon, Error> { +async fn choose_monero_daemon(network: Network) -> Result { let client = reqwest::Client::builder() .timeout(Duration::from_secs(30)) .https_only(false) @@ -159,7 +170,7 @@ async fn choose_monero_daemon(network: Network) -> Result<&'static MoneroDaemon, match daemon.is_available(&client).await { Ok(true) => { tracing::debug!(%daemon, "Found available Monero daemon"); - return Ok(daemon); + return Ok(*daemon); } Err(err) => { tracing::debug!(%err, %daemon, "Failed to connect to Monero daemon"); @@ -320,11 +331,21 @@ impl WalletRpc { .local_addr()? .port(); - let daemon_address = match daemon_address { - Some(daemon_address) => daemon_address, - None => choose_monero_daemon(network).await?.to_string(), + let daemon = match daemon_address { + Some(daemon_address) => { + let daemon = MoneroDaemon::from_str(daemon_address, network)?; + + if !daemon.is_available(&reqwest::Client::new()).await? { + bail!("Specified daemon is not available or not on the correct network"); + } + + daemon + } + None => choose_monero_daemon(network).await?, }; + let daemon_address = daemon.to_string(); + tracing::debug!( %daemon_address, %port, @@ -465,22 +486,36 @@ impl WalletRpc { } } +fn extract_host_and_port(address: String) -> Result<(&'static str, u16), Error> { + // Strip the protocol (anything before "://") + let stripped_address = if let Some(pos) = address.find("://") { + address[(pos + 3)..].to_string() + } else { + address + }; + + // Split the remaining address into parts (host and port) + let parts: Vec<&str> = stripped_address.split(':').collect(); + + if parts.len() == 2 { + let host = parts[0].to_string(); + let port = parts[1].parse::()?; + + // Leak the host string to create a 'static lifetime string + let static_str_host: &'static str = Box::leak(host.into_boxed_str()); + return Ok((static_str_host, port)); + } + + bail!( + "Could not extract host and port from address: {}", + stripped_address + ) +} + #[cfg(test)] mod tests { use super::*; - fn extract_host_and_port(address: String) -> (&'static str, u16) { - let parts: Vec<&str> = address.split(':').collect(); - - if parts.len() == 2 { - let host = parts[0].to_string(); - let port = parts[1].parse::().unwrap(); - let static_str_host: &'static str = Box::leak(host.into_boxed_str()); - return (static_str_host, port); - } - panic!("Could not extract host and port from address: {}", address) - } - #[tokio::test] async fn test_is_daemon_available_success() { let mut server = mockito::Server::new_async().await; @@ -501,7 +536,7 @@ mod tests { ) .create(); - let (host, port) = extract_host_and_port(server.host_with_port()); + let (host, port) = extract_host_and_port(server.host_with_port()).unwrap(); let client = reqwest::Client::new(); let result = MoneroDaemon::new(host, port, Network::Mainnet) @@ -532,7 +567,7 @@ mod tests { ) .create(); - let (host, port) = extract_host_and_port(server.host_with_port()); + let (host, port) = extract_host_and_port(server.host_with_port()).unwrap(); let client = reqwest::Client::new(); let result = MoneroDaemon::new(host, port, Network::Stagenet) @@ -563,7 +598,7 @@ mod tests { ) .create(); - let (host, port) = extract_host_and_port(server.host_with_port()); + let (host, port) = extract_host_and_port(server.host_with_port()).unwrap(); let client = reqwest::Client::new(); let result = MoneroDaemon::new(host, port, Network::Mainnet)