mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-12-15 16:48:58 -05:00
feat(GUI): Add settings for theme, fiat currency and remote nodes (#128)
This commit is contained in:
parent
27d6e23b93
commit
3e79bb3712
37 changed files with 1133 additions and 267 deletions
|
|
@ -69,7 +69,7 @@ impl Wallet {
|
|||
err => err?,
|
||||
};
|
||||
|
||||
let client = Client::new(electrum_rpc_url, env_config.bitcoin_sync_interval())?;
|
||||
let client = Client::new(electrum_rpc_url, env_config.bitcoin_sync_interval(), 5)?;
|
||||
|
||||
let network = wallet.network();
|
||||
|
||||
|
|
@ -722,9 +722,9 @@ pub struct Client {
|
|||
}
|
||||
|
||||
impl Client {
|
||||
fn new(electrum_rpc_url: Url, interval: Duration) -> Result<Self> {
|
||||
pub fn new(electrum_rpc_url: Url, interval: Duration, retry_count: u8) -> Result<Self> {
|
||||
let config = bdk::electrum_client::ConfigBuilder::default()
|
||||
.retry(5)
|
||||
.retry(retry_count)
|
||||
.build();
|
||||
|
||||
let electrum = bdk::electrum_client::Client::from_config(electrum_rpc_url.as_str(), config)
|
||||
|
|
|
|||
|
|
@ -324,15 +324,6 @@ impl ContextBuilder {
|
|||
)
|
||||
.await?;
|
||||
|
||||
// If we are connected to the Bitcoin blockchain and if there is a handle to Tauri present,
|
||||
// we start a background task to watch for timelock changes.
|
||||
if let Some(wallet) = bitcoin_wallet.clone() {
|
||||
if self.tauri_handle.is_some() {
|
||||
let watcher = Watcher::new(wallet, db.clone(), self.tauri_handle.clone());
|
||||
tokio::spawn(watcher.run());
|
||||
}
|
||||
}
|
||||
|
||||
// We initialize the Monero wallet below
|
||||
// To display the progress to the user, we emit events to the Tauri frontend
|
||||
self.tauri_handle
|
||||
|
|
@ -360,6 +351,15 @@ impl ContextBuilder {
|
|||
|
||||
let tor_socks5_port = self.tor.map_or(9050, |tor| tor.tor_socks5_port);
|
||||
|
||||
// If we are connected to the Bitcoin blockchain and if there is a handle to Tauri present,
|
||||
// we start a background task to watch for timelock changes.
|
||||
if let Some(wallet) = bitcoin_wallet.clone() {
|
||||
if self.tauri_handle.is_some() {
|
||||
let watcher = Watcher::new(wallet, db.clone(), self.tauri_handle.clone());
|
||||
tokio::spawn(watcher.run());
|
||||
}
|
||||
}
|
||||
|
||||
let context = Context {
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
|
|
@ -473,6 +473,11 @@ async fn init_monero_wallet(
|
|||
|
||||
let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?;
|
||||
|
||||
tracing::debug!(
|
||||
address = monero_daemon_address,
|
||||
"Attempting to start monero-wallet-rpc process"
|
||||
);
|
||||
|
||||
let monero_wallet_rpc_process = monero_wallet_rpc
|
||||
.run(network, Some(monero_daemon_address))
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
use super::tauri_bindings::TauriHandle;
|
||||
use crate::bitcoin::{CancelTimelock, ExpiredTimelocks, PunishTimelock, TxLock};
|
||||
use crate::bitcoin::{wallet, CancelTimelock, ExpiredTimelocks, PunishTimelock, TxLock};
|
||||
use crate::cli::api::tauri_bindings::{TauriEmitter, TauriSwapProgressEvent};
|
||||
use crate::cli::api::Context;
|
||||
use crate::cli::{list_sellers as list_sellers_impl, EventLoop, Seller, SellerStatus};
|
||||
use crate::common::get_logs;
|
||||
use crate::libp2p_ext::MultiAddrExt;
|
||||
use crate::monero::wallet_rpc::MoneroDaemon;
|
||||
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||
use crate::network::swarm;
|
||||
use crate::protocol::bob::{BobState, Swap};
|
||||
use crate::protocol::{bob, State};
|
||||
use crate::{bitcoin, cli, monero, rpc};
|
||||
use ::bitcoin::Txid;
|
||||
use ::monero::Network;
|
||||
use anyhow::{bail, Context as AnyContext, Result};
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::PeerId;
|
||||
use once_cell::sync::Lazy;
|
||||
use qrcode::render::unicode;
|
||||
use qrcode::QrCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -25,6 +28,7 @@ use std::net::SocketAddr;
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::debug_span;
|
||||
use tracing::Instrument;
|
||||
use tracing::Span;
|
||||
|
|
@ -1294,3 +1298,77 @@ where
|
|||
|
||||
Ok((btc_swap_amount, fees))
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct CheckMoneroNodeArgs {
|
||||
pub url: String,
|
||||
pub network: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct CheckMoneroNodeResponse {
|
||||
pub available: bool,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("this is not one of the known monero networks")]
|
||||
struct UnknownMoneroNetwork(String);
|
||||
|
||||
impl CheckMoneroNodeArgs {
|
||||
pub async fn request(self) -> Result<CheckMoneroNodeResponse> {
|
||||
let network = match self.network.to_lowercase().as_str() {
|
||||
// When the GUI says testnet, it means monero stagenet
|
||||
"mainnet" => Network::Mainnet,
|
||||
"testnet" => Network::Stagenet,
|
||||
otherwise => anyhow::bail!(UnknownMoneroNetwork(otherwise.to_string())),
|
||||
};
|
||||
|
||||
static CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
||||
reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.https_only(false)
|
||||
.build()
|
||||
.expect("reqwest client to work")
|
||||
});
|
||||
|
||||
let Ok(monero_daemon) = MoneroDaemon::from_str(self.url, network) else {
|
||||
return Ok(CheckMoneroNodeResponse { available: false });
|
||||
};
|
||||
|
||||
let Ok(available) = monero_daemon.is_available(&CLIENT).await else {
|
||||
return Ok(CheckMoneroNodeResponse { available: false });
|
||||
};
|
||||
|
||||
Ok(CheckMoneroNodeResponse { available })
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct CheckElectrumNodeArgs {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct CheckElectrumNodeResponse {
|
||||
pub available: bool,
|
||||
}
|
||||
|
||||
impl CheckElectrumNodeArgs {
|
||||
pub async fn request(self) -> Result<CheckElectrumNodeResponse> {
|
||||
// Check if the URL is valid
|
||||
let Ok(url) = self.url.parse() else {
|
||||
return Ok(CheckElectrumNodeResponse { available: false });
|
||||
};
|
||||
|
||||
// Check if the node is available
|
||||
let res = wallet::Client::new(url, Duration::from_secs(10), 0);
|
||||
|
||||
Ok(CheckElectrumNodeResponse {
|
||||
available: res.is_ok(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
pub mod wallet;
|
||||
mod wallet_rpc;
|
||||
pub mod wallet_rpc;
|
||||
|
||||
pub use ::monero::network::Network;
|
||||
pub use ::monero::{Address, PrivateKey, PublicKey};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use big_bytes::BigByte;
|
|||
use data_encoding::HEXLOWER;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use monero_rpc::wallet::{Client, MoneroWalletRpc as _};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::header::CONTENT_LENGTH;
|
||||
use reqwest::Url;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -22,24 +23,26 @@ use tokio_util::io::StreamReader;
|
|||
|
||||
// See: https://www.moneroworld.com/#nodes, https://monero.fail
|
||||
// We don't need any testnet nodes because we don't support testnet at all
|
||||
const MONERO_DAEMONS: [MoneroDaemon; 16] = [
|
||||
MoneroDaemon::new("xmr-node.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("nodex.monerujo.io", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("nodes.hashvault.pro", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("p2pmd.xmrvsbeast.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.monerodevs.org", 18089, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr-node-usa-east.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr-node-uk.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.community.rino.io", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("testingjohnross.com", 20031, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr.litepay.ch", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.trocador.app", 18089, Network::Mainnet),
|
||||
MoneroDaemon::new("stagenet.xmr-tw.org", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("node.monerodevs.org", 38089, Network::Stagenet),
|
||||
MoneroDaemon::new("singapore.node.xmr.pm", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("xmr-lux.boldsuck.org", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("stagenet.community.rino.io", 38081, Network::Stagenet),
|
||||
];
|
||||
const MONERO_DAEMONS: Lazy<[MoneroDaemon; 16]> = Lazy::new(|| {
|
||||
[
|
||||
MoneroDaemon::new("xmr-node.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("nodex.monerujo.io", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("nodes.hashvault.pro", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("p2pmd.xmrvsbeast.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.monerodevs.org", 18089, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr-node-usa-east.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr-node-uk.cakewallet.com", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.community.rino.io", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("testingjohnross.com", 20031, Network::Mainnet),
|
||||
MoneroDaemon::new("xmr.litepay.ch", 18081, Network::Mainnet),
|
||||
MoneroDaemon::new("node.trocador.app", 18089, Network::Mainnet),
|
||||
MoneroDaemon::new("stagenet.xmr-tw.org", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("node.monerodevs.org", 38089, Network::Stagenet),
|
||||
MoneroDaemon::new("singapore.node.xmr.pm", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("xmr-lux.boldsuck.org", 38081, Network::Stagenet),
|
||||
MoneroDaemon::new("stagenet.community.rino.io", 38081, Network::Stagenet),
|
||||
]
|
||||
});
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
compile_error!("unsupported operating system");
|
||||
|
|
@ -87,26 +90,26 @@ pub struct WalletRpcProcess {
|
|||
port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct MoneroDaemon {
|
||||
address: &'static str,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MoneroDaemon {
|
||||
address: String,
|
||||
port: u16,
|
||||
network: Network,
|
||||
}
|
||||
|
||||
impl MoneroDaemon {
|
||||
const fn new(address: &'static str, port: u16, network: Network) -> Self {
|
||||
Self {
|
||||
address,
|
||||
pub fn new(address: impl Into<String>, port: u16, network: Network) -> MoneroDaemon {
|
||||
MoneroDaemon {
|
||||
address: address.into(),
|
||||
port,
|
||||
network,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(address: String, network: Network) -> Result<Self, Error> {
|
||||
let (address, port) = extract_host_and_port(address)?;
|
||||
pub fn from_str(address: impl Into<String>, network: Network) -> Result<MoneroDaemon, Error> {
|
||||
let (address, port) = extract_host_and_port(address.into())?;
|
||||
|
||||
Ok(Self {
|
||||
Ok(MoneroDaemon {
|
||||
address,
|
||||
port,
|
||||
network,
|
||||
|
|
@ -114,7 +117,7 @@ impl MoneroDaemon {
|
|||
}
|
||||
|
||||
/// 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<bool, Error> {
|
||||
pub async fn is_available(&self, client: &reqwest::Client) -> Result<bool, Error> {
|
||||
let url = format!("http://{}:{}/get_info", self.address, self.port);
|
||||
let res = client
|
||||
.get(url)
|
||||
|
|
@ -162,15 +165,14 @@ async fn choose_monero_daemon(network: Network) -> Result<MoneroDaemon, Error> {
|
|||
.build()?;
|
||||
|
||||
// We only want to check for daemons that match the specified network
|
||||
let network_matching_daemons = MONERO_DAEMONS
|
||||
.iter()
|
||||
.filter(|daemon| daemon.network == network);
|
||||
let daemons = &*MONERO_DAEMONS;
|
||||
let network_matching_daemons = daemons.iter().filter(|daemon| daemon.network == network);
|
||||
|
||||
for daemon in network_matching_daemons {
|
||||
match daemon.is_available(&client).await {
|
||||
Ok(true) => {
|
||||
tracing::debug!(%daemon, "Found available Monero daemon");
|
||||
return Ok(*daemon);
|
||||
return Ok(daemon.clone());
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::debug!(%err, %daemon, "Failed to connect to Monero daemon");
|
||||
|
|
@ -402,7 +404,6 @@ impl WalletRpc {
|
|||
line?;
|
||||
}
|
||||
|
||||
// Send a json rpc request to make sure monero_wallet_rpc is ready
|
||||
Client::localhost(port)?.get_version().await?;
|
||||
|
||||
Ok(WalletRpcProcess {
|
||||
|
|
@ -486,7 +487,7 @@ impl WalletRpc {
|
|||
}
|
||||
}
|
||||
|
||||
fn extract_host_and_port(address: String) -> Result<(&'static str, u16), Error> {
|
||||
fn extract_host_and_port(address: String) -> Result<(String, u16), Error> {
|
||||
// Strip the protocol (anything before "://")
|
||||
let stripped_address = if let Some(pos) = address.find("://") {
|
||||
address[(pos + 3)..].to_string()
|
||||
|
|
@ -501,9 +502,7 @@ fn extract_host_and_port(address: String) -> Result<(&'static str, u16), Error>
|
|||
let host = parts[0].to_string();
|
||||
let port = parts[1].parse::<u16>()?;
|
||||
|
||||
// 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));
|
||||
return Ok((host, port));
|
||||
}
|
||||
|
||||
bail!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue