bp_util/
args.rs

1// Modern, minimalistic & standard-compliant cold wallet library.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2020-2023 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2020-2023 LNP/BP Standards Association. All rights reserved.
9// Copyright (C) 2020-2023 Dr Maxim Orlovsky. All rights reserved.
10//
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15//     http://www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22
23use 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/// Command-line arguments
35#[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    /// Set verbosity level.
40    ///
41    /// Can be used multiple times to increase verbosity.
42    #[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    /// Command to execute.
55    #[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}