use std::error::Error;
use bitcoin::{BlockHash, Network, Transaction, Txid};
use bitcoincore_rpc::jsonrpc::Error::Rpc;
use bitcoincore_rpc::Error::JsonRpc;
use bitcoincore_rpc::{Auth, Client, RpcApi};
pub type BitcoinRpcError = Box<dyn Error + Send + Sync>;
pub trait BitcoinRpc: Send + Sync {
fn get_transaction(&self, txid: &Txid) -> Result<Option<Transaction>, BitcoinRpcError>;
fn get_transaction_with_block_hash(
&self,
txid: &Txid,
) -> Result<Option<(Transaction, Option<BlockHash>)>, BitcoinRpcError>;
fn get_block_height(&self, block_hash: &BlockHash) -> Result<usize, BitcoinRpcError>;
fn network(&self) -> Result<Network, BitcoinRpcError>;
}
pub struct BitcoinCoreRpc {
client: Client,
}
impl BitcoinCoreRpc {
pub fn new(url: &str, user: String, password: String) -> Self {
Self {
client: Client::new(url, Auth::UserPass(user, password))
.expect("Failed to create Bitcoin RPC client"),
}
}
}
fn not_found_as_none<T>(
result: Result<T, bitcoincore_rpc::Error>,
) -> Result<Option<T>, BitcoinRpcError> {
match result {
Ok(value) => Ok(Some(value)),
Err(JsonRpc(Rpc(ref rpc_error))) if rpc_error.code == -5 => Ok(None),
Err(error) => Err(error.into()),
}
}
impl BitcoinRpc for BitcoinCoreRpc {
fn get_transaction(&self, txid: &Txid) -> Result<Option<Transaction>, BitcoinRpcError> {
not_found_as_none(self.client.get_raw_transaction(txid, None))
}
fn get_transaction_with_block_hash(
&self,
txid: &Txid,
) -> Result<Option<(Transaction, Option<BlockHash>)>, BitcoinRpcError> {
let Some(info) = not_found_as_none(self.client.get_raw_transaction_info(txid, None))?
else {
return Ok(None);
};
Ok(info.transaction().ok().map(|tx| (tx, info.blockhash)))
}
fn get_block_height(&self, block_hash: &BlockHash) -> Result<usize, BitcoinRpcError> {
Ok(self.client.get_block_header_info(block_hash)?.height)
}
fn network(&self) -> Result<Network, BitcoinRpcError> {
Ok(self.client.get_blockchain_info()?.chain)
}
}