We only have one bitcoin client now, simplifying
This commit is contained in:
114
side-node/src/bitcoin/client.rs
Normal file
114
side-node/src/bitcoin/client.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::fs;
|
||||
|
||||
use bdk::{
|
||||
bitcoin::{psbt::PartiallySignedTransaction, Network, Transaction},
|
||||
blockchain::EsploraBlockchain,
|
||||
database::MemoryDatabase,
|
||||
keys::{bip39::Mnemonic, DerivableKey, ExtendedKey},
|
||||
template::Bip84,
|
||||
wallet::AddressInfo,
|
||||
KeychainKind, SignOptions, SyncOptions, Wallet,
|
||||
};
|
||||
|
||||
use crate::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>,
|
||||
}
|
||||
|
||||
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_wallet(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");
|
||||
|
||||
tracing::info!("Creating {name}'s wallet from mnemonic: {words}");
|
||||
let xkey: ExtendedKey = Mnemonic::parse(words)
|
||||
.expect("couldn't parse mnemonic words")
|
||||
.into_extended_key()
|
||||
.expect("couldn't turn mnemonic into extended key");
|
||||
|
||||
let xprv = xkey
|
||||
.into_xprv(Network::Testnet)
|
||||
.expect("couldn't turn xkey into xprv");
|
||||
|
||||
let external_descriptor = Bip84(xprv, KeychainKind::External);
|
||||
let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal));
|
||||
|
||||
let wallet = Wallet::new(
|
||||
external_descriptor,
|
||||
internal_descriptor,
|
||||
network,
|
||||
MemoryDatabase::default(),
|
||||
)?;
|
||||
|
||||
let blockchain = EsploraBlockchain::new("https://mutinynet.com/api", 20);
|
||||
|
||||
let esplora = BitcoinClient {
|
||||
name: name.to_string(),
|
||||
wallet,
|
||||
blockchain,
|
||||
};
|
||||
|
||||
Ok(esplora)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user