cs_epic_wallet_controller/
command.rs

1// Copyright 2019 The Epic Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Epic wallet command-line function implementations
16
17use crate::api::TLSConfig;
18use crate::config::{EpicboxConfig, TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME};
19use crate::core::{core, global};
20use crate::error::Error;
21
22use crate::impls::{
23	create_sender, EpicboxChannel, EpicboxListenChannel, KeybaseAllChannels, SlateGetter as _,
24	SlateReceiver as _,
25};
26use crate::impls::{EmojiSlate, PathToSlate, SlatePutter};
27use crate::keychain;
28use crate::libwallet::{
29	address, Error as LibwalletError, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof,
30	WalletInst, WalletLCProvider,
31};
32
33use crate::util::secp::key::SecretKey;
34use crate::util::{to_hex, Mutex, ZeroingString};
35use crate::{controller, display};
36
37use serde_json as json;
38use std::fs::File;
39use std::io::{Read, Write};
40use std::sync::Arc;
41use std::thread;
42
43use std::time::Duration;
44use uuid::Uuid;
45
46fn show_recovery_phrase(phrase: ZeroingString) {
47	println!("Your recovery phrase is:");
48	println!();
49	println!("{}", &*phrase);
50	println!();
51	println!("Please back-up these words in a non-digital format.");
52}
53
54/// Arguments common to all wallet commands
55#[derive(Clone)]
56pub struct GlobalArgs {
57	pub account: String,
58	pub api_secret: Option<String>,
59	pub node_api_secret: Option<String>,
60	pub show_spent: bool,
61	pub chain_type: global::ChainTypes,
62	pub password: Option<ZeroingString>,
63	pub tls_conf: Option<TLSConfig>,
64}
65
66/// Arguments for init command
67pub struct InitArgs {
68	/// BIP39 recovery phrase length
69	pub list_length: usize,
70	pub password: ZeroingString,
71	pub config: WalletConfig,
72	pub recovery_phrase: Option<ZeroingString>,
73	pub restore: bool,
74}
75
76pub fn init<L, C, K>(
77	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
78	g_args: &GlobalArgs,
79	args: InitArgs,
80) -> Result<(), epic_wallet_libwallet::Error>
81where
82	L: WalletLCProvider<'static, C, K> + 'static,
83	C: NodeClient + 'static,
84	K: keychain::Keychain + 'static,
85{
86	let mut w_lock = wallet.lock();
87	let p = w_lock.lc_provider()?;
88	p.create_config(
89		&g_args.chain_type,
90		WALLET_CONFIG_FILE_NAME,
91		None,
92		None,
93		None,
94		None,
95	)?;
96	p.create_wallet(
97		None,
98		args.recovery_phrase,
99		args.list_length,
100		args.password.clone(),
101		false,
102	)?;
103
104	let m = p.get_mnemonic(None, args.password)?;
105	show_recovery_phrase(m);
106	Ok(())
107}
108
109/// Argument for recover
110pub struct RecoverArgs {
111	pub passphrase: ZeroingString,
112}
113
114pub fn recover<L, C, K>(
115	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
116	args: RecoverArgs,
117) -> Result<(), epic_wallet_libwallet::Error>
118where
119	L: WalletLCProvider<'static, C, K> + 'static,
120	C: NodeClient + 'static,
121	K: keychain::Keychain + 'static,
122{
123	let mut w_lock = wallet.lock();
124	let p = w_lock.lc_provider()?;
125	let m = p.get_mnemonic(None, args.passphrase)?;
126	show_recovery_phrase(m);
127	Ok(())
128}
129
130/// Arguments for listen command
131pub struct ListenArgs {
132	pub method: String,
133}
134
135pub fn listen<L, C, K>(
136	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
137	keychain_mask: Arc<Mutex<Option<SecretKey>>>,
138	config: &WalletConfig,
139	tor_config: &TorConfig,
140	epicbox_config: &EpicboxConfig,
141	args: &ListenArgs,
142	g_args: &GlobalArgs,
143) -> Result<(), LibwalletError>
144where
145	L: WalletLCProvider<'static, C, K> + 'static,
146	C: NodeClient + 'static,
147	K: keychain::Keychain + 'static,
148{
149	let res = match args.method.as_str() {
150		"http" => controller::foreign_listener(
151			wallet.clone(),
152			keychain_mask,
153			&config.api_listen_addr(),
154			g_args.tls_conf.clone(),
155			tor_config.use_tor_listener,
156		),
157		"keybase" => KeybaseAllChannels::new().unwrap().listen(
158			wallet.clone(),
159			keychain_mask.clone(),
160			config.clone(),
161		),
162		"epicbox" => {
163			let mut reconnections = 0;
164			loop {
165				let listener = EpicboxListenChannel::new()?.listen(
166					wallet.clone(),
167					keychain_mask.clone(),
168					epicbox_config.clone(),
169					&mut reconnections,
170				);
171				info!("Reconnect to epicbox");
172				match listener {
173					Err(e) => {
174						error!("Error in listener loop ({})", e);
175					}
176					Ok(_) => (),
177				}
178				if reconnections >= 5 {
179					break;
180				}
181				let duration = std::time::Duration::from_secs(20);
182				std::thread::sleep(duration);
183			}
184			return Err(LibwalletError::EpicboxReconnectLimit);
185		}
186		method => {
187			return Err(LibwalletError::ArgumentError(format!(
188				"No listener for method {}",
189				method
190			)));
191		}
192	};
193
194	debug!("{}", args.method.clone());
195
196	if let Err(e) = res {
197		return Err(LibwalletError::LibWallet(format!("{}", e)));
198	}
199	Ok(())
200}
201
202pub fn owner_api<L, C, K>(
203	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
204	keychain_mask: Option<SecretKey>,
205	config: &WalletConfig,
206	tor_config: &TorConfig,
207	epicbox_config: &EpicboxConfig,
208	g_args: &GlobalArgs,
209) -> Result<(), LibwalletError>
210where
211	L: WalletLCProvider<'static, C, K> + Send + Sync + 'static,
212	C: NodeClient + 'static,
213	K: keychain::Keychain + 'static,
214{
215	// keychain mask needs to be a sinlge instance, in case the foreign API is
216	// also being run at the same time
217	let km = Arc::new(Mutex::new(keychain_mask));
218	let res = controller::owner_listener(
219		wallet,
220		km,
221		config.owner_api_listen_addr().as_str(),
222		g_args.api_secret.clone(),
223		g_args.tls_conf.clone(),
224		config.owner_api_include_foreign.clone(),
225		Some(tor_config.clone()),
226		Some(epicbox_config.clone()),
227	);
228	if let Err(e) = res {
229		return Err(LibwalletError::LibWallet(format!("{}", e)));
230	}
231	Ok(())
232}
233
234/// Arguments for account command
235pub struct AccountArgs {
236	pub create: Option<String>,
237}
238
239pub fn account<L, C, K>(
240	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
241	keychain_mask: Option<&SecretKey>,
242	args: AccountArgs,
243) -> Result<(), LibwalletError>
244where
245	L: WalletLCProvider<'static, C, K> + 'static,
246	C: NodeClient + 'static,
247	K: keychain::Keychain + 'static,
248{
249	if args.create.is_none() {
250		let res = controller::owner_single_use(wallet, keychain_mask, |api, m| {
251			let acct_mappings = api.accounts(m)?;
252			// give logging thread a moment to catch up
253			thread::sleep(Duration::from_millis(200));
254			display::accounts(acct_mappings);
255			Ok(())
256		});
257		if let Err(e) = res {
258			error!("Error listing accounts: {}", e);
259			return Err(LibwalletError::LibWallet(format!("{}", e)));
260		}
261	} else {
262		let label = args.create.unwrap();
263		let res = controller::owner_single_use(wallet, keychain_mask, |api, m| {
264			api.create_account_path(m, &label)?;
265			thread::sleep(Duration::from_millis(200));
266			info!("Account: '{}' Created!", label);
267			Ok(())
268		});
269		if let Err(e) = res {
270			thread::sleep(Duration::from_millis(200));
271			error!("Error creating account '{}': {}", label, e);
272			return Err(LibwalletError::LibWallet(format!("{}", e)));
273		}
274	}
275	Ok(())
276}
277
278/// Arguments for the send command
279pub struct SendArgs {
280	pub amount: u64,
281	pub message: Option<String>,
282	pub minimum_confirmations: u64,
283	pub selection_strategy: String,
284	pub estimate_selection_strategies: bool,
285	pub method: String,
286	pub dest: String,
287	pub change_outputs: usize,
288	pub fluff: bool,
289	pub max_outputs: usize,
290	pub target_slate_version: Option<u16>,
291	pub payment_proof_address: Option<String>,
292	pub ttl_blocks: Option<u64>,
293}
294
295pub fn send<L, C, K>(
296	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
297	keychain_mask: Option<&SecretKey>,
298	tor_config: Option<TorConfig>,
299	epicbox_config: Option<EpicboxConfig>,
300	args: SendArgs,
301	dark_scheme: bool,
302) -> Result<(), LibwalletError>
303where
304	L: WalletLCProvider<'static, C, K> + 'static,
305	C: NodeClient + 'static,
306	K: keychain::Keychain + 'static,
307{
308	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
309		if args.estimate_selection_strategies {
310			let strategies = vec!["smallest", "all"]
311				.into_iter()
312				.map(|strategy| {
313					let init_args = InitTxArgs {
314						src_acct_name: None,
315						amount: args.amount,
316						minimum_confirmations: args.minimum_confirmations,
317						max_outputs: args.max_outputs as u32,
318						num_change_outputs: args.change_outputs as u32,
319						selection_strategy_is_use_all: strategy == "all",
320						estimate_only: Some(true),
321						..Default::default()
322					};
323					let slate = api.init_send_tx(m, init_args).unwrap();
324					(strategy, slate.amount, slate.fee)
325				})
326				.collect();
327			display::estimate(args.amount, strategies, dark_scheme);
328		} else {
329			let payment_proof_recipient_address = match args.payment_proof_address {
330				Some(ref p) => Some(address::ed25519_parse_pubkey(p)?),
331				None => None,
332			};
333			let init_args = InitTxArgs {
334				src_acct_name: None,
335				amount: args.amount,
336				minimum_confirmations: args.minimum_confirmations,
337				max_outputs: args.max_outputs as u32,
338				num_change_outputs: args.change_outputs as u32,
339				selection_strategy_is_use_all: args.selection_strategy == "all",
340				message: args.message.clone(),
341				target_slate_version: args.target_slate_version,
342				payment_proof_recipient_address,
343				ttl_blocks: args.ttl_blocks,
344				send_args: None,
345				..Default::default()
346			};
347			let result = api.init_send_tx(m, init_args);
348			let mut slate = match result {
349				Ok(s) => {
350					info!(
351						"Tx created: {} epic to {} (strategy '{}')",
352						core::amount_to_hr_string(args.amount, false),
353						args.dest,
354						args.selection_strategy,
355					);
356					s
357				}
358				Err(e) => {
359					info!("Tx not created: {}", e);
360					return Err(e);
361				}
362			};
363
364			match args.method.as_str() {
365				"emoji" => {
366					println!("{}", EmojiSlate().encode(&slate));
367					api.tx_lock_outputs(m, &slate, 0, Some(args.dest))?;
368					return Ok(());
369				}
370				"file" => {
371					PathToSlate((&args.dest).into()).put_tx(&slate)?;
372					api.tx_lock_outputs(m, &slate, 0, Some(args.dest))?;
373					return Ok(());
374				}
375				"self" => {
376					api.tx_lock_outputs(m, &slate, 0, Some(args.dest.clone()))?;
377					let km = match keychain_mask.as_ref() {
378						None => None,
379						Some(&m) => Some(m.to_owned()),
380					};
381					controller::foreign_single_use(wallet, km, |api| {
382						slate = api.receive_tx(&slate, Some(&args.dest), None)?;
383						Ok(())
384					})?;
385				}
386				"epicbox" => {
387					let epicbox_channel = Box::new(EpicboxChannel::new(&args.dest, epicbox_config))
388						.expect("error starting epicbox");
389
390					let km = match keychain_mask.as_ref() {
391						None => None,
392						Some(&m) => Some(m.to_owned()),
393					};
394					slate = epicbox_channel.send(wallet, km, &slate)?;
395
396					api.tx_lock_outputs(m, &slate, 0, Some(args.dest))?;
397
398					return Ok(());
399				}
400				method => {
401					let sender = create_sender(method, &args.dest, tor_config)?;
402
403					slate = sender.send_tx(&slate)?;
404					api.tx_lock_outputs(m, &slate, 0, Some(args.dest))?;
405				}
406			}
407
408			api.verify_slate_messages(m, &slate).map_err(|e| {
409				error!("Error validating participant messages: {}", e);
410				e
411			})?;
412			slate = api.finalize_tx(m, &slate)?;
413			let result = api.post_tx(m, &slate.tx, args.fluff);
414			match result {
415				Ok(_) => {
416					info!("Tx sent ok",);
417					return Ok(());
418				}
419				Err(e) => {
420					error!("Tx sent fail: {}", e);
421					return Err(e);
422				}
423			}
424		}
425		Ok(())
426	})?;
427	Ok(())
428}
429
430/// Receive command argument
431pub struct ReceiveArgs {
432	pub input: String,
433	pub message: Option<String>,
434	pub method: String,
435}
436
437pub fn receive<L, C, K>(
438	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
439	keychain_mask: Option<&SecretKey>,
440	g_args: &GlobalArgs,
441	args: ReceiveArgs,
442) -> Result<(), LibwalletError>
443where
444	L: WalletLCProvider<'static, C, K>,
445	C: NodeClient + 'static,
446	K: keychain::Keychain + 'static,
447{
448	let method = args.method.as_str();
449	let mut slate;
450	if method == "emoji" {
451		slate = EmojiSlate().decode(&args.input.as_str())?;
452	} else {
453		slate = PathToSlate((&args.input).into()).get_tx()?;
454	}
455
456	let km = match keychain_mask.as_ref() {
457		None => None,
458		Some(&m) => Some(m.to_owned()),
459	};
460	controller::foreign_single_use(wallet, km, |api| {
461		if let Err(e) = api.verify_slate_messages(&slate) {
462			error!("Error validating participant messages: {}", e);
463			return Err(e);
464		}
465		slate = api.receive_tx(&slate, Some(&g_args.account), args.message.clone())?;
466		Ok(())
467	})?;
468	if method == "emoji" {
469		println!("\n\nThis is your response emoji string. Please send it back to the payer to finalize the transaction:\n\n{}", EmojiSlate().encode(&slate));
470		info!("Response emoji.response generated, and can be sent back to the transaction originator.");
471	} else {
472		PathToSlate(format!("{}.response", args.input).into()).put_tx(&slate)?;
473		info!(
474			"Response file {}.response generated, and can be sent back to the transaction originator.",
475			args.input
476		);
477	}
478
479	Ok(())
480}
481
482/// Finalize command args
483pub struct FinalizeArgs {
484	pub method: String,
485	pub input: String,
486	pub fluff: bool,
487	pub nopost: bool,
488	pub dest: Option<String>,
489}
490
491pub fn finalize<L, C, K>(
492	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
493	keychain_mask: Option<&SecretKey>,
494	args: FinalizeArgs,
495) -> Result<(), LibwalletError>
496where
497	L: WalletLCProvider<'static, C, K> + 'static,
498	C: NodeClient + 'static,
499	K: keychain::Keychain + 'static,
500{
501	let method = args.method.as_str();
502	let mut slate;
503	if method == "emoji" {
504		slate = EmojiSlate().decode(&args.input.as_str())?;
505	} else {
506		slate = PathToSlate((&args.input).into()).get_tx()?;
507	}
508
509	// Rather than duplicating the entire command, we'll just
510	// try to determine what kind of finalization this is
511	// based on the slate contents
512	// for now, we can tell this is an invoice transaction
513	// if the receipient (participant 1) hasn't completed sigs
514	let part_data = slate.participant_with_id(1);
515	let is_invoice = {
516		match part_data {
517			None => {
518				error!("Expected slate participant data missing");
519				return Err(Error::ArgumentError(format!(
520					"Expected Slate participant data missing"
521				)))?;
522			}
523			Some(p) => !p.is_complete(),
524		}
525	};
526
527	if is_invoice {
528		let km = match keychain_mask.as_ref() {
529			None => None,
530			Some(&m) => Some(m.to_owned()),
531		};
532		controller::foreign_single_use(wallet.clone(), km, |api| {
533			if let Err(e) = api.verify_slate_messages(&slate) {
534				error!("Error validating participant messages: {}", e);
535				return Err(e);
536			}
537			slate = api.finalize_invoice_tx(&mut slate)?;
538			Ok(())
539		})?;
540	} else {
541		controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
542			if let Err(e) = api.verify_slate_messages(m, &slate) {
543				error!("Error validating participant messages: {}", e);
544				return Err(e);
545			}
546			slate = api.finalize_tx(m, &mut slate)?;
547			Ok(())
548		})?;
549	}
550
551	if !args.nopost {
552		controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
553			let result = api.post_tx(m, &slate.tx, args.fluff);
554			match result {
555				Ok(_) => {
556					info!(
557						"Transaction sent successfully, check the wallet again for confirmation."
558					);
559					Ok(())
560				}
561				Err(e) => {
562					error!("Tx not sent: {}", e);
563					Err(e)
564				}
565			}
566		})?;
567	}
568
569	if args.dest.is_some() {
570		PathToSlate((&args.dest.unwrap()).into()).put_tx(&slate)?;
571	}
572
573	Ok(())
574}
575
576/// Issue Invoice Args
577pub struct IssueInvoiceArgs {
578	/// output file
579	pub dest: String,
580	/// issue invoice tx args
581	pub issue_args: IssueInvoiceTxArgs,
582}
583
584pub fn issue_invoice_tx<L, C, K>(
585	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
586	keychain_mask: Option<&SecretKey>,
587	args: IssueInvoiceArgs,
588) -> Result<(), LibwalletError>
589where
590	L: WalletLCProvider<'static, C, K> + 'static,
591	C: NodeClient + 'static,
592	K: keychain::Keychain + 'static,
593{
594	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
595		let slate = api.issue_invoice_tx(m, args.issue_args)?;
596		PathToSlate((&args.dest).into()).put_tx(&slate)?;
597		Ok(())
598	})?;
599	Ok(())
600}
601
602/// Arguments for the process_invoice command
603pub struct ProcessInvoiceArgs {
604	pub message: Option<String>,
605	pub minimum_confirmations: u64,
606	pub selection_strategy: String,
607	pub method: String,
608	pub dest: String,
609	pub max_outputs: usize,
610	pub input: String,
611	pub estimate_selection_strategies: bool,
612	pub ttl_blocks: Option<u64>,
613}
614
615/// Process invoice
616pub fn process_invoice<L, C, K>(
617	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
618	keychain_mask: Option<&SecretKey>,
619	tor_config: Option<TorConfig>,
620	args: ProcessInvoiceArgs,
621	dark_scheme: bool,
622) -> Result<(), LibwalletError>
623where
624	L: WalletLCProvider<'static, C, K> + 'static,
625	C: NodeClient + 'static,
626	K: keychain::Keychain + 'static,
627{
628	let slate = PathToSlate((&args.input).into()).get_tx()?;
629	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
630		if args.estimate_selection_strategies {
631			let strategies = vec!["smallest", "all"]
632				.into_iter()
633				.map(|strategy| {
634					let init_args = InitTxArgs {
635						src_acct_name: None,
636						amount: slate.amount,
637						minimum_confirmations: args.minimum_confirmations,
638						max_outputs: args.max_outputs as u32,
639						num_change_outputs: 1u32,
640						selection_strategy_is_use_all: strategy == "all",
641						estimate_only: Some(true),
642						..Default::default()
643					};
644					let slate = api.init_send_tx(m, init_args).unwrap();
645					(strategy, slate.amount, slate.fee)
646				})
647				.collect();
648			display::estimate(slate.amount, strategies, dark_scheme);
649		} else {
650			let init_args = InitTxArgs {
651				src_acct_name: None,
652				amount: 0,
653				minimum_confirmations: args.minimum_confirmations,
654				max_outputs: args.max_outputs as u32,
655				num_change_outputs: 1u32,
656				selection_strategy_is_use_all: args.selection_strategy == "all",
657				message: args.message.clone(),
658				ttl_blocks: args.ttl_blocks,
659				send_args: None,
660				..Default::default()
661			};
662			if let Err(e) = api.verify_slate_messages(m, &slate) {
663				error!("Error validating participant messages: {}", e);
664				return Err(e);
665			}
666			let result = api.process_invoice_tx(m, &slate, init_args);
667			let mut slate = match result {
668				Ok(s) => {
669					info!(
670						"Invoice processed: {} epic to {} (strategy '{}')",
671						core::amount_to_hr_string(slate.amount, false),
672						args.dest,
673						args.selection_strategy,
674					);
675					s
676				}
677				Err(e) => {
678					info!("Tx not created: {}", e);
679					return Err(e);
680				}
681			};
682
683			match args.method.as_str() {
684				"file" => {
685					let slate_putter = PathToSlate((&args.dest).into());
686					slate_putter.put_tx(&slate)?;
687					api.tx_lock_outputs(m, &slate, 0, Some(args.dest))?;
688				}
689				"self" => {
690					api.tx_lock_outputs(m, &slate, 0, Some(args.dest))?;
691					let km = match keychain_mask.as_ref() {
692						None => None,
693						Some(&m) => Some(m.to_owned()),
694					};
695					controller::foreign_single_use(wallet, km, |api| {
696						slate = api.finalize_invoice_tx(&slate)?;
697						Ok(())
698					})?;
699				}
700				method => {
701					let sender = create_sender(method, &args.dest, tor_config)?;
702					slate = sender.send_tx(&slate)?;
703					api.tx_lock_outputs(m, &slate, 0, Some(args.dest))?;
704				}
705			}
706		}
707		Ok(())
708	})?;
709	Ok(())
710}
711/// Info command args
712pub struct InfoArgs {
713	pub minimum_confirmations: u64,
714}
715
716pub fn info<L, C, K>(
717	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
718	keychain_mask: Option<&SecretKey>,
719	g_args: &GlobalArgs,
720	args: InfoArgs,
721	dark_scheme: bool,
722) -> Result<(), LibwalletError>
723where
724	L: WalletLCProvider<'static, C, K> + 'static,
725	C: NodeClient + 'static,
726	K: keychain::Keychain + 'static,
727{
728	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
729		let (validated, wallet_info) =
730			api.retrieve_summary_info(m, true, args.minimum_confirmations)?;
731		display::info(&g_args.account, &wallet_info, validated, dark_scheme);
732		Ok(())
733	})?;
734	Ok(())
735}
736
737/// Outputs command args
738pub struct OutputsArgs {
739	pub show_full_history: bool,
740}
741
742pub fn outputs<L, C, K>(
743	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
744	keychain_mask: Option<&SecretKey>,
745	g_args: &GlobalArgs,
746	args: OutputsArgs,
747	dark_scheme: bool,
748) -> Result<(), LibwalletError>
749where
750	L: WalletLCProvider<'static, C, K> + 'static,
751	C: NodeClient + 'static,
752	K: keychain::Keychain + 'static,
753{
754	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
755		let res = api.node_height(m)?;
756		let (validated, outputs) =
757			api.retrieve_outputs(m, g_args.show_spent, true, args.show_full_history, None)?;
758		display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?;
759		Ok(())
760	})?;
761	Ok(())
762}
763
764/// Txs command args
765pub struct TxsArgs {
766	pub id: Option<u32>,
767	pub tx_slate_id: Option<Uuid>,
768}
769
770pub fn txs<L, C, K>(
771	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
772	keychain_mask: Option<&SecretKey>,
773	g_args: &GlobalArgs,
774	args: TxsArgs,
775	dark_scheme: bool,
776) -> Result<(), LibwalletError>
777where
778	L: WalletLCProvider<'static, C, K> + 'static,
779	C: NodeClient + 'static,
780	K: keychain::Keychain + 'static,
781{
782	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
783		let res = api.node_height(m)?;
784		let (validated, txs) = api.retrieve_txs(m, true, args.id, args.tx_slate_id)?;
785		let include_status = !args.id.is_some() && !args.tx_slate_id.is_some();
786		display::txs(
787			&g_args.account,
788			res.height,
789			validated,
790			&txs,
791			include_status,
792			dark_scheme,
793		)?;
794
795		// if given a particular transaction id or uuid, also get and display associated
796		// inputs/outputs and messages
797		let id = if args.id.is_some() {
798			args.id
799		} else if args.tx_slate_id.is_some() {
800			if let Some(tx) = txs.iter().find(|t| t.tx_slate_id == args.tx_slate_id) {
801				Some(tx.id)
802			} else {
803				println!("Could not find a transaction matching given txid.\n");
804				None
805			}
806		} else {
807			None
808		};
809
810		if id.is_some() {
811			let (_, outputs) = api.retrieve_outputs(m, true, false, false, id)?;
812			display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?;
813			// should only be one here, but just in case
814			for tx in txs {
815				display::tx_messages(&tx, dark_scheme)?;
816				display::payment_proof(&tx)?;
817			}
818		}
819
820		Ok(())
821	})?;
822	Ok(())
823}
824
825/// Post
826pub struct PostArgs {
827	pub input: String,
828	pub fluff: bool,
829}
830
831pub fn post<L, C, K>(
832	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
833	keychain_mask: Option<&SecretKey>,
834	args: PostArgs,
835) -> Result<(), LibwalletError>
836where
837	L: WalletLCProvider<'static, C, K> + 'static,
838	C: NodeClient + 'static,
839	K: keychain::Keychain + 'static,
840{
841	let slate = PathToSlate((&args.input).into()).get_tx()?;
842
843	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
844		api.post_tx(m, &slate.tx, args.fluff)?;
845		info!("Posted transaction");
846		return Ok(());
847	})?;
848	Ok(())
849}
850
851/// Repost
852pub struct RepostArgs {
853	pub id: u32,
854	pub dump_file: Option<String>,
855	pub fluff: bool,
856}
857
858pub fn repost<L, C, K>(
859	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
860	keychain_mask: Option<&SecretKey>,
861	args: RepostArgs,
862) -> Result<(), LibwalletError>
863where
864	L: WalletLCProvider<'static, C, K> + 'static,
865	C: NodeClient + 'static,
866	K: keychain::Keychain + 'static,
867{
868	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
869		let (_, txs) = api.retrieve_txs(m, true, Some(args.id), None)?;
870		let stored_tx = api.get_stored_tx(m, &txs[0])?;
871		if stored_tx.is_none() {
872			error!(
873				"Transaction with id {} does not have transaction data. Not reposting.",
874				args.id
875			);
876			return Ok(());
877		}
878		match args.dump_file {
879			None => {
880				if txs[0].confirmed {
881					error!(
882						"Transaction with id {} is confirmed. Not reposting.",
883						args.id
884					);
885					return Ok(());
886				}
887				api.post_tx(m, &stored_tx.unwrap(), args.fluff)?;
888				info!("Reposted transaction at {}", args.id);
889				return Ok(());
890			}
891			Some(f) => {
892				let mut tx_file = File::create(f.clone())?;
893				tx_file.write_all(json::to_string(&stored_tx).unwrap().as_bytes())?;
894				tx_file.sync_all()?;
895				info!("Dumped transaction data for tx {} to {}", args.id, f);
896				return Ok(());
897			}
898		}
899	})?;
900	Ok(())
901}
902
903/// Cancel
904pub struct CancelArgs {
905	pub tx_id: Option<u32>,
906	pub tx_slate_id: Option<Uuid>,
907	pub tx_id_string: String,
908}
909
910pub fn cancel<L, C, K>(
911	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
912	keychain_mask: Option<&SecretKey>,
913	args: CancelArgs,
914) -> Result<(), LibwalletError>
915where
916	L: WalletLCProvider<'static, C, K> + 'static,
917	C: NodeClient + 'static,
918	K: keychain::Keychain + 'static,
919{
920	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
921		let result = api.cancel_tx(m, args.tx_id, args.tx_slate_id);
922		match result {
923			Ok(_) => {
924				info!("Transaction {} Cancelled", args.tx_id_string);
925				Ok(())
926			}
927			Err(e) => {
928				error!("TX Cancellation failed: {}", e);
929				Err(e)
930			}
931		}
932	})?;
933	Ok(())
934}
935
936/// wallet check
937pub struct CheckArgs {
938	pub delete_unconfirmed: bool,
939	pub start_height: Option<u64>,
940}
941
942pub fn scan<L, C, K>(
943	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
944	keychain_mask: Option<&SecretKey>,
945	args: CheckArgs,
946) -> Result<(), LibwalletError>
947where
948	L: WalletLCProvider<'static, C, K> + 'static,
949	C: NodeClient + 'static,
950	K: keychain::Keychain + 'static,
951{
952	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
953		warn!("Starting output scan ...",);
954		let result = api.scan(m, args.start_height, args.delete_unconfirmed);
955		match result {
956			Ok(_) => {
957				warn!("Wallet check complete",);
958				Ok(())
959			}
960			Err(e) => {
961				error!("Wallet check failed: {}", e);
962				error!("Backtrace: {}", e);
963				Err(e)
964			}
965		}
966	})?;
967	Ok(())
968}
969
970/// Payment Proof Address
971pub fn address<L, C, K>(
972	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
973	g_args: &GlobalArgs,
974	keychain_mask: Option<&SecretKey>,
975	epicbox_config: EpicboxConfig,
976) -> Result<(), LibwalletError>
977where
978	L: WalletLCProvider<'static, C, K> + 'static,
979	C: NodeClient + 'static,
980	K: keychain::Keychain + 'static,
981{
982	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
983		// Just address at derivation index 0 for now
984		let pub_key = api.get_public_proof_address(m, 0)?;
985		let result = address::onion_v3_from_pubkey(&pub_key);
986		let address = api.get_public_address(m, 0)?;
987
988		match result {
989			Ok(a) => {
990				println!();
991				println!("Epicbox address for account - {}", g_args.account);
992				println!("-------------------------------------");
993				println!(
994					"{}@{}",
995					address.public_key,
996					epicbox_config.epicbox_domain.unwrap()
997				);
998				println!();
999				println!("Public Proof Address for account - {}", g_args.account);
1000				println!("-------------------------------------");
1001				println!("{}", to_hex(pub_key.as_bytes().to_vec()));
1002				println!();
1003				println!("TOR Onion V3 Address for account - {}", g_args.account);
1004				println!("-------------------------------------");
1005				println!("{}", a);
1006				println!();
1007				Ok(())
1008			}
1009			Err(e) => {
1010				error!("Address retrieval failed: {}", e);
1011				error!("Backtrace: {}", e);
1012				Err(e)
1013			}
1014		}
1015	})?;
1016	Ok(())
1017}
1018
1019/// Proof Export Args
1020pub struct ProofExportArgs {
1021	pub output_file: String,
1022	pub id: Option<u32>,
1023	pub tx_slate_id: Option<Uuid>,
1024}
1025
1026pub fn proof_export<L, C, K>(
1027	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
1028	keychain_mask: Option<&SecretKey>,
1029	args: ProofExportArgs,
1030) -> Result<(), LibwalletError>
1031where
1032	L: WalletLCProvider<'static, C, K> + 'static,
1033	C: NodeClient + 'static,
1034	K: keychain::Keychain + 'static,
1035{
1036	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
1037		let result = api.retrieve_payment_proof(m, true, args.id, args.tx_slate_id);
1038		match result {
1039			Ok(p) => {
1040				// actually export proof
1041				let mut proof_file = File::create(args.output_file.clone())?;
1042				proof_file.write_all(json::to_string_pretty(&p).unwrap().as_bytes())?;
1043				proof_file.sync_all()?;
1044				warn!("Payment proof exported to {}", args.output_file);
1045				Ok(())
1046			}
1047			Err(e) => {
1048				error!("Proof export failed: {}", e);
1049				Err(e)
1050			}
1051		}
1052	})?;
1053	Ok(())
1054}
1055
1056/// Proof Verify Args
1057pub struct ProofVerifyArgs {
1058	pub input_file: String,
1059}
1060
1061pub fn proof_verify<L, C, K>(
1062	wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
1063	keychain_mask: Option<&SecretKey>,
1064	args: ProofVerifyArgs,
1065) -> Result<(), LibwalletError>
1066where
1067	L: WalletLCProvider<'static, C, K> + 'static,
1068	C: NodeClient + 'static,
1069	K: keychain::Keychain + 'static,
1070{
1071	controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
1072		let mut proof_f = match File::open(&args.input_file) {
1073			Ok(p) => p,
1074			Err(e) => {
1075				let msg = format!("{}", e);
1076				error!(
1077					"Unable to open payment proof file at {}: {}",
1078					args.input_file, e
1079				);
1080				return Err(LibwalletError::PaymentProofParsing(msg));
1081			}
1082		};
1083		let mut proof = String::new();
1084		proof_f.read_to_string(&mut proof)?;
1085		// read
1086		let proof: PaymentProof = match json::from_str(&proof) {
1087			Ok(p) => p,
1088			Err(e) => {
1089				let msg = format!("{}", e);
1090				error!("Unable to parse payment proof file: {}", e);
1091				return Err(LibwalletError::PaymentProofParsing(msg));
1092			}
1093		};
1094		let result = api.verify_payment_proof(m, &proof);
1095		match result {
1096			Ok((iam_sender, iam_recipient)) => {
1097				println!("Payment proof's signatures are valid.");
1098				if iam_sender {
1099					println!("The proof's sender address belongs to this wallet.");
1100				}
1101				if iam_recipient {
1102					println!("The proof's recipient address belongs to this wallet.");
1103				}
1104				if !iam_recipient && !iam_sender {
1105					println!(
1106						"Neither the proof's sender nor recipient address belongs to this wallet."
1107					);
1108				}
1109				Ok(())
1110			}
1111			Err(e) => {
1112				error!("Proof not valid: {}", e);
1113				Err(e)
1114			}
1115		}
1116	})?;
1117	Ok(())
1118}