1use std::fmt::Debug;
24use std::path::PathBuf;
25
26use bpwallet::{AnyIndexer, Runtime};
27use clap::Subcommand;
28use descriptors::Descriptor;
29use strict_encoding::Ident;
30
31use crate::opts::{DescrStdOpts, DescriptorOpts, DEFAULT_ELECTRUM};
32use crate::{Config, GeneralOpts, ResolverOpt, RuntimeError, WalletOpts};
33
34#[derive(Parser)]
36#[derive(Clone, Eq, PartialEq, Debug)]
37#[command(author, version, about)]
38pub struct Args<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts = DescrStdOpts> {
39 #[clap(short, long, global = true, action = clap::ArgAction::Count)]
43 pub verbose: u8,
44
45 #[command(flatten)]
46 pub wallet: WalletOpts<O>,
47
48 #[command(flatten)]
49 pub resolver: ResolverOpt,
50
51 #[command(flatten)]
52 pub general: GeneralOpts,
53
54 #[clap(subcommand)]
56 pub command: C,
57}
58
59impl<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts> Args<C, O> {
60 pub fn translate<C1: Clone + Eq + Debug + Subcommand>(&self, cmd: &C1) -> Args<C1, O> {
61 Args {
62 verbose: self.verbose.clone(),
63 wallet: self.wallet.clone(),
64 resolver: self.resolver.clone(),
65 general: self.general.clone(),
66 command: cmd.clone(),
67 }
68 }
69}
70
71pub trait Exec {
72 type Error: std::error::Error;
73 const CONF_FILE_NAME: &'static str;
74
75 fn exec(self, config: Config, name: &'static str) -> Result<(), Self::Error>;
76}
77
78impl<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts> Args<C, O> {
79 pub fn process(&mut self) { self.general.process(); }
80
81 pub fn conf_path(&self, name: &'static str) -> PathBuf {
82 let mut conf_path = self.general.base_dir();
83 conf_path.push(name);
84 conf_path.set_extension("toml");
85 conf_path
86 }
87
88 pub fn bp_runtime<D: Descriptor>(&self, conf: &Config) -> Result<Runtime<D>, RuntimeError>
89 where for<'de> D: From<O::Descr> + serde::Serialize + serde::Deserialize<'de> {
90 eprint!("Loading descriptor");
91 let mut runtime: Runtime<D> = if let Some(d) = self.wallet.descriptor_opts.descriptor() {
92 eprint!(" from command-line argument ... ");
93 Runtime::new_standard(d.into(), self.general.network)
94 } else if let Some(wallet_path) = self.wallet.wallet_path.clone() {
95 eprint!(" from specified wallet directory ... ");
96 Runtime::load_standard(wallet_path)?
97 } else {
98 let wallet_name = self
99 .wallet
100 .name
101 .as_ref()
102 .map(Ident::to_string)
103 .unwrap_or(conf.default_wallet.clone());
104 eprint!(" from wallet {wallet_name} ... ");
105 Runtime::load_standard(self.general.wallet_dir(wallet_name))?
106 };
107 let mut sync = self.resolver.sync;
108 if runtime.warnings().is_empty() {
109 eprintln!("success");
110 } else {
111 eprintln!("complete with warnings:");
112 for warning in runtime.warnings() {
113 eprintln!("- {warning}");
114 }
115 sync = true;
116 runtime.reset_warnings();
117 }
118
119 if sync || self.wallet.descriptor_opts.is_some() {
120 eprint!("Syncing");
121 let indexer = if self.resolver.electrum != DEFAULT_ELECTRUM {
122 AnyIndexer::Electrum(Box::new(electrum::Client::new(&self.resolver.electrum)?))
123 } else {
124 AnyIndexer::Esplora(Box::new(
125 esplora::Builder::new(&self.resolver.esplora).build_blocking()?,
126 ))
127 };
128 if let Err(errors) = runtime.sync(&indexer) {
129 eprintln!(" partial, some requests has failed:");
130 for err in errors {
131 eprintln!("- {err}");
132 }
133 } else {
134 eprintln!(" success");
135 }
136 runtime.try_store()?;
137 }
138
139 Ok(runtime)
140 }
141}