[wasm] Fix SystemTime for wasm and refactor the cli part
This commit is contained in:
		
							parent
							
								
									e5ff696e73
								
							
						
					
					
						commit
						1ff9852cff
					
				@ -18,6 +18,7 @@ sled = { version = "0.31.0", optional = true }
 | 
				
			|||||||
electrum-client = { git = "https://github.com/MagicalBitcoin/rust-electrum-client.git", optional = true }
 | 
					electrum-client = { git = "https://github.com/MagicalBitcoin/rust-electrum-client.git", optional = true }
 | 
				
			||||||
reqwest = { version = "0.10", optional = true, features = ["json"] }
 | 
					reqwest = { version = "0.10", optional = true, features = ["json"] }
 | 
				
			||||||
futures = { version = "0.3", optional = true }
 | 
					futures = { version = "0.3", optional = true }
 | 
				
			||||||
 | 
					clap = { version = "2.33", optional = true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[features]
 | 
					[features]
 | 
				
			||||||
minimal = []
 | 
					minimal = []
 | 
				
			||||||
@ -26,18 +27,19 @@ default = ["key-value-db", "electrum"]
 | 
				
			|||||||
electrum = ["electrum-client"]
 | 
					electrum = ["electrum-client"]
 | 
				
			||||||
esplora = ["reqwest", "futures"]
 | 
					esplora = ["reqwest", "futures"]
 | 
				
			||||||
key-value-db = ["sled"]
 | 
					key-value-db = ["sled"]
 | 
				
			||||||
 | 
					cli-utils = ["clap"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dev-dependencies]
 | 
					[dev-dependencies]
 | 
				
			||||||
tokio = { version = "0.2", features = ["macros"] }
 | 
					tokio = { version = "0.2", features = ["macros"] }
 | 
				
			||||||
lazy_static = "1.4"
 | 
					lazy_static = "1.4"
 | 
				
			||||||
rustyline = "5.0" # newer version requires 2018 edition
 | 
					rustyline = "6.0"
 | 
				
			||||||
clap = "2.33"
 | 
					 | 
				
			||||||
dirs = "2.0"
 | 
					dirs = "2.0"
 | 
				
			||||||
env_logger = "0.7"
 | 
					env_logger = "0.7"
 | 
				
			||||||
rand = "0.7"
 | 
					rand = "0.7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[example]]
 | 
					[[example]]
 | 
				
			||||||
name = "repl"
 | 
					name = "repl"
 | 
				
			||||||
 | 
					required-features = ["cli-utils"]
 | 
				
			||||||
[[example]]
 | 
					[[example]]
 | 
				
			||||||
name = "psbt"
 | 
					name = "psbt"
 | 
				
			||||||
[[example]]
 | 
					[[example]]
 | 
				
			||||||
@ -52,4 +54,5 @@ required-features = ["compiler"]
 | 
				
			|||||||
[[example]]
 | 
					[[example]]
 | 
				
			||||||
name = "magic"
 | 
					name = "magic"
 | 
				
			||||||
path = "examples/repl.rs"
 | 
					path = "examples/repl.rs"
 | 
				
			||||||
 | 
					required-features = ["cli-utils"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										297
									
								
								examples/repl.rs
									
									
									
									
									
								
							
							
						
						
									
										297
									
								
								examples/repl.rs
									
									
									
									
									
								
							@ -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::fs;
 | 
				
			||||||
use std::path::PathBuf;
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use std::str::FromStr;
 | 
					 | 
				
			||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use rustyline::error::ReadlineError;
 | 
					use rustyline::error::ReadlineError;
 | 
				
			||||||
use rustyline::Editor;
 | 
					use rustyline::Editor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use clap::AppSettings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[allow(unused_imports)]
 | 
					#[allow(unused_imports)]
 | 
				
			||||||
use log::{debug, error, info, trace, LevelFilter};
 | 
					use log::{debug, error, info, trace, LevelFilter};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
 | 
					use bitcoin::Network;
 | 
				
			||||||
use bitcoin::util::psbt::PartiallySignedTransaction;
 | 
					 | 
				
			||||||
use bitcoin::{Address, Network, OutPoint};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use magical_bitcoin_wallet::bitcoin;
 | 
					use magical_bitcoin_wallet::bitcoin;
 | 
				
			||||||
use magical_bitcoin_wallet::blockchain::ElectrumBlockchain;
 | 
					use magical_bitcoin_wallet::blockchain::ElectrumBlockchain;
 | 
				
			||||||
 | 
					use magical_bitcoin_wallet::cli;
 | 
				
			||||||
use magical_bitcoin_wallet::sled;
 | 
					use magical_bitcoin_wallet::sled;
 | 
				
			||||||
use magical_bitcoin_wallet::types::ScriptType;
 | 
					 | 
				
			||||||
use magical_bitcoin_wallet::{Client, Wallet};
 | 
					use magical_bitcoin_wallet::{Client, Wallet};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn prepare_home_dir() -> PathBuf {
 | 
					fn prepare_home_dir() -> PathBuf {
 | 
				
			||||||
@ -43,204 +32,14 @@ fn prepare_home_dir() -> PathBuf {
 | 
				
			|||||||
    dir
 | 
					    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]
 | 
					#[tokio::main]
 | 
				
			||||||
async fn main() {
 | 
					async fn main() {
 | 
				
			||||||
    env_logger::init();
 | 
					    env_logger::init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let app = App::new("Magical Bitcoin Wallet")
 | 
					    let app = cli::make_cli_subcommands();
 | 
				
			||||||
        .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 mut repl_app = app.clone().setting(AppSettings::NoBinaryName);
 | 
					    let mut repl_app = app.clone().setting(AppSettings::NoBinaryName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let app = app
 | 
					    let app = cli::add_global_flags(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 matches = app.get_matches();
 | 
					    let matches = app.get_matches();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -280,86 +79,6 @@ async fn main() {
 | 
				
			|||||||
    .unwrap();
 | 
					    .unwrap();
 | 
				
			||||||
    let wallet = Arc::new(wallet);
 | 
					    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") {
 | 
					    if let Some(_sub_matches) = matches.subcommand_matches("repl") {
 | 
				
			||||||
        let mut rl = Editor::<()>::new();
 | 
					        let mut rl = Editor::<()>::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -382,7 +101,7 @@ async fn main() {
 | 
				
			|||||||
                        continue;
 | 
					                        continue;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    handle_matches(Arc::clone(&wallet), matches.unwrap()).await;
 | 
					                    cli::handle_matches(&Arc::clone(&wallet), matches.unwrap()).await;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Err(ReadlineError::Interrupted) => continue,
 | 
					                Err(ReadlineError::Interrupted) => continue,
 | 
				
			||||||
                Err(ReadlineError::Eof) => break,
 | 
					                Err(ReadlineError::Eof) => break,
 | 
				
			||||||
@ -395,6 +114,6 @@ async fn main() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // rl.save_history("history.txt").unwrap();
 | 
					    // rl.save_history("history.txt").unwrap();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        handle_matches(wallet, matches).await;
 | 
					        cli::handle_matches(&wallet, matches).await;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										300
									
								
								src/cli.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								src/cli.rs
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -25,6 +25,9 @@ pub use blockchain::esplora::EsploraBlockchain;
 | 
				
			|||||||
#[cfg(feature = "key-value-db")]
 | 
					#[cfg(feature = "key-value-db")]
 | 
				
			||||||
pub extern crate sled;
 | 
					pub extern crate sled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(feature = "cli-utils")]
 | 
				
			||||||
 | 
					pub mod cli;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[macro_use]
 | 
					#[macro_use]
 | 
				
			||||||
pub mod error;
 | 
					pub mod error;
 | 
				
			||||||
pub mod blockchain;
 | 
					pub mod blockchain;
 | 
				
			||||||
 | 
				
			|||||||
@ -498,6 +498,7 @@ where
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Internals
 | 
					    // Internals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[cfg(not(target_arch = "wasm32"))]
 | 
				
			||||||
    fn get_timestamp() -> u64 {
 | 
					    fn get_timestamp() -> u64 {
 | 
				
			||||||
        SystemTime::now()
 | 
					        SystemTime::now()
 | 
				
			||||||
            .duration_since(UNIX_EPOCH)
 | 
					            .duration_since(UNIX_EPOCH)
 | 
				
			||||||
@ -505,6 +506,11 @@ where
 | 
				
			|||||||
            .as_secs()
 | 
					            .as_secs()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[cfg(target_arch = "wasm32")]
 | 
				
			||||||
 | 
					    fn get_timestamp() -> u64 {
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn get_descriptor_for(&self, script_type: ScriptType) -> &ExtendedDescriptor {
 | 
					    fn get_descriptor_for(&self, script_type: ScriptType) -> &ExtendedDescriptor {
 | 
				
			||||||
        let desc = match script_type {
 | 
					        let desc = match script_type {
 | 
				
			||||||
            ScriptType::External => &self.descriptor,
 | 
					            ScriptType::External => &self.descriptor,
 | 
				
			||||||
@ -646,6 +652,7 @@ where
 | 
				
			|||||||
            // safe to run only on the descriptor because we assume the change descriptor also has
 | 
					            // safe to run only on the descriptor because we assume the change descriptor also has
 | 
				
			||||||
            // the same structure
 | 
					            // the same structure
 | 
				
			||||||
            let desc = self.descriptor.derive_from_psbt_input(psbt, n);
 | 
					            let desc = self.descriptor.derive_from_psbt_input(psbt, n);
 | 
				
			||||||
 | 
					            debug!("{:?}", psbt.inputs[n].hd_keypaths);
 | 
				
			||||||
            debug!("reconstructed descriptor is {:?}", desc);
 | 
					            debug!("reconstructed descriptor is {:?}", desc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let desc = match desc {
 | 
					            let desc = match desc {
 | 
				
			||||||
@ -765,6 +772,7 @@ where
 | 
				
			|||||||
        // cache a few of our addresses
 | 
					        // cache a few of our addresses
 | 
				
			||||||
        if last_addr.is_none() {
 | 
					        if last_addr.is_none() {
 | 
				
			||||||
            let mut address_batch = self.database.borrow().begin_batch();
 | 
					            let mut address_batch = self.database.borrow().begin_batch();
 | 
				
			||||||
 | 
					            #[cfg(not(target_arch = "wasm32"))]
 | 
				
			||||||
            let start = Instant::now();
 | 
					            let start = Instant::now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for i in 0..=max_address {
 | 
					            for i in 0..=max_address {
 | 
				
			||||||
@ -790,6 +798,7 @@ where
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            #[cfg(not(target_arch = "wasm32"))]
 | 
				
			||||||
            info!(
 | 
					            info!(
 | 
				
			||||||
                "derivation of {} addresses, took {} ms",
 | 
					                "derivation of {} addresses, took {} ms",
 | 
				
			||||||
                max_address,
 | 
					                max_address,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user