use crate::error::BDKCliError as Error;
#[cfg(any(feature = "sqlite", feature = "redb"))]
use crate::persister::Persister;
-#[cfg(feature = "cbf")]
-use crate::utils::BlockchainClient::KyotoClient;
use crate::utils::*;
#[cfg(feature = "redb")]
use bdk_redb::Store as RedbStore;
bip39::WordCount,
};
use bdk_wallet::miniscript::miniscript;
-use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource};
-use bdk_wallet::bitcoin::consensus::encode::serialize_hex;
-use bdk_wallet::bitcoin::script::PushBytesBuf;
-use bdk_wallet::bitcoin::Network;
-use bdk_wallet::bitcoin::{secp256k1::Secp256k1, Txid};
-use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, Sequence};
-use bdk_wallet::descriptor::{Descriptor, Segwitv0};
-use bdk_wallet::keys::bip39::WordCount;
#[cfg(feature = "sqlite")]
use bdk_wallet::rusqlite::Connection;
use bdk_wallet::{KeychainKind, SignOptions, Wallet};
#[cfg(feature = "compiler")]
use bdk_wallet::{
+ bitcoin::XOnlyPublicKey,
descriptor::{Descriptor, Legacy, Miniscript},
- miniscript::{Tap, descriptor::TapTree, policy::Concrete},
descriptor::{Legacy, Miniscript},
miniscript::policy::Concrete,
+ miniscript::{Tap, descriptor::TapTree, policy::Concrete},
};
use cli_table::{Cell, CellStruct, Style, Table, format::Justify};
use serde_json::json;
-use bdk_wallet::{KeychainKind, SignOptions, Wallet};
+#[cfg(feature = "cbf")]
+use {
+ crate::utils::BlockchainClient::KyotoClient,
+ bdk_kyoto::{Info, LightClient},
+ tokio::select,
+};
#[cfg(feature = "electrum")]
use crate::utils::BlockchainClient::Electrum;
#[cfg(feature = "compiler")]
use bdk_wallet::bitcoin::XOnlyPublicKey;
use bdk_wallet::bitcoin::base64::prelude::*;
-use bdk_wallet::keys::DescriptorKey::Secret;
-use bdk_wallet::keys::{
- DerivableKey, DescriptorKey, DescriptorKey::Secret, DescriptorPublicKey, ExtendedKey,
- GeneratableKey, GeneratedKey, bip39::WordCount,
-};
-use bdk_wallet::miniscript::miniscript;
-use serde_json::{Value, json};
+use serde_json::Value;
use std::collections::BTreeMap;
#[cfg(any(feature = "electrum", feature = "esplora"))]
use std::collections::HashSet;
use std::convert::TryFrom;
-use std::fmt;
#[cfg(any(feature = "repl", feature = "electrum", feature = "esplora"))]
use std::io::Write;
use std::str::FromStr;
-
-#[cfg(feature = "electrum")]
-use crate::utils::BlockchainClient::Electrum;
-#[cfg(feature = "cbf")]
-use bdk_kyoto::{Info, LightClient};
-use bdk_wallet::bitcoin::base64::prelude::*;
-#[cfg(feature = "cbf")]
-use tokio::select;
+#[cfg(any(feature = "redb", feature = "compiler"))]
+use std::sync::Arc;
#[cfg(any(
feature = "electrum",
feature = "esplora",
))]
use {
crate::commands::OnlineWalletSubCommand::*,
- bdk_wallet::bitcoin::{consensus::Decodable, hex::FromHex, Transaction},
+ bdk_wallet::bitcoin::{Transaction, consensus::Decodable, hex::FromHex},
};
#[cfg(feature = "esplora")]
use {crate::utils::BlockchainClient::Esplora, bdk_esplora::EsploraAsyncExt};
subcommand: descriptor_subcommand,
} => {
let network = cli_opts.network;
- let descriptor = handle_descriptor_subcommand(network, descriptor_subcommand)
- .map_err(|e| Error::Generic(e.to_string()))?;
+ let descriptor = handle_descriptor_subcommand(network, descriptor_subcommand)?;
let json = serde_json::to_string_pretty(&descriptor)?;
Ok(json)
}
multipath,
key,
} => {
- let (descriptor_type, derivation_path_str) = match r#type {
- 44 => (DescriptorType::Bip44, "m/44h/1h/0h"),
- 49 => (DescriptorType::Bip49, "m/49h/1h/0h"),
- 84 => (DescriptorType::Bip84, "m/84h/1h/0h"),
- 86 => (DescriptorType::Bip86, "m/86h/1h/0h"),
- _ => return Err(Error::UnsupportedScriptType(r#type)),
+ let descriptor_type = match r#type {
+ 44 => DescriptorType::Bip44,
+ 49 => DescriptorType::Bip49,
+ 84 => DescriptorType::Bip84,
+ 86 => DescriptorType::Bip86,
+ _ => {
+ return Err(Error::Generic(
+ "Unsupported script type: {r#type}".to_string(),
+ ));
+ }
};
match (multipath, key.as_ref()) {
- (true, Some(k)) => generate_multipath_descriptor(&network, r#type, k),
+ // generate multipath descriptors
+ (true, Some(k)) => generate_descriptors(&network, descriptor_type, k, true),
(false, Some(k)) => {
if is_mnemonic(k) {
- generate_descriptor_from_mnemonic_string(
- k,
- network,
- derivation_path_str,
- descriptor_type,
- )
+ // generate descriptors from given mnemonic string
+ generate_descriptor_from_mnemonic_string(k, network, descriptor_type)
} else {
- generate_standard_descriptor(&network, r#type, k)
+ // generate descriptors from key
+ generate_descriptors(&network, descriptor_type, k, false)
}
}
+ // generate mnemonic and descriptors
(false, None) => generate_new_descriptor_with_mnemonic(network, descriptor_type),
- _ => Err(Error::InvalidArguments(
- "Provide a key or weak string".to_string(),
- )),
+ _ => Err(Error::Generic("Provide a key or string".to_string())),
}
}
- DescriptorSubCommand::Info { descriptor } => {
- let parsed: Descriptor<DescriptorPublicKey> = descriptor
- .parse()
- .map_err(|e| Error::Generic(format!("Failed to parse descriptor: {}", e)))?;
-
- let checksum = parsed.to_string();
- let script_type = match parsed {
- Descriptor::Wpkh(_) => "wpkh",
- Descriptor::Pkh(_) => "pkh",
- Descriptor::Sh(_) => "sh",
- Descriptor::Tr(_) => "tr",
- _ => "other",
- };
-
- let json = json!({
- "descriptor": checksum,
- "type": script_type,
- "is_multipath": descriptor.contains("/*"),
- });
-
- Ok(json)
- }
- }
-}
-
-pub fn generate_standard_descriptor(
- network: &Network,
- script_type: u8,
- key: &str,
-) -> Result<Value, Error> {
- let descriptor_type = match script_type {
- 44 => DescriptorType::Bip44,
- 49 => DescriptorType::Bip49,
- 84 => DescriptorType::Bip84,
- 86 => DescriptorType::Bip86,
- _ => return Err(Error::UnsupportedScriptType(script_type)),
- };
-
- generate_descriptor_from_key_by_type(network, key, descriptor_type)
-}
-
-impl fmt::Display for DescriptorType {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let s = match self {
- DescriptorType::Bip44 => "bip44",
- DescriptorType::Bip49 => "bip49",
- DescriptorType::Bip84 => "bip84",
- DescriptorType::Bip86 => "bip86",
- };
- write!(f, "{}", s)
}
}
//!
//! This module includes all the utility tools used by the App.
use crate::error::BDKCliError as Error;
-use std::{fmt::Display, path::{Path, PathBuf}, str::FromStr};
-use std::str::FromStr;
-
-use std::path::{Path, PathBuf};
+use std::{
+ fmt::{Display, Formatter},
+ path::{Path, PathBuf},
+ str::FromStr,
+};
use crate::commands::WalletOpts;
#[cfg(feature = "cbf")]
builder::Builder,
};
use bdk_wallet::bitcoin::{Address, Network, OutPoint, ScriptBuf};
+use bdk_wallet::miniscript::Legacy;
#[cfg(any(
feature = "electrum",
use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
use bdk_wallet::bip39::{Language, Mnemonic};
-use bdk_wallet::bitcoin::bip32::ChildNumber;
+use bdk_wallet::bitcoin::bip32::Fingerprint;
use bdk_wallet::bitcoin::{
bip32::{DerivationPath, Xpriv, Xpub},
secp256k1::Secp256k1,
Ok(())
}
-pub(crate) fn shorten(displayable: impl Display, start: u8, end: u8) -> String {
- let displayable = displayable.to_string();
- let start_str: &str = &displayable[0..start as usize];
- let end_str: &str = &displayable[displayable.len() - end as usize..];
- format!("{start_str}...{end_str}")
-pub fn generate_descriptor_from_key_by_type(
- network: &Network,
- key: &str,
- descriptor_type: DescriptorType,
-) -> Result<serde_json::Value, Error> {
- let derivation_path = match descriptor_type {
- DescriptorType::Bip44 => "m/44h/1h/0h",
- DescriptorType::Bip49 => "m/49h/1h/0h",
- DescriptorType::Bip84 => "m/84h/1h/0h",
- DescriptorType::Bip86 => "m/86h/1h/0h",
- };
-
- generate_bip_descriptor_from_key(network, key, derivation_path, descriptor_type)
-}
-
-pub fn generate_new_descriptor_with_mnemonic(
- network: Network,
- descriptor_type: DescriptorType,
-) -> Result<serde_json::Value, Error> {
- let secp = Secp256k1::new();
-
- // Generate a new BIP39 mnemonic
- let mnemonic: GeneratedKey<Mnemonic, Segwitv0> =
- Mnemonic::generate((WordCount::Words12, Language::English)).map_err(|e| {
- Error::MnemonicGenerationError(format!("Mnemonic generation failed: {:?}", e))
- })?;
-
- let seed = mnemonic.to_seed("");
- let xprv =
- Xpriv::new_master(network, &seed).map_err(|e| Error::XprivCreationError(e.to_string()))?;
-
- let origin = xprv.fingerprint(&secp);
-
- let (derivation_base, external_fmt, internal_fmt) = match descriptor_type {
- DescriptorType::Bip44 => ("/44h/1h/0h", "pkh", "pkh"),
- DescriptorType::Bip49 => ("/49h/1h/0h", "sh(wpkh", "sh(wpkh"),
- DescriptorType::Bip84 => ("/84h/1h/0h", "wpkh", "wpkh"),
- DescriptorType::Bip86 => ("/86h/1h/0h", "tr", "tr"),
- };
- let path = DerivationPath::from_str(&format!("m{}", derivation_base))
- .map_err(|e| Error::Generic(e.to_string()))?;
-
- let derived_xprv = xprv
- .derive_priv(&secp, &path)
- .map_err(|e| Error::Generic(e.to_string()))?;
-
- let xprv_str = derived_xprv.to_string();
-
- // Construct descriptors
- let external_desc = match descriptor_type {
- DescriptorType::Bip49 => format!(
- "{}([{}{}]{}{}))",
- external_fmt, origin, derivation_base, xprv_str, "/0"
- ),
- _ => format!(
- "{}([{}{}]{}{})",
- external_fmt, origin, derivation_base, xprv_str, "/0"
- ),
- };
-
- let internal_desc = match descriptor_type {
- DescriptorType::Bip49 => format!(
- "{}([{}{}]{}{}))",
- internal_fmt, origin, derivation_base, xprv_str, "/1"
- ),
- _ => format!(
- "{}([{}{}]{}{})",
- internal_fmt, origin, derivation_base, xprv_str, "/1"
- ),
- };
-
- // Parse descriptors
- let (ext_desc, ext_keymap) =
- Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &external_desc)
- .map_err(|e| Error::DescriptorParsingError(e.to_string()))?;
-
- let (int_desc, int_keymap) =
- Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &internal_desc).map_err(
- |e| {
- Error::DescriptorParsingError(format!("Failed to parse internal descriptor: {}", e))
- },
- )?;
-
- Ok(serde_json::json!({
- "type": descriptor_type.to_string(),
- "mnemonic": mnemonic.to_string(),
- "private_descriptors": {
- "external": ext_desc.to_string_with_secret(&ext_keymap),
- "internal": int_desc.to_string_with_secret(&int_keymap),
- },
- "public_descriptors": {
- "external": ext_desc.to_string(),
- "internal": int_desc.to_string(),
- }
- }))
+pub fn is_mnemonic(s: &str) -> bool {
+ let word_count = s.split_whitespace().count();
+ (12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace())
}
-pub fn generate_multipath_descriptor(
+pub fn generate_descriptors(
network: &Network,
- script_type: u8,
+ descriptor_type: DescriptorType,
key: &str,
+ multipath_label: bool,
) -> Result<Value, Error> {
- use DescriptorType::*;
-
- let descriptor_type = match script_type {
- 44 => Bip44,
- 49 => Bip49,
- 84 => Bip84,
- 86 => Bip86,
- _ => return Err(Error::UnsupportedScriptType(script_type)),
- };
-
type DescriptorConstructor =
fn(DescriptorPublicKey) -> Result<Descriptor<DescriptorPublicKey>, Error>;
- let (derivation_base, descriptor_constructor): (&str, DescriptorConstructor) =
- match descriptor_type {
- Bip44 => ("/44h/1h/0h", |key| {
- Descriptor::new_pkh(key).map_err(Error::from)
- }),
- Bip49 => ("/49h/1h/0h", |key| {
- Descriptor::new_sh_wpkh(key).map_err(Error::from)
- }),
- Bip84 => ("/84h/1h/0h", |key| {
- Descriptor::new_wpkh(key).map_err(Error::from)
- }),
- Bip86 => ("/86h/1h/0h", |key| {
- Descriptor::new_tr(key, None).map_err(Error::from)
- }),
- };
+ let purpose = match descriptor_type {
+ DescriptorType::Bip44 => 44,
+ DescriptorType::Bip49 => 49,
+ DescriptorType::Bip84 => 84,
+ DescriptorType::Bip86 => 86,
+ };
+
+ let derivation_base = format!("/{purpose}h/1h/0h");
+
+ let descriptor_constructor: DescriptorConstructor = match descriptor_type {
+ DescriptorType::Bip44 => |key| Descriptor::new_pkh(key).map_err(Error::from),
+ DescriptorType::Bip49 => |key| Descriptor::new_sh_wpkh(key).map_err(Error::from),
+ DescriptorType::Bip84 => |key| Descriptor::new_wpkh(key).map_err(Error::from),
+ DescriptorType::Bip86 => |key| Descriptor::new_tr(key, None).map_err(Error::from),
+ };
let secp = Secp256k1::new();
- let derivation_path = DerivationPath::from_str(&format!("m{}", derivation_base))
- .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
+ let derivation_path = DerivationPath::from_str(&format!("m{derivation_base}"))?;
- // Determine if it's an xprv or xpub
let is_private = key.starts_with("xprv") || key.starts_with("tprv");
- // Use xprv or xpub accordingly
type DescriptorBuilderFn = Box<dyn Fn(u32) -> Result<(String, Option<String>), Error>>;
- let (fingerprint, make_desc): (_, DescriptorBuilderFn) = if is_private {
- let xprv: Xpriv = key
- .parse()
- .map_err(|e| Error::InvalidKey(format!("Invalid xprv: {e}")))?;
+ let (fingerprint, make_desc): (Fingerprint, DescriptorBuilderFn) = if is_private {
+ let xprv: Xpriv = key.parse()?;
let fingerprint = xprv.fingerprint(&secp);
let closure = move |change: u32| -> Result<(String, Option<String>), Error> {
- let branch_path = DerivationPath::from_str(&change.to_string())
- .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
+ let branch_path = DerivationPath::from_str(&change.to_string())?;
let desc_xprv = DescriptorXKey {
origin: Some((fingerprint, derivation_path.clone())),
let desc_secret = DescriptorSecretKey::XPrv(desc_xprv.clone());
let (desc_key, keymap, _) = match descriptor_type {
- DescriptorType::Bip84 | DescriptorType::Bip49 | DescriptorType::Bip44 => {
- IntoDescriptorKey::<Segwitv0>::into_descriptor_key(desc_secret)
- .map_err(|e| Error::DescriptorKeyError(e.to_string()))?
- .extract(&secp)
- .map_err(|e| Error::DescriptorKeyError(e.to_string()))?
+ DescriptorType::Bip44 => {
+ IntoDescriptorKey::<Legacy>::into_descriptor_key(desc_secret)?.extract(&secp)?
+ }
+ DescriptorType::Bip84 | DescriptorType::Bip49 => {
+ IntoDescriptorKey::<Segwitv0>::into_descriptor_key(desc_secret)?
+ .extract(&secp)?
+ }
+ DescriptorType::Bip86 => {
+ IntoDescriptorKey::<Tap>::into_descriptor_key(desc_secret)?.extract(&secp)?
}
- DescriptorType::Bip86 => IntoDescriptorKey::<Tap>::into_descriptor_key(desc_secret)
- .map_err(|e| Error::DescriptorKeyError(e.to_string()))?
- .extract(&secp)
- .map_err(|e| Error::DescriptorKeyError(e.to_string()))?,
};
- let public_descriptor = descriptor_constructor(desc_key.clone())?;
- let private_descriptor = descriptor_constructor(desc_key)?;
-
+ let descriptor = descriptor_constructor(desc_key)?;
Ok((
- public_descriptor.to_string(),
- Some(private_descriptor.to_string_with_secret(&keymap)),
+ descriptor.to_string(),
+ Some(descriptor.to_string_with_secret(&keymap)),
))
};
(fingerprint, Box::new(closure))
} else {
- let xpub: Xpub = key
- .parse()
- .map_err(|e| Error::InvalidKey(format!("Invalid xpub: {e}")))?;
+ let xpub: Xpub = key.parse()?;
let fingerprint = xpub.fingerprint();
let closure = move |change: u32| -> Result<(String, Option<String>), Error> {
- let branch_path = DerivationPath::from_str(&change.to_string())
- .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
+ let branch_path = DerivationPath::from_str(&change.to_string())?;
let desc_xpub = DescriptorXKey {
origin: Some((fingerprint, derivation_path.clone())),
(fingerprint, Box::new(closure))
};
- // Build descriptors
let (external_pub, external_priv) = make_desc(0)?;
let (internal_pub, internal_priv) = make_desc(1)?;
+ let type_label = if multipath_label {
+ format!("{descriptor_type}-multipath")
+ } else {
+ descriptor_type.to_string()
+ };
+
let mut result = json!({
- "type": format!("{}-multipath", descriptor_type),
+ "type": type_label,
"public_descriptors": {
"external": external_pub,
"internal": internal_pub
Ok(result)
}
-pub fn generate_bip_descriptor_from_key(
- network: &Network,
- key: &str,
- derivation_path_str: &str,
+pub fn generate_new_descriptor_with_mnemonic(
+ network: Network,
descriptor_type: DescriptorType,
) -> Result<serde_json::Value, Error> {
- let secp = Secp256k1::new();
-
- let derivation_path: DerivationPath = derivation_path_str
- .parse()
- .map_err(|e| Error::InvalidDerivationPath(format!("DerivationPath Error: {e}")))?;
-
- let xprv: Xpriv = key
- .parse()
- .map_err(|e| Error::InvalidKey(format!("Invalid xprv: {e}")))?;
-
- let fingerprint = xprv.fingerprint(&secp);
-
- let make_desc_key = |branch: u32| -> Result<(String, String), Error> {
- let branch_path = DerivationPath::from(vec![ChildNumber::Normal { index: branch }]);
-
- let desc_xprv = DescriptorXKey {
- origin: Some((fingerprint, derivation_path.clone())),
- xkey: xprv,
- derivation_path: branch_path.clone(),
- wildcard: Wildcard::Unhardened,
- };
-
- let desc_secret = DescriptorSecretKey::XPrv(desc_xprv.clone());
-
- let (desc_key, keymap, _) =
- IntoDescriptorKey::<Segwitv0>::into_descriptor_key(desc_secret.clone())
- .map_err(|e| Error::DescriptorKeyError(e.to_string()))?
- .extract(&secp)
- .map_err(|e| Error::DescriptorKeyError(e.to_string()))?;
-
- let public_descriptor = match descriptor_type {
- DescriptorType::Bip84 => Descriptor::new_wpkh(desc_key.clone())?,
- DescriptorType::Bip86 => Descriptor::new_tr(desc_key.clone(), None)?,
- DescriptorType::Bip49 => Descriptor::new_sh_wpkh(desc_key.clone())?,
- DescriptorType::Bip44 => Descriptor::new_pkh(desc_key.clone())?,
- };
-
- let private_descriptor = match descriptor_type {
- DescriptorType::Bip84 => Descriptor::new_wpkh(desc_key)?,
- DescriptorType::Bip86 => Descriptor::new_tr(desc_key, None)?,
- DescriptorType::Bip49 => Descriptor::new_sh_wpkh(desc_key)?,
- DescriptorType::Bip44 => Descriptor::new_pkh(desc_key)?,
- };
-
- Ok((
- public_descriptor.to_string(),
- private_descriptor.to_string_with_secret(&keymap),
- ))
- };
+ let mnemonic: GeneratedKey<Mnemonic, Segwitv0> =
+ Mnemonic::generate((WordCount::Words12, Language::English)).map_err(Error::BIP39Error)?;
- let (external_pub, external_priv) = make_desc_key(0)?;
- let (internal_pub, internal_priv) = make_desc_key(1)?;
+ let seed = mnemonic.to_seed("");
+ let xprv = Xpriv::new_master(network, &seed)?;
- Ok(json!({
- "type": descriptor_type.to_string(),
- "fingerprint": fingerprint.to_string(),
- "network": network.to_string(),
- "private_descriptors": {
- "external": external_priv,
- "internal": internal_priv
- },
- "public_descriptors": {
- "external": external_pub,
- "internal": internal_pub
- }
- }))
+ let mut result = generate_descriptors(&network, descriptor_type, &xprv.to_string(), false)?;
+ result["mnemonic"] = json!(mnemonic.to_string());
+ Ok(result)
}
pub fn generate_descriptor_from_mnemonic_string(
mnemonic_str: &str,
network: Network,
- derivation_path_str: &str,
descriptor_type: DescriptorType,
) -> Result<serde_json::Value, Error> {
- let secp = Secp256k1::new();
-
- let mnemonic = Mnemonic::parse_in(Language::English, mnemonic_str)
- .map_err(|e| Error::Generic(e.to_string()))?;
- let ext_key: ExtendedKey = mnemonic
- .into_extended_key()
- .map_err(|e| Error::Generic(e.to_string()))?;
+ let mnemonic = Mnemonic::parse_in(Language::English, mnemonic_str)?;
+ let ext_key: ExtendedKey = mnemonic.into_extended_key()?;
let xprv = ext_key
.into_xprv(network)
.ok_or_else(|| Error::Generic("No xprv found".to_string()))?;
- let _fingerprint = xprv.fingerprint(&secp);
- let derivation_path: DerivationPath = derivation_path_str
- .parse()
- .map_err(|e| Error::InvalidDerivationPath(format!("DerivationPath Error: {e}")))?;
-
- let xprv = xprv
- .derive_priv(&secp, &derivation_path)
- .map_err(|e| Error::InvalidKey(format!("Failed to derive xprv: {e}")))?;
-
- generate_bip_descriptor_from_key(
- &network,
- &xprv.to_string(),
- derivation_path_str,
- descriptor_type,
- )
+ let mut result = generate_descriptors(&network, descriptor_type, &xprv.to_string(), false)?;
+ result["mnemonic"] = json!(mnemonic_str);
+ Ok(result)
}
-pub fn is_mnemonic(s: &str) -> bool {
- let word_count = s.split_whitespace().count();
- (12..=24).contains(&word_count) && s.chars().all(|c| c.is_alphanumeric() || c.is_whitespace())
-}
// Enum for descriptor types
#[derive(Debug, Clone, Copy)]
pub enum DescriptorType {
Bip84,
Bip86,
}
+
+impl Display for DescriptorType {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let s = match self {
+ DescriptorType::Bip44 => "bip44",
+ DescriptorType::Bip49 => "bip49",
+ DescriptorType::Bip84 => "bip84",
+ DescriptorType::Bip86 => "bip86",
+ };
+ write!(f, "{s}")
+ }
+}
+
+pub(crate) fn shorten(displayable: impl Display, start: u8, end: u8) -> String {
+ let displayable = displayable.to_string();
+ let start_str: &str = &displayable[0..start as usize];
+ let end_str: &str = &displayable[displayable.len() - end as usize..];
+ format!("{start_str}...{end_str}")
+}