Merge #1236: Display details of wallet policy in installer
59e7131aeef4478933c6923155ed59535e218c96 Display wallet policy in wallet settings (edouardparis) 52755ed8129041433e3d9695a3c5f93b094c80ed Display details of wallet policy in installer (edouardparis) Pull request description:   close #620 ACKs for top commit: edouardparis: Self-ACK 59e7131aeef4478933c6923155ed59535e218c96 Tree-SHA512: 2c272b22cbed496648d8c16c8f5639f6dfa174ca6c5891036d91e9a568e0ada4c5db69576f6d1bc08f44fbae172ba66714e6278ed87fd3cf51e95f00135e63b6
This commit is contained in:
commit
f2e8e11a12
@ -5,7 +5,10 @@ use std::sync::Arc;
|
||||
|
||||
use iced::{Command, Subscription};
|
||||
|
||||
use liana::miniscript::bitcoin::{bip32::Fingerprint, Network};
|
||||
use liana::{
|
||||
descriptors::LianaDescriptor,
|
||||
miniscript::bitcoin::{bip32::Fingerprint, Network},
|
||||
};
|
||||
|
||||
use liana_ui::{
|
||||
component::{form, modal},
|
||||
@ -23,7 +26,7 @@ use crate::{
|
||||
pub struct WalletSettingsState {
|
||||
data_dir: PathBuf,
|
||||
warning: Option<Error>,
|
||||
descriptor: String,
|
||||
descriptor: LianaDescriptor,
|
||||
keys_aliases: Vec<(Fingerprint, form::Value<String>)>,
|
||||
wallet: Arc<Wallet>,
|
||||
modal: Option<RegisterWalletModal>,
|
||||
@ -35,7 +38,7 @@ impl WalletSettingsState {
|
||||
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>) -> Self {
|
||||
WalletSettingsState {
|
||||
data_dir,
|
||||
descriptor: wallet.main_descriptor.to_string(),
|
||||
descriptor: wallet.main_descriptor.clone(),
|
||||
keys_aliases: Self::keys_aliases(&wallet),
|
||||
wallet,
|
||||
warning: None,
|
||||
@ -177,7 +180,7 @@ impl State for WalletSettingsState {
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
wallet: Arc<Wallet>,
|
||||
) -> Command<Message> {
|
||||
self.descriptor = wallet.main_descriptor.to_string();
|
||||
self.descriptor = wallet.main_descriptor.clone();
|
||||
self.keys_aliases = Self::keys_aliases(&wallet);
|
||||
self.wallet = wallet;
|
||||
Command::perform(
|
||||
|
||||
@ -3,12 +3,13 @@ use std::str::FromStr;
|
||||
|
||||
use iced::{
|
||||
alignment,
|
||||
widget::{radio, scrollable, Space},
|
||||
widget::{radio, scrollable, tooltip as iced_tooltip, Space},
|
||||
Alignment, Length,
|
||||
};
|
||||
|
||||
use liana::{
|
||||
config::BitcoindRpcAuth,
|
||||
descriptors::{LianaDescriptor, LianaPolicy},
|
||||
miniscript::bitcoin::{bip32::Fingerprint, Network},
|
||||
};
|
||||
|
||||
@ -685,8 +686,8 @@ fn is_ok_and<T, E>(res: &Result<T, E>, f: impl FnOnce(&T) -> bool) -> bool {
|
||||
pub fn wallet_settings<'a>(
|
||||
cache: &'a Cache,
|
||||
warning: Option<&Error>,
|
||||
descriptor: &'a str,
|
||||
keys_aliases: &[(Fingerprint, form::Value<String>)],
|
||||
descriptor: &'a LianaDescriptor,
|
||||
keys_aliases: &'a [(Fingerprint, form::Value<String>)],
|
||||
processing: bool,
|
||||
updated: bool,
|
||||
) -> Element<'a, Message> {
|
||||
@ -712,88 +713,276 @@ pub fn wallet_settings<'a>(
|
||||
.on_press(Message::Settings(SettingsMessage::EditWalletSettings)),
|
||||
),
|
||||
)
|
||||
.push(card::simple(
|
||||
Column::new()
|
||||
.push(text("Wallet descriptor:").bold())
|
||||
.push(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(text(descriptor.to_owned()).small())
|
||||
.push(Space::with_height(Length::Fixed(5.0))),
|
||||
)
|
||||
.direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Properties::new().width(5).scroller_width(5),
|
||||
)),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(10)
|
||||
.push(Column::new().width(Length::Fill))
|
||||
.push(
|
||||
button::secondary(Some(icon::clipboard_icon()), "Copy")
|
||||
.on_press(Message::Clipboard(descriptor.to_owned())),
|
||||
.push(
|
||||
card::simple(
|
||||
Column::new()
|
||||
.push(text("Wallet descriptor:").bold())
|
||||
.push(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(text(descriptor.to_string()).small())
|
||||
.push(Space::with_height(Length::Fixed(5.0))),
|
||||
)
|
||||
.push(
|
||||
button::primary(
|
||||
Some(icon::chip_icon()),
|
||||
"Register on hardware device",
|
||||
)
|
||||
.on_press(Message::Settings(SettingsMessage::RegisterWallet)),
|
||||
.direction(
|
||||
scrollable::Direction::Horizontal(
|
||||
scrollable::Properties::new().width(5).scroller_width(5),
|
||||
),
|
||||
),
|
||||
)
|
||||
.spacing(10),
|
||||
))
|
||||
.push(card::simple(
|
||||
Column::new()
|
||||
.push(text("Fingerprint aliases:").bold())
|
||||
.push(keys_aliases.iter().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (fingerprint, name)| {
|
||||
let fg = *fingerprint;
|
||||
col.push(
|
||||
Row::new()
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.push(text(fg.to_string()).bold().width(Length::Fixed(100.0)))
|
||||
.push(
|
||||
form::Form::new("Alias", name, move |msg| {
|
||||
Message::Settings(
|
||||
SettingsMessage::FingerprintAliasEdited(fg, msg),
|
||||
)
|
||||
})
|
||||
.warning("Please enter correct alias")
|
||||
.size(P1_SIZE)
|
||||
.padding(10),
|
||||
),
|
||||
)
|
||||
},
|
||||
))
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(Space::with_width(Length::Fill))
|
||||
.push_maybe(if updated {
|
||||
Some(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(icon::circle_check_icon().style(color::GREEN))
|
||||
.push(text("Updated").style(color::GREEN)),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(10)
|
||||
.push(Column::new().width(Length::Fill))
|
||||
.push(
|
||||
button::secondary(Some(icon::clipboard_icon()), "Copy")
|
||||
.on_press(Message::Clipboard(descriptor.to_string())),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(if !processing {
|
||||
button::primary(None, "Update")
|
||||
.on_press(Message::Settings(SettingsMessage::Save))
|
||||
} else {
|
||||
button::primary(None, "Updating")
|
||||
}),
|
||||
)
|
||||
.spacing(10),
|
||||
)),
|
||||
.push(
|
||||
button::primary(
|
||||
Some(icon::chip_icon()),
|
||||
"Register on hardware device",
|
||||
)
|
||||
.on_press(Message::Settings(SettingsMessage::RegisterWallet)),
|
||||
),
|
||||
)
|
||||
.spacing(10),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
card::simple(display_policy(descriptor.policy(), keys_aliases)).width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
card::simple(
|
||||
Column::new()
|
||||
.push(text("Fingerprint aliases:").bold())
|
||||
.push(keys_aliases.iter().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (fingerprint, name)| {
|
||||
let fg = *fingerprint;
|
||||
col.push(
|
||||
Row::new()
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
text(fg.to_string()).bold().width(Length::Fixed(100.0)),
|
||||
)
|
||||
.push(
|
||||
form::Form::new("Alias", name, move |msg| {
|
||||
Message::Settings(
|
||||
SettingsMessage::FingerprintAliasEdited(
|
||||
fg, msg,
|
||||
),
|
||||
)
|
||||
})
|
||||
.warning("Please enter correct alias")
|
||||
.size(P1_SIZE)
|
||||
.padding(10),
|
||||
),
|
||||
)
|
||||
},
|
||||
))
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(Space::with_width(Length::Fill))
|
||||
.push_maybe(if updated {
|
||||
Some(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(icon::circle_check_icon().style(color::GREEN))
|
||||
.push(text("Updated").style(color::GREEN)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(if !processing {
|
||||
button::primary(None, "Update")
|
||||
.on_press(Message::Settings(SettingsMessage::Save))
|
||||
} else {
|
||||
button::primary(None, "Updating")
|
||||
}),
|
||||
)
|
||||
.spacing(10),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn display_policy(
|
||||
policy: LianaPolicy,
|
||||
keys_aliases: &[(Fingerprint, form::Value<String>)],
|
||||
) -> Element<'_, Message> {
|
||||
let (primary_threshold, primary_keys) = policy.primary_path().thresh_origins();
|
||||
let recovery_paths = policy.recovery_paths();
|
||||
|
||||
// The iteration over an HashMap keys can have a different order at each refresh
|
||||
let mut primary_keys: Vec<Fingerprint> = primary_keys.into_keys().collect();
|
||||
primary_keys.sort();
|
||||
|
||||
let mut col = Column::new().push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(
|
||||
text(format!(
|
||||
"{} signature{}",
|
||||
primary_threshold,
|
||||
if primary_threshold > 1 { "s" } else { "" }
|
||||
))
|
||||
.bold(),
|
||||
)
|
||||
.push(if primary_keys.len() > 1 {
|
||||
text(format!("out of {} by", primary_keys.len()))
|
||||
} else {
|
||||
text("by")
|
||||
})
|
||||
.push(
|
||||
primary_keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(Row::new().spacing(5), |row, (i, k)| {
|
||||
let content = if let Some(alias) = keys_aliases
|
||||
.iter()
|
||||
.find(|(fg, _)| fg == k)
|
||||
.map(|(_, f)| &f.value)
|
||||
{
|
||||
Container::new(
|
||||
iced_tooltip::Tooltip::new(
|
||||
text(alias).bold(),
|
||||
text(k.to_string()),
|
||||
iced_tooltip::Position::Bottom,
|
||||
)
|
||||
.style(theme::Container::Card(theme::Card::Simple)),
|
||||
)
|
||||
} else {
|
||||
Container::new(text(k.to_string()))
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple))
|
||||
};
|
||||
if primary_keys.len() == 1 || i == primary_keys.len() - 1 {
|
||||
row.push(content)
|
||||
} else if i <= primary_keys.len() - 2 {
|
||||
row.push(content).push(text("and"))
|
||||
} else {
|
||||
row.push(content).push(text(","))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.push(text("can always spend this wallet's funds (Primary path)")),
|
||||
);
|
||||
for (i, (sequence, recovery_path)) in recovery_paths.iter().enumerate() {
|
||||
let (threshold, recovery_keys) = recovery_path.thresh_origins();
|
||||
|
||||
// The iteration over an HashMap keys can have a different order at each refresh
|
||||
let mut recovery_keys: Vec<Fingerprint> = recovery_keys.into_keys().collect();
|
||||
recovery_keys.sort();
|
||||
|
||||
col = col.push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(
|
||||
text(format!(
|
||||
"{} signature{}",
|
||||
threshold,
|
||||
if threshold > 1 { "s" } else { "" }
|
||||
))
|
||||
.bold(),
|
||||
)
|
||||
.push(if recovery_keys.len() > 1 {
|
||||
text(format!("out of {} by", recovery_keys.len()))
|
||||
} else {
|
||||
text("by")
|
||||
})
|
||||
.push(recovery_keys.iter().enumerate().fold(
|
||||
Row::new().spacing(5),
|
||||
|row, (i, k)| {
|
||||
let content = if let Some(alias) = keys_aliases
|
||||
.iter()
|
||||
.find(|(fg, _)| fg == k)
|
||||
.map(|(_, f)| &f.value)
|
||||
{
|
||||
Container::new(
|
||||
iced_tooltip::Tooltip::new(
|
||||
text(alias).bold(),
|
||||
text(k.to_string()),
|
||||
iced_tooltip::Position::Bottom,
|
||||
)
|
||||
.style(theme::Container::Card(theme::Card::Simple)),
|
||||
)
|
||||
} else {
|
||||
Container::new(text(k.to_string()))
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple))
|
||||
};
|
||||
if recovery_keys.len() == 1 || i == recovery_keys.len() - 1 {
|
||||
row.push(content)
|
||||
} else if i <= recovery_keys.len() - 2 {
|
||||
row.push(content).push(text("and"))
|
||||
} else {
|
||||
row.push(content).push(text(","))
|
||||
}
|
||||
},
|
||||
))
|
||||
.push(text("can spend coins inactive for"))
|
||||
.push(
|
||||
text(format!(
|
||||
"{} blocks (~{})",
|
||||
sequence,
|
||||
expire_message_units(*sequence as u32).join(",")
|
||||
))
|
||||
.bold(),
|
||||
)
|
||||
.push(text(format!("(Recovery path #{})", i + 1))),
|
||||
);
|
||||
}
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(text("The wallet policy:").bold())
|
||||
.push(scrollable(col).direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Properties::new().width(5).scroller_width(5),
|
||||
)))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// returns y,m,d
|
||||
fn expire_message_units(sequence: u32) -> Vec<String> {
|
||||
let mut n_minutes = sequence * 10;
|
||||
let n_years = n_minutes / 525960;
|
||||
n_minutes -= n_years * 525960;
|
||||
let n_months = n_minutes / 43830;
|
||||
n_minutes -= n_months * 43830;
|
||||
let n_days = n_minutes / 1440;
|
||||
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
if n_years != 0 || n_months != 0 || n_days != 0 {
|
||||
[(n_years, "y"), (n_months, "m"), (n_days, "d")]
|
||||
.iter()
|
||||
.filter_map(|(n, u)| {
|
||||
if *n != 0 {
|
||||
Some(format!("{}{}", n, u))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
n_minutes -= n_days * 1440;
|
||||
let n_hours = n_minutes / 60;
|
||||
n_minutes -= n_hours * 60;
|
||||
[(n_hours, "h"), (n_minutes, "m")]
|
||||
.iter()
|
||||
.filter_map(|(n, u)| {
|
||||
if *n != 0 {
|
||||
Some(format!("{}{}", n, u))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_wallet_modal<'a>(
|
||||
warning: Option<&Error>,
|
||||
hws: &'a [HardwareWallet],
|
||||
|
||||
@ -1363,6 +1363,7 @@ impl From<RegisterDescriptor> for Box<dyn Step> {
|
||||
pub struct BackupDescriptor {
|
||||
done: bool,
|
||||
descriptor: Option<LianaDescriptor>,
|
||||
key_aliases: HashMap<Fingerprint, String>,
|
||||
}
|
||||
|
||||
impl Step for BackupDescriptor {
|
||||
@ -1377,6 +1378,12 @@ impl Step for BackupDescriptor {
|
||||
self.descriptor.clone_from(&ctx.descriptor);
|
||||
self.done = false;
|
||||
}
|
||||
self.key_aliases = ctx
|
||||
.keys
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|k| (k.master_fingerprint, k.name))
|
||||
.collect()
|
||||
}
|
||||
fn view<'a>(
|
||||
&'a self,
|
||||
@ -1384,8 +1391,13 @@ impl Step for BackupDescriptor {
|
||||
progress: (usize, usize),
|
||||
email: Option<&'a str>,
|
||||
) -> Element<Message> {
|
||||
let desc = self.descriptor.as_ref().unwrap();
|
||||
view::backup_descriptor(progress, email, desc.to_string(), self.done)
|
||||
view::backup_descriptor(
|
||||
progress,
|
||||
email,
|
||||
self.descriptor.as_ref().expect("Must be a descriptor"),
|
||||
&self.key_aliases,
|
||||
self.done,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,15 +3,23 @@ use iced::widget::{
|
||||
checkbox, container, pick_list, radio, scrollable, scrollable::Properties, slider, Button,
|
||||
Space, TextInput,
|
||||
};
|
||||
use iced::{alignment, widget::progress_bar, Alignment, Length};
|
||||
use iced::{
|
||||
alignment,
|
||||
widget::{progress_bar, tooltip as iced_tooltip},
|
||||
Alignment, Length,
|
||||
};
|
||||
|
||||
use async_hwi::DeviceKind;
|
||||
use liana_ui::component::text;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
|
||||
use liana::miniscript::bitcoin::{self, bip32::Fingerprint};
|
||||
use liana::{
|
||||
descriptors::{LianaDescriptor, LianaPolicy},
|
||||
miniscript::bitcoin::{self, bip32::Fingerprint},
|
||||
};
|
||||
use liana_ui::{
|
||||
color,
|
||||
component::{
|
||||
@ -1007,12 +1015,13 @@ pub fn register_descriptor<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn backup_descriptor(
|
||||
pub fn backup_descriptor<'a>(
|
||||
progress: (usize, usize),
|
||||
email: Option<&str>,
|
||||
descriptor: String,
|
||||
email: Option<&'a str>,
|
||||
descriptor: &'a LianaDescriptor,
|
||||
keys_aliases: &'a HashMap<Fingerprint, String>,
|
||||
done: bool,
|
||||
) -> Element<'_, Message> {
|
||||
) -> Element<'a, Message> {
|
||||
layout(
|
||||
progress,
|
||||
email,
|
||||
@ -1046,28 +1055,37 @@ pub fn backup_descriptor(
|
||||
))
|
||||
.max_width(1000),
|
||||
)
|
||||
.push(card::simple(
|
||||
Column::new()
|
||||
.push(text("The descriptor:").small().bold())
|
||||
.push(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(text(descriptor.to_owned()).small())
|
||||
.push(Space::with_height(Length::Fixed(5.0))),
|
||||
.push(
|
||||
card::simple(
|
||||
Column::new()
|
||||
.push(text("The descriptor:").small().bold())
|
||||
.push(
|
||||
scrollable(
|
||||
Column::new()
|
||||
.push(text(descriptor.to_string()).small())
|
||||
.push(Space::with_height(Length::Fixed(5.0))),
|
||||
)
|
||||
.direction(
|
||||
scrollable::Direction::Horizontal(
|
||||
scrollable::Properties::new().width(5).scroller_width(5),
|
||||
),
|
||||
),
|
||||
)
|
||||
.direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Properties::new().width(5).scroller_width(5),
|
||||
)),
|
||||
)
|
||||
.push(
|
||||
Row::new().push(Column::new().width(Length::Fill)).push(
|
||||
button::secondary(Some(icon::clipboard_icon()), "Copy")
|
||||
.on_press(Message::Clibpboard(descriptor)),
|
||||
),
|
||||
)
|
||||
.spacing(10)
|
||||
.max_width(1000),
|
||||
))
|
||||
.push(
|
||||
Row::new().push(Column::new().width(Length::Fill)).push(
|
||||
button::secondary(Some(icon::clipboard_icon()), "Copy")
|
||||
.on_press(Message::Clibpboard(descriptor.to_string())),
|
||||
),
|
||||
)
|
||||
.spacing(10),
|
||||
)
|
||||
.max_width(1500),
|
||||
)
|
||||
.push(
|
||||
card::simple(display_policy(descriptor.policy(), keys_aliases))
|
||||
.width(Length::Fill)
|
||||
.max_width(1500),
|
||||
)
|
||||
.push(
|
||||
checkbox("I have backed up my descriptor", done).on_toggle(Message::UserActionDone),
|
||||
)
|
||||
@ -1084,6 +1102,161 @@ pub fn backup_descriptor(
|
||||
)
|
||||
}
|
||||
|
||||
fn display_policy(
|
||||
policy: LianaPolicy,
|
||||
keys_aliases: &HashMap<Fingerprint, String>,
|
||||
) -> Element<'_, Message> {
|
||||
let (primary_threshold, primary_keys) = policy.primary_path().thresh_origins();
|
||||
let recovery_paths = policy.recovery_paths();
|
||||
let mut col = Column::new().push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(
|
||||
text(format!(
|
||||
"{} signature{}",
|
||||
primary_threshold,
|
||||
if primary_threshold > 1 { "s" } else { "" }
|
||||
))
|
||||
.bold(),
|
||||
)
|
||||
.push(if primary_keys.len() > 1 {
|
||||
text(format!("out of {} by", primary_keys.len()))
|
||||
} else {
|
||||
text("by")
|
||||
})
|
||||
.push(
|
||||
primary_keys
|
||||
.keys()
|
||||
.enumerate()
|
||||
.fold(Row::new().spacing(5), |row, (i, k)| {
|
||||
let content = if let Some(alias) = keys_aliases.get(k) {
|
||||
Container::new(
|
||||
iced_tooltip::Tooltip::new(
|
||||
text(alias).bold(),
|
||||
text(k.to_string()),
|
||||
iced_tooltip::Position::Bottom,
|
||||
)
|
||||
.style(theme::Container::Card(theme::Card::Simple)),
|
||||
)
|
||||
} else {
|
||||
Container::new(text(k.to_string()))
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple))
|
||||
};
|
||||
if primary_keys.len() == 1 || i == primary_keys.len() - 1 {
|
||||
row.push(content)
|
||||
} else if i <= primary_keys.len() - 2 {
|
||||
row.push(content).push(text("and"))
|
||||
} else {
|
||||
row.push(content).push(text(","))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.push(text("can always spend this wallet's funds (Primary path)")),
|
||||
);
|
||||
for (i, (sequence, recovery_path)) in recovery_paths.iter().enumerate() {
|
||||
let (threshold, recovery_keys) = recovery_path.thresh_origins();
|
||||
col = col.push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(
|
||||
text(format!(
|
||||
"{} signature{}",
|
||||
threshold,
|
||||
if threshold > 1 { "s" } else { "" }
|
||||
))
|
||||
.bold(),
|
||||
)
|
||||
.push(if recovery_keys.len() > 1 {
|
||||
text(format!("out of {} by", recovery_keys.len()))
|
||||
} else {
|
||||
text("by")
|
||||
})
|
||||
.push(recovery_keys.keys().enumerate().fold(
|
||||
Row::new().spacing(5),
|
||||
|row, (i, k)| {
|
||||
let content = if let Some(alias) = keys_aliases.get(k) {
|
||||
Container::new(
|
||||
iced_tooltip::Tooltip::new(
|
||||
text(alias).bold(),
|
||||
text(k.to_string()),
|
||||
iced_tooltip::Position::Bottom,
|
||||
)
|
||||
.style(theme::Container::Card(theme::Card::Simple)),
|
||||
)
|
||||
} else {
|
||||
Container::new(text(k.to_string()))
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple))
|
||||
};
|
||||
if recovery_keys.len() == 1 || i == recovery_keys.len() - 1 {
|
||||
row.push(content)
|
||||
} else if i <= recovery_keys.len() - 2 {
|
||||
row.push(content).push(text("and"))
|
||||
} else {
|
||||
row.push(content).push(text(","))
|
||||
}
|
||||
},
|
||||
))
|
||||
.push(text("can spend coins inactive for"))
|
||||
.push(
|
||||
text(format!(
|
||||
"{} blocks (~{})",
|
||||
sequence,
|
||||
expire_message_units(*sequence as u32).join(",")
|
||||
))
|
||||
.bold(),
|
||||
)
|
||||
.push(text(format!("(Recovery path #{})", i + 1))),
|
||||
);
|
||||
}
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(text("The wallet policy:").bold())
|
||||
.push(scrollable(col).direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Properties::new().width(5).scroller_width(5),
|
||||
)))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// returns y,m,d
|
||||
fn expire_message_units(sequence: u32) -> Vec<String> {
|
||||
let mut n_minutes = sequence * 10;
|
||||
let n_years = n_minutes / 525960;
|
||||
n_minutes -= n_years * 525960;
|
||||
let n_months = n_minutes / 43830;
|
||||
n_minutes -= n_months * 43830;
|
||||
let n_days = n_minutes / 1440;
|
||||
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
if n_years != 0 || n_months != 0 || n_days != 0 {
|
||||
[(n_years, "y"), (n_months, "m"), (n_days, "d")]
|
||||
.iter()
|
||||
.filter_map(|(n, u)| {
|
||||
if *n != 0 {
|
||||
Some(format!("{}{}", n, u))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
n_minutes -= n_days * 1440;
|
||||
let n_hours = n_minutes / 60;
|
||||
n_minutes -= n_hours * 60;
|
||||
[(n_hours, "h"), (n_minutes, "m")]
|
||||
.iter()
|
||||
.filter_map(|(n, u)| {
|
||||
if *n != 0 {
|
||||
Some(format!("{}{}", n, u))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_backup<'a>() -> Element<'a, Message> {
|
||||
text(prompt::BACKUP_DESCRIPTOR_HELP).small().into()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user