diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index ff222ca..686e7f7 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -11,7 +11,7 @@ jobs: matrix: rust: - 1.60.0 # STABLE - - 1.56.0 # MSRV + - 1.57.0 # MSRV features: - default - electrum,sqlite-db @@ -80,7 +80,7 @@ jobs: - run: sudo apt-get update || exit 1 - run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1 - name: Set default toolchain - run: rustup default 1.56.1 # MSRV + run: rustup default 1.57.0 # MSRV - name: Set profile run: rustup set profile minimal - name: Add target wasm32 diff --git a/Cargo.lock b/Cargo.lock index 160b5cf..9e516b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,15 +28,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "async-trait" version = "0.1.58" @@ -134,6 +125,7 @@ dependencies = [ "bdk", "bdk-macros", "bdk-reserves", + "clap", "dirs-next", "electrsd", "env_logger", @@ -147,7 +139,6 @@ dependencies = [ "secp256k1 0.22.1", "serde", "serde_json", - "structopt", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -382,17 +373,41 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ - "ansi_term", "atty", "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", "strsim", + "termcolor", "textwrap", - "unicode-width", - "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -885,12 +900,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -1318,6 +1330,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_str_bytes" +version = "6.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" + [[package]] name = "parking_lot" version = "0.11.2" @@ -2115,33 +2133,9 @@ checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" [[package]] name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -2190,12 +2184,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.11.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" @@ -2463,12 +2454,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index fb08e91..4e4c0eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ license = "MIT" [dependencies] bdk = { version = "0.24", default-features = false, features = ["all-keys"] } bdk-macros = "0.6" -structopt = "0.3" +clap = { version = "3.2.22", features = ["derive"] } serde_json = "1.0" log = "0.4" zeroize = "<1.4.0" diff --git a/src/commands.rs b/src/commands.rs index 91c3ab6..6bc5999 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,23 +13,21 @@ //! All subcommands are defined in the below enums. #![allow(clippy::large_enum_variant)] -use structopt::clap::AppSettings; - -use structopt::StructOpt; +use clap::{AppSettings, Parser, Subcommand}; use bdk::bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey}; use bdk::bitcoin::{Address, Network, OutPoint, Script}; +use crate::utils::{parse_outpoint, parse_recipient}; #[cfg(any( feature = "compact_filters", feature = "electrum", feature = "esplora", feature = "rpc" ))] -use crate::utils::parse_proxy_auth; -use crate::utils::{parse_outpoint, parse_recipient}; +use {crate::utils::parse_proxy_auth, clap::Args}; -#[derive(PartialEq, Clone, Debug, StructOpt)] +#[derive(PartialEq, Clone, Debug, Parser)] /// The BDK Command Line Wallet App /// /// bdk-cli is a light weight command line bitcoin wallet, powered by BDK. @@ -41,13 +39,13 @@ use crate::utils::{parse_outpoint, parse_recipient}; /// bdk-cli is also a fully functioning Bitcoin wallet with taproot support! /// /// For more information checkout -#[structopt(version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"), +#[clap(version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"), author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))] pub struct CliOpts { /// Sets the network. - #[structopt( + #[clap( name = "NETWORK", - short = "n", + short = 'n', long = "network", default_value = "testnet", possible_values = &["bitcoin", "testnet", "signet", "regtest"] @@ -55,16 +53,16 @@ pub struct CliOpts { pub network: Network, /// Sets the wallet data directory. /// Default value : "~/.bdk-bitcoin - #[structopt(name = "DATADIR", short = "d", long = "datadir")] + #[clap(name = "DATADIR", short = 'd', long = "datadir")] pub datadir: Option, /// Top level cli sub-commands. - #[structopt(subcommand)] + #[clap(subcommand)] pub subcommand: CliSubCommand, } /// Top level cli sub-commands. -#[derive(Debug, StructOpt, Clone, PartialEq)] -#[structopt(rename_all = "snake")] +#[derive(Debug, Subcommand, Clone, PartialEq)] +#[clap(rename_all = "snake")] pub enum CliSubCommand { /// Node operation subcommands. /// @@ -75,9 +73,9 @@ pub enum CliSubCommand { /// Feel free to open a feature request issue in /// if you need extra rpc calls not covered in the command list. #[cfg(feature = "regtest-node")] - #[structopt(long_about = "Regtest Node mode")] + #[clap(long_about = "Regtest Node mode")] Node { - #[structopt(subcommand)] + #[clap(subcommand)] subcommand: NodeSubCommand, }, /// Wallet operations. @@ -87,9 +85,9 @@ pub enum CliSubCommand { /// needs backend like `sync` and `broadcast`, compile the binary with specific backend feature /// and use the configuration options below to configure for that backend. Wallet { - #[structopt(flatten)] + #[clap(flatten)] wallet_opts: WalletOpts, - #[structopt(subcommand)] + #[clap(subcommand)] subcommand: WalletSubCommand, }, /// Key management operations. @@ -100,18 +98,18 @@ pub enum CliSubCommand { /// These sub-commands are **EXPERIMENTAL** and should only be used for testing. Do not use this /// feature to create keys that secure actual funds on the Bitcoin mainnet. Key { - #[structopt(subcommand)] + #[clap(subcommand)] subcommand: KeySubCommand, }, /// Compile a miniscript policy to an output descriptor. #[cfg(feature = "compiler")] - #[structopt(long_about = "Miniscript policy compiler")] + #[clap(long_about = "Miniscript policy compiler")] Compile { /// Sets the spending policy to compile. - #[structopt(name = "POLICY", required = true, index = 1)] + #[clap(name = "POLICY", required = true, index = 1)] policy: String, /// Sets the script type used to embed the compiled policy. - #[structopt(name = "TYPE", short = "t", long = "type", default_value = "wsh", possible_values = &["sh","wsh", "sh-wsh"])] + #[clap(name = "TYPE", short = 't', long = "type", default_value = "wsh", possible_values = &["sh","wsh", "sh-wsh"])] script_type: String, }, #[cfg(feature = "repl")] @@ -120,7 +118,7 @@ pub enum CliSubCommand { /// REPL command loop can be used to make recurring callbacks to an already loaded wallet. /// This mode is useful for hands on live testing of wallet operations. Repl { - #[structopt(flatten)] + #[clap(flatten)] wallet_opts: WalletOpts, }, /// Proof of reserves operations. @@ -129,25 +127,25 @@ pub enum CliSubCommand { #[cfg(all(feature = "reserves", feature = "electrum"))] ExternalReserves { /// Sets the challenge message with which the proof was produced. - #[structopt(name = "MESSAGE", required = true, index = 1)] + #[clap(name = "MESSAGE", required = true, index = 1)] message: String, /// Sets the proof in form of a PSBT to verify. - #[structopt(name = "PSBT", required = true, index = 2)] + #[clap(name = "PSBT", required = true, index = 2)] psbt: String, /// Sets the number of block confirmations for UTXOs to be considered. - #[structopt(name = "CONFIRMATIONS", required = true, index = 3)] + #[clap(name = "CONFIRMATIONS", required = true, index = 3)] confirmations: usize, /// Sets the addresses for which the proof was produced. - #[structopt(name = "ADDRESSES", required = true, index = 4)] + #[clap(name = "ADDRESSES", required = true, index = 4)] addresses: Vec, - #[structopt(flatten)] + #[clap(flatten)] electrum_opts: ElectrumOpts, }, } /// Backend Node operation subcommands. -#[derive(Debug, StructOpt, Clone, PartialEq)] -#[structopt(rename_all = "lower")] +#[derive(Debug, Subcommand, Clone, PartialEq)] +#[clap(rename_all = "lower")] #[cfg(any(feature = "regtest-node"))] pub enum NodeSubCommand { /// Get info. @@ -161,12 +159,12 @@ pub enum NodeSubCommand { /// Send to an external wallet address. SendToAddress { address: String, amount: u64 }, /// Execute any bitcoin-cli commands. - #[structopt(external_subcommand)] + #[clap(external_subcommand)] BitcoinCli(Vec), } /// Wallet operation subcommands. -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Subcommand, Clone, PartialEq)] pub enum WalletSubCommand { #[cfg(any( feature = "electrum", @@ -174,60 +172,60 @@ pub enum WalletSubCommand { feature = "compact_filters", feature = "rpc" ))] - #[structopt(flatten)] + #[clap(flatten)] OnlineWalletSubCommand(OnlineWalletSubCommand), - #[structopt(flatten)] + #[clap(flatten)] OfflineWalletSubCommand(OfflineWalletSubCommand), } /// Config options wallet operations can take. -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Parser, Clone, PartialEq)] pub struct WalletOpts { /// Selects the wallet to use. - #[structopt(name = "WALLET_NAME", short = "w", long = "wallet")] + #[clap(name = "WALLET_NAME", short = 'w', long = "wallet")] pub wallet: Option, /// Adds verbosity, returns PSBT in JSON format alongside serialized, displays expanded objects. - #[structopt(name = "VERBOSE", short = "v", long = "verbose")] + #[clap(name = "VERBOSE", short = 'v', long = "verbose")] pub verbose: bool, /// Sets the descriptor to use for the external addresses. - #[structopt(name = "DESCRIPTOR", short = "d", long = "descriptor", required = true)] + #[clap(name = "DESCRIPTOR", short = 'd', long = "descriptor", required = true)] pub descriptor: String, /// Sets the descriptor to use for internal addresses. - #[structopt(name = "CHANGE_DESCRIPTOR", short = "c", long = "change_descriptor")] + #[clap(name = "CHANGE_DESCRIPTOR", short = 'c', long = "change_descriptor")] pub change_descriptor: Option, #[cfg(feature = "electrum")] - #[structopt(flatten)] + #[clap(flatten)] pub electrum_opts: ElectrumOpts, #[cfg(feature = "esplora")] - #[structopt(flatten)] + #[clap(flatten)] pub esplora_opts: EsploraOpts, #[cfg(feature = "compact_filters")] - #[structopt(flatten)] + #[clap(flatten)] pub compactfilter_opts: CompactFilterOpts, #[cfg(feature = "rpc")] - #[structopt(flatten)] + #[clap(flatten)] pub rpc_opts: RpcOpts, #[cfg(any(feature = "compact_filters", feature = "electrum", feature = "esplora"))] - #[structopt(flatten)] + #[clap(flatten)] pub proxy_opts: ProxyOpts, } /// Options to configure a SOCKS5 proxy for a blockchain client connection. #[cfg(any(feature = "compact_filters", feature = "electrum", feature = "esplora"))] -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Args, Clone, PartialEq)] pub struct ProxyOpts { /// Sets the SOCKS5 proxy for a blockchain client. - #[structopt(name = "PROXY_ADDRS:PORT", long = "proxy", short = "p")] + #[clap(name = "PROXY_ADDRS:PORT", long = "proxy", short = 'p')] pub proxy: Option, /// Sets the SOCKS5 proxy credential. - #[structopt(name="PROXY_USER:PASSWD", long="proxy_auth", short="a", parse(try_from_str = parse_proxy_auth))] + #[clap(name="PROXY_USER:PASSWD", long="proxy_auth", short='a', value_parser = parse_proxy_auth)] pub proxy_auth: Option<(String, String)>, /// Sets the SOCKS5 proxy retries for the blockchain client. - #[structopt( + #[clap( name = "PROXY_RETRIES", - short = "r", + short = 'r', long = "retries", default_value = "5" )] @@ -236,25 +234,25 @@ pub struct ProxyOpts { /// Options to configure a BIP157 Compact Filter backend. #[cfg(feature = "compact_filters")] -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Args, Clone, PartialEq)] pub struct CompactFilterOpts { /// Sets the full node network address. - #[structopt( + #[clap( name = "ADDRESS:PORT", - short = "n", + short = 'n', long = "node", default_value = "127.0.0.1:18444" )] pub address: Vec, /// Sets the number of parallel node connections. - #[structopt(name = "CONNECTIONS", long = "conn_count", default_value = "4")] + #[clap(name = "CONNECTIONS", long = "conn_count", default_value = "4")] pub conn_count: usize, /// Optionally skip initial `skip_blocks` blocks. - #[structopt( + #[clap( name = "SKIP_BLOCKS", - short = "k", + short = 'k', long = "skip_blocks", default_value = "0" )] @@ -263,35 +261,35 @@ pub struct CompactFilterOpts { /// Options to configure a bitcoin core rpc backend. #[cfg(feature = "rpc")] -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Args, Clone, PartialEq)] pub struct RpcOpts { /// Sets the full node address for rpc connection. - #[structopt( + #[clap( name = "ADDRESS:PORT", - short = "n", + short = 'n', long = "node", default_value = "127.0.0.1:18443" )] pub address: String, /// Sets the rpc basic authentication. - #[structopt( + #[clap( name = "USER:PASSWD", - short = "a", + short = 'a', long = "basic-auth", - parse(try_from_str = parse_proxy_auth), + value_parser = parse_proxy_auth, default_value = "user:password", )] pub basic_auth: (String, String), /// Sets an optional cookie authentication. - #[structopt(name = "COOKIE", long = "cookie")] + #[clap(name = "COOKIE", long = "cookie")] pub cookie: Option, /// Time in unix seconds in which initial sync will start scanning from (0 to start from genesis). - #[structopt( + #[clap( name = "RPC_START_TIME", - short = "s", + short = 's', long = "start-time", default_value = "0" )] @@ -300,25 +298,25 @@ pub struct RpcOpts { /// Options to configure electrum backend. #[cfg(feature = "electrum")] -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Args, Clone, PartialEq)] pub struct ElectrumOpts { /// Sets the SOCKS5 proxy timeout for the Electrum client. - #[structopt(name = "PROXY_TIMEOUT", short = "t", long = "timeout")] + #[clap(name = "PROXY_TIMEOUT", short = 't', long = "timeout")] pub timeout: Option, /// Sets the Electrum server to use. - #[structopt( + #[clap( name = "ELECTRUM_URL", - short = "s", + short = 's', long = "server", default_value = "ssl://electrum.blockstream.info:60002" )] pub server: String, /// Stop searching addresses for transactions after finding an unused gap of this length. - #[structopt( + #[clap( name = "STOP_GAP", long = "stop_gap", - short = "g", + short = 'g', default_value = "10" )] pub stop_gap: usize, @@ -326,38 +324,38 @@ pub struct ElectrumOpts { /// Options to configure Esplora backend. #[cfg(feature = "esplora")] -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Args, Clone, PartialEq)] pub struct EsploraOpts { /// Use the esplora server if given as parameter. - #[structopt( + #[clap( name = "ESPLORA_URL", - short = "s", + short = 's', long = "server", default_value = "https://blockstream.info/testnet/api/" )] pub server: String, /// Socket timeout. - #[structopt(name = "TIMEOUT", long = "timeout", default_value = "5")] + #[clap(name = "TIMEOUT", long = "timeout", default_value = "5")] pub timeout: u64, /// Stop searching addresses for transactions after finding an unused gap of this length. - #[structopt( + #[clap( name = "STOP_GAP", long = "stop_gap", - short = "g", + short = 'g', default_value = "10" )] pub stop_gap: usize, /// Number of parallel requests sent to the esplora service. - #[structopt(name = "CONCURRENCY", long = "conc", default_value = "4")] + #[clap(name = "CONCURRENCY", long = "conc", default_value = "4")] pub conc: u8, } /// Wallet subcommands that can be issued without a blockchain backend. -#[derive(Debug, StructOpt, Clone, PartialEq)] -#[structopt(rename_all = "snake")] +#[derive(Debug, Subcommand, Clone, PartialEq)] +#[clap(rename_all = "snake")] pub enum OfflineWalletSubCommand { /// Generates a new external address. GetNewAddress, @@ -370,45 +368,47 @@ pub enum OfflineWalletSubCommand { /// Creates a new unsigned transaction. CreateTx { /// Adds a recipient to the transaction. - #[structopt(name = "ADDRESS:SAT", long = "to", required = true, parse(try_from_str = parse_recipient))] + // Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704. + // Address and amount parsing is done at run time in handler function. + #[clap(name = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)] recipients: Vec<(Script, u64)>, /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0. - #[structopt(short = "all", long = "send_all")] + #[clap(long = "send_all", short = 'a')] send_all: bool, /// Enables Replace-By-Fee (BIP125). - #[structopt(short = "rbf", long = "enable_rbf")] + #[clap(long = "enable_rbf", short = 'r')] enable_rbf: bool, /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. - #[structopt(long = "offline_signer")] + #[clap(long = "offline_signer")] offline_signer: bool, /// Selects which utxos *must* be spent. - #[structopt(name = "MUST_SPEND_TXID:VOUT", long = "utxos", parse(try_from_str = parse_outpoint))] + #[clap(name = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] utxos: Option>, /// Marks a utxo as unspendable. - #[structopt(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", parse(try_from_str = parse_outpoint))] + #[clap(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] unspendable: Option>, /// Fee rate to use in sat/vbyte. - #[structopt(name = "SATS_VBYTE", short = "fee", long = "fee_rate")] + #[clap(name = "SATS_VBYTE", short = 'f', long = "fee_rate")] fee_rate: Option, /// Selects which policy should be used to satisfy the external descriptor. - #[structopt(name = "EXT_POLICY", long = "external_policy")] + #[clap(name = "EXT_POLICY", long = "external_policy")] external_policy: Option, /// Selects which policy should be used to satisfy the internal descriptor. - #[structopt(name = "INT_POLICY", long = "internal_policy")] + #[clap(name = "INT_POLICY", long = "internal_policy")] internal_policy: Option, /// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes) - #[structopt( + #[clap( name = "ADD_STRING", long = "add_string", - short = "s", + short = 's', conflicts_with = "ADD_DATA" )] add_string: Option, /// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes) - #[structopt( + #[clap( name = "ADD_DATA", long = "add_data", - short = "o", + short = 'o', conflicts_with = "ADD_STRING" )] add_data: Option, //base 64 econding @@ -416,22 +416,22 @@ pub enum OfflineWalletSubCommand { /// Bumps the fees of an RBF transaction. BumpFee { /// TXID of the transaction to update. - #[structopt(name = "TXID", short = "txid", long = "txid")] + #[clap(name = "TXID", long = "txid")] txid: String, /// Allows the wallet to reduce the amount to the specified address in order to increase fees. - #[structopt(name = "SHRINK_ADDRESS", short = "s", long = "shrink")] + #[clap(name = "SHRINK_ADDRESS", long = "shrink")] shrink_address: Option
, /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output. - #[structopt(long = "offline_signer")] + #[clap(long = "offline_signer")] offline_signer: bool, /// Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used. - #[structopt(name = "MUST_SPEND_TXID:VOUT", long = "utxos", parse(try_from_str = parse_outpoint))] + #[clap(name = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)] utxos: Option>, /// Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees. - #[structopt(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", parse(try_from_str = parse_outpoint))] + #[clap(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] unspendable: Option>, /// The new targeted fee rate in sat/vbyte. - #[structopt(name = "SATS_VBYTE", short = "fee", long = "fee_rate")] + #[clap(name = "SATS_VBYTE", short = 'f', long = "fee_rate")] fee_rate: f32, }, /// Returns the available spending policies for the descriptor. @@ -441,44 +441,44 @@ pub enum OfflineWalletSubCommand { /// Signs and tries to finalize a PSBT. Sign { /// Sets the PSBT to sign. - #[structopt(name = "BASE64_PSBT", long = "psbt")] + #[clap(name = "BASE64_PSBT", long = "psbt")] psbt: String, /// Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor. - #[structopt(name = "HEIGHT", long = "assume_height")] + #[clap(name = "HEIGHT", long = "assume_height")] assume_height: Option, /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided. - #[structopt(name = "WITNESS", long = "trust_witness_utxo")] + #[clap(name = "WITNESS", long = "trust_witness_utxo")] trust_witness_utxo: Option, }, /// Extracts a raw transaction from a PSBT. ExtractPsbt { /// Sets the PSBT to extract - #[structopt(name = "BASE64_PSBT", long = "psbt")] + #[clap(name = "BASE64_PSBT", long = "psbt")] psbt: String, }, /// Finalizes a PSBT. FinalizePsbt { /// Sets the PSBT to finalize. - #[structopt(name = "BASE64_PSBT", long = "psbt")] + #[clap(name = "BASE64_PSBT", long = "psbt")] psbt: String, /// Assume the blockchain has reached a specific height. - #[structopt(name = "HEIGHT", long = "assume_height")] + #[clap(name = "HEIGHT", long = "assume_height")] assume_height: Option, /// Whether the signer should trust the witness_utxo, if the non_witness_utxo hasn’t been provided. - #[structopt(name = "WITNESS", long = "trust_witness_utxo")] + #[clap(name = "WITNESS", long = "trust_witness_utxo")] trust_witness_utxo: Option, }, /// Combines multiple PSBTs into one. CombinePsbt { /// Add one PSBT to combine. This option can be repeated multiple times, one for each PSBT. - #[structopt(name = "BASE64_PSBT", long = "psbt", required = true)] + #[clap(name = "BASE64_PSBT", long = "psbt", required = true)] psbt: Vec, }, } /// Wallet subcommands that needs a blockchain backend. -#[derive(Debug, StructOpt, Clone, PartialEq)] -#[structopt(rename_all = "snake")] +#[derive(Debug, Subcommand, Clone, PartialEq)] +#[clap(rename_all = "snake")] #[cfg(any( feature = "electrum", feature = "esplora", @@ -491,7 +491,7 @@ pub enum OnlineWalletSubCommand { /// Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract. Broadcast { /// Sets the PSBT to sign. - #[structopt( + #[clap( name = "BASE64_PSBT", long = "psbt", required_unless = "RAWTX", @@ -499,7 +499,7 @@ pub enum OnlineWalletSubCommand { )] psbt: Option, /// Sets the raw transaction to broadcast. - #[structopt( + #[clap( name = "RAWTX", long = "tx", required_unless = "BASE64_PSBT", @@ -511,81 +511,81 @@ pub enum OnlineWalletSubCommand { #[cfg(feature = "reserves")] ProduceProof { /// Sets the message. - #[structopt(name = "MESSAGE", long = "message")] + #[clap(name = "MESSAGE", long = "message")] msg: String, }, /// Verify a proof of reserves for our wallet. #[cfg(feature = "reserves")] VerifyProof { /// Sets the PSBT to verify. - #[structopt(name = "BASE64_PSBT", long = "psbt")] + #[clap(name = "BASE64_PSBT", long = "psbt")] psbt: String, /// Sets the message to verify. - #[structopt(name = "MESSAGE", long = "message")] + #[clap(name = "MESSAGE", long = "message")] msg: String, /// Sets the number of block confirmations for UTXOs to be considered. - #[structopt(name = "CONFIRMATIONS", long = "confirmations", default_value = "6")] + #[clap(name = "CONFIRMATIONS", long = "confirmations", default_value = "6")] confirmations: u32, }, } /// Subcommands for Key operations. -#[derive(Debug, StructOpt, Clone, PartialEq)] +#[derive(Debug, Subcommand, Clone, PartialEq)] pub enum KeySubCommand { /// Generates new random seed mnemonic phrase and corresponding master extended key. Generate { /// Entropy level based on number of random seed mnemonic words. - #[structopt( + #[clap( name = "WORD_COUNT", - short = "e", + short = 'e', long = "entropy", default_value = "24", possible_values = &["12","24"], )] word_count: usize, /// Seed password. - #[structopt(name = "PASSWORD", short = "p", long = "password")] + #[clap(name = "PASSWORD", short = 'p', long = "password")] password: Option, }, /// Restore a master extended key from seed backup mnemonic words. Restore { /// Seed mnemonic words, must be quoted (eg. "word1 word2 ..."). - #[structopt(name = "MNEMONIC", short = "m", long = "mnemonic")] + #[clap(name = "MNEMONIC", short = 'm', long = "mnemonic")] mnemonic: String, /// Seed password. - #[structopt(name = "PASSWORD", short = "p", long = "password")] + #[clap(name = "PASSWORD", short = 'p', long = "password")] password: Option, }, /// Derive a child key pair from a master extended key and a derivation path string (eg. "m/84'/1'/0'/0" or "m/84h/1h/0h/0"). Derive { /// Extended private key to derive from. - #[structopt(name = "XPRV", short = "x", long = "xprv")] + #[clap(name = "XPRV", short = 'x', long = "xprv")] xprv: ExtendedPrivKey, /// Path to use to derive extended public key from extended private key. - #[structopt(name = "PATH", short = "p", long = "path")] + #[clap(name = "PATH", short = 'p', long = "path")] path: DerivationPath, }, } /// Subcommands available in REPL mode. #[cfg(any(feature = "repl", target_arch = "wasm32"))] -#[derive(Debug, StructOpt, Clone, PartialEq)] -#[structopt(global_settings =&[AppSettings::NoBinaryName], rename_all = "lower")] +#[derive(Debug, Parser, Clone, PartialEq)] +#[clap(global_settings =&[AppSettings::NoBinaryName], rename_all = "lower")] pub enum ReplSubCommand { /// Execute wallet commands. Wallet { - #[structopt(subcommand)] + #[clap(subcommand)] subcommand: WalletSubCommand, }, /// Execute key commands. Key { - #[structopt(subcommand)] + #[clap(subcommand)] subcommand: KeySubCommand, }, /// Execute node commands. #[cfg(feature = "regtest-node")] Node { - #[structopt(subcommand)] + #[clap(subcommand)] subcommand: NodeSubCommand, }, /// Exit REPL loop. @@ -611,7 +611,6 @@ mod test { SyncOptions, Wallet, }; use std::str::{self, FromStr}; - use structopt::StructOpt; use super::OfflineWalletSubCommand::{BumpFee, CreateTx, GetNewAddress}; #[cfg(any( @@ -632,6 +631,12 @@ mod test { #[cfg(feature = "repl")] use regex::Regex; + #[test] + fn test_clap_args() { + use clap::CommandFactory; + CliOpts::command().debug_assert(); + } + #[test] fn test_parse_wallet_get_new_address() { let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet", @@ -857,7 +862,7 @@ mod test { "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)", "--proxy", "127.0.0.1:9005", "--proxy_auth", "random_user:random_passwd", - "--node", "127.0.0.1:18444", "127.2.3.1:19695", + "--node", "127.0.0.1:18444", "--conn_count", "4", "--skip_blocks", "5", "get_new_address"]; @@ -874,7 +879,7 @@ mod test { descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(), change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()), compactfilter_opts: CompactFilterOpts{ - address: vec!["127.0.0.1:18444".to_string(), "127.2.3.1:19695".to_string()], + address: vec!["127.0.0.1:18444".to_string()], conn_count: 4, skip_blocks: 5, }, @@ -959,20 +964,22 @@ mod test { let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet", "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)", - "create_tx", "--to", "n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ:123456","mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf:78910", + "create_tx", "--to", "n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ:123456", "--to", "mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf:78910", "--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1", "--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2", "--add_string","Hello BDK", ]; - let cli_opts = CliOpts::from_iter(&cli_args); + let cli_opts = CliOpts::parse_from(&cli_args); let script1 = Address::from_str("n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ") .unwrap() .script_pubkey(); + let script2 = Address::from_str("mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf") .unwrap() .script_pubkey(); + let outpoint1 = OutPoint::from_str( "87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1", ) @@ -1356,6 +1363,7 @@ mod test { let expected_cli_opts = CliOpts { network: Network::Bitcoin, + datadir: None, subcommand: CliSubCommand::Wallet { wallet_opts: WalletOpts { wallet: None, @@ -1375,7 +1383,7 @@ mod test { retries: 5, }, }, - subcommand: OnlineWalletSubCommand(VerifyProof { + subcommand: OnlineWalletSubCommand(OnlineWalletSubCommand::VerifyProof { psbt: psbt.to_string(), msg: message.to_string(), confirmations: 6, @@ -1411,6 +1419,7 @@ mod test { let expected_cli_opts = CliOpts { network: Network::Bitcoin, + datadir: None, subcommand: CliSubCommand::Wallet { wallet_opts: WalletOpts { wallet: None, @@ -1430,7 +1439,7 @@ mod test { retries: 5, }, }, - subcommand: OnlineWalletSubCommand(VerifyProof { + subcommand: OnlineWalletSubCommand(OnlineWalletSubCommand::VerifyProof { psbt: psbt.to_string(), msg: message.to_string(), confirmations: 0, diff --git a/src/handlers.rs b/src/handlers.rs index c630571..422657e 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -24,7 +24,7 @@ use crate::commands::*; use crate::utils::*; use bdk::{database::BatchDatabase, wallet::AddressIndex, Error, FeeRate, KeychainKind, Wallet}; -use structopt::StructOpt; +use clap::Parser; use bdk::bitcoin::consensus::encode::{deserialize, serialize, serialize_hex}; #[cfg(any( @@ -272,7 +272,7 @@ where let mut psbts = psbt .iter() .map(|s| { - let psbt = base64::decode(&s).map_err(|e| Error::Generic(e.to_string()))?; + let psbt = base64::decode(s).map_err(|e| Error::Generic(e.to_string()))?; let psbt: PartiallySignedTransaction = deserialize(&psbt)?; Ok(psbt) }) diff --git a/src/main.rs b/src/main.rs index eeebddf..ced420f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ use crate::commands::CliOpts; use crate::handlers::*; use bdk::{bitcoin, Error}; use bdk_macros::{maybe_async, maybe_await}; -use structopt::StructOpt; +use clap::Parser; #[cfg(any(feature = "repl", target_arch = "wasm32"))] const REPL_LINE_SPLIT_REGEX: &str = r#""([^"]*)"|'([^']*)'|([\w\-]+)"#; @@ -36,7 +36,7 @@ const REPL_LINE_SPLIT_REGEX: &str = r#""([^"]*)"|'([^']*)'|([\w\-]+)"#; fn main() { env_logger::init(); - let cli_opts: CliOpts = CliOpts::from_args(); + let cli_opts: CliOpts = CliOpts::parse(); let network = cli_opts.network; debug!("network: {:?}", network); diff --git a/src/wasm.rs b/src/wasm.rs index 0a9e7ad..5e69ffc 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -9,6 +9,7 @@ use bitcoin::*; use bdk::blockchain::AnyBlockchain; use bdk::database::AnyDatabase; use bdk::miniscript::{MiniscriptKey, Translator}; +use clap::Parser; use js_sys::Promise; use regex::Regex; use std::collections::HashMap; @@ -17,7 +18,6 @@ use std::ops::Deref; use std::path::PathBuf; use std::rc::Rc; use std::str::FromStr; -use structopt::StructOpt; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise;