115 lines
3.7 KiB
Rust
115 lines
3.7 KiB
Rust
use bdk::{
|
|
bitcoin::{psbt::PartiallySignedTransaction, secp256k1::PublicKey, Network, Transaction},
|
|
blockchain::EsploraBlockchain,
|
|
database::MemoryDatabase,
|
|
keys::ExtendedKey,
|
|
template::Bip84,
|
|
wallet::AddressInfo,
|
|
KeychainKind, SignOptions, SyncOptions, Wallet,
|
|
};
|
|
|
|
use crate::{bitcoin::keys, utils};
|
|
|
|
/// A client that uses Esplora to interact with the Bitcoin network.
|
|
pub struct BitcoinClient {
|
|
pub(crate) blockchain: bdk::blockchain::EsploraBlockchain,
|
|
name: String,
|
|
pub(crate) wallet: Wallet<MemoryDatabase>,
|
|
pub(crate) external_public_key: PublicKey,
|
|
}
|
|
|
|
impl BitcoinClient {
|
|
pub(crate) fn sync(&self) -> anyhow::Result<()> {
|
|
self.wallet.sync(&self.blockchain, SyncOptions::default())?;
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn broadcast(&self, tx: &Transaction) -> anyhow::Result<()> {
|
|
tracing::info!(
|
|
"broadcasting transaction, output will be at https://mutinynet.com/tx/{}",
|
|
tx.txid()
|
|
);
|
|
let _ = self.blockchain.broadcast(&tx);
|
|
Ok(())
|
|
}
|
|
|
|
/// Builds and signs a send transaction to send coins between addresses.
|
|
///
|
|
/// Does NOT send it, you must call `broadcast` to do that.
|
|
///
|
|
/// We could split the creation and signing easily if needed.
|
|
pub(crate) fn build_and_sign_send_tx(
|
|
&mut self,
|
|
recipient: AddressInfo,
|
|
amount: u64,
|
|
) -> Result<bdk::bitcoin::Transaction, anyhow::Error> {
|
|
let mut tx_builder = self.wallet.build_tx();
|
|
tx_builder
|
|
.add_recipient(recipient.script_pubkey(), amount)
|
|
.enable_rbf();
|
|
let (mut psbt, _) = tx_builder.finish()?;
|
|
let tx = self.sign(&mut psbt, true)?.extract_tx();
|
|
Ok(tx)
|
|
}
|
|
|
|
pub(crate) fn sign(
|
|
&self,
|
|
psbt: &mut PartiallySignedTransaction,
|
|
finalize: bool,
|
|
) -> Result<PartiallySignedTransaction, anyhow::Error> {
|
|
tracing::info!("{} signing PSBT", self.name);
|
|
|
|
let options = SignOptions {
|
|
try_finalize: finalize,
|
|
..Default::default()
|
|
};
|
|
|
|
let finalized = self.wallet.sign(psbt, options)?;
|
|
|
|
// make sure the PSBT is finalized if we asked for it
|
|
if finalize {
|
|
assert!(finalized)
|
|
}
|
|
Ok(psbt.to_owned())
|
|
}
|
|
|
|
/// Creates a Bitcoin descriptor wallet with the mnemonic in the given user directory.
|
|
pub(crate) fn create(name: &str, network: Network) -> anyhow::Result<BitcoinClient> {
|
|
let keys_dir = utils::home(name);
|
|
|
|
// let mnemonic_path = crate::utils::side_paths(keys_dir).1; // TODO: this tuple stinks
|
|
// let words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file");
|
|
|
|
let xkey: ExtendedKey = keys::load_from_file(&keys_dir)?;
|
|
|
|
let xprv = xkey
|
|
.into_xprv(Network::Signet)
|
|
.expect("couldn't turn xkey into xprv");
|
|
|
|
let secp = bdk::bitcoin::secp256k1::Secp256k1::new();
|
|
|
|
let external_descriptor1 = Bip84(xprv.clone(), KeychainKind::External);
|
|
let external_descriptor2 = Bip84(xprv, KeychainKind::External);
|
|
let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal));
|
|
|
|
let wallet = Wallet::new(
|
|
external_descriptor1,
|
|
internal_descriptor,
|
|
network,
|
|
MemoryDatabase::default(),
|
|
)?;
|
|
|
|
let blockchain = EsploraBlockchain::new("https://mutinynet.com/api", 20);
|
|
let external_public_key = external_descriptor2.0.private_key.public_key(&secp);
|
|
|
|
let esplora = BitcoinClient {
|
|
name: name.to_string(),
|
|
wallet,
|
|
blockchain,
|
|
external_public_key,
|
|
};
|
|
|
|
Ok(esplora)
|
|
}
|
|
}
|