From: Vihiga Tyonum Date: Thu, 6 Feb 2025 12:14:32 +0000 (+0100) Subject: refactor: replace bdk::error with custom error X-Git-Tag: v1.0.0~7^2~16 X-Git-Url: http://internal-gitweb-vhost/?a=commitdiff_plain;h=50233dd3fd1b752f209df74813b2dc028105cf6f;p=bdk-cli refactor: replace bdk::error with custom error - replace bdk::error with custom error enum - update imports and handling of errors [Ticket: X] --- diff --git a/Cargo.lock b/Cargo.lock index 69e0360..ae97e2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,7 +209,6 @@ dependencies = [ "clap", "dirs-next", "electrsd", - "electrum-client 0.23.0", "env_logger", "fd-lock", "getrandom 0.2.10", @@ -893,23 +892,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "electrum-client" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ed4d35bb98a55540bb5b735731486febddf9cc9b6e96f5b3fd2536eed81a4e" -dependencies = [ - "bitcoin 0.32.5", - "byteorder", - "libc", - "log", - "rustls 0.23.22", - "serde", - "serde_json", - "webpki-roots 0.25.4", - "winapi", -] - [[package]] name = "encoding_rs" version = "0.8.32" diff --git a/Cargo.toml b/Cargo.toml index 35caf59..c7a3e9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,8 +34,6 @@ bdk_electrum ={ version = "0.20.1", optional = true} bdk_esplora ={ version = "0.20.1", features = ["async"], optional = true} bdk_bitcoind_rpc = {version = "0.17.1", optional = true} hwi = {version = "0.10.0", optional = true} -electrum-client = "0.23.0" - # Platform-specific dependencies [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/src/commands.rs b/src/commands.rs index a80b810..707c3cf 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1041,7 +1041,7 @@ mod test { subcommand: WalletSubCommand::OfflineWalletSubCommand(CreateTx { recipients: vec![(script1, 123456), (script2, 78910)], send_all: false, - enable_rbf: false, + enable_rbf: true, offline_signer: false, utxos: Some(vec!(outpoint1, outpoint2)), unspendable: None, diff --git a/src/handlers.rs b/src/handlers.rs index 37efff8..e4ed88c 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -22,47 +22,44 @@ use crate::commands::OfflineWalletSubCommand::*; ))] use crate::commands::OnlineWalletSubCommand::*; use crate::commands::*; +use crate::error::BDKCliError as Error; use crate::utils::*; -use bdk::Error; use bdk_wallet::bip39::{Language, Mnemonic}; use bdk_wallet::bitcoin::script::PushBytesBuf; use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, Sequence}; use bdk_wallet::rusqlite::Connection; use bdk_wallet::keys::bip39::WordCount; -use bdk_wallet::{KeychainKind, PersistedWallet}; +use bdk_wallet::{KeychainKind, PersistedWallet, SignOptions}; use clap::Parser; -use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource}; -use bdk_wallet::bitcoin::consensus::encode::{serialize, serialize_hex}; #[cfg(any( feature = "electrum", feature = "esplora", feature = "compact_filters", feature = "rpc" ))] -use bdk_wallet::bitcoin::hashes::hex::FromHex; +use bdk_reserves::bdk::{ + blockchain::{log_progress, Blockchain}, + SyncOptions, +}; +use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource}; +use bdk_wallet::bitcoin::consensus::encode::{serialize, serialize_hex}; use bdk_wallet::bitcoin::{secp256k1::Secp256k1, Network, Txid}; -#[cfg(any( - feature = "electrum", - feature = "esplora", - feature = "compact_filters", - feature = "rpc" -))] -use bdk_wallet::blockchain::{log_progress, Blockchain}; use bdk_wallet::descriptor::Segwitv0; #[cfg(feature = "compiler")] use bdk_wallet::descriptor::{Descriptor, Legacy, Miniscript}; #[cfg(all(feature = "reserves", feature = "electrum"))] -use bdk_wallet::electrum_client::{Client, ElectrumApi}; -#[cfg(feature = "hardware-signer")] -use bdk_wallet::hwi::{ - interface::HWIClient, - types::{HWIChain, HWIDescriptor}, -}; +use electrum_client::{Client, ElectrumApi}; + use bdk_wallet::keys::DescriptorKey::Secret; use bdk_wallet::keys::{DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey}; use bdk_wallet::miniscript::miniscript; +#[cfg(feature = "hardware-signer")] +use hwi::{ + types::{HWIChain, HWIDescriptor}, + HWIClient, +}; use bdk_macros::maybe_async; #[cfg(any( @@ -72,23 +69,16 @@ use bdk_macros::maybe_async; feature = "rpc" ))] use bdk_macros::maybe_await; +#[cfg(all(feature = "reserves", feature = "electrum"))] +use bdk_reserves::bdk::{bitcoin::Address, blockchain::Capability}; #[cfg(feature = "reserves")] -use bdk_reserves::reserves::verify_proof; -#[cfg(feature = "reserves")] -use bdk_reserves::reserves::ProofOfReserves; +use bdk_reserves::reserves::{verify_proof, ProofOfReserves}; #[cfg(feature = "compiler")] use bdk_wallet::miniscript::policy::Concrete; -#[cfg(feature = "hardware-signer")] -use bdk_wallet::wallet::signer::SignerError; -use bdk_wallet::SignOptions; -#[cfg(all(feature = "reserves", feature = "electrum"))] -use bdk_wallet::{bitcoin::Address, blockchain::Capability}; #[cfg(feature = "repl")] use regex::Regex; #[cfg(feature = "repl")] -use rustyline::error::ReadlineError; -#[cfg(feature = "repl")] -use rustyline::Editor; +use rustyline::{error::ReadlineError, Editor}; use serde_json::json; use std::str::FromStr; @@ -197,14 +187,11 @@ pub fn handle_offline_wallet_subcommand( ]; for (policy, keychain) in policies.into_iter().flatten() { - let policy = serde_json::from_str::>>(&policy) - .map_err(|s| Error::Generic(s.to_string()))?; + let policy = serde_json::from_str::>>(&policy)?; tx_builder.policy_path(policy, keychain); } - let psbt = tx_builder - .finish() - .map_err(|e| Error::Generic(e.to_string()))?; + let psbt = tx_builder.finish()?; let serialized_psbt = psbt.serialize(); let psbt_base64 = base64::encode(&serialized_psbt); @@ -225,11 +212,9 @@ pub fn handle_offline_wallet_subcommand( unspendable, fee_rate, } => { - let txid = Txid::from_str(txid.as_str()).map_err(|s| Error::Generic(s.to_string()))?; + let txid = Txid::from_str(txid.as_str())?; - let mut tx_builder = wallet - .build_fee_bump(txid) - .map_err(|e| Error::Generic(e.to_string()))?; + let mut tx_builder = wallet.build_fee_bump(txid)?; let fee_rate = FeeRate::from_sat_per_vb(fee_rate as u64).unwrap_or(FeeRate::BROADCAST_MIN); tx_builder.fee_rate(fee_rate); @@ -251,9 +236,7 @@ pub fn handle_offline_wallet_subcommand( tx_builder.unspendable(unspendable); } - let psbt = tx_builder - .finish() - .map_err(|e| Error::Generic(e.to_string()))?; + let psbt = tx_builder.finish()?; let serialized_psbt = psbt.serialize(); let psbt_base64 = base64::encode(serialized_psbt); @@ -261,12 +244,8 @@ pub fn handle_offline_wallet_subcommand( Ok(json!({"psbt": psbt_base64, "details": psbt})) } Policies => { - let external_policy = wallet - .policies(KeychainKind::External) - .map_err(|e| Error::Generic(e.to_string()))?; - let internal_policy = wallet - .policies(KeychainKind::Internal) - .map_err(|e| Error::Generic(e.to_string()))?; + let external_policy = wallet.policies(KeychainKind::External)?; + let internal_policy = wallet.policies(KeychainKind::Internal)?; Ok(json!({ "external": external_policy, @@ -282,17 +261,14 @@ pub fn handle_offline_wallet_subcommand( assume_height, trust_witness_utxo, } => { - let psbt_bytes = base64::decode(psbt).map_err(|e| Error::Generic(e.to_string()))?; - let mut psbt = - Psbt::deserialize(&psbt_bytes).map_err(|e| Error::Generic(e.to_string()))?; + let psbt_bytes = base64::decode(psbt)?; + let mut psbt = Psbt::deserialize(&psbt_bytes)?; let signopt = SignOptions { assume_height, trust_witness_utxo: trust_witness_utxo.unwrap_or(false), ..Default::default() }; - let finalized = wallet - .sign(&mut psbt, signopt) - .map_err(|e| Error::Generic(e.to_string()))?; + let finalized = wallet.sign(&mut psbt, signopt)?; if wallet_opts.verbose { Ok( json!({"psbt": base64::encode(serialize(&psbt_bytes)),"is_finalized": finalized, "serialized_psbt": psbt}), @@ -304,13 +280,9 @@ pub fn handle_offline_wallet_subcommand( } } ExtractPsbt { psbt } => { - let psbt_serialized = - base64::decode(psbt).map_err(|e| Error::Generic(e.to_string()))?; - let psbt = - Psbt::deserialize(&psbt_serialized).map_err(|e| Error::Generic(e.to_string()))?; - let raw_tx = psbt - .extract_tx() - .map_err(|e| Error::Generic(e.to_string()))?; + let psbt_serialized = base64::decode(psbt)?; + let psbt = Psbt::deserialize(&psbt_serialized)?; + let raw_tx = psbt.extract_tx()?; Ok(json!({"raw_tx": serialize_hex(&raw_tx),})) } FinalizePsbt { @@ -318,18 +290,15 @@ pub fn handle_offline_wallet_subcommand( assume_height, trust_witness_utxo, } => { - let psbt_bytes = base64::decode(psbt).map_err(|e| Error::Generic(e.to_string()))?; - let mut psbt: Psbt = - Psbt::deserialize(&psbt_bytes).map_err(|e| Error::Generic(e.to_string()))?; + let psbt_bytes = base64::decode(psbt)?; + let mut psbt: Psbt = Psbt::deserialize(&psbt_bytes)?; let signopt = SignOptions { assume_height, trust_witness_utxo: trust_witness_utxo.unwrap_or(false), ..Default::default() }; - let finalized = wallet - .finalize_psbt(&mut psbt, signopt) - .map_err(|e| Error::Generic(e.to_string()))?; + let finalized = wallet.finalize_psbt(&mut psbt, signopt)?; if wallet_opts.verbose { Ok( json!({ "psbt": base64::encode(serialize(&psbt_bytes)),"is_finalized": finalized, "serialized_psbt": psbt}), @@ -344,10 +313,8 @@ pub fn handle_offline_wallet_subcommand( let mut psbts = psbt .iter() .map(|s| { - let psbt = base64::decode(s).map_err(|e| Error::Generic(e.to_string()))?; - let psbt: Psbt = - Psbt::deserialize(&psbt).map_err(|e| Error::Generic(e.to_string()))?; - Ok(psbt) + let psbt = base64::decode(s)?; + Ok(Psbt::deserialize(&psbt)?) }) .collect::, Error>>()?; @@ -376,17 +343,11 @@ pub fn handle_offline_wallet_subcommand( feature = "compact_filters", feature = "rpc" ))] -pub(crate) fn handle_online_wallet_subcommand( - wallet: &Wallet, +pub(crate) fn handle_online_wallet_subcommand( + wallet: &Wallet, blockchain: &B, online_subcommand: OnlineWalletSubCommand, -) -> Result -where - B: Blockchain, - D: BatchDatabase, -{ - use bdk::SyncOptions; - +) -> Result { match online_subcommand { Sync => { maybe_await!(wallet.sync( @@ -401,11 +362,11 @@ where let tx = match (psbt, tx) { (Some(psbt), None) => { let psbt = base64::decode(&psbt).map_err(|e| Error::Generic(e.to_string()))?; - let psbt: PartiallySignedTransaction = deserialize(&psbt)?; + let psbt: Psbt = Psbt::deserialize(&psbt)?; is_final(&psbt)?; psbt.extract_tx() } - (None, Some(tx)) => deserialize(&Vec::::from_hex(&tx)?)?, + (None, Some(tx)) => Psbt::deserialize(&Vec::::from_hex(&tx)?)?, (Some(_), Some(_)) => panic!("Both `psbt` and `tx` options not allowed"), (None, None) => panic!("Missing `psbt` and `tx` option"), }; @@ -435,8 +396,8 @@ where msg, confirmations, } => { - let psbt = base64::decode(&psbt).unwrap(); - let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); + let psbt = base64::decode(&psbt)?; + let psbt: Psbt = Psbt::deserialize(&psbt)?; let current_height = blockchain.get_height()?; let max_confirmation_height = if confirmations == 0 { None @@ -467,7 +428,7 @@ where feature = "compact_filters", feature = "rpc" ))] -pub(crate) fn is_final(psbt: &PartiallySignedTransaction) -> Result<(), Error> { +pub(crate) fn is_final(psbt: &Psbt) -> Result<(), Error> { let unsigned_tx_inputs = psbt.unsigned_tx.input.len(); let psbt_inputs = psbt.inputs.len(); if unsigned_tx_inputs != psbt_inputs { @@ -513,9 +474,7 @@ pub(crate) fn handle_key_subcommand( Mnemonic::generate((mnemonic_type, Language::English)) .map_err(|_| Error::Generic("Mnemonic generation error".to_string()))?; let mnemonic = mnemonic.into_key(); - let xkey: ExtendedKey = (mnemonic.clone(), password) - .into_extended_key() - .map_err(|e| Error::Generic(format!("Extended key generation error: {:?}", e)))?; + let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?; let xprv = xkey.into_xprv(network).ok_or_else(|| { Error::Generic("Privatekey info not found (should not happen)".to_string()) })?; @@ -530,11 +489,8 @@ pub(crate) fn handle_key_subcommand( ) } KeySubCommand::Restore { mnemonic, password } => { - let mnemonic = Mnemonic::parse_in(Language::English, mnemonic) - .map_err(|e| Error::Generic(e.to_string()))?; - let xkey: ExtendedKey = (mnemonic, password) - .into_extended_key() - .map_err(|e| Error::Generic(format!("Extended key generation error: {:?}", e)))?; + let mnemonic = Mnemonic::parse_in(Language::English, mnemonic)?; + let xkey: ExtendedKey = (mnemonic, password).into_extended_key()?; let xprv = xkey.into_xprv(network).ok_or_else(|| { Error::Generic("Privatekey info not found (should not happen)".to_string()) })?; @@ -544,27 +500,20 @@ pub(crate) fn handle_key_subcommand( } KeySubCommand::Derive { xprv, path } => { if xprv.network != network.into() { - return Err(Error::Key(bdk::keys::KeyError::InvalidNetwork)); + return Err(Error::Generic("Invalid network".to_string())); } - let derived_xprv = &xprv - .derive_priv(&secp, &path) - .map_err(|e| Error::Generic(e.to_string()))?; + let derived_xprv = &xprv.derive_priv(&secp, &path)?; let origin: KeySource = (xprv.fingerprint(&secp), path); - let derived_xprv_desc_key: DescriptorKey = derived_xprv - .into_descriptor_key(Some(origin), DerivationPath::default()) - .map_err(|e| Error::Generic(e.to_string()))?; + let derived_xprv_desc_key: DescriptorKey = + derived_xprv.into_descriptor_key(Some(origin), DerivationPath::default())?; if let Secret(desc_seckey, _, _) = derived_xprv_desc_key { - let desc_pubkey = desc_seckey - .to_public(&secp) - .map_err(|e| Error::Generic(e.to_string()))?; + let desc_pubkey = desc_seckey.to_public(&secp)?; Ok(json!({"xpub": desc_pubkey.to_string(), "xprv": desc_seckey.to_string()})) } else { - Err(Error::Key(bdk::keys::KeyError::Message( - "Invalid key variant".to_string(), - ))) + Err(Error::Generic("Invalid key variant".to_string())) } } #[cfg(feature = "hardware-signer")] @@ -574,15 +523,14 @@ pub(crate) fn handle_key_subcommand( Network::Testnet => HWIChain::Test, Network::Regtest => HWIChain::Regtest, Network::Signet => HWIChain::Signet, + Network::Testnet4 => HWIChain::Test, + _ => Err(Error::Generic("Invalid network".to_string()))?, }; - let devices = HWIClient::enumerate().map_err(|e| SignerError::from(e))?; + let devices = HWIClient::enumerate()?; let descriptors = devices.iter().map(|device_| { - let device = device_.clone() - .map_err(|e| SignerError::from(e))?; - let client = HWIClient::get_client(&device, true, chain.clone()) - .map_err(|e| SignerError::from(e))?; - let descriptors: HWIDescriptor = client.get_descriptors(None) - .map_err(|e| SignerError::from(e))?; + let device = device_.clone().map_err(|e| Error::Generic(e.to_string()))?; + let client = HWIClient::get_client(&device, true, chain.clone())?; + let descriptors: HWIDescriptor = client.get_descriptors(None)?; Ok(json!({"device": device.model, "receiving": descriptors.receive[0].to_string(), "change": descriptors.internal[0]})) }).collect::, Error>>()?; Ok(json!(descriptors)) @@ -612,8 +560,7 @@ pub(crate) fn handle_compile_subcommand( "wsh" => Descriptor::new_wsh(segwit_policy), "sh-wsh" => Descriptor::new_sh_wsh(segwit_policy), _ => panic!("Invalid type"), - } - .map_err(Error::Miniscript)?; + }?; Ok(json!({"descriptor": descriptor.to_string()})) } @@ -628,11 +575,10 @@ pub(crate) fn handle_ext_reserves_subcommand( psbt: String, confirmations: usize, addresses: Vec, - electrum_opts: ElectrumOpts, + electrum_opts: ElectrumApi, ) -> Result { - let psbt = base64::decode(&psbt) - .map_err(|e| Error::Generic(format!("Base64 decode error: {:?}", e)))?; - let psbt: PartiallySignedTransaction = deserialize(&psbt)?; + let psbt = base64::decode(&psbt)?; + let psbt: Psbt = Psbt::deserialize(&psbt)?; let client = Client::new(&electrum_opts.server)?; let current_block_height = client.block_headers_subscribe().map(|data| data.height)?; @@ -641,8 +587,7 @@ pub(crate) fn handle_ext_reserves_subcommand( let outpoints_per_addr = addresses .iter() .map(|address| { - let address = Address::from_str(address) - .map_err(|e| Error::Generic(format!("Invalid address: {:?}", e)))?; + let address = Address::from_str(address)?; get_outpoints_for_address(address, &client, max_confirmation_height) }) .collect::>, Error>>()?; @@ -730,8 +675,7 @@ pub(crate) fn handle_command(cli_opts: CliOpts) -> Result { // println!("No previous history."); // } - let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX) - .map_err(|e| Error::Generic(e.to_string()))?; + let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX)?; #[cfg(any( feature = "electrum", @@ -855,19 +799,20 @@ pub(crate) fn handle_command(cli_opts: CliOpts) -> Result { ))] #[cfg(test)] mod test { + use bdk_wallet::bitcoin::Psbt; + use super::is_final; - use bdk::bitcoin::psbt::PartiallySignedTransaction; use std::str::FromStr; #[test] fn test_psbt_is_final() { - let unsigned_psbt = PartiallySignedTransaction::from_str("cHNidP8BAIkBAAAAASWJHzxzyVORV/C3lAynKHVVL7+Rw7/Jj8U9fuvD24olAAAAAAD+////AiBOAAAAAAAAIgAgLzY9yE4jzTFJnHtTjkc+rFAtJ9NB7ENFQ1xLYoKsI1cfqgKVAAAAACIAIFsbWgDeLGU8EA+RGwBDIbcv4gaGG0tbEIhDvwXXa/E7LwEAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BALLAAD/////AgD5ApUAAAAAIgAgWxtaAN4sZTwQD5EbAEMhty/iBoYbS1sQiEO/Bddr8TsAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErAPkClQAAAAAiACBbG1oA3ixlPBAPkRsAQyG3L+IGhhtLWxCIQ78F12vxOwEFR1IhA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDIQLKhV/gEZYmlsQXnsL5/Uqv5Y8O31tmWW1LQqIBkiqzCVKuIgYCyoVf4BGWJpbEF57C+f1Kr+WPDt9bZlltS0KiAZIqswkEboH3lCIGA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDBDS6ZSEAACICAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJBG6B95QiAgPyVdlP9KV1voj+PUHLGIpRL5GRHeKYZgzPJ1fMAvjHgwQ0umUhAA==").unwrap(); + let unsigned_psbt = Psbt::from_str("cHNidP8BAIkBAAAAASWJHzxzyVORV/C3lAynKHVVL7+Rw7/Jj8U9fuvD24olAAAAAAD+////AiBOAAAAAAAAIgAgLzY9yE4jzTFJnHtTjkc+rFAtJ9NB7ENFQ1xLYoKsI1cfqgKVAAAAACIAIFsbWgDeLGU8EA+RGwBDIbcv4gaGG0tbEIhDvwXXa/E7LwEAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BALLAAD/////AgD5ApUAAAAAIgAgWxtaAN4sZTwQD5EbAEMhty/iBoYbS1sQiEO/Bddr8TsAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErAPkClQAAAAAiACBbG1oA3ixlPBAPkRsAQyG3L+IGhhtLWxCIQ78F12vxOwEFR1IhA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDIQLKhV/gEZYmlsQXnsL5/Uqv5Y8O31tmWW1LQqIBkiqzCVKuIgYCyoVf4BGWJpbEF57C+f1Kr+WPDt9bZlltS0KiAZIqswkEboH3lCIGA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDBDS6ZSEAACICAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJBG6B95QiAgPyVdlP9KV1voj+PUHLGIpRL5GRHeKYZgzPJ1fMAvjHgwQ0umUhAA==").unwrap(); assert!(is_final(&unsigned_psbt).is_err()); - let part_signed_psbt = PartiallySignedTransaction::from_str("cHNidP8BAIkBAAAAASWJHzxzyVORV/C3lAynKHVVL7+Rw7/Jj8U9fuvD24olAAAAAAD+////AiBOAAAAAAAAIgAgLzY9yE4jzTFJnHtTjkc+rFAtJ9NB7ENFQ1xLYoKsI1cfqgKVAAAAACIAIFsbWgDeLGU8EA+RGwBDIbcv4gaGG0tbEIhDvwXXa/E7LwEAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BALLAAD/////AgD5ApUAAAAAIgAgWxtaAN4sZTwQD5EbAEMhty/iBoYbS1sQiEO/Bddr8TsAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErAPkClQAAAAAiACBbG1oA3ixlPBAPkRsAQyG3L+IGhhtLWxCIQ78F12vxOyICA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDSDBFAiEAnNPpu6wNX2HXYz8s2q5nXug4cWfvCGD3SSH2CNKm+yECIEQO7/URhUPsGoknMTE+GrYJf9Wxqn9QsuN9FGj32cQpAQEFR1IhA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDIQLKhV/gEZYmlsQXnsL5/Uqv5Y8O31tmWW1LQqIBkiqzCVKuIgYCyoVf4BGWJpbEF57C+f1Kr+WPDt9bZlltS0KiAZIqswkEboH3lCIGA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDBDS6ZSEAACICAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJBG6B95QiAgPyVdlP9KV1voj+PUHLGIpRL5GRHeKYZgzPJ1fMAvjHgwQ0umUhAA==").unwrap(); + let part_signed_psbt = Psbt::from_str("cHNidP8BAIkBAAAAASWJHzxzyVORV/C3lAynKHVVL7+Rw7/Jj8U9fuvD24olAAAAAAD+////AiBOAAAAAAAAIgAgLzY9yE4jzTFJnHtTjkc+rFAtJ9NB7ENFQ1xLYoKsI1cfqgKVAAAAACIAIFsbWgDeLGU8EA+RGwBDIbcv4gaGG0tbEIhDvwXXa/E7LwEAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BALLAAD/////AgD5ApUAAAAAIgAgWxtaAN4sZTwQD5EbAEMhty/iBoYbS1sQiEO/Bddr8TsAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErAPkClQAAAAAiACBbG1oA3ixlPBAPkRsAQyG3L+IGhhtLWxCIQ78F12vxOyICA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDSDBFAiEAnNPpu6wNX2HXYz8s2q5nXug4cWfvCGD3SSH2CNKm+yECIEQO7/URhUPsGoknMTE+GrYJf9Wxqn9QsuN9FGj32cQpAQEFR1IhA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDIQLKhV/gEZYmlsQXnsL5/Uqv5Y8O31tmWW1LQqIBkiqzCVKuIgYCyoVf4BGWJpbEF57C+f1Kr+WPDt9bZlltS0KiAZIqswkEboH3lCIGA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDBDS6ZSEAACICAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJBG6B95QiAgPyVdlP9KV1voj+PUHLGIpRL5GRHeKYZgzPJ1fMAvjHgwQ0umUhAA==").unwrap(); assert!(is_final(&part_signed_psbt).is_err()); - let full_signed_psbt = PartiallySignedTransaction::from_str("cHNidP8BAIkBAAAAASWJHzxzyVORV/C3lAynKHVVL7+Rw7/Jj8U9fuvD24olAAAAAAD+////AiBOAAAAAAAAIgAgLzY9yE4jzTFJnHtTjkc+rFAtJ9NB7ENFQ1xLYoKsI1cfqgKVAAAAACIAIFsbWgDeLGU8EA+RGwBDIbcv4gaGG0tbEIhDvwXXa/E7LwEAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BALLAAD/////AgD5ApUAAAAAIgAgWxtaAN4sZTwQD5EbAEMhty/iBoYbS1sQiEO/Bddr8TsAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErAPkClQAAAAAiACBbG1oA3ixlPBAPkRsAQyG3L+IGhhtLWxCIQ78F12vxOwEFR1IhA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDIQLKhV/gEZYmlsQXnsL5/Uqv5Y8O31tmWW1LQqIBkiqzCVKuIgYCyoVf4BGWJpbEF57C+f1Kr+WPDt9bZlltS0KiAZIqswkEboH3lCIGA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDBDS6ZSEBBwABCNsEAEgwRQIhAJzT6busDV9h12M/LNquZ17oOHFn7whg90kh9gjSpvshAiBEDu/1EYVD7BqJJzExPhq2CX/Vsap/ULLjfRRo99nEKQFHMEQCIGoFCvJ2zPB7PCpznh4+1jsY03kMie49KPoPDdr7/T9TAiB3jV7wzR9BH11FSbi+8U8gSX95PrBlnp1lOBgTUIUw3QFHUiED8lXZT/Sldb6I/j1ByxiKUS+RkR3imGYMzydXzAL4x4MhAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJUq4AACICAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJBG6B95QiAgPyVdlP9KV1voj+PUHLGIpRL5GRHeKYZgzPJ1fMAvjHgwQ0umUhAA==").unwrap(); + let full_signed_psbt = Psbt::from_str("cHNidP8BAIkBAAAAASWJHzxzyVORV/C3lAynKHVVL7+Rw7/Jj8U9fuvD24olAAAAAAD+////AiBOAAAAAAAAIgAgLzY9yE4jzTFJnHtTjkc+rFAtJ9NB7ENFQ1xLYoKsI1cfqgKVAAAAACIAIFsbWgDeLGU8EA+RGwBDIbcv4gaGG0tbEIhDvwXXa/E7LwEAAAABALUCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////BALLAAD/////AgD5ApUAAAAAIgAgWxtaAN4sZTwQD5EbAEMhty/iBoYbS1sQiEO/Bddr8TsAAAAAAAAAACZqJKohqe3i9hw/cdHe/T+pmd+jaVN1XGkGiXmZYrSL69g2l06M+QEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQErAPkClQAAAAAiACBbG1oA3ixlPBAPkRsAQyG3L+IGhhtLWxCIQ78F12vxOwEFR1IhA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDIQLKhV/gEZYmlsQXnsL5/Uqv5Y8O31tmWW1LQqIBkiqzCVKuIgYCyoVf4BGWJpbEF57C+f1Kr+WPDt9bZlltS0KiAZIqswkEboH3lCIGA/JV2U/0pXW+iP49QcsYilEvkZEd4phmDM8nV8wC+MeDBDS6ZSEBBwABCNsEAEgwRQIhAJzT6busDV9h12M/LNquZ17oOHFn7whg90kh9gjSpvshAiBEDu/1EYVD7BqJJzExPhq2CX/Vsap/ULLjfRRo99nEKQFHMEQCIGoFCvJ2zPB7PCpznh4+1jsY03kMie49KPoPDdr7/T9TAiB3jV7wzR9BH11FSbi+8U8gSX95PrBlnp1lOBgTUIUw3QFHUiED8lXZT/Sldb6I/j1ByxiKUS+RkR3imGYMzydXzAL4x4MhAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJUq4AACICAsqFX+ARliaWxBeewvn9Sq/ljw7fW2ZZbUtCogGSKrMJBG6B95QiAgPyVdlP9KV1voj+PUHLGIpRL5GRHeKYZgzPJ1fMAvjHgwQ0umUhAA==").unwrap(); assert!(is_final(&full_signed_psbt).is_ok()); } } diff --git a/src/main.rs b/src/main.rs index 18708c0..a3b82d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ #![warn(missing_docs)] mod commands; +mod error; mod handlers; mod nodes; mod utils; @@ -22,8 +23,8 @@ use bitcoin::Network; use log::{debug, error, warn}; use crate::commands::CliOpts; +use crate::error::BDKCliError as Error; use crate::handlers::*; -use bdk::Error; use bdk_macros::{maybe_async, maybe_await}; use bdk_wallet::bitcoin; use clap::Parser; diff --git a/src/utils.rs b/src/utils.rs index 906ea5e..02fd6ed 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,7 +10,7 @@ //! //! This module includes all the utility tools used by the App. -use bdk::Error; +use crate::error::BDKCliError as Error; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -68,8 +68,7 @@ pub(crate) fn maybe_descriptor_wallet_name( wallet_opts.change_descriptor.as_deref(), network, &Secp256k1::new(), - ) - .map_err(|e| Error::Generic(e.to_string()))?; + )?; let mut wallet_opts = wallet_opts; wallet_opts.wallet = Some(wallet_name); @@ -97,10 +96,10 @@ pub(crate) fn parse_recipient(s: &str) -> Result<(ScriptBuf, u64), String> { feature = "rpc" ))] /// Parse the proxy (Socket:Port) argument from the cli input. -pub(crate) fn parse_proxy_auth(s: &str) -> Result<(String, String), String> { +pub(crate) fn parse_proxy_auth(s: &str) -> Result<(String, String), Error> { let parts: Vec<_> = s.split(':').collect(); if parts.len() != 2 { - return Err("Invalid format".to_string()); + return Err(Error::Generic("Invalid format".to_string())); } let user = parts[0].to_string(); @@ -116,9 +115,7 @@ pub fn get_outpoints_for_address( client: &Client, max_confirmation_height: Option, ) -> Result, Error> { - let unspents = client - .script_list_unspent(&address.script_pubkey()) - .map_err(Error::Electrum)?; + let unspents = client.script_list_unspent(&address.script_pubkey())?; unspents .iter() @@ -128,9 +125,7 @@ pub fn get_outpoints_for_address( .map(|utxo| { let tx = match client.transaction_get(&utxo.tx_hash) { Ok(tx) => tx, - Err(e) => { - return Err(e).map_err(Error::Electrum); - } + Err(e) => return Err(e).map_err(|e| Error::Generic(e.to_string()))?, }; Ok(( @@ -145,14 +140,13 @@ pub fn get_outpoints_for_address( } /// Parse a outpoint (Txid:Vout) argument from cli input. -pub(crate) fn parse_outpoint(s: &str) -> Result { - OutPoint::from_str(s).map_err(|e| e.to_string()) +pub(crate) fn parse_outpoint(s: &str) -> Result { + Ok(OutPoint::from_str(s)?) } /// Parse an address string into `Address`. -pub(crate) fn parse_address(address_str: &str) -> Result { - let unchecked_address = - Address::from_str(address_str).map_err(|e| format!("Failed to parse address: {}", e))?; +pub(crate) fn parse_address(address_str: &str) -> Result { + let unchecked_address = Address::from_str(address_str)?; Ok(unchecked_address.assume_checked()) } diff --git a/src/wasm.rs b/src/wasm.rs index 9b44ce9..7bc0934 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,4 +1,5 @@ use crate::commands::*; +use crate::error::BDKCliError as Error; use crate::handlers::*; use crate::nodes::Nodes; use crate::utils::*; @@ -6,14 +7,11 @@ use bdk_wallet::*; use bitcoin::*; -use bdk_wallet::blockchain::AnyBlockchain; -use bdk_wallet::database::AnyDatabase; use bdk_wallet::miniscript::{MiniscriptKey, Translator}; use clap::Parser; use js_sys::Promise; use regex::Regex; use std::collections::HashMap; -use std::error::Error; use std::ops::Deref; use std::path::PathBuf; use std::rc::Rc; @@ -30,7 +28,7 @@ use serde::Deserialize; #[wasm_bindgen] pub struct WasmWallet { - wallet: Rc>, + wallet: Rc, wallet_opts: Rc, blockchain: Rc, network: Network, @@ -44,11 +42,8 @@ pub fn log_init() { #[wasm_bindgen] impl WasmWallet { #[wasm_bindgen(constructor)] - pub fn new(network: String, wallet_opts: Vec) -> Result { - fn new_inner( - network: String, - wallet_opts: Vec, - ) -> Result> { + pub fn new(network: String, wallet_opts: Vec) -> Result { + fn new_inner(network: String, wallet_opts: Vec) -> Result { // Both open_database and new_blockchain need a home path to be passed // in, even tho it won't be used let dummy_home_dir = PathBuf::new(); @@ -80,11 +75,11 @@ impl WasmWallet { async fn run_command_inner( command: String, - wallet: Rc>, + wallet: Rc, wallet_opts: Rc, blockchain: Rc, network: Network, - ) -> Result> { + ) -> Result { let split_regex = Regex::new(crate::REPL_LINE_SPLIT_REGEX)?; let split_line: Vec<&str> = split_regex .captures_iter(&command) @@ -131,40 +126,40 @@ struct AliasMap { } #[cfg(feature = "compiler")] -impl Translator for AliasMap { +impl Translator for AliasMap { // Provides the translation public keys P -> Q - fn pk(&mut self, pk: &String) -> Result { + fn pk(&mut self, pk: &String) -> Result { self.inner .get(pk) .map(|a| a.into_key()) - .ok_or(bdk::Error::Generic("Couldn't map alias".to_string())) // Dummy Err + .ok_or(Error::Generic("Couldn't map alias".to_string())) // Dummy Err } - fn sha256(&mut self, sha256: &String) -> Result { + fn sha256(&mut self, sha256: &String) -> Result { Ok(sha256.to_string()) } - fn hash256(&mut self, hash256: &String) -> Result { + fn hash256(&mut self, hash256: &String) -> Result { Ok(hash256.to_string()) } - fn ripemd160(&mut self, ripemd160: &String) -> Result { + fn ripemd160(&mut self, ripemd160: &String) -> Result { Ok(ripemd160.to_string()) } - fn hash160(&mut self, hash160: &String) -> Result { + fn hash160(&mut self, hash160: &String) -> Result { Ok(hash160.to_string()) } } #[wasm_bindgen] #[cfg(feature = "compiler")] -pub fn compile(policy: String, aliases: String, script_type: String) -> Result { +pub fn compile(policy: String, aliases: String, script_type: String) -> Result { fn compile_inner( policy: String, aliases: String, script_type: String, - ) -> Result> { + ) -> Result { use std::collections::HashMap; let aliases: HashMap = serde_json::from_str(&aliases)?; let mut aliases = AliasMap { inner: aliases }; @@ -175,11 +170,10 @@ pub fn compile(policy: String, aliases: String, script_type: String) -> Result Descriptor::new_sh(policy.compile()?)?, "wsh" => Descriptor::new_wsh(policy.compile()?)?, "sh-wsh" => Descriptor::new_sh_wsh(policy.compile()?)?, - _ => return Err(Box::::from("InvalidScriptType")), + _ => return Err(Error::Generic("InvalidScriptType".to_string())), }; - let descriptor: Result, bdk::Error> = - descriptor.translate_pk(&mut aliases); + let descriptor: Result, Error> = descriptor.translate_pk(&mut aliases); let descriptor = descriptor?; Ok(descriptor.to_string().into()) @@ -213,10 +207,8 @@ impl Alias { key.to_wif() } Alias::GenExt { extra: path } => { - let generated: GeneratedKey< - bitcoin::util::bip32::ExtendedPrivKey, - miniscript::Legacy, - > = GeneratableDefaultOptions::generate_default().unwrap(); + let generated: GeneratedKey = + GeneratableDefaultOptions::generate_default().unwrap(); let mut xprv = generated.into_key(); xprv.network = Network::Testnet;