Add liana remote backend
This commit is contained in:
parent
ca662eea6a
commit
5e2fe3b6c1
@ -29,10 +29,10 @@ impl std::fmt::Display for Error {
|
||||
DaemonError::Unexpected(e) => write!(f, "{}", e),
|
||||
DaemonError::NoAnswer => write!(f, "Daemon did not answer"),
|
||||
DaemonError::DaemonStopped => write!(f, "Daemon stopped"),
|
||||
DaemonError::Transport(Some(ErrorKind::ConnectionRefused), _) => {
|
||||
DaemonError::RpcSocket(Some(ErrorKind::ConnectionRefused), _) => {
|
||||
write!(f, "Failed to connect to daemon")
|
||||
}
|
||||
DaemonError::Transport(kind, e) => {
|
||||
DaemonError::RpcSocket(kind, e) => {
|
||||
if let Some(k) = kind {
|
||||
write!(f, "{} [{:?}]", e, k)
|
||||
} else {
|
||||
@ -48,6 +48,9 @@ impl std::fmt::Display for Error {
|
||||
DaemonError::Rpc(code, e) => {
|
||||
write!(f, "[{:?}] {}", code, e)
|
||||
}
|
||||
DaemonError::Http(code, e) => {
|
||||
write!(f, "[{:?}] {}", code, e)
|
||||
}
|
||||
DaemonError::CoinSelectionError => write!(f, "{}", e),
|
||||
},
|
||||
Self::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||
|
||||
@ -23,8 +23,7 @@ pub enum Message {
|
||||
View(view::Message),
|
||||
LoadDaemonConfig(Box<DaemonConfig>),
|
||||
DaemonConfigLoaded(Result<(), Error>),
|
||||
LoadWallet,
|
||||
WalletLoaded(Result<Arc<Wallet>, Error>),
|
||||
LoadWallet(Wallet),
|
||||
Info(Result<GetInfoResult, Error>),
|
||||
ReceiveAddress(Result<(Address, ChildNumber), Error>),
|
||||
Coins(Result<Vec<Coin>, Error>),
|
||||
@ -34,7 +33,7 @@ pub enum Message {
|
||||
RbfPsbt(Result<Txid, Error>),
|
||||
Recovery(Result<SpendTx, Error>),
|
||||
Signed(Fingerprint, Result<Psbt, Error>),
|
||||
WalletRegistered(Result<Fingerprint, Error>),
|
||||
WalletUpdated(Result<Arc<Wallet>, Error>),
|
||||
Updated(Result<(), Error>),
|
||||
Saved(Result<(), Error>),
|
||||
Verified(Fingerprint, Result<(), Error>),
|
||||
|
||||
@ -36,7 +36,7 @@ use state::{
|
||||
use crate::{
|
||||
app::{cache::Cache, error::Error, menu::Menu, wallet::Wallet},
|
||||
bitcoind::Bitcoind,
|
||||
daemon::{embedded::EmbeddedDaemon, Daemon},
|
||||
daemon::{embedded::EmbeddedDaemon, Daemon, DaemonBackend},
|
||||
};
|
||||
|
||||
use self::state::SettingsState;
|
||||
@ -116,7 +116,6 @@ impl Panels {
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
data_dir: PathBuf,
|
||||
cache: Cache,
|
||||
config: Config,
|
||||
wallet: Arc<Wallet>,
|
||||
@ -135,17 +134,11 @@ impl App {
|
||||
data_dir: PathBuf,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
) -> (App, Command<Message>) {
|
||||
let mut panels = Panels::new(
|
||||
&cache,
|
||||
wallet.clone(),
|
||||
data_dir.clone(),
|
||||
internal_bitcoind.as_ref(),
|
||||
);
|
||||
let mut panels = Panels::new(&cache, wallet.clone(), data_dir, internal_bitcoind.as_ref());
|
||||
let cmd = panels.home.reload(daemon.clone(), wallet.clone());
|
||||
(
|
||||
Self {
|
||||
panels,
|
||||
data_dir,
|
||||
cache,
|
||||
config,
|
||||
daemon,
|
||||
@ -218,14 +211,26 @@ impl App {
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::batch(vec![
|
||||
time::every(Duration::from_secs(10)).map(|_| Message::Tick),
|
||||
time::every(Duration::from_secs(
|
||||
// LianaLite has no rescan feature, the cache refresh loop is only
|
||||
// to fetch the new block height tip which is only used to warn user
|
||||
// about recovery availability.
|
||||
if self.daemon.backend() == DaemonBackend::RemoteBackend {
|
||||
120
|
||||
// For the rescan feature, we set a higher frequency of cache refresh
|
||||
// to give to user an up-to-date view of the rescan progress.
|
||||
} else {
|
||||
10
|
||||
},
|
||||
))
|
||||
.map(|_| Message::Tick),
|
||||
self.panels.current().subscription(),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
info!("Close requested");
|
||||
if !self.daemon.is_external() {
|
||||
if self.daemon.backend() == DaemonBackend::EmbeddedLianad {
|
||||
if let Err(e) = Handle::current().block_on(async { self.daemon.stop().await }) {
|
||||
error!("{}", e);
|
||||
} else {
|
||||
@ -245,6 +250,7 @@ impl App {
|
||||
Command::perform(
|
||||
async move {
|
||||
// we check every 10 second if the daemon poller is alive
|
||||
// or if the access token is not expired.
|
||||
daemon.is_alive().await?;
|
||||
|
||||
let info = daemon.get_info().await?;
|
||||
@ -276,9 +282,13 @@ impl App {
|
||||
let res = self.load_daemon_config(&path, *cfg);
|
||||
self.update(Message::DaemonConfigLoaded(res))
|
||||
}
|
||||
Message::LoadWallet => {
|
||||
let res = self.load_wallet();
|
||||
self.update(Message::WalletLoaded(res))
|
||||
Message::WalletUpdated(Ok(wallet)) => {
|
||||
self.wallet = wallet.clone();
|
||||
self.panels.current_mut().update(
|
||||
self.daemon.clone(),
|
||||
&self.cache,
|
||||
Message::WalletUpdated(Ok(wallet)),
|
||||
)
|
||||
}
|
||||
Message::View(view::Message::Menu(menu)) => self.set_current_panel(menu),
|
||||
Message::View(view::Message::Clipboard(text)) => clipboard::write(text),
|
||||
@ -313,15 +323,6 @@ impl App {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_wallet(&mut self) -> Result<Arc<Wallet>, Error> {
|
||||
let wallet = Wallet::new(self.wallet.main_descriptor.clone())
|
||||
.load_settings(&self.data_dir, self.cache.network)?;
|
||||
|
||||
self.wallet = Arc::new(wallet);
|
||||
|
||||
Ok(self.wallet.clone())
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
let content = self.panels.current().view(&self.cache).map(Message::View);
|
||||
if self.cache.network != bitcoin::Network::Bitcoin {
|
||||
|
||||
@ -14,7 +14,7 @@ use wallet::WalletSettingsState;
|
||||
|
||||
use crate::{
|
||||
app::{cache::Cache, error::Error, message::Message, state::State, view, wallet::Wallet},
|
||||
daemon::Daemon,
|
||||
daemon::{Daemon, DaemonBackend},
|
||||
};
|
||||
|
||||
pub struct SettingsState {
|
||||
@ -48,7 +48,7 @@ impl State for SettingsState {
|
||||
BitcoindSettingsState::new(
|
||||
daemon.config().cloned(),
|
||||
cache,
|
||||
daemon.is_external(),
|
||||
daemon.backend() != DaemonBackend::EmbeddedLianad,
|
||||
self.internal_bitcoind,
|
||||
)
|
||||
.into(),
|
||||
|
||||
@ -16,7 +16,7 @@ use crate::{
|
||||
app::{
|
||||
cache::Cache, error::Error, message::Message, settings, state::State, view, wallet::Wallet,
|
||||
},
|
||||
daemon::Daemon,
|
||||
daemon::{Daemon, DaemonBackend},
|
||||
hw::{HardwareWallet, HardwareWalletConfig, HardwareWallets},
|
||||
};
|
||||
|
||||
@ -106,30 +106,21 @@ impl State for WalletSettingsState {
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
match message {
|
||||
Message::Updated(res) => match res {
|
||||
Ok(()) => {
|
||||
self.processing = false;
|
||||
self.updated = true;
|
||||
Command::perform(async {}, |_| Message::LoadWallet)
|
||||
}
|
||||
Err(e) => {
|
||||
self.processing = false;
|
||||
self.warning = Some(e);
|
||||
Message::WalletUpdated(res) => {
|
||||
self.processing = false;
|
||||
if let Some(modal) = &mut self.modal {
|
||||
modal.update(daemon, cache, Message::WalletUpdated(res))
|
||||
} else {
|
||||
match res {
|
||||
Ok(wallet) => {
|
||||
self.keys_aliases = Self::keys_aliases(&wallet);
|
||||
self.wallet = wallet;
|
||||
self.updated = true;
|
||||
}
|
||||
Err(e) => self.warning = Some(e),
|
||||
};
|
||||
Command::none()
|
||||
}
|
||||
},
|
||||
Message::WalletLoaded(res) => {
|
||||
match res {
|
||||
Ok(wallet) => {
|
||||
if let Some(modal) = &mut self.modal {
|
||||
modal.wallet = wallet.clone();
|
||||
}
|
||||
self.keys_aliases = Self::keys_aliases(&wallet);
|
||||
self.wallet = wallet;
|
||||
}
|
||||
Err(e) => self.warning = Some(e),
|
||||
};
|
||||
Command::none()
|
||||
}
|
||||
Message::View(view::Message::Settings(
|
||||
view::SettingsMessage::FingerprintAliasEdited(fg, value),
|
||||
@ -156,8 +147,9 @@ impl State for WalletSettingsState {
|
||||
.iter()
|
||||
.map(|(fg, name)| (*fg, name.value.to_owned()))
|
||||
.collect(),
|
||||
daemon,
|
||||
),
|
||||
Message::Updated,
|
||||
Message::WalletUpdated,
|
||||
)
|
||||
}
|
||||
Message::View(view::Message::Close) => {
|
||||
@ -246,7 +238,7 @@ impl RegisterWalletModal {
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
cache: &Cache,
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
@ -263,13 +255,16 @@ impl RegisterWalletModal {
|
||||
Command::none()
|
||||
}
|
||||
},
|
||||
Message::WalletRegistered(res) => {
|
||||
Message::WalletUpdated(res) => {
|
||||
self.processing = false;
|
||||
self.chosen_hw = None;
|
||||
match res {
|
||||
Ok(fingerprint) => {
|
||||
self.registered.insert(fingerprint);
|
||||
return Command::perform(async {}, |_| Message::LoadWallet);
|
||||
Ok(wallet) => {
|
||||
self.registered = HashSet::new();
|
||||
for hw in &wallet.hardware_wallets {
|
||||
self.registered.insert(hw.fingerprint);
|
||||
}
|
||||
self.wallet = wallet;
|
||||
}
|
||||
Err(e) => {
|
||||
if !matches!(e, Error::HardwareWallet(async_hwi::Error::UserRefused)) {
|
||||
@ -295,8 +290,9 @@ impl RegisterWalletModal {
|
||||
device.clone(),
|
||||
*fingerprint,
|
||||
self.wallet.clone(),
|
||||
daemon,
|
||||
),
|
||||
Message::WalletRegistered,
|
||||
Message::WalletUpdated,
|
||||
)
|
||||
} else {
|
||||
Command::none()
|
||||
@ -313,40 +309,61 @@ async fn register_wallet(
|
||||
hw: std::sync::Arc<dyn async_hwi::HWI + Send + Sync>,
|
||||
fingerprint: Fingerprint,
|
||||
wallet: Arc<Wallet>,
|
||||
) -> Result<Fingerprint, Error> {
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
) -> Result<Arc<Wallet>, Error> {
|
||||
let hmac = hw
|
||||
.register_wallet(&wallet.name, &wallet.main_descriptor.to_string())
|
||||
.await
|
||||
.map_err(Error::from)?;
|
||||
|
||||
if let Some(hmac) = hmac {
|
||||
let mut settings = settings::Settings::from_file(data_dir.clone(), network)?;
|
||||
let checksum = wallet.descriptor_checksum();
|
||||
if let Some(wallet_setting) = settings
|
||||
.wallets
|
||||
.iter_mut()
|
||||
.find(|w| w.descriptor_checksum == checksum)
|
||||
{
|
||||
let kind = hw.device_kind().to_string();
|
||||
if let Some(hw_config) = wallet_setting
|
||||
.hardware_wallets
|
||||
let kind = hw.device_kind().to_string();
|
||||
let hw_cfg = HardwareWalletConfig {
|
||||
kind: kind.clone(),
|
||||
token: hex::encode(hmac),
|
||||
fingerprint,
|
||||
};
|
||||
|
||||
if daemon.backend() != DaemonBackend::RemoteBackend {
|
||||
let mut settings = settings::Settings::from_file(data_dir.clone(), network)?;
|
||||
let checksum = wallet.descriptor_checksum();
|
||||
|
||||
if let Some(wallet_setting) = settings
|
||||
.wallets
|
||||
.iter_mut()
|
||||
.find(|cfg| cfg.kind == kind && cfg.fingerprint == fingerprint)
|
||||
.find(|w| w.descriptor_checksum == checksum)
|
||||
{
|
||||
hw_config.token = hex::encode(hmac);
|
||||
} else {
|
||||
wallet_setting.hardware_wallets.push(HardwareWalletConfig {
|
||||
kind,
|
||||
token: hex::encode(hmac),
|
||||
fingerprint,
|
||||
})
|
||||
if let Some(hw_config) = wallet_setting
|
||||
.hardware_wallets
|
||||
.iter_mut()
|
||||
.find(|cfg| cfg.kind == kind && cfg.fingerprint == fingerprint)
|
||||
{
|
||||
*hw_config = hw_cfg.clone();
|
||||
} else {
|
||||
wallet_setting.hardware_wallets.push(hw_cfg.clone())
|
||||
}
|
||||
}
|
||||
|
||||
settings.to_file(data_dir, network)?;
|
||||
}
|
||||
|
||||
settings.to_file(data_dir, network)?;
|
||||
let mut wallet = wallet.as_ref().clone();
|
||||
if let Some(hw_config) = wallet
|
||||
.hardware_wallets
|
||||
.iter_mut()
|
||||
.find(|cfg| cfg.kind == kind && cfg.fingerprint == fingerprint)
|
||||
{
|
||||
*hw_config = hw_cfg.clone();
|
||||
} else {
|
||||
wallet.hardware_wallets.push(hw_cfg)
|
||||
}
|
||||
daemon
|
||||
.update_wallet_metadata(&wallet.keys_aliases, &wallet.hardware_wallets)
|
||||
.await?;
|
||||
return Ok(Arc::new(wallet));
|
||||
}
|
||||
|
||||
Ok(fingerprint)
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
async fn update_keys_aliases(
|
||||
@ -354,24 +371,34 @@ async fn update_keys_aliases(
|
||||
network: Network,
|
||||
wallet: Arc<Wallet>,
|
||||
keys_aliases: Vec<(Fingerprint, String)>,
|
||||
) -> Result<(), Error> {
|
||||
let mut settings = settings::Settings::from_file(data_dir.clone(), network)?;
|
||||
let checksum = wallet.descriptor_checksum();
|
||||
if let Some(wallet_setting) = settings
|
||||
.wallets
|
||||
.iter_mut()
|
||||
.find(|w| w.descriptor_checksum == checksum)
|
||||
{
|
||||
wallet_setting.keys = keys_aliases
|
||||
.into_iter()
|
||||
.map(|(master_fingerprint, name)| settings::KeySetting {
|
||||
master_fingerprint,
|
||||
name,
|
||||
})
|
||||
.collect();
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
) -> Result<Arc<Wallet>, Error> {
|
||||
if daemon.backend() != DaemonBackend::RemoteBackend {
|
||||
let mut settings = settings::Settings::from_file(data_dir.clone(), network)?;
|
||||
let checksum = wallet.descriptor_checksum();
|
||||
if let Some(wallet_setting) = settings
|
||||
.wallets
|
||||
.iter_mut()
|
||||
.find(|w| w.descriptor_checksum == checksum)
|
||||
{
|
||||
wallet_setting.keys = keys_aliases
|
||||
.iter()
|
||||
.map(|(master_fingerprint, name)| settings::KeySetting {
|
||||
master_fingerprint: *master_fingerprint,
|
||||
name: name.clone(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
settings.to_file(data_dir, network)?;
|
||||
}
|
||||
|
||||
settings.to_file(data_dir, network)?;
|
||||
let mut wallet = wallet.as_ref().clone();
|
||||
wallet.keys_aliases = keys_aliases.into_iter().collect();
|
||||
|
||||
Ok(())
|
||||
daemon
|
||||
.update_wallet_metadata(&wallet.keys_aliases, &wallet.hardware_wallets)
|
||||
.await?;
|
||||
|
||||
Ok(Arc::new(wallet))
|
||||
}
|
||||
|
||||
@ -25,12 +25,16 @@ impl From<&Error> for WarningMessage {
|
||||
WarningMessage("Internal error".to_string())
|
||||
}
|
||||
}
|
||||
DaemonError::Http(Some(code), error) => {
|
||||
WarningMessage(format!("HTTP error {}: {}", code, error))
|
||||
}
|
||||
DaemonError::Http(None, error) => WarningMessage(format!("HTTP error: {}", error)),
|
||||
DaemonError::Unexpected(_) => WarningMessage("Unknown error".to_string()),
|
||||
DaemonError::Start(_) => WarningMessage("Daemon failed to start".to_string()),
|
||||
DaemonError::ClientNotSupported => {
|
||||
WarningMessage("Daemon client is not supported".to_string())
|
||||
}
|
||||
DaemonError::NoAnswer | DaemonError::Transport(..) => {
|
||||
DaemonError::NoAnswer | DaemonError::RpcSocket(..) => {
|
||||
WarningMessage("Communication with Daemon failed".to_string())
|
||||
}
|
||||
DaemonError::DaemonStopped => WarningMessage("Daemon stopped".to_string()),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{app::settings, hw::HardwareWalletConfig, signer::Signer};
|
||||
|
||||
@ -24,13 +25,13 @@ pub fn wallet_name(main_descriptor: &LianaDescriptor) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Wallet {
|
||||
pub name: String,
|
||||
pub main_descriptor: LianaDescriptor,
|
||||
pub keys_aliases: HashMap<Fingerprint, String>,
|
||||
pub hardware_wallets: Vec<HardwareWalletConfig>,
|
||||
pub signer: Option<Signer>,
|
||||
pub signer: Option<Arc<Signer>>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
@ -60,7 +61,7 @@ impl Wallet {
|
||||
}
|
||||
|
||||
pub fn with_signer(mut self, signer: Signer) -> Self {
|
||||
self.signer = Some(signer);
|
||||
self.signer = Some(Arc::new(signer));
|
||||
self
|
||||
}
|
||||
|
||||
@ -87,12 +88,12 @@ impl Wallet {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn load_settings(
|
||||
pub fn load_from_settings(
|
||||
self,
|
||||
datadir_path: &Path,
|
||||
network: bitcoin::Network,
|
||||
) -> Result<Self, WalletError> {
|
||||
let mut wallet = match settings::Settings::from_file(datadir_path.to_path_buf(), network) {
|
||||
let wallet = match settings::Settings::from_file(datadir_path.to_path_buf(), network) {
|
||||
Ok(settings) => {
|
||||
if let Some(wallet_setting) = settings.wallets.first() {
|
||||
self.with_name(wallet_setting.name.clone())
|
||||
@ -114,6 +115,14 @@ impl Wallet {
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
pub fn load_hotsigners(
|
||||
self,
|
||||
datadir_path: &Path,
|
||||
network: bitcoin::Network,
|
||||
) -> Result<Self, WalletError> {
|
||||
let hot_signers = match HotSigner::from_datadir(datadir_path, network) {
|
||||
Ok(signers) => signers,
|
||||
Err(e) => match e {
|
||||
@ -129,15 +138,15 @@ impl Wallet {
|
||||
};
|
||||
|
||||
let curve = bitcoin::secp256k1::Secp256k1::signing_only();
|
||||
let keys = wallet.descriptor_keys();
|
||||
let keys = self.descriptor_keys();
|
||||
if let Some(hot_signer) = hot_signers
|
||||
.into_iter()
|
||||
.find(|s| keys.contains(&s.fingerprint(&curve)))
|
||||
{
|
||||
wallet = wallet.with_signer(Signer::new(hot_signer));
|
||||
Ok(self.with_signer(Signer::new(hot_signer)))
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
Ok(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -240,13 +240,13 @@ impl error::Error for Error {
|
||||
impl From<Error> for super::DaemonError {
|
||||
fn from(e: Error) -> super::DaemonError {
|
||||
match e {
|
||||
Error::Io(e) => super::DaemonError::Transport(Some(e.kind()), format!("io: {:?}", e)),
|
||||
Error::Json(e) => super::DaemonError::Transport(None, format!("json decode: {}", e)),
|
||||
Error::Io(e) => super::DaemonError::RpcSocket(Some(e.kind()), format!("io: {:?}", e)),
|
||||
Error::Json(e) => super::DaemonError::RpcSocket(None, format!("json decode: {}", e)),
|
||||
Error::NonceMismatch => {
|
||||
super::DaemonError::Transport(None, format!("transport: {}", e))
|
||||
super::DaemonError::RpcSocket(None, format!("transport: {}", e))
|
||||
}
|
||||
Error::VersionMismatch => {
|
||||
super::DaemonError::Transport(None, format!("transport: {}", e))
|
||||
super::DaemonError::RpcSocket(None, format!("transport: {}", e))
|
||||
}
|
||||
Error::NoErrorOrResult => super::DaemonError::NoAnswer,
|
||||
Error::NotSupported => super::DaemonError::ClientNotSupported,
|
||||
|
||||
@ -18,7 +18,7 @@ use liana::{
|
||||
miniscript::bitcoin::{address, psbt::Psbt, Address, OutPoint, Txid},
|
||||
};
|
||||
|
||||
use super::{model::*, Daemon, DaemonError};
|
||||
use super::{model::*, Daemon, DaemonBackend, DaemonError};
|
||||
|
||||
pub trait Client {
|
||||
type Error: Into<DaemonError> + Debug;
|
||||
@ -55,8 +55,8 @@ impl<C: Client> Lianad<C> {
|
||||
|
||||
#[async_trait]
|
||||
impl<C: Client + Send + Sync + Debug> Daemon for Lianad<C> {
|
||||
fn is_external(&self) -> bool {
|
||||
true
|
||||
fn backend(&self) -> DaemonBackend {
|
||||
DaemonBackend::ExternalLianad
|
||||
}
|
||||
|
||||
fn config(&self) -> Option<&Config> {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::{model::*, Daemon, DaemonError};
|
||||
use super::{model::*, Daemon, DaemonBackend, DaemonError};
|
||||
use async_trait::async_trait;
|
||||
use liana::{
|
||||
commands::{CoinStatus, LabelItem},
|
||||
@ -49,8 +49,8 @@ impl std::fmt::Debug for EmbeddedDaemon {
|
||||
|
||||
#[async_trait]
|
||||
impl Daemon for EmbeddedDaemon {
|
||||
fn is_external(&self) -> bool {
|
||||
false
|
||||
fn backend(&self) -> DaemonBackend {
|
||||
DaemonBackend::EmbeddedLianad
|
||||
}
|
||||
|
||||
fn config(&self) -> Option<&Config> {
|
||||
|
||||
@ -13,16 +13,22 @@ use async_trait::async_trait;
|
||||
use liana::{
|
||||
commands::{CoinStatus, LabelItem, TransactionInfo},
|
||||
config::Config,
|
||||
miniscript::bitcoin::{address, psbt::Psbt, secp256k1, Address, OutPoint, Txid},
|
||||
miniscript::bitcoin::{
|
||||
address, bip32::Fingerprint, psbt::Psbt, secp256k1, Address, OutPoint, Txid,
|
||||
},
|
||||
StartupError,
|
||||
};
|
||||
|
||||
use crate::hw::HardwareWalletConfig;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DaemonError {
|
||||
/// Something was wrong with the request.
|
||||
Rpc(i32, String),
|
||||
/// Something was wrong with the communication.
|
||||
Transport(Option<ErrorKind>, String),
|
||||
/// Something was wrong with the rpc socket communication.
|
||||
RpcSocket(Option<ErrorKind>, String),
|
||||
/// Something was wrong with the http communication.
|
||||
Http(Option<u16>, String),
|
||||
/// Something unexpected happened.
|
||||
Unexpected(String),
|
||||
/// No response.
|
||||
@ -43,7 +49,8 @@ impl std::fmt::Display for DaemonError {
|
||||
Self::Rpc(code, e) => write!(f, "Daemon error rpc call: [{:?}] {}", code, e),
|
||||
Self::NoAnswer => write!(f, "Daemon returned no answer"),
|
||||
Self::DaemonStopped => write!(f, "Daemon stopped"),
|
||||
Self::Transport(kind, e) => write!(f, "Daemon transport error: [{:?}] {}", kind, e),
|
||||
Self::RpcSocket(kind, e) => write!(f, "Daemon transport error: [{:?}] {}", kind, e),
|
||||
Self::Http(kind, e) => write!(f, "Http error: [{:?}] {}", kind, e),
|
||||
Self::Unexpected(e) => write!(f, "Daemon unexpected error: {}", e),
|
||||
Self::Start(e) => write!(f, "Daemon did not start: {}", e),
|
||||
Self::ClientNotSupported => write!(f, "Daemon communication is not supported"),
|
||||
@ -52,9 +59,16 @@ impl std::fmt::Display for DaemonError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DaemonBackend {
|
||||
EmbeddedLianad,
|
||||
ExternalLianad,
|
||||
RemoteBackend,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Daemon: Debug {
|
||||
fn is_external(&self) -> bool;
|
||||
fn backend(&self) -> DaemonBackend;
|
||||
fn config(&self) -> Option<&Config>;
|
||||
async fn is_alive(&self) -> Result<(), DaemonError>;
|
||||
async fn stop(&self) -> Result<(), DaemonError>;
|
||||
@ -263,6 +277,10 @@ pub trait Daemon: Debug {
|
||||
}
|
||||
}
|
||||
|
||||
if txids.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let txs = self.list_txs(&txids).await?.transactions;
|
||||
let mut txs = txs
|
||||
.into_iter()
|
||||
@ -295,6 +313,14 @@ pub trait Daemon: Debug {
|
||||
load_labels(self, &mut txs).await?;
|
||||
Ok(txs)
|
||||
}
|
||||
/// Implemented by LianaLite backend
|
||||
async fn update_wallet_metadata(
|
||||
&self,
|
||||
_fingerprint_aliases: &HashMap<Fingerprint, String>,
|
||||
_hws: &[HardwareWalletConfig],
|
||||
) -> Result<(), DaemonError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_labels<T: model::Labelled, D: Daemon + ?Sized>(
|
||||
|
||||
@ -2,10 +2,7 @@ pub mod api;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
@ -17,6 +14,7 @@ use liana::{
|
||||
miniscript::bitcoin::{address, psbt::Psbt, Address, Network, OutPoint, Txid},
|
||||
};
|
||||
use reqwest::{Error, IntoUrl, Method, RequestBuilder, Response};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
daemon::{model::*, Daemon, DaemonBackend, DaemonError},
|
||||
@ -53,11 +51,10 @@ fn request<U: IntoUrl>(
|
||||
req
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackendClient {
|
||||
auth: Arc<RwLock<auth::AccessTokenResponse>>,
|
||||
auth_client: auth::AuthClient,
|
||||
auth_refreshing: AtomicBool,
|
||||
|
||||
url: String,
|
||||
network: Network,
|
||||
@ -90,7 +87,6 @@ impl BackendClient {
|
||||
Ok(Self {
|
||||
auth: Arc::new(RwLock::new(credentials)),
|
||||
auth_client,
|
||||
auth_refreshing: AtomicBool::new(false),
|
||||
network: Network::Signet,
|
||||
url,
|
||||
user_id,
|
||||
@ -112,18 +108,15 @@ impl BackendClient {
|
||||
))
|
||||
}
|
||||
|
||||
fn request<U: IntoUrl>(&self, method: Method, url: U) -> Result<RequestBuilder, DaemonError> {
|
||||
let access_token = &self
|
||||
.auth
|
||||
.read()
|
||||
.map_err(|e| DaemonError::Unexpected(e.to_string()))?
|
||||
.access_token;
|
||||
Ok(request(&self.http, method, url, access_token))
|
||||
async fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
||||
let access_token = &self.auth.read().await.access_token;
|
||||
request(&self.http, method, url, access_token)
|
||||
}
|
||||
|
||||
pub async fn list_wallets(&self) -> Result<Vec<api::Wallet>, DaemonError> {
|
||||
let response = self
|
||||
.request(Method::GET, &format!("{}/v1/wallets", self.url))?
|
||||
.request(Method::GET, &format!("{}/v1/wallets", self.url))
|
||||
.await
|
||||
.send()
|
||||
.await?;
|
||||
if !response.status().is_success() {
|
||||
@ -138,7 +131,7 @@ impl BackendClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackendWalletClient {
|
||||
inner: BackendClient,
|
||||
wallet_uuid: String,
|
||||
@ -177,7 +170,8 @@ impl BackendWalletClient {
|
||||
.request(
|
||||
Method::GET,
|
||||
&format!("{}/v1/wallets/{}/psbts", self.inner.url, self.wallet_uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?;
|
||||
@ -219,7 +213,8 @@ impl BackendWalletClient {
|
||||
"{}/v1/wallets/{}/transactions",
|
||||
self.inner.url, self.wallet_uuid
|
||||
),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?;
|
||||
@ -254,7 +249,8 @@ impl BackendWalletClient {
|
||||
"{}/v1/wallets/{}/transactions",
|
||||
self.inner.url, self.wallet_uuid
|
||||
),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?;
|
||||
@ -300,7 +296,8 @@ impl BackendWalletClient {
|
||||
.request(
|
||||
Method::GET,
|
||||
&format!("{}/v1/wallets/{}/coins", self.inner.url, self.wallet_uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.query(&query)
|
||||
.send()
|
||||
.await?;
|
||||
@ -316,13 +313,8 @@ impl BackendWalletClient {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn auth(&self) -> Result<AccessTokenResponse, DaemonError> {
|
||||
Ok(self
|
||||
.inner
|
||||
.auth
|
||||
.read()
|
||||
.map_err(|e| DaemonError::Unexpected(e.to_string()))?
|
||||
.clone())
|
||||
async fn auth(&self) -> AccessTokenResponse {
|
||||
self.inner.auth.read().await.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,27 +329,25 @@ impl Daemon for BackendWalletClient {
|
||||
}
|
||||
|
||||
/// refresh the token if close to expiration.
|
||||
/// auth_refreshing enforce that no other thread will try to refresh the
|
||||
/// access credentials in the same time.
|
||||
async fn is_alive(&self) -> Result<(), DaemonError> {
|
||||
let auth = self.auth()?;
|
||||
if auth.expires_at < Utc::now().timestamp() + 60
|
||||
&& !self.inner.auth_refreshing.load(Ordering::Relaxed)
|
||||
{
|
||||
self.inner.auth_refreshing.store(true, Ordering::Relaxed);
|
||||
let new = self
|
||||
.inner
|
||||
.auth_client
|
||||
.refresh_token(&auth.refresh_token)
|
||||
.await?;
|
||||
let mut old = self
|
||||
.inner
|
||||
.auth
|
||||
.write()
|
||||
.map_err(|e| DaemonError::Unexpected(e.to_string()))?;
|
||||
*old = new;
|
||||
tracing::info!("Liana backend access was refreshed");
|
||||
self.inner.auth_refreshing.store(false, Ordering::Relaxed);
|
||||
let auth = self.auth().await;
|
||||
if auth.expires_at < Utc::now().timestamp() + 60 {
|
||||
match self.inner.auth.try_write() {
|
||||
Err(_) => {
|
||||
// something is using the lock, we will try next time.
|
||||
return Ok(());
|
||||
}
|
||||
Ok(mut old) => {
|
||||
let new = self
|
||||
.inner
|
||||
.auth_client
|
||||
.refresh_token(&auth.refresh_token)
|
||||
.await?;
|
||||
|
||||
*old = new;
|
||||
tracing::info!("Liana backend access was refreshed");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -391,7 +381,8 @@ impl Daemon for BackendWalletClient {
|
||||
"{}/v1/wallets/{}/addresses",
|
||||
self.inner.url, self.wallet_uuid
|
||||
),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@ -527,7 +518,8 @@ impl Daemon for BackendWalletClient {
|
||||
"{}/v1/wallets/{}/psbts/generate",
|
||||
self.inner.url, self.wallet_uuid
|
||||
),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.json(&api::payload::GeneratePsbt {
|
||||
save: false,
|
||||
feerate: feerate_vb,
|
||||
@ -563,7 +555,8 @@ impl Daemon for BackendWalletClient {
|
||||
"{}/v1/wallets/{}/psbts/rbf",
|
||||
self.inner.url, self.wallet_uuid
|
||||
),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.json(&api::payload::GenerateRbfPsbt {
|
||||
txid: *txid,
|
||||
is_cancel,
|
||||
@ -591,7 +584,8 @@ impl Daemon for BackendWalletClient {
|
||||
.request(
|
||||
Method::POST,
|
||||
&format!("{}/v1/wallets/{}/psbts", self.inner.url, self.wallet_uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.json(&api::payload::ImportPsbt {
|
||||
psbt: psbt.to_string(),
|
||||
})
|
||||
@ -625,7 +619,8 @@ impl Daemon for BackendWalletClient {
|
||||
.request(
|
||||
Method::DELETE,
|
||||
&format!("{}/v1/psbts/{}", self.inner.url, psbt.uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@ -653,7 +648,8 @@ impl Daemon for BackendWalletClient {
|
||||
.request(
|
||||
Method::POST,
|
||||
&format!("{}/v1/psbts/{}/broadcast", self.inner.url, psbt.uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@ -685,7 +681,8 @@ impl Daemon for BackendWalletClient {
|
||||
"{}/v1/wallets/{}/psbts/recovery",
|
||||
self.inner.url, self.wallet_uuid
|
||||
),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.json(&api::payload::GenerateRecoveryPsbt {
|
||||
save: false,
|
||||
feerate: feerate_vb,
|
||||
@ -715,7 +712,8 @@ impl Daemon for BackendWalletClient {
|
||||
.request(
|
||||
Method::GET,
|
||||
&format!("{}/v1/wallets/{}/labels", self.inner.url, self.wallet_uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.query(&[("items", chunk.join(","))])
|
||||
.send()
|
||||
.await?;
|
||||
@ -743,7 +741,8 @@ impl Daemon for BackendWalletClient {
|
||||
.request(
|
||||
Method::POST,
|
||||
&format!("{}/v1/wallets/{}/labels", self.inner.url, self.wallet_uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.json(&api::payload::Labels {
|
||||
labels: items
|
||||
.iter()
|
||||
@ -858,7 +857,7 @@ impl Daemon for BackendWalletClient {
|
||||
hws: &[HardwareWalletConfig],
|
||||
) -> Result<(), DaemonError> {
|
||||
let wallet = self.get_wallet().await?;
|
||||
let ledger_kinds = vec![
|
||||
let ledger_kinds = [
|
||||
async_hwi::DeviceKind::Ledger.to_string(),
|
||||
async_hwi::DeviceKind::LedgerSimulator.to_string(),
|
||||
];
|
||||
@ -873,7 +872,8 @@ impl Daemon for BackendWalletClient {
|
||||
.request(
|
||||
Method::PATCH,
|
||||
&format!("{}/v1/wallets/{}", self.inner.url, self.wallet_uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.json(&api::payload::UpdateWallet {
|
||||
ledger_hmac: Some(api::payload::UpdateLedgerHmac {
|
||||
fingerprint: cfg.fingerprint.to_string(),
|
||||
@ -908,7 +908,8 @@ impl Daemon for BackendWalletClient {
|
||||
.request(
|
||||
Method::PATCH,
|
||||
&format!("{}/v1/wallets/{}", self.inner.url, self.wallet_uuid),
|
||||
)?
|
||||
)
|
||||
.await
|
||||
.json(&api::payload::UpdateWallet {
|
||||
ledger_hmac: None,
|
||||
fingerprint_aliases: Some(
|
||||
|
||||
@ -22,6 +22,7 @@ use liana_ui::{
|
||||
widget::*,
|
||||
};
|
||||
|
||||
use crate::daemon::DaemonBackend;
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
@ -128,8 +129,8 @@ impl Loader {
|
||||
self.step = Step::Error(Box::new(e));
|
||||
}
|
||||
Error::Daemon(DaemonError::ClientNotSupported)
|
||||
| Error::Daemon(DaemonError::Transport(Some(ErrorKind::ConnectionRefused), _))
|
||||
| Error::Daemon(DaemonError::Transport(Some(ErrorKind::NotFound), _)) => {
|
||||
| Error::Daemon(DaemonError::RpcSocket(Some(ErrorKind::ConnectionRefused), _))
|
||||
| Error::Daemon(DaemonError::RpcSocket(Some(ErrorKind::NotFound), _)) => {
|
||||
if let Some(daemon_config_path) = self.gui_config.daemon_config_path.clone() {
|
||||
self.step = Step::StartingDaemon;
|
||||
self.daemon_started = true;
|
||||
@ -226,7 +227,7 @@ impl Loader {
|
||||
pub fn stop(&mut self) {
|
||||
info!("Close requested");
|
||||
if let Step::Syncing { daemon, .. } = &mut self.step {
|
||||
if !daemon.is_external() {
|
||||
if daemon.backend() == DaemonBackend::EmbeddedLianad {
|
||||
info!("Stopping internal daemon...");
|
||||
if let Err(e) = Handle::current().block_on(async { daemon.stop().await }) {
|
||||
warn!("Internal daemon failed to stop: {}", e);
|
||||
@ -366,7 +367,9 @@ pub async fn load_application(
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let wallet = Wallet::new(info.descriptors.main).load_settings(&datadir_path, network)?;
|
||||
let wallet = Wallet::new(info.descriptors.main)
|
||||
.load_from_settings(&datadir_path, network)?
|
||||
.load_hotsigners(&datadir_path, network)?;
|
||||
|
||||
let coins = daemon
|
||||
.list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[])
|
||||
|
||||
123
gui/src/main.rs
123
gui/src/main.rs
@ -1,5 +1,9 @@
|
||||
#![windows_subsystem = "windows"]
|
||||
|
||||
use std::{
|
||||
collections::HashMap, error::Error, io::Write, path::PathBuf, process, str::FromStr, sync::Arc,
|
||||
};
|
||||
|
||||
use iced::{
|
||||
event::{self, Event},
|
||||
executor, keyboard,
|
||||
@ -7,7 +11,6 @@ use iced::{
|
||||
window::settings::PlatformSpecific,
|
||||
Application, Command, Settings, Size, Subscription,
|
||||
};
|
||||
use std::{error::Error, io::Write, path::PathBuf, process, str::FromStr};
|
||||
use tracing::{error, info};
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
extern crate serde;
|
||||
@ -19,11 +22,15 @@ use liana_ui::{component::text, font, image, theme, widget::Element};
|
||||
use liana_gui::{
|
||||
app::{
|
||||
self,
|
||||
cache::Cache,
|
||||
config::{default_datadir, ConfigError},
|
||||
wallet::Wallet,
|
||||
App,
|
||||
},
|
||||
hw::HardwareWalletConfig,
|
||||
installer::{self, Installer},
|
||||
launcher::{self, Launcher},
|
||||
lianalite::client::{auth::AuthClient, backend::BackendClient, get_service_config},
|
||||
loader::{self, Loader},
|
||||
logger::Logger,
|
||||
VERSION,
|
||||
@ -34,6 +41,8 @@ enum Arg {
|
||||
ConfigPath(PathBuf),
|
||||
DatadirPath(PathBuf),
|
||||
Network(bitcoin::Network),
|
||||
Email(String),
|
||||
RefreshToken(String),
|
||||
}
|
||||
|
||||
fn parse_args(args: Vec<String>) -> Result<Vec<Arg>, Box<dyn Error>> {
|
||||
@ -57,6 +66,18 @@ fn parse_args(args: Vec<String>) -> Result<Vec<Arg>, Box<dyn Error>> {
|
||||
} else {
|
||||
return Err("missing arg to --datadir".into());
|
||||
}
|
||||
} else if arg == "--email" {
|
||||
if let Some(a) = args.get(i + 1) {
|
||||
res.push(Arg::Email(a.to_string()));
|
||||
} else {
|
||||
return Err("missing arg to --email".into());
|
||||
}
|
||||
} else if arg == "--refresh_token" {
|
||||
if let Some(a) = args.get(i + 1) {
|
||||
res.push(Arg::RefreshToken(a.to_string()));
|
||||
} else {
|
||||
return Err("missing arg to --access_token".into());
|
||||
}
|
||||
} else if arg.contains("--") {
|
||||
let network = bitcoin::Network::from_str(args[i].trim_start_matches("--"))?;
|
||||
res.push(Arg::Network(network));
|
||||
@ -164,6 +185,99 @@ impl Application for GUI {
|
||||
cmds.push(command.map(|msg| Message::Load(Box::new(msg))));
|
||||
State::Loader(Box::new(loader))
|
||||
}
|
||||
Config::RunWithRemoteBackend(email, refresh_token) => {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
// Spawn the root task
|
||||
let (wallet, client) = rt.block_on(async {
|
||||
let config = get_service_config(bitcoin::Network::Signet).await.unwrap();
|
||||
let backend_url = config.backend_api_url.to_owned();
|
||||
|
||||
let supabase_client =
|
||||
AuthClient::new(config.auth_api_url, config.auth_api_public_key);
|
||||
let access = match refresh_token {
|
||||
None => {
|
||||
supabase_client.sign_in_otp(&email).await.unwrap();
|
||||
|
||||
eprintln!("Please enter token:");
|
||||
let mut token = String::new();
|
||||
std::io::stdin()
|
||||
.read_line(&mut token)
|
||||
.expect("Failed to read line");
|
||||
|
||||
supabase_client
|
||||
.verify_otp(&email, token.trim_end())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
Some(token) => supabase_client.refresh_token(&token).await.unwrap(),
|
||||
};
|
||||
|
||||
let client =
|
||||
BackendClient::connect(supabase_client, backend_url, access.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let (client, wallet) = client.connect_first().await.unwrap();
|
||||
eprintln!(
|
||||
"Connected, next time connect directly without otp verification with:"
|
||||
);
|
||||
eprintln!(
|
||||
"cargo run -- --email {} --refresh_token {}",
|
||||
email, access.refresh_token
|
||||
);
|
||||
|
||||
(wallet, client)
|
||||
});
|
||||
let hws: Vec<HardwareWalletConfig> = wallet
|
||||
.metadata
|
||||
.ledger_hmacs
|
||||
.into_iter()
|
||||
.map(|ledger_hmac| HardwareWalletConfig {
|
||||
kind: async_hwi::DeviceKind::Ledger.to_string(),
|
||||
fingerprint: ledger_hmac.fingerprint,
|
||||
token: ledger_hmac.hmac,
|
||||
})
|
||||
.collect();
|
||||
let aliases: HashMap<bitcoin::bip32::Fingerprint, String> = wallet
|
||||
.metadata
|
||||
.fingerprint_aliases
|
||||
.into_iter()
|
||||
.filter_map(|a| {
|
||||
if a.user_id == client.user_id() {
|
||||
Some((a.fingerprint, a.alias))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let (app, command) = App::new(
|
||||
Cache {
|
||||
network: bitcoin::Network::Signet,
|
||||
coins: Vec::new(),
|
||||
rescan_progress: None,
|
||||
datadir_path: default_datadir().unwrap(),
|
||||
blockheight: wallet.tip_height.unwrap_or(0),
|
||||
},
|
||||
Arc::new(
|
||||
Wallet::new(wallet.descriptor)
|
||||
.with_name(wallet.name)
|
||||
.with_key_aliases(aliases)
|
||||
.with_hardware_wallets(hws),
|
||||
),
|
||||
app::Config {
|
||||
daemon_config_path: None,
|
||||
daemon_rpc_path: None,
|
||||
log_level: None,
|
||||
debug: None,
|
||||
start_internal_bitcoind: false,
|
||||
},
|
||||
Arc::new(client),
|
||||
default_datadir().unwrap(),
|
||||
None,
|
||||
);
|
||||
cmds.push(command.map(|msg| Message::Run(Box::new(msg))));
|
||||
State::App(app)
|
||||
}
|
||||
};
|
||||
(
|
||||
Self {
|
||||
@ -361,6 +475,7 @@ pub enum Config {
|
||||
Run(PathBuf, app::Config, bitcoin::Network),
|
||||
Launcher(PathBuf),
|
||||
Install(PathBuf, bitcoin::Network),
|
||||
RunWithRemoteBackend(String, Option<String>),
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@ -390,6 +505,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let datadir_path = default_datadir().unwrap();
|
||||
Config::new(datadir_path, None)
|
||||
}
|
||||
[Arg::Email(email)] => Ok(Config::RunWithRemoteBackend(email.to_string(), None)),
|
||||
[Arg::Email(email), Arg::RefreshToken(token)]
|
||||
| [Arg::RefreshToken(token), Arg::Email(email)] => Ok(Config::RunWithRemoteBackend(
|
||||
email.to_string(),
|
||||
Some(token.to_string()),
|
||||
)),
|
||||
[Arg::Network(network)] => {
|
||||
let datadir_path = default_datadir().unwrap();
|
||||
Config::new(datadir_path, Some(*network))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user