use anyhow::{Result, Context, bail};
use bitcoin::{Address, Network, ScriptBuf, Script};
use bitcoin::address::NetworkUnchecked;
use bitcoin::secp256k1::{Secp256k1, XOnlyPublicKey};
use bitcoin::taproot::{TapLeaf, TapTree, LeafVersion};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressType {
P2PKH,
P2SH,
P2WPKH,
P2WSH,
P2TR,
Unknown,
}
pub fn get_address_type(address: &Address) -> AddressType {
if address.is_p2pkh() {
AddressType::P2PKH
} else if address.is_p2sh() {
AddressType::P2SH
} else if address.is_p2wpkh() {
AddressType::P2WPKH
} else if address.is_p2wsh() {
AddressType::P2WSH
} else if address.is_p2tr() {
AddressType::P2TR
} else {
AddressType::Unknown
}
}
pub fn is_segwit_address(address: &Address) -> bool {
address.is_p2wpkh() || address.is_p2wsh() || address.is_p2tr()
}
pub fn is_taproot_address(address: &Address) -> bool {
address.is_p2tr()
}
pub fn get_script_pubkey(address: &Address) -> ScriptBuf {
address.script_pubkey()
}
pub fn create_p2pkh_address(pubkey_hash: &[u8; 20], network: Network) -> Result<Address> {
Address::p2pkh(
bitcoin::PubkeyHash::from_slice(pubkey_hash)
.context("Invalid public key hash")?,
network,
).context("Failed to create P2PKH address")
}
pub fn create_p2sh_address(script: &Script, network: Network) -> Result<Address> {
Address::p2sh(
&bitcoin::ScriptHash::from_script(script)
.context("Failed to hash script")?,
network,
).context("Failed to create P2SH address")
}
pub fn create_p2wpkh_address(pubkey_hash: &[u8; 20], network: Network) -> Result<Address> {
Address::p2wpkh(
&bitcoin::WPubkeyHash::from_slice(pubkey_hash)
.context("Invalid witness public key hash")?,
network,
).context("Failed to create P2WPKH address")
}
pub fn create_p2wsh_address(script: &Script, network: Network) -> Result<Address> {
Address::p2wsh(
&bitcoin::WScriptHash::from_script(script)
.context("Failed to hash witness script")?,
network,
).context("Failed to create P2WSH address")
}
pub fn create_p2tr_address(internal_key: &XOnlyPublicKey, network: Network) -> Result<Address> {
let secp = Secp256k1::new();
Address::p2tr(
&secp,
*internal_key,
None,
network,
).context("Failed to create P2TR address")
}
pub fn create_p2tr_address_with_scripts(
internal_key: &XOnlyPublicKey,
scripts: &[ScriptBuf],
network: Network,
) -> Result<Address> {
let secp = Secp256k1::new();
let mut builder = TapTree::builder();
for (i, script) in scripts.iter().enumerate() {
let leaf = TapLeaf::new(LeafVersion::TapScript, script.clone());
builder = builder.add_leaf(i as u8, leaf)
.context("Failed to add leaf to Taproot tree")?;
}
let tap_tree = builder.finalize(&secp, *internal_key)
.context("Failed to finalize Taproot tree")?;
let output_key = tap_tree.output_key();
Address::p2tr(
&secp,
output_key,
None,
network,
).context("Failed to create P2TR address with scripts")
}
pub fn parse_address(address: &str, network: Network) -> Result<Address> {
match Address::from_str(address) {
Ok(address_unchecked) => {
match address_unchecked.require_network(network) {
Ok(address) => Ok(address),
Err(_) => {
bail!("Address network mismatch: expected {:?}", network)
}
}
},
Err(e) => {
bail!("Invalid address: {}", e)
}
}
}
pub fn recommended_address_type(network: Network) -> AddressType {
match network {
Network::Bitcoin | Network::Testnet | Network::Signet => AddressType::P2TR,
Network::Regtest => AddressType::P2WPKH,
}
}
pub fn format_address(address: &Address) -> String {
let address_type = get_address_type(address);
match address_type {
AddressType::P2PKH => format!("P2PKH: {}", address),
AddressType::P2SH => format!("P2SH: {}", address),
AddressType::P2WPKH => format!("P2WPKH: {}", address),
AddressType::P2WSH => format!("P2WSH: {}", address),
AddressType::P2TR => format!("P2TR: {}", address),
AddressType::Unknown => format!("Unknown: {}", address),
}
}