Files
bft-crdt-experiment/side-node/src/bitcoin/client.rs
2024-07-29 10:11:15 +01:00

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)
}
}