1use 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#[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
66pub struct InitArgs {
68 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
109pub 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
130pub 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 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
234pub 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 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
278pub 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
430pub 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
482pub 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 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
576pub struct IssueInvoiceArgs {
578 pub dest: String,
580 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
602pub 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
615pub 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}
711pub 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
737pub 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
764pub 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 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 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
825pub 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
851pub 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
903pub 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
936pub 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
970pub 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 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
1019pub 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 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
1056pub 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 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}