diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2e50a840b..c82f2f870 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,14 +1,14 @@ use std::result::Result; use std::sync::Arc; -use swap::{ - cli::api::{ +use swap::cli::{ + api::{ request::{ BalanceArgs, BuyXmrArgs, GetHistoryArgs, ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs, }, - Context, + Context, ContextBuilder, }, - cli::command::{Bitcoin, Monero}, + command::{Bitcoin, Monero}, }; use tauri::{Manager, RunEvent}; @@ -63,25 +63,20 @@ tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs); fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box> { tauri::async_runtime::block_on(async { - let context = Context::build( - Some(Bitcoin { + let context = ContextBuilder::new(true) + .with_bitcoin(Bitcoin { bitcoin_electrum_rpc_url: None, bitcoin_target_block: None, - }), - Some(Monero { + }) + .with_monero(Monero { monero_daemon_address: None, - }), - None, - None, - true, - true, - true, - None, - ) - .await - .unwrap() - .with_tauri_handle(app.app_handle().to_owned()); - + }) + .with_json(true) + .with_debug(true) + .with_tauri(app.app_handle().to_owned()) + .build() + .await + .expect("failed to create context"); app.manage(Arc::new(context)); }); diff --git a/swap/src/cli/api.rs b/swap/src/cli/api.rs index dbb86f1e8..fbe5ab5c5 100644 --- a/swap/src/cli/api.rs +++ b/swap/src/cli/api.rs @@ -12,7 +12,6 @@ use anyhow::{anyhow, bail, Context as AnyContext, Error, Result}; use futures::future::try_join_all; use std::fmt; use std::future::Future; -use std::net::SocketAddr; use std::path::PathBuf; use std::sync::{Arc, Mutex as SyncMutex, Once}; use tauri::AppHandle; @@ -28,7 +27,6 @@ static START: Once = Once::new(); pub struct Config { tor_socks5_port: u16, namespace: XmrBtcNamespace, - server_address: Option, pub env_config: EnvConfig, seed: Option, debug: bool, @@ -184,32 +182,109 @@ pub struct Context { monero_rpc_process: Option>>, } -#[allow(clippy::too_many_arguments)] -impl Context { - pub async fn build( - bitcoin: Option, - monero: Option, - tor: Option, - data: Option, - is_testnet: bool, - debug: bool, - json: bool, - server_address: Option, - ) -> Result { - let data_dir = data::data_dir_from(data, is_testnet)?; - let env_config = env_config_from(is_testnet); +/// A conveniant builder struct for [`Context`]. +#[derive(Debug)] +#[must_use = "ContextBuilder must be built to be useful"] +pub struct ContextBuilder { + monero: Option, + bitcoin: Option, + tor: Option, + data: Option, + is_testnet: bool, + debug: bool, + json: bool, + tauri_handle: Option, +} + +impl ContextBuilder { + /// Start building a context + pub fn new(is_testnet: bool) -> Self { + if is_testnet { + Self::testnet() + } else { + Self::mainnet() + } + } + + /// Basic builder with default options for mainnet + pub fn mainnet() -> Self { + ContextBuilder { + monero: None, + bitcoin: None, + tor: None, + data: None, + is_testnet: false, + debug: false, + json: false, + tauri_handle: None, + } + } + + /// Basic builder with default options for testnet + pub fn testnet() -> Self { + let mut builder = Self::mainnet(); + builder.is_testnet = true; + builder + } + + /// Configures the Context to initialize a Monero wallet with the given configuration. + pub fn with_monero(mut self, monero: impl Into>) -> Self { + self.monero = monero.into(); + self + } + + /// Configures the Context to initialize a Bitcoin wallet with the given configuration. + pub fn with_bitcoin(mut self, bitcoin: impl Into>) -> Self { + self.bitcoin = bitcoin.into(); + self + } + + /// Configures the Context to use Tor with the given configuration. + pub fn with_tor(mut self, tor: impl Into>) -> Self { + self.tor = tor.into(); + self + } + + /// Attach a handle to Tauri to the Context for emitting events etc. + pub fn with_tauri(mut self, tauri: impl Into>) -> Self { + self.tauri_handle = tauri.into(); + self + } + + /// Configures where the data and logs are saved in the filesystem + pub fn with_data_dir(mut self, data: impl Into>) -> Self { + self.data = data.into(); + self + } + + /// Whether to include debug level logging messages (default false) + pub fn with_debug(mut self, debug: bool) -> Self { + self.debug = debug; + self + } + + /// Set logging format to json (default false) + pub fn with_json(mut self, json: bool) -> Self { + self.json = json; + self + } + + /// Takes the builder, initializes the context by initializing the wallets and other components and returns the Context. + pub async fn build(self) -> Result { + let data_dir = data::data_dir_from(self.data, self.is_testnet)?; + let env_config = env_config_from(self.is_testnet); START.call_once(|| { - let _ = cli::tracing::init(debug, json, data_dir.join("logs")); + let _ = cli::tracing::init(self.debug, self.json, data_dir.join("logs")); }); let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read seed in file")?; let bitcoin_wallet = { - if let Some(bitcoin) = bitcoin { + if let Some(bitcoin) = self.bitcoin { let (bitcoin_electrum_rpc_url, bitcoin_target_block) = - bitcoin.apply_defaults(is_testnet)?; + bitcoin.apply_defaults(self.is_testnet)?; Some(Arc::new( init_bitcoin_wallet( bitcoin_electrum_rpc_url, @@ -226,8 +301,8 @@ impl Context { }; let (monero_wallet, monero_rpc_process) = { - if let Some(monero) = monero { - let monero_daemon_address = monero.apply_defaults(is_testnet); + if let Some(monero) = self.monero { + let monero_daemon_address = monero.apply_defaults(self.is_testnet); let (wlt, prc) = init_monero_wallet(data_dir.clone(), monero_daemon_address, env_config).await?; (Some(Arc::new(wlt)), Some(Arc::new(SyncMutex::new(prc)))) @@ -236,7 +311,7 @@ impl Context { } }; - let tor_socks5_port = tor.map_or(9050, |tor| tor.tor_socks5_port); + let tor_socks5_port = self.tor.map_or(9050, |tor| tor.tor_socks5_port); let context = Context { db: open_db(data_dir.join("sqlite")).await?, @@ -245,23 +320,24 @@ impl Context { monero_rpc_process, config: Config { tor_socks5_port, - namespace: XmrBtcNamespace::from_is_testnet(is_testnet), + namespace: XmrBtcNamespace::from_is_testnet(self.is_testnet), env_config, seed: Some(seed), - server_address, - debug, - json, - is_testnet, + debug: self.debug, + json: self.json, + is_testnet: self.is_testnet, data_dir, }, swap_lock: Arc::new(SwapLock::new()), tasks: Arc::new(PendingTaskList::default()), - tauri_handle: None, + tauri_handle: self.tauri_handle.map(TauriHandle::new), }; Ok(context) } +} +impl Context { pub fn with_tauri_handle(mut self, tauri_handle: AppHandle) -> Self { self.tauri_handle = Some(TauriHandle::new(tauri_handle)); @@ -394,7 +470,6 @@ impl Config { Self { tor_socks5_port: 9050, namespace: XmrBtcNamespace::from_is_testnet(false), - server_address: None, env_config, seed: Some(seed), debug: false, diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index d747d4b04..f797c9465 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -18,6 +18,8 @@ use structopt::{clap, StructOpt}; use url::Url; use uuid::Uuid; +use super::api::ContextBuilder; + // See: https://moneroworld.com/ pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.community.rino.io:18081"; pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.community.rino.io:38081"; @@ -79,17 +81,15 @@ where bitcoin_address::validate_is_testnet(bitcoin_change_address, is_testnet)?; let context = Arc::new( - Context::build( - Some(bitcoin), - Some(monero), - Some(tor), - data, - is_testnet, - debug, - json, - None, - ) - .await?, + ContextBuilder::new(is_testnet) + .with_bitcoin(bitcoin) + .with_monero(monero) + .with_tor(tor) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); BuyXmrArgs { @@ -104,7 +104,12 @@ where } CliCommand::History => { let context = Arc::new( - Context::build(None, None, None, data, is_testnet, debug, json, None).await?, + ContextBuilder::new(is_testnet) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); GetHistoryArgs {}.request(context.clone()).await?; @@ -113,7 +118,12 @@ where } CliCommand::Config => { let context = Arc::new( - Context::build(None, None, None, data, is_testnet, debug, json, None).await?, + ContextBuilder::new(is_testnet) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); GetConfigArgs {}.request(context.clone()).await?; @@ -122,17 +132,13 @@ where } CliCommand::Balance { bitcoin } => { let context = Arc::new( - Context::build( - Some(bitcoin), - None, - None, - data, - is_testnet, - debug, - json, - None, - ) - .await?, + ContextBuilder::new(is_testnet) + .with_bitcoin(bitcoin) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); BalanceArgs { @@ -150,17 +156,15 @@ where tor, } => { let context = Arc::new( - Context::build( - Some(bitcoin), - Some(monero), - Some(tor), - data, - is_testnet, - debug, - json, - server_address, - ) - .await?, + ContextBuilder::new(is_testnet) + .with_bitcoin(bitcoin) + .with_monero(monero) + .with_tor(tor) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); StartDaemonArgs { server_address } @@ -177,17 +181,13 @@ where let address = bitcoin_address::validate_is_testnet(address, is_testnet)?; let context = Arc::new( - Context::build( - Some(bitcoin), - None, - None, - data, - is_testnet, - debug, - json, - None, - ) - .await?, + ContextBuilder::new(is_testnet) + .with_bitcoin(bitcoin) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); WithdrawBtcArgs { amount, address } @@ -203,17 +203,15 @@ where tor, } => { let context = Arc::new( - Context::build( - Some(bitcoin), - Some(monero), - Some(tor), - data, - is_testnet, - debug, - json, - None, - ) - .await?, + ContextBuilder::new(is_testnet) + .with_bitcoin(bitcoin) + .with_monero(monero) + .with_tor(tor) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); ResumeSwapArgs { swap_id }.request(context.clone()).await?; @@ -226,17 +224,14 @@ where tor, } => { let context = Arc::new( - Context::build( - Some(bitcoin), - None, - Some(tor), - data, - is_testnet, - debug, - json, - None, - ) - .await?, + ContextBuilder::new(is_testnet) + .with_bitcoin(bitcoin) + .with_tor(tor) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); CancelAndRefundArgs { swap_id } @@ -250,7 +245,13 @@ where tor, } => { let context = Arc::new( - Context::build(None, None, Some(tor), data, is_testnet, debug, json, None).await?, + ContextBuilder::new(is_testnet) + .with_tor(tor) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); ListSellersArgs { rendezvous_point } @@ -261,17 +262,13 @@ where } CliCommand::ExportBitcoinWallet { bitcoin } => { let context = Arc::new( - Context::build( - Some(bitcoin), - None, - None, - data, - is_testnet, - debug, - json, - None, - ) - .await?, + ContextBuilder::new(is_testnet) + .with_bitcoin(bitcoin) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); ExportBitcoinWalletArgs {}.request(context.clone()).await?; @@ -282,7 +279,12 @@ where swap_id: SwapId { swap_id }, } => { let context = Arc::new( - Context::build(None, None, None, data, is_testnet, debug, json, None).await?, + ContextBuilder::new(is_testnet) + .with_data_dir(data) + .with_debug(debug) + .with_json(json) + .build() + .await?, ); MoneroRecoveryArgs { swap_id }