Repository: bitcoindevkit/bdk-cli (language: rust)

Issue:
RUSTSEC-2021-0139: ansi_term is Unmaintained

> ansi_term is Unmaintained

| Details             |                                                |
| ------------------- | ---------------------------------------------- |
| Status              | unmaintained                |
| Package             | `ansi_term`                      |
| Version             | `0.12.1`                   |
| URL                 | [https://github.com/ogham/rust-ansi-term/issues/72](https://github.com/ogham/rust-ansi-term/issues/72) |
| Date                | 2021-08-18                         |

The maintainer has adviced this crate is deprecated and will not
receive any maintenance.

The crate does not seem to have much dependencies and may or may not be ok to use as-is.

Last release seems to have been three years ago.

## Possible Alternative(s)

 The below list has not been vetted in any way and may or may not contain alternatives;

 - [anstyle](https://github.com/epage/anstyle)
 - [nu-ansi-term](https://crates.io/crates/nu-ansi-term)
 - [yansi](https://crates.io/crates/yansi)

See [advisory page](https://rustsec.org/advisories/RUSTSEC-2021-0139.html) for additional details.



Discussion / hints:
This is causing from `clap` of `structopt`.. 
```
$ cargo tree -i ansi_term
ansi_term v0.12.1
└── clap v2.34.0
    └── structopt v0.3.26
        └── bdk-cli v0.5.0 
```

`structopt` doesn't support `clap 3.**` .. So we need to wait on them to fix this..  
I think clap as of 3.x now includes structopt like features. See: https://docs.rs/clap/latest/clap/

So rather than updating structopt we should just switch to clap 3.x, then we should be able to close this issue.
Sounds good idea.. Thats what I think we should do..  Shall we just close this one, and create as an enhancement issue?? Would be easier to find for anyone who might be interested taking it up, till I get to it..

Fix patch:
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 <https://bitcoindevkit.org/>
-#[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<std::path::PathBuf>,
     /// 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 <https://github.com/bitcoindevkit/bdk-cli>
     /// 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<String>,
-        #[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<String>),
 }
 
 /// 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<String>,
     /// 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<String>,
     #[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<String>,
 
     /// 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<String>,
 
     /// 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<String>,
 
     /// 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<u8>,
     /// 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<Vec<OutPoint>>,
         /// 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<Vec<OutPoint>>,
         /// 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<f32>,
         /// 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<String>,
         /// 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<String>,
         /// 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<String>,
         /// 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<String>, //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<Address>,
         /// 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<Vec<OutPoint>>,
         /// 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<Vec<OutPoint>>,
         /// 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<u32>,
         /// 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<bool>,
     },
     /// 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<u32>,
         /// 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<bool>,
     },
     /// 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<String>,
     },
 }
 
 /// 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<String>,
         /// 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<String>,
     },
     /// 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<String>,
     },
     /// 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;
 


Test patch: