]> Untitled Git - bdk-cli/commitdiff
[wasm] Fix SystemTime for wasm and refactor the cli part
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 8 May 2020 21:30:45 +0000 (23:30 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Fri, 8 May 2020 21:30:45 +0000 (23:30 +0200)
Cargo.toml
examples/repl.rs
src/cli.rs [new file with mode: 0644]

index 7bb030e579ab69b8df990dbcf3ca121910aa6877..5137c7f2d34c6ca34b3dde98bbda55d4759fec4f 100644 (file)
@@ -18,6 +18,7 @@ sled = { version = "0.31.0", optional = true }
 electrum-client = { git = "https://github.com/MagicalBitcoin/rust-electrum-client.git", optional = true }
 reqwest = { version = "0.10", optional = true, features = ["json"] }
 futures = { version = "0.3", optional = true }
+clap = { version = "2.33", optional = true }
 
 [features]
 minimal = []
@@ -26,18 +27,19 @@ default = ["key-value-db", "electrum"]
 electrum = ["electrum-client"]
 esplora = ["reqwest", "futures"]
 key-value-db = ["sled"]
+cli-utils = ["clap"]
 
 [dev-dependencies]
 tokio = { version = "0.2", features = ["macros"] }
 lazy_static = "1.4"
-rustyline = "5.0" # newer version requires 2018 edition
-clap = "2.33"
+rustyline = "6.0"
 dirs = "2.0"
 env_logger = "0.7"
 rand = "0.7"
 
 [[example]]
 name = "repl"
+required-features = ["cli-utils"]
 [[example]]
 name = "psbt"
 [[example]]
@@ -52,4 +54,5 @@ required-features = ["compiler"]
 [[example]]
 name = "magic"
 path = "examples/repl.rs"
+required-features = ["cli-utils"]
 
index 74d366eb9c669b49095cfe5441da034b35cc563a..5a78bed3ff5f724f16f7c48aa0ecbc1913083319 100644 (file)
@@ -1,32 +1,21 @@
-extern crate base64;
-extern crate clap;
-extern crate dirs;
-extern crate env_logger;
-extern crate log;
-extern crate magical_bitcoin_wallet;
-extern crate rustyline;
-
 use std::fs;
 use std::path::PathBuf;
-use std::str::FromStr;
 use std::sync::Arc;
 
-use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
-
 use rustyline::error::ReadlineError;
 use rustyline::Editor;
 
+use clap::AppSettings;
+
 #[allow(unused_imports)]
 use log::{debug, error, info, trace, LevelFilter};
 
-use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
-use bitcoin::util::psbt::PartiallySignedTransaction;
-use bitcoin::{Address, Network, OutPoint};
+use bitcoin::Network;
 
 use magical_bitcoin_wallet::bitcoin;
 use magical_bitcoin_wallet::blockchain::ElectrumBlockchain;
+use magical_bitcoin_wallet::cli;
 use magical_bitcoin_wallet::sled;
-use magical_bitcoin_wallet::types::ScriptType;
 use magical_bitcoin_wallet::{Client, Wallet};
 
 fn prepare_home_dir() -> PathBuf {
@@ -43,204 +32,14 @@ fn prepare_home_dir() -> PathBuf {
     dir
 }
 
-fn parse_addressee(s: &str) -> Result<(Address, u64), String> {
-    let parts: Vec<_> = s.split(":").collect();
-    if parts.len() != 2 {
-        return Err("Invalid format".to_string());
-    }
-
-    let addr = Address::from_str(&parts[0]);
-    if let Err(e) = addr {
-        return Err(format!("{:?}", e));
-    }
-    let val = u64::from_str(&parts[1]);
-    if let Err(e) = val {
-        return Err(format!("{:?}", e));
-    }
-
-    Ok((addr.unwrap(), val.unwrap()))
-}
-
-fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
-    OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
-}
-
-fn addressee_validator(s: String) -> Result<(), String> {
-    parse_addressee(&s).map(|_| ())
-}
-
-fn outpoint_validator(s: String) -> Result<(), String> {
-    parse_outpoint(&s).map(|_| ())
-}
-
 #[tokio::main]
 async fn main() {
     env_logger::init();
 
-    let app = App::new("Magical Bitcoin Wallet")
-        .version(option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"))
-        .author(option_env!("CARGO_PKG_AUTHORS").unwrap_or(""))
-        .about("A modern, lightweight, descriptor-based wallet")
-        .subcommand(
-            SubCommand::with_name("get_new_address").about("Generates a new external address"),
-        )
-        .subcommand(SubCommand::with_name("sync").about("Syncs with the chosen Electrum server"))
-        .subcommand(
-            SubCommand::with_name("list_unspent").about("Lists the available spendable UTXOs"),
-        )
-        .subcommand(
-            SubCommand::with_name("get_balance").about("Returns the current wallet balance"),
-        )
-        .subcommand(
-            SubCommand::with_name("create_tx")
-                .about("Creates a new unsigned tranasaction")
-                .arg(
-                    Arg::with_name("to")
-                        .long("to")
-                        .value_name("ADDRESS:SAT")
-                        .help("Adds an addressee to the transaction")
-                        .takes_value(true)
-                        .number_of_values(1)
-                        .required(true)
-                        .multiple(true)
-                        .validator(addressee_validator),
-                )
-                .arg(
-                    Arg::with_name("send_all")
-                        .short("all")
-                        .long("send_all")
-                        .help("Sends all the funds (or all the selected utxos). Requires only one addressees of value 0"),
-                )
-                .arg(
-                    Arg::with_name("utxos")
-                        .long("utxos")
-                        .value_name("TXID:VOUT")
-                        .help("Selects which utxos *must* be spent")
-                        .takes_value(true)
-                        .number_of_values(1)
-                        .multiple(true)
-                        .validator(outpoint_validator),
-                )
-                .arg(
-                    Arg::with_name("unspendable")
-                        .long("unspendable")
-                        .value_name("TXID:VOUT")
-                        .help("Marks an utxo as unspendable")
-                        .takes_value(true)
-                        .number_of_values(1)
-                        .multiple(true)
-                        .validator(outpoint_validator),
-                )
-                .arg(
-                    Arg::with_name("fee_rate")
-                        .short("fee")
-                        .long("fee_rate")
-                        .value_name("SATS_VBYTE")
-                        .help("Fee rate to use in sat/vbyte")
-                        .takes_value(true),
-                )
-                .arg(
-                    Arg::with_name("policy")
-                        .long("policy")
-                        .value_name("POLICY")
-                        .help("Selects which policy will be used to satisfy the descriptor")
-                        .takes_value(true)
-                        .number_of_values(1),
-                ),
-        )
-        .subcommand(
-            SubCommand::with_name("policies")
-                .about("Returns the available spending policies for the descriptor")
-            )
-        .subcommand(
-            SubCommand::with_name("sign")
-                .about("Signs and tries to finalize a PSBT")
-                .arg(
-                    Arg::with_name("psbt")
-                        .long("psbt")
-                        .value_name("BASE64_PSBT")
-                        .help("Sets the PSBT to sign")
-                        .takes_value(true)
-                        .number_of_values(1)
-                        .required(true),
-                )
-                .arg(
-                    Arg::with_name("assume_height")
-                        .long("assume_height")
-                        .value_name("HEIGHT")
-                        .help("Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor")
-                        .takes_value(true)
-                        .number_of_values(1)
-                        .required(false),
-                ))
-        .subcommand(
-            SubCommand::with_name("broadcast")
-                .about("Extracts the finalized transaction from a PSBT and broadcasts it to the network")
-                .arg(
-                    Arg::with_name("psbt")
-                        .long("psbt")
-                        .value_name("BASE64_PSBT")
-                        .help("Sets the PSBT to broadcast")
-                        .takes_value(true)
-                        .number_of_values(1)
-                        .required(true),
-                ));
-
+    let app = cli::make_cli_subcommands();
     let mut repl_app = app.clone().setting(AppSettings::NoBinaryName);
 
-    let app = app
-        .arg(
-            Arg::with_name("network")
-                .short("n")
-                .long("network")
-                .value_name("NETWORK")
-                .help("Sets the network")
-                .takes_value(true)
-                .default_value("testnet")
-                .possible_values(&["testnet", "regtest"]),
-        )
-        .arg(
-            Arg::with_name("wallet")
-                .short("w")
-                .long("wallet")
-                .value_name("WALLET_NAME")
-                .help("Selects the wallet to use")
-                .takes_value(true)
-                .default_value("main"),
-        )
-        .arg(
-            Arg::with_name("server")
-                .short("s")
-                .long("server")
-                .value_name("SERVER:PORT")
-                .help("Sets the Electrum server to use")
-                .takes_value(true)
-                .default_value("tn.not.fyi:55001"),
-        )
-        .arg(
-            Arg::with_name("descriptor")
-                .short("d")
-                .long("descriptor")
-                .value_name("DESCRIPTOR")
-                .help("Sets the descriptor to use for the external addresses")
-                .required(true)
-                .takes_value(true),
-        )
-        .arg(
-            Arg::with_name("change_descriptor")
-                .short("c")
-                .long("change_descriptor")
-                .value_name("DESCRIPTOR")
-                .help("Sets the descriptor to use for internal addresses")
-                .takes_value(true),
-        )
-        .arg(
-            Arg::with_name("v")
-                .short("v")
-                .multiple(true)
-                .help("Sets the level of verbosity"),
-        )
-        .subcommand(SubCommand::with_name("repl").about("Opens an interactive shell"));
+    let app = cli::add_global_flags(app);
 
     let matches = app.get_matches();
 
@@ -280,86 +79,6 @@ async fn main() {
     .unwrap();
     let wallet = Arc::new(wallet);
 
-    // TODO: print errors in a nice way
-    async fn handle_matches<C, D>(wallet: Arc<Wallet<C, D>>, matches: ArgMatches<'_>)
-    where
-        C: magical_bitcoin_wallet::blockchain::OnlineBlockchain,
-        D: magical_bitcoin_wallet::database::BatchDatabase,
-    {
-        if let Some(_sub_matches) = matches.subcommand_matches("get_new_address") {
-            println!("{}", wallet.get_new_address().unwrap().to_string());
-        } else if let Some(_sub_matches) = matches.subcommand_matches("sync") {
-            wallet.sync(None, None).await.unwrap();
-        } else if let Some(_sub_matches) = matches.subcommand_matches("list_unspent") {
-            for utxo in wallet.list_unspent().unwrap() {
-                println!("{} value {} SAT", utxo.outpoint, utxo.txout.value);
-            }
-        } else if let Some(_sub_matches) = matches.subcommand_matches("get_balance") {
-            println!("{} SAT", wallet.get_balance().unwrap());
-        } else if let Some(sub_matches) = matches.subcommand_matches("create_tx") {
-            let addressees = sub_matches
-                .values_of("to")
-                .unwrap()
-                .map(|s| parse_addressee(s).unwrap())
-                .collect();
-            let send_all = sub_matches.is_present("send_all");
-            let fee_rate = sub_matches
-                .value_of("fee_rate")
-                .map(|s| f32::from_str(s).unwrap())
-                .unwrap_or(1.0);
-            let utxos = sub_matches
-                .values_of("utxos")
-                .map(|s| s.map(|i| parse_outpoint(i).unwrap()).collect());
-            let unspendable = sub_matches
-                .values_of("unspendable")
-                .map(|s| s.map(|i| parse_outpoint(i).unwrap()).collect());
-            let policy: Option<Vec<_>> = sub_matches
-                .value_of("policy")
-                .map(|s| serde_json::from_str::<Vec<Vec<usize>>>(&s).unwrap());
-
-            let result = wallet
-                .create_tx(
-                    addressees,
-                    send_all,
-                    fee_rate * 1e-5,
-                    policy,
-                    utxos,
-                    unspendable,
-                )
-                .unwrap();
-            println!("{:#?}", result.1);
-            println!("PSBT: {}", base64::encode(&serialize(&result.0)));
-        } else if let Some(_sub_matches) = matches.subcommand_matches("policies") {
-            println!(
-                "External: {}",
-                serde_json::to_string(&wallet.policies(ScriptType::External).unwrap()).unwrap()
-            );
-            println!(
-                "Internal: {}",
-                serde_json::to_string(&wallet.policies(ScriptType::Internal).unwrap()).unwrap()
-            );
-        } else if let Some(sub_matches) = matches.subcommand_matches("sign") {
-            let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
-            let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
-            let assume_height = sub_matches
-                .value_of("assume_height")
-                .and_then(|s| Some(s.parse().unwrap()));
-            let (psbt, finalized) = wallet.sign(psbt, assume_height).unwrap();
-
-            println!("PSBT: {}", base64::encode(&serialize(&psbt)));
-            println!("Finalized: {}", finalized);
-            if finalized {
-                println!("Extracted: {}", serialize_hex(&psbt.extract_tx()));
-            }
-        } else if let Some(sub_matches) = matches.subcommand_matches("broadcast") {
-            let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
-            let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
-            let (txid, _) = wallet.broadcast(psbt).await.unwrap();
-
-            println!("TXID: {}", txid);
-        }
-    };
-
     if let Some(_sub_matches) = matches.subcommand_matches("repl") {
         let mut rl = Editor::<()>::new();
 
@@ -382,7 +101,7 @@ async fn main() {
                         continue;
                     }
 
-                    handle_matches(Arc::clone(&wallet), matches.unwrap()).await;
+                    cli::handle_matches(&Arc::clone(&wallet), matches.unwrap()).await;
                 }
                 Err(ReadlineError::Interrupted) => continue,
                 Err(ReadlineError::Eof) => break,
@@ -395,6 +114,6 @@ async fn main() {
 
     // rl.save_history("history.txt").unwrap();
     } else {
-        handle_matches(wallet, matches).await;
+        cli::handle_matches(&wallet, matches).await;
     }
 }
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644 (file)
index 0000000..5b24544
--- /dev/null
@@ -0,0 +1,300 @@
+use std::str::FromStr;
+
+use clap::{App, Arg, ArgMatches, SubCommand};
+
+#[allow(unused_imports)]
+use log::{debug, error, info, trace, LevelFilter};
+
+use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
+use bitcoin::util::psbt::PartiallySignedTransaction;
+use bitcoin::{Address, OutPoint};
+
+use crate::error::Error;
+use crate::types::ScriptType;
+use crate::Wallet;
+
+fn parse_addressee(s: &str) -> Result<(Address, u64), String> {
+    let parts: Vec<_> = s.split(":").collect();
+    if parts.len() != 2 {
+        return Err("Invalid format".to_string());
+    }
+
+    let addr = Address::from_str(&parts[0]);
+    if let Err(e) = addr {
+        return Err(format!("{:?}", e));
+    }
+    let val = u64::from_str(&parts[1]);
+    if let Err(e) = val {
+        return Err(format!("{:?}", e));
+    }
+
+    Ok((addr.unwrap(), val.unwrap()))
+}
+
+fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
+    OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
+}
+
+fn addressee_validator(s: String) -> Result<(), String> {
+    parse_addressee(&s).map(|_| ())
+}
+
+fn outpoint_validator(s: String) -> Result<(), String> {
+    parse_outpoint(&s).map(|_| ())
+}
+
+pub fn make_cli_subcommands<'a, 'b>() -> App<'a, 'b> {
+    App::new("Magical Bitcoin Wallet")
+        .version(option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"))
+        .author(option_env!("CARGO_PKG_AUTHORS").unwrap_or(""))
+        .about("A modern, lightweight, descriptor-based wallet")
+        .subcommand(
+            SubCommand::with_name("get_new_address").about("Generates a new external address"),
+        )
+        .subcommand(SubCommand::with_name("sync").about("Syncs with the chosen Electrum server"))
+        .subcommand(
+            SubCommand::with_name("list_unspent").about("Lists the available spendable UTXOs"),
+        )
+        .subcommand(
+            SubCommand::with_name("get_balance").about("Returns the current wallet balance"),
+        )
+        .subcommand(
+            SubCommand::with_name("create_tx")
+                .about("Creates a new unsigned tranasaction")
+                .arg(
+                    Arg::with_name("to")
+                        .long("to")
+                        .value_name("ADDRESS:SAT")
+                        .help("Adds an addressee to the transaction")
+                        .takes_value(true)
+                        .number_of_values(1)
+                        .required(true)
+                        .multiple(true)
+                        .validator(addressee_validator),
+                )
+                .arg(
+                    Arg::with_name("send_all")
+                        .short("all")
+                        .long("send_all")
+                        .help("Sends all the funds (or all the selected utxos). Requires only one addressees of value 0"),
+                )
+                .arg(
+                    Arg::with_name("utxos")
+                        .long("utxos")
+                        .value_name("TXID:VOUT")
+                        .help("Selects which utxos *must* be spent")
+                        .takes_value(true)
+                        .number_of_values(1)
+                        .multiple(true)
+                        .validator(outpoint_validator),
+                )
+                .arg(
+                    Arg::with_name("unspendable")
+                        .long("unspendable")
+                        .value_name("TXID:VOUT")
+                        .help("Marks an utxo as unspendable")
+                        .takes_value(true)
+                        .number_of_values(1)
+                        .multiple(true)
+                        .validator(outpoint_validator),
+                )
+                .arg(
+                    Arg::with_name("fee_rate")
+                        .short("fee")
+                        .long("fee_rate")
+                        .value_name("SATS_VBYTE")
+                        .help("Fee rate to use in sat/vbyte")
+                        .takes_value(true),
+                )
+                .arg(
+                    Arg::with_name("policy")
+                        .long("policy")
+                        .value_name("POLICY")
+                        .help("Selects which policy will be used to satisfy the descriptor")
+                        .takes_value(true)
+                        .number_of_values(1),
+                ),
+        )
+        .subcommand(
+            SubCommand::with_name("policies")
+                .about("Returns the available spending policies for the descriptor")
+            )
+        .subcommand(
+            SubCommand::with_name("sign")
+                .about("Signs and tries to finalize a PSBT")
+                .arg(
+                    Arg::with_name("psbt")
+                        .long("psbt")
+                        .value_name("BASE64_PSBT")
+                        .help("Sets the PSBT to sign")
+                        .takes_value(true)
+                        .number_of_values(1)
+                        .required(true),
+                )
+                .arg(
+                    Arg::with_name("assume_height")
+                        .long("assume_height")
+                        .value_name("HEIGHT")
+                        .help("Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor")
+                        .takes_value(true)
+                        .number_of_values(1)
+                        .required(false),
+                ))
+        .subcommand(
+            SubCommand::with_name("broadcast")
+                .about("Extracts the finalized transaction from a PSBT and broadcasts it to the network")
+                .arg(
+                    Arg::with_name("psbt")
+                        .long("psbt")
+                        .value_name("BASE64_PSBT")
+                        .help("Sets the PSBT to broadcast")
+                        .takes_value(true)
+                        .number_of_values(1)
+                        .required(true),
+                ))
+}
+
+pub fn add_global_flags<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
+    app.arg(
+        Arg::with_name("network")
+            .short("n")
+            .long("network")
+            .value_name("NETWORK")
+            .help("Sets the network")
+            .takes_value(true)
+            .default_value("testnet")
+            .possible_values(&["testnet", "regtest"]),
+    )
+    .arg(
+        Arg::with_name("wallet")
+            .short("w")
+            .long("wallet")
+            .value_name("WALLET_NAME")
+            .help("Selects the wallet to use")
+            .takes_value(true)
+            .default_value("main"),
+    )
+    .arg(
+        Arg::with_name("server")
+            .short("s")
+            .long("server")
+            .value_name("SERVER:PORT")
+            .help("Sets the Electrum server to use")
+            .takes_value(true)
+            .default_value("tn.not.fyi:55001"),
+    )
+    .arg(
+        Arg::with_name("descriptor")
+            .short("d")
+            .long("descriptor")
+            .value_name("DESCRIPTOR")
+            .help("Sets the descriptor to use for the external addresses")
+            .required(true)
+            .takes_value(true),
+    )
+    .arg(
+        Arg::with_name("change_descriptor")
+            .short("c")
+            .long("change_descriptor")
+            .value_name("DESCRIPTOR")
+            .help("Sets the descriptor to use for internal addresses")
+            .takes_value(true),
+    )
+    .arg(
+        Arg::with_name("v")
+            .short("v")
+            .multiple(true)
+            .help("Sets the level of verbosity"),
+    )
+    .subcommand(SubCommand::with_name("repl").about("Opens an interactive shell"))
+}
+
+pub async fn handle_matches<C, D>(
+    wallet: &Wallet<C, D>,
+    matches: ArgMatches<'_>,
+) -> Result<Option<String>, Error>
+where
+    C: crate::blockchain::OnlineBlockchain,
+    D: crate::database::BatchDatabase,
+{
+    if let Some(_sub_matches) = matches.subcommand_matches("get_new_address") {
+        Ok(Some(format!("{}", wallet.get_new_address()?)))
+    } else if let Some(_sub_matches) = matches.subcommand_matches("sync") {
+        wallet.sync(None, None).await?;
+        Ok(None)
+    } else if let Some(_sub_matches) = matches.subcommand_matches("list_unspent") {
+        let mut res = String::new();
+        for utxo in wallet.list_unspent()? {
+            res += &format!("{} value {} SAT\n", utxo.outpoint, utxo.txout.value);
+        }
+
+        Ok(Some(res))
+    } else if let Some(_sub_matches) = matches.subcommand_matches("get_balance") {
+        Ok(Some(format!("{} SAT", wallet.get_balance()?)))
+    } else if let Some(sub_matches) = matches.subcommand_matches("create_tx") {
+        let addressees = sub_matches
+            .values_of("to")
+            .unwrap()
+            .map(|s| parse_addressee(s).unwrap())
+            .collect();
+        let send_all = sub_matches.is_present("send_all");
+        let fee_rate = sub_matches
+            .value_of("fee_rate")
+            .map(|s| f32::from_str(s).unwrap())
+            .unwrap_or(1.0);
+        let utxos = sub_matches
+            .values_of("utxos")
+            .map(|s| s.map(|i| parse_outpoint(i).unwrap()).collect());
+        let unspendable = sub_matches
+            .values_of("unspendable")
+            .map(|s| s.map(|i| parse_outpoint(i).unwrap()).collect());
+        let policy: Option<Vec<_>> = sub_matches
+            .value_of("policy")
+            .map(|s| serde_json::from_str::<Vec<Vec<usize>>>(&s).unwrap());
+
+        let result = wallet.create_tx(
+            addressees,
+            send_all,
+            fee_rate * 1e-5,
+            policy,
+            utxos,
+            unspendable,
+        )?;
+        Ok(Some(format!(
+            "{:#?}\nPSBT: {}",
+            result.1,
+            base64::encode(&serialize(&result.0))
+        )))
+    } else if let Some(_sub_matches) = matches.subcommand_matches("policies") {
+        Ok(Some(format!(
+            "External: {}\nInternal:{}",
+            serde_json::to_string(&wallet.policies(ScriptType::External)?).unwrap(),
+            serde_json::to_string(&wallet.policies(ScriptType::Internal)?).unwrap(),
+        )))
+    } else if let Some(sub_matches) = matches.subcommand_matches("sign") {
+        let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
+        let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
+        let assume_height = sub_matches
+            .value_of("assume_height")
+            .and_then(|s| Some(s.parse().unwrap()));
+        let (psbt, finalized) = wallet.sign(psbt, assume_height)?;
+
+        let mut res = String::new();
+
+        res += &format!("PSBT: {}\n", base64::encode(&serialize(&psbt)));
+        res += &format!("Finalized: {}", finalized);
+        if finalized {
+            res += &format!("\nExtracted: {}", serialize_hex(&psbt.extract_tx()));
+        }
+
+        Ok(Some(res))
+    } else if let Some(sub_matches) = matches.subcommand_matches("broadcast") {
+        let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
+        let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
+        let (txid, _) = wallet.broadcast(psbt).await?;
+
+        Ok(Some(format!("TXID: {}", txid)))
+    } else {
+        Ok(None)
+    }
+}