diff --git a/src-gui/src/renderer/components/PromiseInvokeButton.tsx b/src-gui/src/renderer/components/PromiseInvokeButton.tsx index cc9c4989..47c9354a 100644 --- a/src-gui/src/renderer/components/PromiseInvokeButton.tsx +++ b/src-gui/src/renderer/components/PromiseInvokeButton.tsx @@ -6,6 +6,7 @@ import { ReactNode, useEffect, useState } from "react"; interface IpcInvokeButtonProps { onSuccess?: (data: T) => void; onClick: () => Promise; + onPendingChange?: (bool) => void; isLoadingOverride?: boolean; isIconButton?: boolean; loadIcon?: ReactNode; @@ -24,26 +25,22 @@ export default function PromiseInvokeButton({ isIconButton, displayErrorSnackbar, tooltipTitle, + onPendingChange, ...rest }: IpcInvokeButtonProps & ButtonProps) { const { enqueueSnackbar } = useSnackbar(); const [isPending, setIsPending] = useState(false); - const [hasMinLoadingTimePassed, setHasMinLoadingTimePassed] = useState(false); - const isLoading = (isPending && hasMinLoadingTimePassed) || isLoadingOverride; + const isLoading = isPending || isLoadingOverride; const actualEndIcon = isLoading ? loadIcon || : endIcon; - useEffect(() => { - setHasMinLoadingTimePassed(false); - setTimeout(() => setHasMinLoadingTimePassed(true), 100); - }, [isPending]); - async function handleClick(event: React.MouseEvent) { if (!isPending) { try { + onPendingChange?.(true); setIsPending(true); let result = await onClick(); onSuccess?.(result); @@ -56,32 +53,23 @@ export default function PromiseInvokeButton({ } } finally { setIsPending(false); + onPendingChange?.(false); } } } const isDisabled = disabled || isLoading; - return ( - - - {isIconButton ? ( - - {actualEndIcon} - - ) : ( - + )} + ); } diff --git a/src-gui/src/renderer/components/modal/wallet/pages/AddressInputPage.tsx b/src-gui/src/renderer/components/modal/wallet/pages/AddressInputPage.tsx index 37d4126a..4c48e78e 100644 --- a/src-gui/src/renderer/components/modal/wallet/pages/AddressInputPage.tsx +++ b/src-gui/src/renderer/components/modal/wallet/pages/AddressInputPage.tsx @@ -1,17 +1,18 @@ -import { useState } from 'react'; -import { Button, DialogActions, DialogContentText } from '@material-ui/core'; -import BitcoinAddressTextField from '../../../inputs/BitcoinAddressTextField'; -import WithdrawDialogContent from '../WithdrawDialogContent'; -import IpcInvokeButton from '../../../IpcInvokeButton'; +import { useState } from "react"; +import { Button, DialogActions, DialogContentText } from "@material-ui/core"; +import BitcoinAddressTextField from "../../../inputs/BitcoinAddressTextField"; +import WithdrawDialogContent from "../WithdrawDialogContent"; +import IpcInvokeButton from "../../../IpcInvokeButton"; export default function AddressInputPage({ - onCancel, + withdrawAddress, + setWithdrawAddress, + setWithdrawAddressValid, }: { - onCancel: () => void; + withdrawAddress: string; + setWithdrawAddress: (address: string) => void; + setWithdrawAddressValid: (valid: boolean) => void; }) { - const [withdrawAddressValid, setWithdrawAddressValid] = useState(false); - const [withdrawAddress, setWithdrawAddress] = useState(''); - return ( <> @@ -28,22 +29,6 @@ export default function AddressInputPage({ fullWidth /> - - - - - Withdraw - - ); } diff --git a/src-gui/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx b/src-gui/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx index 153e9941..546bf481 100644 --- a/src-gui/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx +++ b/src-gui/src/renderer/components/modal/wallet/pages/BitcoinWithdrawTxInMempoolPage.tsx @@ -1,6 +1,6 @@ -import { Button, DialogActions, DialogContentText } from '@material-ui/core'; -import BitcoinTransactionInfoBox from '../../swap/BitcoinTransactionInfoBox'; -import WithdrawDialogContent from '../WithdrawDialogContent'; +import { Button, DialogActions, DialogContentText } from "@material-ui/core"; +import BitcoinTransactionInfoBox from "../../swap/BitcoinTransactionInfoBox"; +import WithdrawDialogContent from "../WithdrawDialogContent"; export default function BtcTxInMempoolPageContent({ withdrawTxId, @@ -23,14 +23,6 @@ export default function BtcTxInMempoolPageContent({ additionalContent={null} /> - - - - ); } diff --git a/src-gui/src/renderer/components/modal/wallet/pages/InitiatedPage.tsx b/src-gui/src/renderer/components/modal/wallet/pages/InitiatedPage.tsx deleted file mode 100644 index 875737a3..00000000 --- a/src-gui/src/renderer/components/modal/wallet/pages/InitiatedPage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Button, DialogActions } from '@material-ui/core'; -import CircularProgressWithSubtitle from '../../swap/CircularProgressWithSubtitle'; -import WithdrawDialogContent from '../WithdrawDialogContent'; - -export default function InitiatedPage({ onCancel }: { onCancel: () => void }) { - return ( - <> - - - - - - - - - ); -} diff --git a/src-gui/src/renderer/rpc.ts b/src-gui/src/renderer/rpc.ts index 1d814702..ed5826a9 100644 --- a/src-gui/src/renderer/rpc.ts +++ b/src-gui/src/renderer/rpc.ts @@ -3,7 +3,6 @@ import { store } from "./store/storeRenderer"; import { rpcSetBalance, rpcSetSwapInfo } from "store/features/rpcSlice"; export async function checkBitcoinBalance() { - // TODO: use tauri-bindgen here const response = (await invoke("get_balance")) as { balance: number; }; @@ -16,3 +15,17 @@ export async function getRawSwapInfos() { (response as any[]).forEach((info) => store.dispatch(rpcSetSwapInfo(info))); } + +export async function withdrawBtc(address: string): Promise { + const response = (await invoke("withdraw_btc", { + args: { + address, + amount: null, + }, + })) as { + txid: string; + amount: number; + }; + + return response.txid; +} diff --git a/src-gui/src/store/config.ts b/src-gui/src/store/config.ts index 2c475a5e..036f7d70 100644 --- a/src-gui/src/store/config.ts +++ b/src-gui/src/store/config.ts @@ -1,18 +1,15 @@ -import { ExtendedProviderStatus } from 'models/apiModel'; +import { ExtendedProviderStatus } from "models/apiModel"; -export const isTestnet = () => - false +export const isTestnet = () => true; -export const isExternalRpc = () => - true +export const isExternalRpc = () => true; -export const isDevelopment = - true +export const isDevelopment = true; export function getStubTestnetProvider(): ExtendedProviderStatus | null { return null; } export const getPlatform = () => { - return 'mac'; + return "mac"; }; diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8f12d3bb..7fa632b3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "unstoppableswap-gui-rs" version = "0.0.0" -authors = ["binarybaron", "einliterflasche", "unstoppableswap"] +authors = [ "binarybaron", "einliterflasche", "unstoppableswap" ] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -9,15 +9,15 @@ description = "GUI for XMR<>BTC Atomic Swaps written in Rust" [lib] name = "unstoppableswap_gui_rs_lib" -crate-type = ["lib", "cdylib", "staticlib"] +crate-type = [ "lib", "cdylib", "staticlib" ] [build-dependencies] -tauri-build = { version = "2.0.0-rc.1", features = ["config-json5"] } +tauri-build = { version = "2.0.0-rc.1", features = [ "config-json5" ] } [dependencies] anyhow = "1" once_cell = "1" -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = [ "derive" ] } serde_json = "1" swap = { path = "../swap" } -tauri = { version = "2.0.0-rc.1", features = ["config-json5"] } +tauri = { version = "2.0.0-rc.1", features = [ "config-json5" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 336f7b71..d53a81e1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,20 +1,17 @@ -use std::sync::Arc; - -use once_cell::sync::OnceCell; use std::result::Result; +use std::sync::Arc; use swap::{ api::{ request::{ get_balance as get_balance_impl, get_swap_infos_all as get_swap_infos_all_impl, - BalanceArgs, BalanceResponse, GetSwapInfoResponse, + withdraw_btc as withdraw_btc_impl, BalanceArgs, BalanceResponse, GetSwapInfoResponse, + WithdrawBtcArgs, WithdrawBtcResponse, }, Context, }, cli::command::{Bitcoin, Monero}, }; - -// Lazy load the Context -static CONTEXT: OnceCell> = OnceCell::new(); +use tauri::{Manager, State}; trait ToStringResult { fn to_string_result(self) -> Result; @@ -31,24 +28,46 @@ impl ToStringResult for Result { } #[tauri::command] -async fn get_balance() -> Result { - let context = CONTEXT.get().unwrap(); - +async fn get_balance(context: State<'_, Arc>) -> Result { get_balance_impl( BalanceArgs { force_refresh: true, }, - context.clone(), + context.inner().clone(), ) .await .to_string_result() } #[tauri::command] -async fn get_swap_infos_all() -> Result, String> { - let context = CONTEXT.get().unwrap(); +async fn get_swap_infos_all( + context: State<'_, Arc>, +) -> Result, String> { + get_swap_infos_all_impl(context.inner().clone()) + .await + .to_string_result() +} - get_swap_infos_all_impl(context.clone()) +/*macro_rules! tauri_command { + ($command_name:ident, $command_args:ident, $command_response:ident) => { + #[tauri::command] + async fn $command_name( + context: State<'_, Context>, + args: $command_args, + ) -> Result<$command_response, String> { + swap::api::request::$command_name(args, context) + .await + .to_string_result() + } + }; +}*/ + +#[tauri::command] +async fn withdraw_btc( + context: State<'_, Arc>, + args: WithdrawBtcArgs, +) -> Result { + withdraw_btc_impl(args, context.inner().clone()) .await .to_string_result() } @@ -73,9 +92,7 @@ fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box> .await .unwrap(); - CONTEXT - .set(Arc::new(context)) - .expect("Failed to initialize cli context"); + app.manage(Arc::new(context)); }); Ok(()) @@ -84,7 +101,11 @@ fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box> #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() - .invoke_handler(tauri::generate_handler![get_balance, get_swap_infos_all]) + .invoke_handler(tauri::generate_handler![ + get_balance, + get_swap_infos_all, + withdraw_btc + ]) .setup(setup) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/swap/src/api/request.rs b/swap/src/api/request.rs index 12b0ad5a..93eaaebc 100644 --- a/swap/src/api/request.rs +++ b/swap/src/api/request.rs @@ -21,7 +21,7 @@ use std::future::Future; use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; -use tracing::{debug_span, field, Instrument, Span}; +use tracing::Instrument; use uuid::Uuid; #[derive(PartialEq, Debug)] @@ -53,9 +53,9 @@ pub struct MoneroRecoveryArgs { pub swap_id: Uuid, } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct WithdrawBtcArgs { - pub amount: Option, + pub amount: Option, pub address: bitcoin::Address, } @@ -119,6 +119,12 @@ pub struct GetSwapInfoResponse { pub timelock: Option, } +#[derive(Serialize, Deserialize, Debug)] +pub struct WithdrawBtcResponse { + amount: u64, + txid: String, +} + #[derive(Serialize, Deserialize)] pub struct Seller { pub peer_id: String, @@ -645,7 +651,7 @@ pub async fn get_config(context: Arc) -> Result { pub async fn withdraw_btc( withdraw_btc: WithdrawBtcArgs, context: Arc, -) -> Result { +) -> Result { let WithdrawBtcArgs { address, amount } = withdraw_btc; let bitcoin_wallet = context .bitcoin_wallet @@ -653,7 +659,7 @@ pub async fn withdraw_btc( .context("Could not get Bitcoin wallet")?; let amount = match amount { - Some(amount) => amount, + Some(amount) => Amount::from_sat(amount), None => { bitcoin_wallet .max_giveable(address.script_pubkey().len()) @@ -669,11 +675,10 @@ pub async fn withdraw_btc( .broadcast(signed_tx.clone(), "withdraw") .await?; - Ok(json!({ - "signed_tx": signed_tx, - "amount": amount.to_sat(), - "txid": signed_tx.txid(), - })) + Ok(WithdrawBtcResponse { + txid: signed_tx.txid().to_string(), + amount: amount.to_sat(), + }) } #[tracing::instrument(fields(method = "start_daemon"), skip(context))] @@ -848,17 +853,7 @@ impl Request { } } - async fn handle_cmd(self, context: Arc) -> Result> { - match self.cmd { - Method::Balance(args) => { - let response = get_balance(args, context).await?; - Ok(Box::new(response) as Box) - } - _ => todo!(), - } - } - - pub async fn call(self, context: Arc) -> Result { + pub async fn call(self, _: Arc) -> Result { unreachable!("This function should never be called") } } diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 530ae766..2c2b36e0 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -1,7 +1,6 @@ #![warn( unused_extern_crates, missing_copy_implementations, - rust_2018_idioms, clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::fallible_impl_from, @@ -12,8 +11,10 @@ #![forbid(unsafe_code)] #![allow(non_snake_case)] -use crate::cli::command::{parse_args_and_apply_defaults, ParseResult}; -use crate::common::check_latest_version; +use crate::{ + cli::command::{parse_args_and_apply_defaults, ParseResult}, + common::check_latest_version, +}; use anyhow::Result; use std::env; diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 6f7e8614..b00e65e7 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -1,8 +1,8 @@ use crate::api::request::{ buy_xmr, cancel_and_refund, export_bitcoin_wallet, get_balance, get_config, get_history, list_sellers, monero_recovery, resume_swap, start_daemon, withdraw_btc, BalanceArgs, - BuyXmrArgs, CancelAndRefundArgs, ListSellersArgs, Method, MoneroRecoveryArgs, Request, - ResumeArgs, StartDaemonArgs, WithdrawBtcArgs, + BuyXmrArgs, CancelAndRefundArgs, ListSellersArgs, MoneroRecoveryArgs, ResumeArgs, + StartDaemonArgs, WithdrawBtcArgs, }; use crate::api::Context; use crate::bitcoin::{bitcoin_address, Amount}; @@ -10,7 +10,6 @@ use crate::monero; use crate::monero::monero_address; use anyhow::Result; use libp2p::core::Multiaddr; -use serde_json::Value; use std::ffi::OsString; use std::net::SocketAddr; use std::path::PathBuf; @@ -193,7 +192,14 @@ where .await?, ); - withdraw_btc(WithdrawBtcArgs { amount, address }, context).await?; + withdraw_btc( + WithdrawBtcArgs { + amount: amount.map(Amount::to_sat), + address, + }, + context, + ) + .await?; Ok(()) } diff --git a/swap/src/rpc.rs b/swap/src/rpc.rs index dccd49d5..75ec9ae0 100644 --- a/swap/src/rpc.rs +++ b/swap/src/rpc.rs @@ -1,11 +1,11 @@ use crate::api::Context; -use std::{net::SocketAddr, sync::Arc}; +use std::net::SocketAddr; use thiserror::Error; use tower_http::cors::CorsLayer; use jsonrpsee::{ core::server::host_filtering::AllowHosts, - server::{RpcModule, ServerBuilder, ServerHandle}, + server::{ServerBuilder, ServerHandle}, }; pub mod methods; diff --git a/swap/src/rpc/methods.rs b/swap/src/rpc/methods.rs index ef17bad4..d50e029a 100644 --- a/swap/src/rpc/methods.rs +++ b/swap/src/rpc/methods.rs @@ -13,7 +13,6 @@ use jsonrpsee::server::RpcModule; use libp2p::core::Multiaddr; use std::collections::HashMap; use std::str::FromStr; -use std::sync::Arc; use uuid::Uuid; trait ConvertToJsonRpseeError { @@ -29,7 +28,7 @@ impl ConvertToJsonRpseeError for Result { pub fn register_modules(outer_context: Context) -> Result> { let mut module = RpcModule::new(outer_context); - module.register_async_method("suspend_current_swap", |params, context| async move { + module.register_async_method("suspend_current_swap", |_, context| async move { suspend_current_swap(context).await.to_jsonrpsee_result() })?; @@ -66,11 +65,11 @@ pub fn register_modules(outer_context: Context) -> Result> { .to_jsonrpsee_result() })?; - module.register_async_method("get_history", |params, context| async move { + module.register_async_method("get_history", |_, context| async move { get_history(context).await.to_jsonrpsee_result() })?; - module.register_async_method("get_raw_states", |params, context| async move { + module.register_async_method("get_raw_states", |_, context| async move { get_raw_states(context).await.to_jsonrpsee_result() })?; @@ -131,7 +130,8 @@ pub fn register_modules(outer_context: Context) -> Result> { ::bitcoin::Amount::from_str_in(amount_str, ::bitcoin::Denomination::Bitcoin) .map_err(|_| { jsonrpsee_core::Error::Custom("Unable to parse amount".to_string()) - })?, + })? + .to_sat(), ) } else { None @@ -224,7 +224,7 @@ pub fn register_modules(outer_context: Context) -> Result> { .to_jsonrpsee_result() })?; - module.register_async_method("get_current_swap", |params, context| async move { + module.register_async_method("get_current_swap", |_, context| async move { get_current_swap(context).await.to_jsonrpsee_result() })?;