]> Untitled Git - bdk-cli/commitdiff
Add RPC backend
authorrajarshimaitra <rajarshi149@gmail.com>
Fri, 13 Aug 2021 11:09:54 +0000 (16:39 +0530)
committerrajarshimaitra <rajarshi149@gmail.com>
Thu, 7 Oct 2021 15:33:43 +0000 (21:03 +0530)
Expose the RPC backend feature via cli arg options.
RPC backend can be connected via all default parameters
without specifying any arg options.

Cargo.lock
Cargo.toml
src/bdk_cli.rs
src/lib.rs

index 25888a174147de3829338f88923fe930896d3f93..aa5a6c0373e4e829102253af59d263df0cf949ee 100644 (file)
@@ -78,6 +78,16 @@ dependencies = [
  "rustc-demangle",
 ]
 
+[[package]]
+name = "base64"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
+dependencies = [
+ "byteorder",
+ "safemem",
+]
+
 [[package]]
 name = "base64"
 version = "0.10.1"
@@ -117,12 +127,13 @@ dependencies = [
  "async-trait",
  "bdk-macros 0.6.0",
  "bitcoin",
+ "bitcoincore-rpc",
  "cc",
  "electrum-client",
  "futures",
  "js-sys",
  "lazy_static",
- "log",
+ "log 0.4.14",
  "miniscript",
  "rand 0.7.3",
  "reqwest",
@@ -146,7 +157,7 @@ dependencies = [
  "clap",
  "dirs-next 2.0.0",
  "env_logger",
- "log",
+ "log 0.4.14",
  "regex",
  "rustyline",
  "serde_json",
@@ -223,6 +234,31 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bitcoincore-rpc"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d708433972bf78bd5f909d1d288f9ac1cceeab1460edb954e962f83e1f440a3"
+dependencies = [
+ "bitcoincore-rpc-json",
+ "jsonrpc",
+ "log 0.4.14",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "bitcoincore-rpc-json"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "977e55a945ab1e3c446dea93267876703c15e07c7d6eeb1dfa1766b3190c560f"
+dependencies = [
+ "bitcoin",
+ "hex",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -459,8 +495,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "edd12f125852d77980725243b2a8b3bea73cd4c7a22c33bc52b08b664c561dc7"
 dependencies = [
  "bitcoin",
- "log",
- "rustls 0.16.0",
+ "log 0.4.14",
+ "rustls",
  "serde",
  "serde_json",
  "socks",
@@ -485,7 +521,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
 dependencies = [
  "atty",
  "humantime",
- "log",
+ "log 0.4.14",
  "regex",
  "termcolor",
 ]
@@ -546,7 +582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
 dependencies = [
  "matches",
- "percent-encoding",
+ "percent-encoding 2.1.0",
 ]
 
 [[package]]
@@ -756,6 +792,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hex"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
+
 [[package]]
 name = "hmac"
 version = "0.7.1"
@@ -846,6 +888,17 @@ dependencies = [
  "tokio-native-tls",
 ]
 
+[[package]]
+name = "idna"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "idna"
 version = "0.2.3"
@@ -906,6 +959,24 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "jsonrpc"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436f3455a8a4e9c7b14de9f1206198ee5d0bdc2db1b560339d2141093d7dd389"
+dependencies = [
+ "hyper 0.10.16",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "language-tags"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -955,6 +1026,15 @@ dependencies = [
  "scopeguard",
 ]
 
+[[package]]
+name = "log"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
+dependencies = [
+ "log 0.4.14",
+]
+
 [[package]]
 name = "log"
 version = "0.4.14"
@@ -1072,7 +1152,7 @@ dependencies = [
  "bitvec",
  "funty",
  "memchr",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -1179,6 +1259,12 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
 
+[[package]]
+name = "percent-encoding"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
+
 [[package]]
 name = "percent-encoding"
 version = "2.1.0"
@@ -1219,7 +1305,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "syn",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -1230,7 +1316,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
 dependencies = [
  "proc-macro2",
  "quote",
- "version_check",
+ "version_check 0.9.3",
 ]
 
 [[package]]
@@ -1481,7 +1567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e"
 dependencies = [
  "base64 0.10.1",
- "log",
+ "log 0.4.14",
  "ring",
  "sct",
  "webpki",
@@ -1509,7 +1595,7 @@ dependencies = [
  "cfg-if 0.1.10",
  "dirs-next 1.0.2",
  "libc",
- "log",
+ "log 0.4.14",
  "memchr",
  "nix",
  "scopeguard",
@@ -1672,7 +1758,7 @@ dependencies = [
  "fs2",
  "fxhash",
  "libc",
- "log",
+ "log 0.4.14",
  "parking_lot",
 ]
 
@@ -1827,6 +1913,16 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "tiny-bip39"
 version = "0.7.3"
@@ -1953,6 +2049,12 @@ version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
 
+[[package]]
+name = "unicode-bidi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
+
 [[package]]
 name = "unicode-normalization"
 version = "0.1.19"
@@ -2005,6 +2107,17 @@ dependencies = [
  "webpki-roots 0.21.1",
 ]
 
+[[package]]
+name = "url"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
+dependencies = [
+ "idna 0.1.5",
+ "matches",
+ "percent-encoding 1.0.1",
+]
+
 [[package]]
 name = "url"
 version = "2.2.2"
@@ -2012,9 +2125,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
 dependencies = [
  "form_urlencoded",
- "idna",
+ "idna 0.2.3",
  "matches",
- "percent-encoding",
+ "percent-encoding 2.1.0",
 ]
 
 [[package]]
@@ -2035,6 +2148,12 @@ version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
 
+[[package]]
+name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+
 [[package]]
 name = "version_check"
 version = "0.9.3"
@@ -2083,7 +2202,7 @@ checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
 dependencies = [
  "bumpalo",
  "lazy_static",
- "log",
+ "log 0.4.14",
  "proc-macro2",
  "quote",
  "syn",
index 872e30cdb849eb6ed8c460bdfa1e8d2dfc989d32..a159428b434d7c4b89f97b0b2e871e2fc1ee7629 100644 (file)
@@ -37,6 +37,7 @@ esplora-ureq = ["esplora", "bdk/use-esplora-ureq"]
 esplora-reqwest = ["esplora", "bdk/use-esplora-reqwest"]
 compiler = ["bdk/compiler"]
 compact_filters = ["bdk/compact_filters"]
+rpc = ["bdk/rpc"]
 
 [[bin]]
 name = "bdk-cli"
index b11cb31402710b1784f94b05da6d682aecdbb781..4c39ff92ca3483c329703303919a136b4e495927 100644 (file)
@@ -25,6 +25,8 @@
 use std::fs;
 use std::path::PathBuf;
 
+#[cfg(feature = "rpc")]
+use bitcoin::secp256k1::Secp256k1;
 use bitcoin::Network;
 use clap::AppSettings;
 use log::{debug, error, info, warn};
@@ -43,9 +45,17 @@ use bdk::blockchain::electrum::ElectrumBlockchainConfig;
 #[cfg(feature = "esplora")]
 use bdk::blockchain::esplora::EsploraBlockchainConfig;
 
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 use bdk::blockchain::{AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain};
 
+#[cfg(feature = "rpc")]
+use bdk::blockchain::rpc::{wallet_name_from_descriptor, Auth, RpcConfig};
+
 use bdk::database::BatchDatabase;
 use bdk::sled;
 use bdk::sled::Tree;
@@ -54,7 +64,12 @@ use bdk::{bitcoin, Error};
 use bdk_cli::WalletSubCommand;
 use bdk_cli::{CliOpts, CliSubCommand, KeySubCommand, OfflineWalletSubCommand, WalletOpts};
 
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 use bdk_cli::OnlineWalletSubCommand;
 
 #[cfg(feature = "repl")]
@@ -69,7 +84,12 @@ const REPL_LINE_SPLIT_REGEX: &str = r#""([^"]*)"|'([^']*)'|([\w\-]+)"#;
 version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
 author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
 pub enum ReplSubCommand {
-    #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "compact_filters",
+        feature = "rpc"
+    ))]
     #[structopt(flatten)]
     OnlineWalletSubCommand(OnlineWalletSubCommand),
     #[structopt(flatten)]
@@ -107,7 +127,12 @@ fn open_database(wallet_opts: &WalletOpts) -> Tree {
     tree
 }
 
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 fn new_online_wallet<D>(
     network: Network,
     wallet_opts: &WalletOpts,
@@ -163,6 +188,34 @@ where
         })
     };
 
+    #[cfg(feature = "rpc")]
+    let config: AnyBlockchainConfig = {
+        let auth = Auth::UserPass {
+            username: wallet_opts.rpc_opts.auth.0.clone(),
+            password: wallet_opts.rpc_opts.auth.1.clone(),
+        };
+
+        // Use deterministic wallet name derived from descriptor
+        let wallet_name = wallet_name_from_descriptor(
+            &wallet_opts.descriptor[..],
+            wallet_opts.change_descriptor.as_deref(),
+            network,
+            &Secp256k1::new(),
+        )?;
+
+        let mut rpc_url = "http://".to_string();
+        rpc_url.push_str(&wallet_opts.rpc_opts.address[..]);
+
+        let rpc_config = RpcConfig {
+            url: rpc_url,
+            auth,
+            network,
+            wallet_name,
+            skip_blocks: wallet_opts.rpc_opts.skip_blocks,
+        };
+
+        AnyBlockchainConfig::Rpc(rpc_config)
+    };
     let descriptor = wallet_opts.descriptor.as_str();
     let change_descriptor = wallet_opts.change_descriptor.as_deref();
 
@@ -214,7 +267,12 @@ fn main() {
 
 fn handle_command(cli_opts: CliOpts, network: Network) -> Result<String, Error> {
     let result = match cli_opts.subcommand {
-        #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+        #[cfg(any(
+            feature = "electrum",
+            feature = "esplora",
+            feature = "compact_filters",
+            feature = "rpc"
+        ))]
         CliSubCommand::Wallet {
             wallet_opts,
             subcommand: WalletSubCommand::OnlineWalletSubCommand(online_subcommand),
@@ -255,13 +313,19 @@ fn handle_command(cli_opts: CliOpts, network: Network) -> Result<String, Error>
         CliSubCommand::Repl { wallet_opts } => {
             let database = open_database(&wallet_opts);
 
-            #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+            #[cfg(any(
+                feature = "electrum",
+                feature = "esplora",
+                feature = "compact_filters",
+                feature = "rpc"
+            ))]
             let wallet = new_online_wallet(network, &wallet_opts, database)?;
 
             #[cfg(not(any(
                 feature = "electrum",
                 feature = "esplora",
-                feature = "compact_filters"
+                feature = "compact_filters",
+                feature = "rpc"
             )))]
             let wallet = new_offline_wallet(network, &wallet_opts, database)?;
 
@@ -307,7 +371,8 @@ fn handle_command(cli_opts: CliOpts, network: Network) -> Result<String, Error>
                             #[cfg(any(
                                 feature = "electrum",
                                 feature = "esplora",
-                                feature = "compact_filters"
+                                feature = "compact_filters",
+                                feature = "rpc"
                             ))]
                             ReplSubCommand::OnlineWalletSubCommand(online_subcommand) => {
                                 bdk_cli::handle_online_wallet_subcommand(&wallet, online_subcommand)
index bd1ce48eefa2345db45819d5228377c22d8ebe15..bd8d04baf85fe71d9c0b53757f9a755f377f69fc 100644 (file)
@@ -99,7 +99,12 @@ pub extern crate bdk;
 #[macro_use]
 extern crate serde_json;
 
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 #[macro_use]
 extern crate bdk_macros;
 
@@ -110,16 +115,31 @@ pub use structopt;
 use structopt::StructOpt;
 
 use crate::OfflineWalletSubCommand::*;
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 use crate::OnlineWalletSubCommand::*;
 use bdk::bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 use bdk::bitcoin::hashes::hex::FromHex;
 use bdk::bitcoin::secp256k1::Secp256k1;
 use bdk::bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, KeySource};
 use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
 use bdk::bitcoin::{Address, Network, OutPoint, Script, Txid};
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 use bdk::blockchain::{log_progress, Blockchain};
 use bdk::database::BatchDatabase;
 use bdk::descriptor::Segwitv0;
@@ -144,7 +164,7 @@ use bdk::{FeeRate, KeychainKind, Wallet};
 /// # Example
 ///
 /// ```
-/// # #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+/// # #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters", feature = "rpc"))]
 /// # {
 /// # use bdk::bitcoin::Network;
 /// # use structopt::StructOpt;
@@ -153,6 +173,8 @@ use bdk::{FeeRate, KeychainKind, Wallet};
 /// # use bdk_cli::ElectrumOpts;
 /// # #[cfg(feature = "esplora")]
 /// # use bdk_cli::EsploraOpts;
+/// # #[cfg(feature = "rpc")]
+/// # use bdk_cli::RpcOpts;
 /// # #[cfg(feature = "compact_filters")]
 /// # use bdk_cli::CompactFilterOpts;
 /// # #[cfg(any(feature = "compact_filters", feature = "electrum", feature="esplora"))]
@@ -194,6 +216,12 @@ use bdk::{FeeRate, KeychainKind, Wallet};
 ///                   conc: 4,
 ///                   stop_gap: 10
 ///               },
+///                 #[cfg(feature = "rpc")]
+///                 rpc_opts: RpcOpts{
+///                    address: "127.0.0.1:18443".to_string(),
+///                    auth: ("user".to_string(), "password".to_string()),
+///                    skip_blocks: None,
+///                },
 ///                #[cfg(feature = "compact_filters")]
 ///                compactfilter_opts: CompactFilterOpts{
 ///                    address: vec!["127.0.0.1:18444".to_string()],
@@ -216,6 +244,7 @@ use bdk::{FeeRate, KeychainKind, Wallet};
 /// assert_eq!(expected_cli_opts, cli_opts);
 /// # }
 /// ```
+
 #[derive(Debug, StructOpt, Clone, PartialEq)]
 #[structopt(name = "BDK CLI",
 version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
@@ -287,7 +316,12 @@ pub enum CliSubCommand {
 /// client and network connection and an [`OfflineWalletSubCommand`] does not.
 #[derive(Debug, StructOpt, Clone, PartialEq)]
 pub enum WalletSubCommand {
-    #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "compact_filters",
+        feature = "rpc"
+    ))]
     #[structopt(flatten)]
     OnlineWalletSubCommand(OnlineWalletSubCommand),
     #[structopt(flatten)]
@@ -312,6 +346,8 @@ pub enum WalletSubCommand {
 /// # use bdk_cli::EsploraOpts;
 /// # #[cfg(feature = "compact_filters")]
 /// # use bdk_cli::CompactFilterOpts;
+/// # #[cfg(feature = "rpc")]
+/// # use bdk_cli::RpcOpts;
 /// # #[cfg(any(feature = "compact_filters", feature = "electrum", feature="esplora"))]
 /// # use bdk_cli::ProxyOpts;
 ///
@@ -353,6 +389,12 @@ pub enum WalletSubCommand {
 ///                    conn_count: 4,
 ///                    skip_blocks: 0,
 ///                },
+///                 #[cfg(feature = "rpc")]
+///                 rpc_opts: RpcOpts{
+///                    address: "127.0.0.1:18443".to_string(),
+///                    auth: ("user".to_string(), "password".to_string()),
+///                    skip_blocks: None,
+///                },
 ///               #[cfg(any(feature="compact_filters", feature="electrum", feature="esplora"))]
 ///                    proxy_opts: ProxyOpts{
 ///                        proxy: None,
@@ -363,6 +405,7 @@ pub enum WalletSubCommand {
 ///
 /// assert_eq!(expected_wallet_opts, wallet_opts);
 /// ```
+
 #[derive(Debug, StructOpt, Clone, PartialEq)]
 pub struct WalletOpts {
     /// Selects the wallet to use
@@ -391,6 +434,9 @@ pub struct WalletOpts {
     #[cfg(feature = "compact_filters")]
     #[structopt(flatten)]
     pub compactfilter_opts: CompactFilterOpts,
+    #[cfg(feature = "rpc")]
+    #[structopt(flatten)]
+    pub rpc_opts: RpcOpts,
     #[cfg(any(feature = "compact_filters", feature = "electrum", feature = "esplora"))]
     #[structopt(flatten)]
     pub proxy_opts: ProxyOpts,
@@ -449,6 +495,33 @@ pub struct CompactFilterOpts {
     pub skip_blocks: usize,
 }
 
+#[cfg(feature = "rpc")]
+#[derive(Debug, StructOpt, Clone, PartialEq)]
+pub struct RpcOpts {
+    /// Sets the full node address for rpc connection
+    #[structopt(
+        name = "ADDRESS:PORT",
+        short = "n",
+        long = "node",
+        default_value = "127.0.0.1:18443"
+    )]
+    pub address: String,
+
+    /// Sets the rpc authentication username:password
+    #[structopt(
+        name = "USER:PASSWD",
+        short = "a",
+        long = "auth",
+        parse(try_from_str = parse_proxy_auth),
+        default_value = "user:password",
+    )]
+    pub auth: (String, String),
+
+    /// Optionally skip initial `skip_blocks` blocks
+    #[structopt(name = "SKIP_BLOCKS", short = "s", long = "skip-blocks")]
+    pub skip_blocks: Option<u32>,
+}
+
 /// Electrum options
 ///
 /// Electrum blockchain client information used by [`OnlineWalletSubCommand`]s.
@@ -704,7 +777,12 @@ blockchain client and network connection.
 )]
 #[derive(Debug, StructOpt, Clone, PartialEq)]
 #[structopt(rename_all = "snake")]
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 pub enum OnlineWalletSubCommand {
     /// Syncs with the chosen blockchain server
     Sync {
@@ -750,8 +828,12 @@ fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
 
     Ok((addr.unwrap().script_pubkey(), val.unwrap()))
 }
-
-#[cfg(any(feature = "electrum", feature = "compact_filters", feature = "esplora"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "compact_filters",
+    feature = "esplora",
+    feature = "rpc"
+))]
 fn parse_proxy_auth(s: &str) -> Result<(String, String), String> {
     let parts: Vec<_> = s.split(':').collect();
     if parts.len() != 2 {
@@ -959,7 +1041,12 @@ where
 ///
 /// Online wallet sub-commands are described in [`OnlineWalletSubCommand`]. See [`crate`] for
 /// example usage.
-#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+#[cfg(any(
+    feature = "electrum",
+    feature = "esplora",
+    feature = "compact_filters",
+    feature = "rpc"
+))]
 #[maybe_async]
 pub fn handle_online_wallet_subcommand<C, D>(
     wallet: &Wallet<C, D>,
@@ -1144,10 +1231,17 @@ mod test {
     #[cfg(feature = "esplora")]
     use crate::EsploraOpts;
     use crate::OfflineWalletSubCommand::{CreateTx, GetNewAddress};
-    #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "compact_filters",
+        feature = "rpc"
+    ))]
     use crate::OnlineWalletSubCommand::{Broadcast, Sync};
     #[cfg(any(feature = "compact_filters", feature = "electrum", feature = "esplora"))]
     use crate::ProxyOpts;
+    #[cfg(feature = "rpc")]
+    use crate::RpcOpts;
     use crate::{handle_key_subcommand, CliSubCommand, KeySubCommand, WalletSubCommand};
 
     use bdk::bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey};
@@ -1203,7 +1297,13 @@ mod test {
                         proxy: None,
                         proxy_auth: None,
                         retries: 5,
-                    }
+                    },
+                    #[cfg(feature = "rpc")]
+                    rpc_opts: RpcOpts {
+                        address: "127.0.0.1:18443".to_string(),
+                        auth: ("user".to_string(), "password".to_string()),
+                        skip_blocks: None,
+                    },
                 },
                 subcommand: WalletSubCommand::OfflineWalletSubCommand(GetNewAddress),
             },
@@ -1242,6 +1342,12 @@ mod test {
                         proxy: Some("127.0.0.1:9150".to_string()),
                         proxy_auth: None,
                         retries: 3,
+                    },
+                    #[cfg(feature = "rpc")]
+                    rpc_opts: RpcOpts {
+                        address: "127.0.0.1:18443".to_string(),
+                        auth: ("user".to_string(), "password".to_string()),
+                        skip_blocks: None,
                     }
                 },
                 subcommand: WalletSubCommand::OfflineWalletSubCommand(GetNewAddress),
@@ -1331,6 +1437,63 @@ mod test {
         assert_eq!(expected_cli_opts, cli_opts);
     }
 
+    #[cfg(feature = "rpc")]
+    #[test]
+    fn test_parse_wallet_rpc() {
+        let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet",
+                            "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
+                            "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
+                            "--node", "125.67.89.101:56678",
+                            "--auth", "user:password",
+                            "--skip-blocks", "5",
+                            "get_new_address"];
+
+        let cli_opts = CliOpts::from_iter(&cli_args);
+
+        let expected_cli_opts = CliOpts {
+            network: Network::Bitcoin,
+            subcommand: CliSubCommand::Wallet {
+                wallet_opts: WalletOpts {
+                    wallet: "main".to_string(),
+                    verbose: false,
+                    descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
+                    change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
+                    #[cfg(feature = "electrum")]
+                    electrum_opts: ElectrumOpts {
+                        timeout: None,
+                        server: "ssl://electrum.blockstream.info:60002".to_string(),
+                    },
+                    #[cfg(feature = "esplora")]
+                    esplora_opts: EsploraOpts {
+                        server: "https://blockstream.info/api/".to_string(),
+                        concurrency: 5,
+                    },
+                    #[cfg(feature = "compact_filters")]
+                    compactfilter_opts: CompactFilterOpts{
+                        address: vec!["127.0.0.1:18444".to_string()],
+                        skip_blocks: 0,
+                        conn_count: 4,
+                    },
+                    #[cfg(any(feature="compact_filters", feature="electrum"))]
+                    proxy_opts: ProxyOpts{
+                        proxy: None,
+                        proxy_auth: None,
+                        retries: 5,
+                    },
+                    #[cfg(feature = "rpc")]
+                    rpc_opts: RpcOpts {
+                        address: "125.67.89.101:56678".to_string(),
+                        auth: ("user".to_string(), "password".to_string()),
+                        skip_blocks: Some(5),
+                    },
+                },
+                subcommand: WalletSubCommand::OfflineWalletSubCommand(GetNewAddress),
+            },
+        };
+
+        assert_eq!(expected_cli_opts, cli_opts);
+    }
+
     #[cfg(feature = "compact_filters")]
     #[test]
     fn test_parse_wallet_compact_filters() {
@@ -1372,7 +1535,12 @@ mod test {
         assert_eq!(expected_cli_opts, cli_opts);
     }
 
-    #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "compact_filters",
+        feature = "rpc"
+    ))]
     #[test]
     fn test_parse_wallet_sync() {
         let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
@@ -1419,7 +1587,13 @@ mod test {
                         proxy: None,
                         proxy_auth: None,
                         retries: 5,
-                    }
+                    },
+                    #[cfg(feature = "rpc")]
+                    rpc_opts: RpcOpts {
+                        address: "127.0.0.1:18443".to_string(),
+                        auth: ("user".to_string(), "password".to_string()),
+                        skip_blocks: None,
+                    },
                 },
                 subcommand: WalletSubCommand::OnlineWalletSubCommand(Sync {
                     max_addresses: Some(50)
@@ -1494,7 +1668,13 @@ mod test {
                         proxy: None,
                         proxy_auth: None,
                         retries: 5,
-                    }
+                    },
+                    #[cfg(feature = "rpc")]
+                    rpc_opts: RpcOpts {
+                        address: "127.0.0.1:18443".to_string(),
+                        auth: ("user".to_string(), "password".to_string()),
+                        skip_blocks: None,
+                    },
                 },
                 subcommand: WalletSubCommand::OfflineWalletSubCommand(CreateTx {
                     recipients: vec![(script1, 123456), (script2, 78910)],
@@ -1513,7 +1693,12 @@ mod test {
         assert_eq!(expected_cli_opts, cli_opts);
     }
 
-    #[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))]
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "compact_filters",
+        feature = "rpc"
+    ))]
     #[test]
     fn test_parse_wallet_broadcast() {
         let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet",
@@ -1561,7 +1746,13 @@ mod test {
                         proxy: None,
                         proxy_auth: None,
                         retries: 5,
-                    }
+                    },
+                    #[cfg(feature = "rpc")]
+                    rpc_opts: RpcOpts {
+                        address: "127.0.0.1:18443".to_string(),
+                        auth: ("user".to_string(), "password".to_string()),
+                        skip_blocks: None,
+                    },
                 },
                 subcommand: WalletSubCommand::OnlineWalletSubCommand(Broadcast {
                     psbt: Some("cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA=".to_string()),