1use std::path::Path;
13use std::str::FromStr;
14
15use ::wallet::onchain::PublicNetwork;
16use bitcoin::util::bip32::{DerivationPath, Fingerprint};
17use bpro::{ElectrumPreset, Signer, WalletSettings};
18use gladis::Gladis;
19use gtk::prelude::*;
20use gtk::{Dialog, ResponseType};
21use relm::{init, Channel, Relm, StreamHandle, Update, Widget};
22
23use super::spending_row::Condition;
24use super::{xpub_dlg, Msg, ViewModel, Widgets};
25use crate::view::{devices, error_dlg, launch, wallet, NotificationBoxExt};
26
27pub struct Component {
28 model: ViewModel,
29 widgets: Widgets,
30 devices: relm::Component<devices::Component>,
31 channel: Channel<()>,
32 xpub_dlg: relm::Component<xpub_dlg::Component>,
33 launcher_stream: Option<StreamHandle<launch::Msg>>,
34 wallet_stream: Option<StreamHandle<wallet::Msg>>,
35}
36
37impl Component {
38 fn close(&self) {
39 self.widgets.hide();
40 if self.model.template.is_some() {
41 self.launcher_stream
42 .as_ref()
43 .map(|stream| stream.emit(launch::Msg::ShowPage(launch::Page::Template)));
44 }
45 }
46
47 fn new_wallet_path(&self) -> Option<&Path> {
48 if self.model.is_new_wallet() {
49 return Some(self.model.path());
50 }
51 None
52 }
53
54 fn replace_signer(&mut self) {
55 if let Some(signer) = self.model.active_signer.clone() {
56 self.widgets.replace_signer(&signer);
57 self.model.replace_signer(signer);
58 }
59 }
60
61 fn condition_selection_change(&mut self) {
62 let removable = self.widgets.selected_condition_index().is_some()
63 && self.model.spending_model.n_items() > 1;
64 self.widgets.set_remove_condition(removable);
65 }
66
67 fn sync(&mut self) {
68 let res = self.model.update_descriptor();
69 self.widgets
70 .update_descriptor(self.model.descriptor.as_ref());
71 if let Err(err) = res {
72 return self.widgets.show_error(&err.to_string());
73 }
74
75 for signer in &self.model.signers {
76 let network =
77 PublicNetwork::try_from(signer.xpub.network).unwrap_or(PublicNetwork::Testnet);
78 if network.is_testnet() != self.model.network.is_testnet() {
79 return self.widgets.show_error(&format!(
80 "Wallet uses {} while signer {} requires {}",
81 self.model.network,
82 signer.fingerprint(),
83 PublicNetwork::try_from(signer.xpub.network)
84 .as_ref()
85 .map(PublicNetwork::to_string)
86 .unwrap_or(s!("regtest"))
87 ));
88 }
89 }
90
91 if let Some(ref template) = self.model.template {
92 let signer_count = self.model.signers.len() as u16;
93 let min_count = template.min_signer_count;
94 let max_count = template.max_signer_count.unwrap_or(signer_count + 1);
95 if signer_count < min_count {
96 return self
97 .widgets
98 .show_error(&format!("You need at least {} signer(s)", min_count));
99 }
100 if signer_count > max_count {
101 return self.widgets.show_error(&format!(
102 "Excessive signers: you need no more than {} signer(s)",
103 max_count
104 ));
105 }
106 }
107
108 if let Err(err) = self.model.save() {
109 self.widgets.show_error(&err.to_string());
110 } else {
111 self.widgets.hide_message();
112 }
113 }
114}
115
116impl Update for Component {
117 type Model = ViewModel;
119 type ModelParam = ();
121 type Msg = Msg;
123
124 fn model(relm: &Relm<Self>, _model: Self::ModelParam) -> Self::Model {
125 ViewModel::new(relm.stream().clone())
126 }
127
128 fn update(&mut self, event: Msg) {
129 let event = match event {
131 Msg::AddDevices => {
132 self.devices.emit(devices::Msg::Show(self.model.bip43()));
133 return;
134 }
135 Msg::AddReadOnly => {
136 let testnet = self.model.network.is_testnet();
137 self.xpub_dlg
138 .emit(xpub_dlg::Msg::Open(testnet, self.model.bip43()));
139 return;
140 }
141 Msg::SignerSelect => {
142 let signer = self
143 .widgets
144 .selected_signer_xpub()
145 .and_then(|xpub| self.model.signer_by(xpub));
146 self.widgets.update_signer_details(
147 signer.map(|s| (s, self.model.derivation_for(s))),
148 self.model.network,
149 self.model.bip43(),
150 );
151 self.model.active_signer = signer.cloned();
152 return;
153 }
154 Msg::ConditionSelect => {
155 self.condition_selection_change();
156 return;
157 }
158 Msg::ElectrumSelect(preset) if self.model.electrum_model.electrum_preset != preset => {
159 self.model.electrum_model.electrum_preset = preset;
160 self.widgets
161 .update_electrum(&mut self.model.electrum_model, false, false);
162 return;
163 }
164 Msg::ElectrumEdit
165 if self.model.electrum_model.electrum_server != self.widgets.electrum_server() =>
166 {
167 self.model.electrum_model.electrum_preset = ElectrumPreset::Custom;
168 self.model.electrum_model.electrum_server = self.widgets.electrum_server();
169 self.widgets
170 .update_electrum(&mut self.model.electrum_model, false, false);
171 return;
172 }
173 Msg::ElectrumPortChange
174 if self.model.electrum_model.electrum_port != self.widgets.electrum_port() =>
175 {
176 self.model.electrum_model.electrum_preset = ElectrumPreset::Custom;
177 self.model.electrum_model.electrum_port = self.widgets.electrum_port();
178 self.widgets
179 .update_electrum(&mut self.model.electrum_model, false, false);
180 return;
181 }
182 Msg::ElectrumSecChange(sec) if sec != self.model.electrum_model.electrum_sec => {
183 self.model.electrum_model.electrum_sec = sec;
184 self.widgets
185 .update_electrum(&mut self.model.electrum_model, false, false);
186 return;
187 }
188 Msg::ElectrumTest => {
189 self.widgets.start_electrum_test();
190 self.model.test_electrum();
191 return;
192 }
193 Msg::ElectrumTestOk => {
194 self.widgets.complete_electrum_test(None);
195 return;
196 }
197 Msg::ElectrumTestFailed(failure) => {
198 self.widgets.complete_electrum_test(Some(failure));
199 return;
200 }
201 Msg::SetWallet(stream) => {
202 self.wallet_stream = Some(stream);
203 return;
204 }
205 Msg::SetLauncher(stream) => {
206 self.launcher_stream = Some(stream);
207 return;
208 }
209 Msg::Response(ResponseType::Ok) => {
210 let settings = match WalletSettings::try_from(&self.model) {
211 Err(err) => {
212 error_dlg(
213 self.widgets.as_root(),
214 "Error in wallet settings",
215 &err.to_string(),
216 None,
217 );
218 return;
219 }
220 Ok(descr) => descr,
221 };
222 if let Some(path) = self.new_wallet_path() {
223 self.launcher_stream.as_ref().map(|stream| {
224 stream.emit(launch::Msg::WalletCreated(path.to_owned()));
225 });
226 } else {
227 self.wallet_stream.as_ref().map(|stream| {
228 stream.emit(wallet::Msg::Update(
229 settings.signers().clone(),
230 settings.descriptor_classes().clone(),
231 settings.electrum().clone(),
232 ));
233 });
234 }
235 self.widgets.hide();
236 return;
237 }
238 Msg::Response(ResponseType::Cancel) => {
239 self.close();
240 return;
241 }
242 _ => event,
243 };
244
245 match event {
247 Msg::New(template, path) => {
248 if let Err(err) =
249 self.model
250 .replace_from_template(self.model.stream(), template.clone(), path)
251 {
252 error_dlg(
253 self.widgets.as_root(),
254 "Error saving wallet",
255 &self.model.filename(),
256 Some(&err.to_string()),
257 );
258 self.model.template = Some(template);
260 self.close();
261 return;
262 }
263 self.devices
264 .emit(devices::Msg::SetNetwork(self.model.network));
265 self.widgets.reset_ui(&self.model);
266 }
267 Msg::Duplicate(settings, path) => {
268 self.model
269 .replace_from_settings(self.model.stream(), settings, path, true);
270 self.devices
271 .emit(devices::Msg::SetNetwork(self.model.network));
272 self.widgets.reset_ui(&self.model);
273 }
274 Msg::View(settings, path) => {
275 self.model
276 .replace_from_settings(self.model.stream(), settings, path, false);
277 self.widgets.reset_ui(&self.model);
278 }
279 Msg::SignerAddDevice(fingerprint, device) => {
280 self.model.devices.insert(fingerprint, device);
281 self.model.update_signers();
282 self.widgets.update_signers(&self.model.signers);
283 }
284 Msg::SignerAddXpub(xpub) => {
285 if self.model.signers.iter().find(|s| s.xpub == xpub).is_some() {
286 error_dlg(
287 self.widgets.as_root(),
288 "Error",
289 "Can't add xpub since it is already present among signers",
290 None,
291 );
292 return;
293 }
294 self.model.signers.push(Signer::with_xpub(
295 xpub,
296 &self.model.bip43(),
297 self.model.network,
298 ));
299 self.widgets.update_signers(&self.model.signers);
300 }
301 Msg::RemoveSigner => {
302 self.widgets
303 .remove_signer()
304 .map(|index| self.model.signers.remove(index));
305 self.widgets
306 .update_signer_details(None, self.model.network, self.model.bip43());
307 }
308 Msg::SignerFingerprintChange => {
309 let terminal = self.model.terminal_derivation();
310 let fingerprint = match Fingerprint::from_str(&self.widgets.signer_fingerprint()) {
311 Err(_) => {
312 self.widgets.show_error("incorrect fingerprint value");
313 return;
314 }
315 Ok(fingerprint) => {
316 self.widgets.hide_message();
317 fingerprint
318 }
319 };
320 if let Some(ref mut signer) = self.model.active_signer {
321 if signer.master_fp == fingerprint {
322 return;
323 }
324 signer.master_fp = fingerprint;
325 self.widgets
326 .update_signer_derivation(&signer.to_tracking_account(terminal));
327 self.replace_signer();
328 }
329 }
330 Msg::SignerNameChange => {
331 if let Some(ref mut signer) = self.model.active_signer {
332 let name = self.widgets.signer_name();
333 if signer.name == name {
334 return;
335 }
336 signer.name = name;
337 self.replace_signer();
338 }
339 }
340 Msg::SignerOwnershipChange => {
341 if let Some(ref mut signer) = self.model.active_signer {
342 let ownership = self.widgets.signer_ownership();
343 if signer.ownership == ownership {
344 return;
345 }
346 signer.ownership = ownership;
347 self.replace_signer();
348 }
349 }
350 Msg::SignerOriginUpdate => {
351 let terminal = self.model.terminal_derivation();
352 if let Some(ref mut signer) = self.model.active_signer {
353 let orig = self.widgets.signer_origin();
354 match DerivationPath::from_str(&orig) {
355 _ if orig == "m" || orig == "m/" => {
356 signer.origin = DerivationPath::master();
357 }
358 Err(err) => {
359 return self
360 .widgets
361 .show_error(&format!("Invalid key origin: {}", err))
362 }
363 Ok(origin) if signer.origin == origin => return,
364 Ok(origin) => {
365 signer.origin = origin;
366 self.widgets
367 .update_signer_derivation(&signer.to_tracking_account(terminal));
368 self.replace_signer();
369 }
370 }
371 }
372 }
373 Msg::SignerAccountChange => {
374 let terminal = self.model.terminal_derivation();
375 if let Some(ref mut signer) = self.model.active_signer {
376 let account = self.widgets.signer_account();
377 if signer.account == Some(account) {
378 return;
379 }
380 signer.account = Some(account);
381 self.widgets
382 .update_signer_derivation(&signer.to_tracking_account(terminal));
383 self.replace_signer();
384 }
385 }
386 Msg::ConditionAdd => {
387 self.model.spending_model.append(&Condition::default());
388 self.condition_selection_change();
389 }
390 Msg::ConditionRemove => {
391 let index = if let Some(index) = self.widgets.selected_condition_index() {
392 index
393 } else {
394 return;
395 };
396 self.model.spending_model.remove(index as u32);
397 }
398 Msg::ConditionChange => {
399 }
401 Msg::ToggleClass(class) => {
402 if self.widgets.should_update_descr_class(class)
403 && self.model.toggle_descr_class(class)
404 {
405 self.widgets
406 .update_descr_classes(&self.model.descriptor_classes);
407 }
408 }
409 Msg::NetworkChange(network) if network != self.model.network => {
410 self.model.network = network;
411 self.widgets.update_network();
412 self.widgets
413 .update_electrum(&mut self.model.electrum_model, false, false);
414 }
415 _ => {}
416 }
417
418 self.sync();
419 }
420}
421
422impl Widget for Component {
423 type Root = Dialog;
425
426 fn root(&self) -> Self::Root { self.widgets.to_root() }
428
429 fn view(relm: &Relm<Self>, model: Self::Model) -> Self {
430 let glade_src = include_str!("settings.glade");
431 let widgets = Widgets::from_string(glade_src).expect("glade file broken");
432
433 let stream = relm.stream().clone();
434 let (_channel, sender) = Channel::new(move |msg| {
435 stream.emit(msg);
436 });
437
438 let devices = init::<devices::Component>((model.bip43(), model.network, sender.clone()))
439 .expect("error in devices component");
440 let xpub_dlg = init::<xpub_dlg::Component>((model.bip43().into(), sender))
441 .expect("error in xpub dialog component");
442
443 widgets.connect(relm);
444
445 let stream = relm.stream().clone();
446 let (channel, sender) = Channel::new(move |_| stream.emit(Msg::ConditionChange));
447 widgets.bind_spending_model(sender, &model.spending_model);
448
449 Component {
450 model,
451 widgets,
452 devices,
453 xpub_dlg,
454 channel,
455 launcher_stream: None,
456 wallet_stream: None,
457 }
458 }
459}