mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-24 14:15:55 -04:00
feat(gui): DFX.swiss integration (#451)
* feat(gui): Monero wallet * progress * refactor * progress, dont delete wallet, re-fetch approvals and background periodically * show transaction history correctly * Enable fetching tx hashes * Try add the wallet listener event callbacks, not working * fix: Redeem XMR to internal main wallet, not temp wallet * feat(monero-sys): Support signing messages * feat(gui): DFX.swiss integration * refactor: format, slight refactorings * progress * type safety * refactoring of callback system * make free floating functions generic * refactor: Format files * refactor(gui): Split wallet components and redesign balanceOverview component * refactor(gui): Add action buttons and transaction section * wrapper event listener * progress, compiles * works! * WORKS! Event received on balance change * refactor: format and slight refactorings and comments * refactor(gui): Start with implementation of send dialog - new number input - new button variant and size * add @tauri-apps/plugin-dialog * feat(gui): Add permissions for file dialog * fix(monero-harness): Compile issue * feat(gui): Extract seed from Monero wallet and use for derivation, allow opening existing wallet file * feat(gui): Always refresh the approval list from frontend when resolving * fix(monero-rpc-pool): Implement Into<String> for ServerInfo * fix(monero-sys): Use oneshot channel for all wallets * feat(gui, monero-sys): Display recently opened wallets * small refactors * fix(gui): Enable background_sync, display temp "Loading..." if values are null * feat(gui): Remove headers from pages, show selected navigation item * feat(gui): Explicitly tell user if no swaps have been made yet * feat(gui): send sync and history updates * feat(gui): Fetch monero wallet details when context becomes availiable * feat(gui): Display Monero primary address without modal * feat(gui): Make "swap" button on wallet page take you to "/swap" * feat(gui): Rework send modal, adjust number input, added send to field * feat(gui): set block restore height, not working * refactor(gui): Optimize number input and add support for switching between currency * feat(gui): Display real fiat currency prices in send modal * feat(gui): Add error message for too high send amount * feat(gui): Modern UI for SeedSelectionDialog * feat(gui): Wrap MoneroWalletActions * wip * refactoring approval callback * feat(gui): Send Direction of Transaction in History to Frontend * feat(gui): Let user approve transaction before publishing * feat: Display 8 digits for Monero amounts by default * feat(monero-sys): Store pending (non published) transactions in Mutex map inside wallet thread This allows seperating signing and publishing transactions cleanly * dprint fmt * fix(gui): Refresh Monero wallet history C++ struct before serializing * feat(monero-rpc-pool): Fail after three JSON-RPC errors * feat(monero-sys): Add wrapper around verify_wallet_password * feat(gui): Allow opening password-protected Wallets * refactor: fmt, remove receive button * fix(gui): Convert to XMR before converting into Fiat * feat(gui): Add dialog for setting restore height * feat(gui): block height can be changed, blocks when too low * refactor(monero-sys): Remove old WalletListener code * feat(gui): Continually ask for user to select wallet and enter password, if user rejects, offer to select different wallet * refactor(swap): Extract "select Monero wallet" into own function * refactor(tauri): Dont kill monero-wallet-rpc * refactor(tauri): Avoid multiple concurrent Contexts starting * refactor: Change "Cancel" to "Change wallet" on PasswordEntryDialog * feat(gui): show curent block height, fix blockage * Cargo.lock update * refactor(monero-sys): Use match instead of is_err() and expect(...) * refactor: better context for WalletHandle constructor method errors handling * refactor(monero-sys): Common open_with<F>(path: String, daemon: Daemon, wallet_op: F) function * feat: check empty password before requeston password for wallet * feat: Remove "Checking for available remote nodes" from frontend * feat(gui): Allow sweeping entire Monero balance * feat(monero-rpc-pool): Keep alive TCP connections, do not record JSON-RPC errors as failure if >=3 nodes failed If >=3 nodes failed we assume it was an actual issue on our side, not an issue with the node * refactor(swap): Remove dead code * add comment to WalletHandleListener::on_refreshed{...} * feat(gui): show current block height in the field * refactor: remove unused UserCancelledError; * refactor: No Arc<Mutex<_>> for Pending TXs map * refactor: remove redundant } catch (error) { * feat: add our new crates to `OUR_CRATES` in tracing util * fix(gui): Add math.ceil to piconero conversion to ensure integer * fix(gui): Close menu when option is clicked * review and improve/reduce uses of unsafe, also remove unique_ptr wrapper around TransactionHistory to avoid double free * fix(gui): Use monero amount from units.tsx * fix(gui): Use PromiseInvokeButton for simplification for approving of send transaction * update comment, rename function * refactor(gui): Fix alignment of amounts * refactor(gui): Remove sending and refreshing states from wallet * fix(cli, gui): use old seed flow on no tauri, fix minor issues in gui * fix: use the new named function * refactor(gui): Add skeletons for monero wallet when still loading * fix * get working * feat(gui): Add tooltip to buy monero button * refactor: Format files * refactor(gui): Do not store logs in redux-persist --------- Co-authored-by: Maksim Kirillov <maksim.kirillov@staticlabs.de> Co-authored-by: b-enedict <benedict.seuss@gmail.com> Co-authored-by: einliterflasche <einliterflasche@pm.me>
This commit is contained in:
parent
591d0b8e20
commit
69ddd2486d
15 changed files with 658 additions and 81 deletions
252
Cargo.lock
generated
252
Cargo.lock
generated
|
@ -524,7 +524,7 @@ dependencies = [
|
|||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix 1.0.7",
|
||||
"rustix 1.0.8",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
|
@ -556,7 +556,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix 1.0.7",
|
||||
"rustix 1.0.8",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
@ -583,7 +583,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 1.0.7",
|
||||
"rustix 1.0.8",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.59.0",
|
||||
|
@ -777,7 +777,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
|
@ -800,7 +800,7 @@ dependencies = [
|
|||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
|
@ -1096,7 +1096,7 @@ dependencies = [
|
|||
"hmac",
|
||||
"jsonrpc_client",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
|
@ -1912,7 +1912,7 @@ dependencies = [
|
|||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -2578,6 +2578,32 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dfx-swiss-sdk"
|
||||
version = "1.0.0"
|
||||
source = "git+https://github.com/eigenwallet/dfx-swiss-rs#0b7d5dc88e7c6481c527fb8fb246e863b415e45f"
|
||||
dependencies = [
|
||||
"dfx-swiss-sdk-raw",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dfx-swiss-sdk-raw"
|
||||
version = "1.0.0"
|
||||
source = "git+https://github.com/eigenwallet/dfx-swiss-rs#0b7d5dc88e7c6481c527fb8fb246e863b415e45f"
|
||||
dependencies = [
|
||||
"reqwest 0.11.27",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.11.0"
|
||||
|
@ -3273,6 +3299,15 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
|
@ -3280,7 +3315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared",
|
||||
"foreign-types-shared 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3294,6 +3329,12 @@ dependencies = [
|
|||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
|
@ -4359,6 +4400,19 @@ dependencies = [
|
|||
"webpki-roots 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper 0.14.32",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.15"
|
||||
|
@ -4558,7 +4612,7 @@ dependencies = [
|
|||
"netlink-proto",
|
||||
"netlink-sys",
|
||||
"rtnetlink",
|
||||
"system-configuration",
|
||||
"system-configuration 0.6.1",
|
||||
"tokio",
|
||||
"windows 0.53.0",
|
||||
]
|
||||
|
@ -4881,7 +4935,7 @@ source = "git+https://github.com/delta1/rust-jsonrpc-client.git?rev=3b6081697cd6
|
|||
dependencies = [
|
||||
"async-trait",
|
||||
"jsonrpc_client_macro",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
|
@ -5972,7 +6026,7 @@ dependencies = [
|
|||
"monero-rpc",
|
||||
"monero-sys",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"testcontainers",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
@ -6013,7 +6067,7 @@ dependencies = [
|
|||
"monero",
|
||||
"monero-epee-bin-serde",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -6034,7 +6088,7 @@ dependencies = [
|
|||
"monero-rpc",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
|
@ -6165,6 +6219,23 @@ dependencies = [
|
|||
"unsigned-varint 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
|
@ -6753,12 +6824,50 @@ dependencies = [
|
|||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
|
@ -7281,7 +7390,7 @@ dependencies = [
|
|||
"concurrent-queue",
|
||||
"hermit-abi 0.5.2",
|
||||
"pin-project-lite",
|
||||
"rustix 1.0.7",
|
||||
"rustix 1.0.8",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
@ -7955,6 +8064,47 @@ dependencies = [
|
|||
"bytecheck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.27",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.32",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 0.1.2",
|
||||
"system-configuration 0.5.1",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.22"
|
||||
|
@ -7983,7 +8133,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.2",
|
||||
"tokio-util",
|
||||
|
@ -8234,15 +8384,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -8321,6 +8471,15 @@ dependencies = [
|
|||
"security-framework 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.12.0"
|
||||
|
@ -9264,7 +9423,7 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"core-graphics",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
|
@ -9744,6 +9903,7 @@ dependencies = [
|
|||
"curve25519-dalek-ng",
|
||||
"data-encoding",
|
||||
"derive_builder",
|
||||
"dfx-swiss-sdk",
|
||||
"dialoguer",
|
||||
"ecdsa_fun",
|
||||
"ed25519-dalek 1.0.1",
|
||||
|
@ -9768,7 +9928,7 @@ dependencies = [
|
|||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"rust_decimal",
|
||||
"rust_decimal_macros",
|
||||
"rustls 0.23.29",
|
||||
|
@ -9911,6 +10071,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.2"
|
||||
|
@ -9943,6 +10109,17 @@ dependencies = [
|
|||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
|
@ -9951,7 +10128,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
|||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
"system-configuration-sys 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -10084,7 +10271,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
|
@ -10356,7 +10543,7 @@ dependencies = [
|
|||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"reqwest 0.12.22",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -10480,7 +10667,7 @@ dependencies = [
|
|||
"fastrand",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.7",
|
||||
"rustix 1.0.8",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
@ -10707,6 +10894,16 @@ dependencies = [
|
|||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.22.0"
|
||||
|
@ -11890,7 +12087,7 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
|
@ -12363,6 +12560,7 @@ name = "unstoppableswap-gui-rs"
|
|||
version = "3.0.0-beta.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dfx-swiss-sdk",
|
||||
"monero-rpc-pool",
|
||||
"rustls 0.23.29",
|
||||
"serde",
|
||||
|
@ -12379,6 +12577,8 @@ dependencies = [
|
|||
"tauri-plugin-single-instance",
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-updater",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"zip 4.3.0",
|
||||
|
@ -13667,7 +13867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix 1.0.7",
|
||||
"rustix 1.0.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
24
src-gui/src/assets/dfx-logo.svg
Normal file
24
src-gui/src/assets/dfx-logo.svg
Normal file
|
@ -0,0 +1,24 @@
|
|||
<svg width="544" height="170" viewBox="0 0 544 170" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4704_494)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M61.5031 0H124.245C170.646 0 208.267 36.5427 208.267 84.0393C208.267 131.536 169.767 170.018 122.288 170.018H61.5031V135.504H114.046C141.825 135.504 164.541 112.789 164.541 85.009C164.541 57.2293 141.825 34.5136 114.046 34.5136H61.5031V0ZM266.25 31.5686V76.4973H338.294V108.066H266.25V170H226.906V0H355.389V31.5686H266.25ZM495.76 170L454.71 110.975L414.396 170H369.216L432.12 83.5365L372.395 0H417.072L456.183 55.1283L494.557 0H537.061L477.803 82.082L541.191 170H495.778H495.76Z" fill="#072440"/>
|
||||
<path d="M86.1582 126.274C109.821 126.274 129.004 107.092 129.004 83.4287C129.004 59.7657 109.821 40.583 86.1582 40.583C62.4952 40.583 43.3126 59.7657 43.3126 83.4287C43.3126 107.092 62.4952 126.274 86.1582 126.274Z" fill="url(#paint0_linear_4704_494)"/>
|
||||
<path d="M47.1374 132.146C73.1707 132.146 94.2748 111.042 94.2748 85.009C94.2748 58.9757 73.1707 37.8716 47.1374 37.8716C21.1041 37.8716 0 58.9757 0 85.009C0 111.042 21.1041 132.146 47.1374 132.146Z" fill="url(#paint1_linear_4704_494)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_4704_494" x1="122.111" y1="64.6777" x2="45.9618" y2="103.949" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.04" stop-color="#F5516C"/>
|
||||
<stop offset="0.14" stop-color="#C74863"/>
|
||||
<stop offset="0.31" stop-color="#853B57"/>
|
||||
<stop offset="0.44" stop-color="#55324E"/>
|
||||
<stop offset="0.55" stop-color="#382D49"/>
|
||||
<stop offset="0.61" stop-color="#2D2B47"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_4704_494" x1="75.8868" y1="50.7468" x2="15.2815" y2="122.952" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.2" stop-color="#F5516C"/>
|
||||
<stop offset="1" stop-color="#6B3753"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_4704_494">
|
||||
<rect width="541.174" height="170" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,105 @@
|
|||
import {
|
||||
Box,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
Button,
|
||||
DialogContent,
|
||||
Chip,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { EuroSymbol as EuroIcon } from "@mui/icons-material";
|
||||
import DFXSwissLogo from "assets/dfx-logo.svg";
|
||||
import { useState } from "react";
|
||||
import { dfxAuthenticate } from "renderer/rpc";
|
||||
|
||||
function DFXLogo({ height = 24 }: { height?: number }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "white",
|
||||
borderRadius: 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: 1,
|
||||
height,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={DFXSwissLogo}
|
||||
alt="DFX Swiss"
|
||||
style={{ height: "100%", flex: 1 }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Component for DFX button and modal
|
||||
export default function DfxButton() {
|
||||
const [dfxUrl, setDfxUrl] = useState<string | null>(null);
|
||||
|
||||
const handleOpenDfx = async () => {
|
||||
try {
|
||||
// Get authentication token and URL (this will initialize DFX if needed)
|
||||
const response = await dfxAuthenticate();
|
||||
setDfxUrl(response.kyc_url);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("DFX authentication failed:", error);
|
||||
// TODO: Show error snackbar if needed
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setDfxUrl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="Buy Monero with fiat using DFX" enterDelay={500}>
|
||||
<Chip
|
||||
variant="button"
|
||||
icon={<EuroIcon />}
|
||||
label="Buy Monero"
|
||||
clickable
|
||||
onClick={handleOpenDfx}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Dialog
|
||||
open={dfxUrl != null}
|
||||
onClose={handleCloseModal}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<DFXLogo />
|
||||
<Button onClick={handleCloseModal} variant="outlined">
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ p: 0, height: "min(40rem, 80vh)" }}>
|
||||
{dfxUrl && (
|
||||
<iframe
|
||||
src={dfxUrl}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
border: "none",
|
||||
}}
|
||||
title="DFX Swiss"
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -60,7 +60,6 @@ export default function SendAmountInput({
|
|||
|
||||
const handleMaxAmount = () => {
|
||||
if (disabled) return;
|
||||
|
||||
if (onMaxToggled) {
|
||||
onMaxToggled();
|
||||
} else if (onMaxClicked) {
|
||||
|
|
|
@ -61,7 +61,6 @@ export default function SendTransactionContent({
|
|||
|
||||
const handleMaxToggled = () => {
|
||||
if (isSending) return;
|
||||
|
||||
if (isMaxSelected) {
|
||||
// Disable MAX mode - restore previous amount
|
||||
setIsMaxSelected(false);
|
||||
|
@ -76,7 +75,6 @@ export default function SendTransactionContent({
|
|||
|
||||
const handleAmountChange = (newAmount: string) => {
|
||||
if (isSending) return;
|
||||
|
||||
if (newAmount !== "<MAX>") {
|
||||
setIsMaxSelected(false);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import SendTransactionModal from "../SendTransactionModal";
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||
import SetRestoreHeightModal from "../SetRestoreHeightModal";
|
||||
import DfxButton from "./DFXWidget";
|
||||
|
||||
interface WalletActionButtonsProps {
|
||||
balance: {
|
||||
|
@ -32,45 +33,6 @@ interface WalletActionButtonsProps {
|
|||
};
|
||||
}
|
||||
|
||||
function RestoreHeightDialog({
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [restoreHeight, setRestoreHeight] = useState(0);
|
||||
|
||||
const handleRestoreHeight = async () => {
|
||||
await setMoneroRestoreHeight(restoreHeight);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<DialogTitle>Restore Height</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
label="Restore Height"
|
||||
type="number"
|
||||
value={restoreHeight}
|
||||
onChange={(e) => setRestoreHeight(Number(e.target.value))}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<PromiseInvokeButton
|
||||
onInvoke={handleRestoreHeight}
|
||||
displayErrorSnackbar={true}
|
||||
variant="contained"
|
||||
>
|
||||
Restore
|
||||
</PromiseInvokeButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WalletActionButtons({
|
||||
balance,
|
||||
}: WalletActionButtonsProps) {
|
||||
|
@ -121,6 +83,7 @@ export default function WalletActionButtons({
|
|||
variant="button"
|
||||
clickable
|
||||
/>
|
||||
<DfxButton />
|
||||
|
||||
<IconButton onClick={handleMenuClick}>
|
||||
<MoreHorizIcon />
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
SendMoneroResponse,
|
||||
GetMoneroSyncProgressResponse,
|
||||
GetPendingApprovalsResponse,
|
||||
DfxAuthenticateResponse,
|
||||
RejectApprovalArgs,
|
||||
RejectApprovalResponse,
|
||||
SetRestoreHeightArgs,
|
||||
|
@ -621,3 +622,7 @@ export async function saveFilesInDialog(files: Record<string, string>) {
|
|||
files,
|
||||
});
|
||||
}
|
||||
|
||||
export async function dfxAuthenticate(): Promise<DfxAuthenticateResponse> {
|
||||
return await invokeNoArgs<DfxAuthenticateResponse>("dfx_authenticate");
|
||||
}
|
||||
|
|
|
@ -29,9 +29,12 @@ tauri-plugin-process = "^2.0.0"
|
|||
tauri-plugin-shell = "^2.0.0"
|
||||
tauri-plugin-store = "^2.0.0"
|
||||
tauri-plugin-updater = "^2.0.0"
|
||||
tokio = { workspace = true, features = ["rt"] }
|
||||
tokio-util = { version = "0.7", features = ["rt"] }
|
||||
tracing = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
zip = "4.0.0"
|
||||
dfx-swiss-sdk = { git = "https://github.com/eigenwallet/dfx-swiss-rs", subdir = "dfx-swiss-sdk" }
|
||||
|
||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||
tauri-plugin-cli = "^2.0.0"
|
||||
|
|
|
@ -8,13 +8,14 @@ use swap::cli::{
|
|||
request::{
|
||||
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, CheckElectrumNodeArgs,
|
||||
CheckElectrumNodeResponse, CheckMoneroNodeArgs, CheckMoneroNodeResponse, CheckSeedArgs,
|
||||
CheckSeedResponse, ExportBitcoinWalletArgs, GetCurrentSwapArgs, GetDataDirArgs,
|
||||
GetHistoryArgs, GetLogsArgs, GetMoneroAddressesArgs, GetMoneroBalanceArgs,
|
||||
GetMoneroHistoryArgs, GetMoneroMainAddressArgs, GetMoneroSyncProgressArgs,
|
||||
GetPendingApprovalsResponse, GetRestoreHeightArgs, GetSwapInfoArgs,
|
||||
GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, RedactArgs,
|
||||
RejectApprovalArgs, RejectApprovalResponse, ResolveApprovalArgs, ResumeSwapArgs,
|
||||
SendMoneroArgs, SetRestoreHeightArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||
CheckSeedResponse, DfxAuthenticateResponse, ExportBitcoinWalletArgs,
|
||||
GetCurrentSwapArgs, GetDataDirArgs, GetHistoryArgs, GetLogsArgs,
|
||||
GetMoneroAddressesArgs, GetMoneroBalanceArgs, GetMoneroHistoryArgs,
|
||||
GetMoneroMainAddressArgs, GetMoneroSyncProgressArgs, GetPendingApprovalsResponse,
|
||||
GetRestoreHeightArgs, GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs,
|
||||
MoneroRecoveryArgs, RedactArgs, RejectApprovalArgs, RejectApprovalResponse,
|
||||
ResolveApprovalArgs, ResumeSwapArgs, SendMoneroArgs, SetRestoreHeightArgs,
|
||||
SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||
},
|
||||
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle, TauriSettings},
|
||||
Context, ContextBuilder,
|
||||
|
@ -209,7 +210,8 @@ pub fn run() {
|
|||
get_pending_approvals,
|
||||
set_monero_restore_height,
|
||||
reject_approval_request,
|
||||
get_restore_height
|
||||
get_restore_height,
|
||||
dfx_authenticate,
|
||||
])
|
||||
.setup(setup)
|
||||
.build(tauri::generate_context!())
|
||||
|
@ -461,3 +463,92 @@ async fn initialize_context(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn dfx_authenticate(
|
||||
state: tauri::State<'_, State>,
|
||||
) -> Result<DfxAuthenticateResponse, String> {
|
||||
use dfx_swiss_sdk::{DfxClient, SignRequest};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio_util::task::AbortOnDropHandle;
|
||||
|
||||
let context = state.try_get_context()?;
|
||||
|
||||
// Get the monero wallet manager
|
||||
let monero_manager = context
|
||||
.monero_manager
|
||||
.as_ref()
|
||||
.ok_or("Monero wallet manager not available for DFX authentication")?;
|
||||
|
||||
let wallet = monero_manager.main_wallet().await;
|
||||
let address = wallet.main_address().await.to_string();
|
||||
|
||||
// Create channel for authentication
|
||||
let (auth_tx, mut auth_rx) = mpsc::channel::<(SignRequest, oneshot::Sender<String>)>(10);
|
||||
|
||||
// Create DFX client
|
||||
let mut client = DfxClient::new(address, Some("https://api.dfx.swiss".to_string()), auth_tx);
|
||||
|
||||
// Start signing task with AbortOnDropHandle
|
||||
let signing_task = tokio::spawn(async move {
|
||||
tracing::info!("DFX signing service started and listening for requests");
|
||||
|
||||
while let Some((sign_request, response_tx)) = auth_rx.recv().await {
|
||||
tracing::debug!(
|
||||
message = %sign_request.message,
|
||||
blockchains = ?sign_request.blockchains,
|
||||
"Received DFX signing request"
|
||||
);
|
||||
|
||||
// Sign the message using the main Monero wallet
|
||||
let signature = match wallet
|
||||
.sign_message(&sign_request.message, None, false)
|
||||
.await
|
||||
{
|
||||
Ok(sig) => {
|
||||
tracing::debug!(
|
||||
signature_preview = %&sig[..std::cmp::min(50, sig.len())],
|
||||
"Message signed successfully for DFX"
|
||||
);
|
||||
sig
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(error = ?e, "Failed to sign message for DFX");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Send signature back to DFX client
|
||||
if let Err(_) = response_tx.send(signature) {
|
||||
tracing::warn!("Failed to send signature response through channel to DFX client");
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("DFX signing service stopped");
|
||||
});
|
||||
|
||||
// Create AbortOnDropHandle so the task gets cleaned up
|
||||
let _abort_handle = AbortOnDropHandle::new(signing_task);
|
||||
|
||||
// Authenticate with DFX
|
||||
tracing::info!("Starting DFX authentication...");
|
||||
client
|
||||
.authenticate()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to authenticate with DFX: {}", e))?;
|
||||
|
||||
let access_token = client
|
||||
.access_token
|
||||
.as_ref()
|
||||
.ok_or("No access token available after authentication")?
|
||||
.clone();
|
||||
|
||||
let kyc_url = format!("https://app.dfx.swiss/buy?session={}", access_token);
|
||||
|
||||
tracing::info!("DFX authentication completed successfully");
|
||||
|
||||
Ok(DfxAuthenticateResponse {
|
||||
access_token,
|
||||
kyc_url,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ description = "XMR/BTC trustless atomic swaps."
|
|||
name = "swap"
|
||||
|
||||
[features]
|
||||
tauri = ["dep:tauri"]
|
||||
tauri = ["dep:tauri", "dep:dfx-swiss-sdk"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
@ -33,6 +33,7 @@ conquer-once = "0.4"
|
|||
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
|
||||
data-encoding = "2.6"
|
||||
derive_builder = "0.20.2"
|
||||
dfx-swiss-sdk = { git = "https://github.com/eigenwallet/dfx-swiss-rs", subdir = "dfx-swiss-sdk", optional = true }
|
||||
dialoguer = "0.11"
|
||||
ecdsa_fun = { version = "0.10", default-features = false, features = ["libsecp_compat", "serde", "adaptor"] }
|
||||
ed25519-dalek = "1"
|
||||
|
|
|
@ -188,7 +188,7 @@ pub struct Context {
|
|||
pub tasks: Arc<PendingTaskList>,
|
||||
tauri_handle: Option<TauriHandle>,
|
||||
bitcoin_wallet: Option<Arc<bitcoin::Wallet>>,
|
||||
monero_manager: Option<Arc<monero::Wallets>>,
|
||||
pub monero_manager: Option<Arc<monero::Wallets>>,
|
||||
tor_client: Option<Arc<TorClient<TokioRustlsRuntime>>>,
|
||||
#[allow(dead_code)]
|
||||
monero_rpc_pool_handle: Option<Arc<monero_rpc_pool::PoolHandle>>,
|
||||
|
|
|
@ -594,7 +594,6 @@ impl Request for SetRestoreHeightArgs {
|
|||
let year: u16 = date.year;
|
||||
let month: u8 = date.month;
|
||||
let day: u8 = date.day;
|
||||
|
||||
// Validate ranges
|
||||
if month < 1 || month > 12 {
|
||||
bail!("Month must be between 1 and 12");
|
||||
|
@ -632,7 +631,6 @@ impl Request for SetRestoreHeightArgs {
|
|||
};
|
||||
|
||||
wallet.set_restore_height(height).await?;
|
||||
|
||||
wallet.pause_refresh().await;
|
||||
wallet.stop().await;
|
||||
tracing::debug!("Background refresh stopped");
|
||||
|
@ -2001,3 +1999,10 @@ impl Request for GetMoneroSyncProgressArgs {
|
|||
pub struct GetPendingApprovalsResponse {
|
||||
pub approvals: Vec<crate::cli::api::tauri_bindings::ApprovalRequest>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct DfxAuthenticateResponse {
|
||||
pub access_token: String,
|
||||
pub kyc_url: String,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod throttle;
|
||||
pub mod tor;
|
||||
pub mod tracing_util;
|
||||
|
||||
|
|
182
swap/src/common/throttle.rs
Normal file
182
swap/src/common/throttle.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
// copied from: https://github.com/cargo-crates/fns
|
||||
// MIT License
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::time::{self, /* SystemTime, UNIX_EPOCH, */ Duration};
|
||||
|
||||
pub fn throttle<F, T>(closure: F, delay: Duration) -> Throttle<T>
|
||||
where
|
||||
F: Fn(T) -> () + Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let sender = Arc::new(Mutex::new(sender));
|
||||
let throttle_config = Arc::new(Mutex::new(ThrottleConfig {
|
||||
closure: Box::pin(closure),
|
||||
delay,
|
||||
}));
|
||||
|
||||
let dup_throttle_config = throttle_config.clone();
|
||||
let throttle = Throttle {
|
||||
sender: Some(sender),
|
||||
thread: Some(std::thread::spawn(move || {
|
||||
let throttle_config = dup_throttle_config;
|
||||
let mut current_param = None; // 最后被保存为执行的参数
|
||||
let mut closure_time = None; // 闭包最后执行时间
|
||||
loop {
|
||||
if current_param.is_none() {
|
||||
let message = receiver.recv();
|
||||
let now = time::Instant::now();
|
||||
match message {
|
||||
Ok(param) => {
|
||||
if let Some(param) = param {
|
||||
let throttle_config = throttle_config.lock().unwrap();
|
||||
if closure_time.is_none()
|
||||
|| now.duration_since(closure_time.unwrap())
|
||||
>= throttle_config.delay
|
||||
{
|
||||
current_param = None;
|
||||
closure_time = Some(now);
|
||||
(*throttle_config.closure)(param);
|
||||
} else {
|
||||
current_param = Some(param);
|
||||
}
|
||||
} else {
|
||||
current_param = None;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let message = receiver.recv_timeout((*throttle_config.lock().unwrap()).delay);
|
||||
let now = time::Instant::now();
|
||||
match message {
|
||||
Ok(param) => {
|
||||
if let Some(param) = param {
|
||||
let throttle_config = throttle_config.lock().unwrap();
|
||||
if closure_time.is_none()
|
||||
|| now.duration_since(closure_time.unwrap())
|
||||
>= throttle_config.delay
|
||||
{
|
||||
(*throttle_config.closure)(param);
|
||||
current_param = None;
|
||||
closure_time = Some(now);
|
||||
} else {
|
||||
current_param = Some(param);
|
||||
}
|
||||
} else {
|
||||
current_param = None;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
mpsc::RecvTimeoutError::Timeout => {
|
||||
if let Some(param) = current_param.take() {
|
||||
(throttle_config.lock().unwrap().closure)(param);
|
||||
current_param = None;
|
||||
closure_time = None; // 超时执行为额外的执行, 不影响的下一次执行
|
||||
}
|
||||
}
|
||||
mpsc::RecvTimeoutError::Disconnected => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})),
|
||||
throttle_config,
|
||||
};
|
||||
throttle
|
||||
}
|
||||
|
||||
struct ThrottleConfig<T> {
|
||||
closure: Pin<Box<dyn Fn(T) -> () + Send + Sync + 'static>>,
|
||||
delay: Duration,
|
||||
}
|
||||
impl<T> Drop for ThrottleConfig<T> {
|
||||
fn drop(&mut self) {
|
||||
tracing::debug!("drop ThrottleConfig {:?}", format!("{:p}", self));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Throttle<T> {
|
||||
sender: Option<Arc<Mutex<mpsc::Sender<Option<T>>>>>,
|
||||
thread: Option<std::thread::JoinHandle<()>>,
|
||||
throttle_config: Arc<Mutex<ThrottleConfig<T>>>,
|
||||
}
|
||||
impl<T> Throttle<T> {
|
||||
pub fn call(&self, param: T) {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(Some(param))
|
||||
.unwrap();
|
||||
}
|
||||
pub fn terminate(&self) {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(None)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
impl<T> Drop for Throttle<T> {
|
||||
fn drop(&mut self) {
|
||||
self.terminate();
|
||||
tracing::debug!("drop Throttle {:?}", format!("{:p}", self));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let effect_run_times = Arc::new(Mutex::new(0));
|
||||
let param = Arc::new(Mutex::new(0));
|
||||
let dup_effect_run_times = effect_run_times.clone();
|
||||
let dup_param = param.clone();
|
||||
let throttle_fn = throttle(
|
||||
move |param| {
|
||||
*dup_effect_run_times.lock().unwrap() += 1;
|
||||
*dup_param.lock().unwrap() = param;
|
||||
},
|
||||
std::time::Duration::from_millis(100),
|
||||
);
|
||||
{
|
||||
throttle_fn.call(1);
|
||||
throttle_fn.call(2);
|
||||
throttle_fn.call(3);
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
assert_eq!(*effect_run_times.lock().unwrap(), 2); // delay后执行最有一个参数
|
||||
assert_eq!(*param.lock().unwrap(), 3);
|
||||
}
|
||||
|
||||
{
|
||||
throttle_fn.call(4);
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
assert_eq!(*effect_run_times.lock().unwrap(), 3);
|
||||
assert_eq!(*param.lock().unwrap(), 4);
|
||||
}
|
||||
|
||||
{
|
||||
throttle_fn.call(5);
|
||||
throttle_fn.call(6);
|
||||
throttle_fn.terminate(); // 终止最后一次执行
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
assert_eq!(*effect_run_times.lock().unwrap(), 4);
|
||||
assert_eq!(*param.lock().unwrap(), 5);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use crate::common::throttle::{throttle, Throttle};
|
||||
use anyhow::{Context, Result};
|
||||
use monero::{Address, Network};
|
||||
use monero_sys::WalletEventListener;
|
||||
pub use monero_sys::{Daemon, WalletHandle as Wallet, WalletHandleListener};
|
||||
use throttle::{throttle, Throttle};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::cli::api::{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue